Merge "Remove stage concept from split screen APIs" into tm-qpr-dev
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 1b2d905..e263810 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -23,6 +23,7 @@
 #include <cstring>
 #include <filesystem>
 #include <fstream>
+#include <limits>
 #include <memory>
 #include <ostream>
 #include <string>
@@ -295,26 +296,42 @@
   return ok();
 }
 
-Status Idmap2Service::acquireFabricatedOverlayIterator() {
+Status Idmap2Service::acquireFabricatedOverlayIterator(int32_t* _aidl_return) {
+  std::lock_guard l(frro_iter_mutex_);
   if (frro_iter_.has_value()) {
     LOG(WARNING) << "active ffro iterator was not previously released";
   }
   frro_iter_ = std::filesystem::directory_iterator(kIdmapCacheDir);
+  if (frro_iter_id_ == std::numeric_limits<int32_t>::max()) {
+    frro_iter_id_ = 0;
+  } else {
+    ++frro_iter_id_;
+  }
+  *_aidl_return = frro_iter_id_;
   return ok();
 }
 
-Status Idmap2Service::releaseFabricatedOverlayIterator() {
+Status Idmap2Service::releaseFabricatedOverlayIterator(int32_t iteratorId) {
+  std::lock_guard l(frro_iter_mutex_);
   if (!frro_iter_.has_value()) {
     LOG(WARNING) << "no active ffro iterator to release";
+  } else if (frro_iter_id_ != iteratorId) {
+    LOG(WARNING) << "incorrect iterator id in a call to release";
+  } else {
+    frro_iter_.reset();
   }
   return ok();
 }
 
-Status Idmap2Service::nextFabricatedOverlayInfos(
+Status Idmap2Service::nextFabricatedOverlayInfos(int32_t iteratorId,
     std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+  std::lock_guard l(frro_iter_mutex_);
+
   constexpr size_t kMaxEntryCount = 100;
   if (!frro_iter_.has_value()) {
     return error("no active frro iterator");
+  } else if (frro_iter_id_ != iteratorId) {
+    return error("incorrect iterator id in a call to next");
   }
 
   size_t count = 0;
@@ -322,22 +339,22 @@
   auto entry_iter_end = end(*frro_iter_);
   for (; entry_iter != entry_iter_end && count < kMaxEntryCount; ++entry_iter) {
     auto& entry = *entry_iter;
-    if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path())) {
+    if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path().native())) {
       continue;
     }
 
-    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path().native());
     if (!overlay) {
       LOG(WARNING) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
       continue;
     }
 
-    const auto info = (*overlay)->GetManifestInfo();
+    auto info = (*overlay)->GetManifestInfo();
     os::FabricatedOverlayInfo out_info;
-    out_info.packageName = info.package_name;
-    out_info.overlayName = info.name;
-    out_info.targetPackageName = info.target_package;
-    out_info.targetOverlayable = info.target_name;
+    out_info.packageName = std::move(info.package_name);
+    out_info.overlayName = std::move(info.name);
+    out_info.targetPackageName = std::move(info.target_package);
+    out_info.targetOverlayable = std::move(info.target_name);
     out_info.path = entry.path();
     _aidl_return->emplace_back(std::move(out_info));
     count++;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index c61e4bc..cc8cc5f 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -26,7 +26,10 @@
 
 #include <filesystem>
 #include <memory>
+#include <mutex>
+#include <optional>
 #include <string>
+#include <variant>
 #include <vector>
 
 namespace android::os {
@@ -60,11 +63,11 @@
   binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
                                          bool* _aidl_return) override;
 
-  binder::Status acquireFabricatedOverlayIterator() override;
+  binder::Status acquireFabricatedOverlayIterator(int32_t* _aidl_return) override;
 
-  binder::Status releaseFabricatedOverlayIterator() override;
+  binder::Status releaseFabricatedOverlayIterator(int32_t iteratorId) override;
 
-  binder::Status nextFabricatedOverlayInfos(
+  binder::Status nextFabricatedOverlayInfos(int32_t iteratorId,
       std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
 
   binder::Status dumpIdmap(const std::string& overlay_path, std::string* _aidl_return) override;
@@ -74,7 +77,9 @@
   // be able to be recalculated if idmap2 dies and restarts.
   std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
 
+  int32_t frro_iter_id_ = 0;
   std::optional<std::filesystem::directory_iterator> frro_iter_;
+  std::mutex frro_iter_mutex_;
 
   template <typename T>
   using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
diff --git a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 0059cf2..2bbfba9 100644
--- a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -41,9 +41,9 @@
   @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
   boolean deleteFabricatedOverlay(@utf8InCpp String path);
 
-  void acquireFabricatedOverlayIterator();
-  void releaseFabricatedOverlayIterator();
-  List<FabricatedOverlayInfo> nextFabricatedOverlayInfos();
+  int acquireFabricatedOverlayIterator();
+  void releaseFabricatedOverlayIterator(int iteratorId);
+  List<FabricatedOverlayInfo> nextFabricatedOverlayInfos(int iteratorId);
 
   @utf8InCpp String dumpIdmap(@utf8InCpp String overlayApkPath);
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7c1f9c8..855366a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8045,8 +8045,9 @@
                 resultData.prepareToLeaveProcess(this);
             }
             upIntent.prepareToLeaveProcess(this);
-            return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resultCode,
-                    resultData);
+            String resolvedType = upIntent.resolveTypeIfNeeded(getContentResolver());
+            return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resolvedType,
+                    resultCode, resultData);
         } else {
             return mParent.navigateUpToFromChild(this, upIntent);
         }
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 482f456..d1e6780 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -141,11 +141,11 @@
         }
     }
 
-    boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+    boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType, int resultCode,
             Intent resultData) {
         try {
-            return getActivityClientController().navigateUpTo(token, destIntent, resultCode,
-                    resultData);
+            return getActivityClientController().navigateUpTo(token, destIntent, resolvedType,
+                    resultCode, resultData);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e1448..aa5fa5b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@
         return mIsAlarmBroadcast;
     }
 
+    /**
+     * Did this broadcast originate from a push message from the server?
+     *
+     * @return true if this broadcast is a push message, false otherwise.
+     * @hide
+     */
+    public boolean isPushMessagingBroadcast() {
+        return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+    }
+
+    /**
+     * Did this broadcast originate from a push message from the server which was over the allowed
+     * quota?
+     *
+     * @return true if this broadcast is a push message over quota, false otherwise.
+     * @hide
+     */
+    public boolean isPushMessagingOverQuotaBroadcast() {
+        return mTemporaryAppAllowlistReasonCode
+                == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+    }
+
     /** {@hide} */
     public long getRequireCompatChangeId() {
         return mRequireCompatChangeId;
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index f5e5cda..9aa67bc 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -60,8 +60,8 @@
             in SizeConfigurationBuckets sizeConfigurations);
     boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
     boolean shouldUpRecreateTask(in IBinder token, in String destAffinity);
-    boolean navigateUpTo(in IBinder token, in Intent target, int resultCode,
-            in Intent resultData);
+    boolean navigateUpTo(in IBinder token, in Intent target, in String resolvedType,
+            int resultCode, in Intent resultData);
     boolean releaseActivityInstance(in IBinder token);
     boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask);
     boolean finishActivityAffinity(in IBinder token);
diff --git a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
new file mode 100644
index 0000000..dc5a966
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/** @hide */
+oneway interface IInputDeviceBatteryListener {
+
+    /**
+     * Called when there is a change in battery state for a monitored device. This will be called
+     * immediately after the listener is successfully registered for a new device via IInputManager.
+     * The parameters are values exposed through {@link android.hardware.BatteryState}.
+     */
+    void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status, float capacity,
+            long eventTime);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2da12e6c..a645ae4 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -20,6 +20,7 @@
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputDeviceBatteryListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.TouchCalibration;
 import android.os.CombinedVibration;
@@ -155,4 +156,8 @@
     void closeLightSession(int deviceId, in IBinder token);
 
     void cancelCurrentTouch();
+
+    void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+    void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d17a952..97812ce 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.BatteryState;
 import android.hardware.SensorManager;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -66,6 +67,7 @@
 import android.view.VerifiedInputEvent;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
@@ -74,6 +76,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Provides information about input devices and available key layouts.
@@ -111,6 +115,14 @@
     private TabletModeChangedListener mTabletModeChangedListener;
     private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
 
+    private final Object mBatteryListenersLock = new Object();
+    // Maps a deviceId whose battery is currently being monitored to an entry containing the
+    // registered listeners for that device.
+    @GuardedBy("mBatteryListenersLock")
+    private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+    @GuardedBy("mBatteryListenersLock")
+    private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
     private InputDeviceSensorManager mInputDeviceSensorManager;
     /**
      * Broadcast Action: Query available keyboard layouts.
@@ -1752,6 +1764,129 @@
     }
 
     /**
+     * Adds a battery listener to be notified about {@link BatteryState} changes for an input
+     * device. The same listener can be registered for multiple input devices.
+     * The listener will be notified of the initial battery state of the device after it is
+     * successfully registered.
+     * @param deviceId the input device that should be monitored
+     * @param executor an executor on which the callback will be called
+     * @param listener the {@link InputDeviceBatteryListener}
+     * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+     * @hide
+     */
+    public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                mBatteryListeners = new SparseArray<>();
+                mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is currently not being monitored for battery changes.
+                // Start monitoring the device.
+                listenersForDevice = new RegisteredBatteryListeners();
+                mBatteryListeners.put(deviceId, listenersForDevice);
+                try {
+                    mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                // The deviceId is already being monitored for battery changes.
+                // Ensure that the listener is not already registered.
+                for (InputDeviceBatteryListenerDelegate delegate : listenersForDevice.mDelegates) {
+                    if (Objects.equals(listener, delegate.mListener)) {
+                        throw new IllegalArgumentException(
+                                "Attempting to register an InputDeviceBatteryListener that has "
+                                        + "already been registered for deviceId: "
+                                        + deviceId);
+                    }
+                }
+            }
+            final InputDeviceBatteryListenerDelegate delegate =
+                    new InputDeviceBatteryListenerDelegate(listener, executor);
+            listenersForDevice.mDelegates.add(delegate);
+
+            // Notify the listener immediately if we already have the latest battery state.
+            if (listenersForDevice.mLatestBatteryState != null) {
+                delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
+            }
+        }
+    }
+
+    /**
+     * Removes a previously registered battery listener for an input device.
+     * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+     * @hide
+     */
+    public void removeInputDeviceBatteryListener(int deviceId,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                return;
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is not currently being monitored.
+                return;
+            }
+            final List<InputDeviceBatteryListenerDelegate> delegates =
+                    listenersForDevice.mDelegates;
+            for (int i = 0; i < delegates.size();) {
+                if (Objects.equals(listener, delegates.get(i).mListener)) {
+                    delegates.remove(i);
+                    continue;
+                }
+                i++;
+            }
+            if (!delegates.isEmpty()) {
+                return;
+            }
+
+            // There are no more battery listeners for this deviceId. Stop monitoring this device.
+            mBatteryListeners.remove(deviceId);
+            try {
+                mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (mBatteryListeners.size() == 0) {
+                // There are no more devices being monitored, so the registered
+                // IInputDeviceBatteryListener will be automatically dropped by the server.
+                mBatteryListeners = null;
+                mInputDeviceBatteryListener = null;
+            }
+        }
+    }
+
+    /**
+     * A callback used to be notified about battery state changes for an input device. The
+     * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
+     * listener is successfully registered to provide the initial battery state of the device.
+     * @see InputDevice#getBatteryState()
+     * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+     * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+     * @hide
+     */
+    public interface InputDeviceBatteryListener {
+        /**
+         * Called when the battery state of an input device changes.
+         * @param deviceId the input device for which the battery changed.
+         * @param eventTimeMillis the time (in ms) when the battery change took place.
+         *        This timestamp is in the {@link SystemClock#uptimeMillis()} time base.
+         * @param batteryState the new battery state, never null.
+         */
+        void onBatteryStateChanged(
+                int deviceId, long eventTimeMillis, @NonNull BatteryState batteryState);
+    }
+
+    /**
      * Listens for changes in input devices.
      */
     public interface InputDeviceListener {
@@ -1861,4 +1996,76 @@
             }
         }
     }
+
+    private static final class LocalBatteryState extends BatteryState {
+        final int mDeviceId;
+        final boolean mIsPresent;
+        final int mStatus;
+        final float mCapacity;
+        final long mEventTime;
+
+        LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
+                long eventTime) {
+            mDeviceId = deviceId;
+            mIsPresent = isPresent;
+            mStatus = status;
+            mCapacity = capacity;
+            mEventTime = eventTime;
+        }
+
+        @Override
+        public boolean isPresent() {
+            return mIsPresent;
+        }
+
+        @Override
+        public int getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public float getCapacity() {
+            return mCapacity;
+        }
+    }
+
+    private static final class RegisteredBatteryListeners {
+        final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+        LocalBatteryState mLatestBatteryState;
+    }
+
+    private static final class InputDeviceBatteryListenerDelegate {
+        final InputDeviceBatteryListener mListener;
+        final Executor mExecutor;
+
+        InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyBatteryStateChanged(LocalBatteryState batteryState) {
+            mExecutor.execute(() ->
+                    mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
+                            batteryState));
+        }
+    }
+
+    private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+        @Override
+        public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
+                float capacity, long eventTime) {
+            synchronized (mBatteryListenersLock) {
+                if (mBatteryListeners == null) return;
+                final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
+                if (entry == null) return;
+
+                entry.mLatestBatteryState =
+                        new LocalBatteryState(
+                                deviceId, isBatteryPresent, status, capacity, eventTime);
+                for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
+                    delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 58f9336..26e5e95 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -34,6 +34,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.Closeable;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -449,6 +450,16 @@
         return proto.getBytes();
     }
 
+    /**
+     * Writes contents in a binary protobuffer format, using
+     * the android.os.BatteryUsageStatsAtomsProto proto.
+     */
+    public void dumpToProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        writeStatsProto(proto, /* max size */ Integer.MAX_VALUE);
+        proto.flush();
+    }
+
     @NonNull
     private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
         final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3d70138..a7349f9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4388,6 +4388,9 @@
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index fae72a2..ced2a01 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
-import com.android.server.LocalServices;
-
 /**
  * Internal helper for launching dreams to ensure consistency between the
  * <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity.
@@ -75,28 +75,32 @@
     }
 
     private static void startDream(Context context, boolean docked) {
-        DreamManagerInternal dreamManagerService =
-                LocalServices.getService(DreamManagerInternal.class);
-        if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
-            if (docked) {
-                Slog.i(TAG, "Activating dream while docked.");
+        try {
+            IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
+                    ServiceManager.getService(DreamService.DREAM_SERVICE));
+            if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
+                if (docked) {
+                    Slog.i(TAG, "Activating dream while docked.");
 
-                // Wake up.
-                // The power manager will wake up the system automatically when it starts
-                // receiving power from a dock but there is a race between that happening
-                // and the UI mode manager starting a dream.  We want the system to already
-                // be awake by the time this happens.  Otherwise the dream may not start.
-                PowerManager powerManager =
-                        context.getSystemService(PowerManager.class);
-                powerManager.wakeUp(SystemClock.uptimeMillis(),
-                        PowerManager.WAKE_REASON_PLUGGED_IN,
-                        "android.service.dreams:DREAM");
-            } else {
-                Slog.i(TAG, "Activating dream by user request.");
+                    // Wake up.
+                    // The power manager will wake up the system automatically when it starts
+                    // receiving power from a dock but there is a race between that happening
+                    // and the UI mode manager starting a dream.  We want the system to already
+                    // be awake by the time this happens.  Otherwise the dream may not start.
+                    PowerManager powerManager =
+                            context.getSystemService(PowerManager.class);
+                    powerManager.wakeUp(SystemClock.uptimeMillis(),
+                            PowerManager.WAKE_REASON_PLUGGED_IN,
+                            "android.service.dreams:DREAM");
+                } else {
+                    Slog.i(TAG, "Activating dream by user request.");
+                }
+
+                // Dream.
+                dreamManagerService.dream();
             }
-
-            // Dream.
-            dreamManagerService.requestDream();
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Could not start dream when docked.", ex);
         }
     }
 
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 7d56039..7164412 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1022,6 +1022,15 @@
     }
 
     /**
+     * Reports whether the device has a battery.
+     * @return true if the device has a battery, false otherwise.
+     * @hide
+     */
+    public boolean hasBattery() {
+        return mHasBattery;
+    }
+
+    /**
      * Provides information about the range of values for a particular {@link MotionEvent} axis.
      *
      * @see InputDevice#getMotionRange(int)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4caa38..91f36bf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1099,6 +1099,10 @@
                 mInputQueueCallback.onInputQueueCreated(mInputQueue);
             }
         }
+
+        // Update the last resource config in case the resource configuration was changed while
+        // activity relaunched.
+        mLastConfigurationFromResources.setTo(getConfiguration());
     }
 
     private Configuration getConfiguration() {
@@ -8154,6 +8158,7 @@
                     mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
                     mTempInsets, mTempControls, mRelayoutBundle);
             mRelayoutRequested = true;
+
             final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
             if (maybeSyncSeqId > 0) {
                 mSyncSeqId = maybeSyncSeqId;
@@ -8193,6 +8198,12 @@
             }
         }
 
+        if (mSurfaceControl.isValid() && !HardwareRenderer.isDrawingEnabled()) {
+            // When drawing is disabled the window layer won't have a valid buffer.
+            // Set a window crop so input can get delivered to the window.
+            mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply();
+        }
+
         mLastTransformHint = transformHint;
       
         mSurfaceControl.setTransformHint(transformHint);
@@ -8482,6 +8493,10 @@
         if (mLocalSyncState != LOCAL_SYNC_NONE) {
             writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
         }
+        writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+                + mLastReportedMergedConfiguration);
+        writer.println(innerPrefix + "mLastConfigurationFromResources="
+                + mLastConfigurationFromResources);
         writer.println(innerPrefix + "mIsAmbientMode="  + mIsAmbientMode);
         writer.println(innerPrefix + "mUnbufferedInputSource="
                 + Integer.toHexString(mUnbufferedInputSource));
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 3c7cd02..36eaf49 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -51,16 +51,19 @@
             in IWindowContainerTransactionCallback callback);
 
     /**
-     * Starts a transition.
+     * Starts a new transition.
      * @param type The transition type.
-     * @param transitionToken A token associated with the transition to start. If null, a new
-     *                        transition will be created of the provided type.
      * @param t Operations that are part of the transition.
-     * @return a token representing the transition. This will just be transitionToken if it was
-     *         non-null.
+     * @return a token representing the transition.
      */
-    IBinder startTransition(int type, in @nullable IBinder transitionToken,
-            in @nullable WindowContainerTransaction t);
+    IBinder startNewTransition(int type, in @nullable WindowContainerTransaction t);
+
+    /**
+     * Starts the given transition.
+     * @param transitionToken A token associated with the transition to start.
+     * @param t Operations that are part of the transition.
+     */
+    oneway void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
 
     /**
      * Starts a legacy transition.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 641d1a1..fbdd325 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -132,8 +132,11 @@
      */
     public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
 
+    /** This change happened underneath something else. */
+    public static final int FLAG_IS_OCCLUDED = 1 << 15;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 15;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 16;
 
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
@@ -153,6 +156,7 @@
             FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
             FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
             FLAG_IS_BEHIND_STARTING_WINDOW,
+            FLAG_IS_OCCLUDED,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -362,6 +366,9 @@
         if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
         }
+        if ((flags & FLAG_IS_OCCLUDED) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 4ea5ea5..2a80d02 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -84,9 +84,8 @@
     }
 
     /**
-     * Start a transition.
+     * Starts a new transition, don't use this to start an already created one.
      * @param type The type of the transition. This is ignored if a transitionToken is provided.
-     * @param transitionToken An existing transition to start. If null, a new transition is created.
      * @param t The set of window operations that are part of this transition.
      * @return A token identifying the transition. This will be the same as transitionToken if it
      *         was provided.
@@ -94,10 +93,24 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @NonNull
-    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+    public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+        try {
+            return getWindowOrganizerController().startNewTransition(type, t);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts an already created transition.
+     * @param transitionToken An existing transition to start.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    public void startTransition(@NonNull IBinder transitionToken,
             @Nullable WindowContainerTransaction t) {
         try {
-            return getWindowOrganizerController().startTransition(type, transitionToken, t);
+            getWindowOrganizerController().startTransition(transitionToken, t);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 620f177..f5ba275 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1114,6 +1114,19 @@
         resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
         resolveIntent.setComponent(cn);
         resolveIntent.setAction(Intent.ACTION_EDIT);
+        String originalAction = originalIntent.getAction();
+        if (Intent.ACTION_SEND.equals(originalAction)) {
+            if (resolveIntent.getData() == null) {
+                Uri uri = resolveIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+                if (uri != null) {
+                    String mimeType = getContentResolver().getType(uri);
+                    resolveIntent.setDataAndType(uri, mimeType);
+                }
+            }
+        } else {
+            Log.e(TAG, originalAction + " is not supported.");
+            return null;
+        }
         final ResolveInfo ri = getPackageManager().resolveActivity(
                 resolveIntent, PackageManager.GET_META_DATA);
         if (ri == null || ri.activityInfo == null) {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1ec5325..4f74ca7 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -86,7 +86,6 @@
     private final ChooserActivityLogger mChooserActivityLogger;
 
     private int mNumShortcutResults = 0;
-    private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
     private boolean mApplySharingAppLimits;
 
     // Reserve spots for incoming direct share targets by adding placeholders
@@ -265,31 +264,20 @@
             return;
         }
 
-        if (!(info instanceof DisplayResolveInfo)) {
-            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
-            holder.bindIcon(info);
-
-            if (info instanceof SelectableTargetInfo) {
-                // direct share targets should append the application name for a better readout
-                DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
-                CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
-                CharSequence extendedInfo = info.getExtendedInfo();
-                String contentDescription = String.join(" ", info.getDisplayLabel(),
-                        extendedInfo != null ? extendedInfo : "", appName);
-                holder.updateContentDescription(contentDescription);
-            }
-        } else {
+        holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
+        holder.bindIcon(info);
+        if (info instanceof SelectableTargetInfo) {
+            // direct share targets should append the application name for a better readout
+            DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
+            CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+            CharSequence extendedInfo = info.getExtendedInfo();
+            String contentDescription = String.join(" ", info.getDisplayLabel(),
+                    extendedInfo != null ? extendedInfo : "", appName);
+            holder.updateContentDescription(contentDescription);
+        } else if (info instanceof DisplayResolveInfo) {
             DisplayResolveInfo dri = (DisplayResolveInfo) info;
-            holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel());
-            LoadIconTask task = mIconLoaders.get(dri);
-            if (task == null) {
-                task = new LoadIconTask(dri, holder);
-                mIconLoaders.put(dri, task);
-                task.execute();
-            } else {
-                // The holder was potentially changed as the underlying items were
-                // reshuffled, so reset the target holder
-                task.setViewHolder(holder);
+            if (!dri.hasDisplayIcon()) {
+                loadIcon(dri);
             }
         }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0e1ed7b..c70e26f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -55,6 +55,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -1475,14 +1476,21 @@
                 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
         boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
 
-        ResolverListAdapter inactiveAdapter = mMultiProfilePagerAdapter.getInactiveListAdapter();
-        DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
+        final ResolverListAdapter inactiveAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
 
         // Load the icon asynchronously
         ImageView icon = findViewById(R.id.icon);
-        ResolverListAdapter.LoadIconTask iconTask = inactiveAdapter.new LoadIconTask(
-                        otherProfileResolveInfo, new ResolverListAdapter.ViewHolder(icon));
-        iconTask.execute();
+        inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) {
+            @Override
+            protected void onPostExecute(Drawable drawable) {
+                if (!isDestroyed()) {
+                    otherProfileResolveInfo.setDisplayIcon(drawable);
+                    new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
+                }
+            }
+        }.execute();
 
         ((TextView) findViewById(R.id.open_cross_profile)).setText(
                 getResources().getString(
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 66fff5c..f6075b0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -58,7 +58,10 @@
 import com.android.internal.app.chooser.TargetInfo;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class ResolverListAdapter extends BaseAdapter {
     private static final String TAG = "ResolverListAdapter";
@@ -87,6 +90,8 @@
     private Runnable mPostListReadyRunnable;
     private final boolean mIsAudioCaptureDevice;
     private boolean mIsTabLoaded;
+    private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+    private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>();
 
     public ResolverListAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList,
@@ -636,26 +641,47 @@
         if (info == null) {
             holder.icon.setImageDrawable(
                     mContext.getDrawable(R.drawable.resolver_icon_placeholder));
+            holder.bindLabel("", "", false);
             return;
         }
 
-        if (info instanceof DisplayResolveInfo
-                && !((DisplayResolveInfo) info).hasDisplayLabel()) {
-            getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
-        } else {
-            holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
-        }
-
-        if (info instanceof DisplayResolveInfo
-                && !((DisplayResolveInfo) info).hasDisplayIcon()) {
-            new LoadIconTask((DisplayResolveInfo) info, holder).execute();
-        } else {
+        if (info instanceof DisplayResolveInfo) {
+            DisplayResolveInfo dri = (DisplayResolveInfo) info;
+            boolean hasLabel = dri.hasDisplayLabel();
+            holder.bindLabel(
+                    dri.getDisplayLabel(),
+                    dri.getExtendedInfo(),
+                    hasLabel && alwaysShowSubLabel());
             holder.bindIcon(info);
+            if (!hasLabel) {
+                loadLabel(dri);
+            }
+            if (!dri.hasDisplayIcon()) {
+                loadIcon(dri);
+            }
         }
     }
 
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelTask(info, holder);
+    protected final void loadIcon(DisplayResolveInfo info) {
+        LoadIconTask task = mIconLoaders.get(info);
+        if (task == null) {
+            task = new LoadIconTask((DisplayResolveInfo) info);
+            mIconLoaders.put(info, task);
+            task.execute();
+        }
+    }
+
+    private void loadLabel(DisplayResolveInfo info) {
+        LoadLabelTask task = mLabelLoaders.get(info);
+        if (task == null) {
+            task = createLoadLabelTask(info);
+            mLabelLoaders.put(info, task);
+            task.execute();
+        }
+    }
+
+    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+        return new LoadLabelTask(info);
     }
 
     public void onDestroy() {
@@ -666,6 +692,16 @@
         if (mResolverListController != null) {
             mResolverListController.destroy();
         }
+        cancelTasks(mIconLoaders.values());
+        cancelTasks(mLabelLoaders.values());
+        mIconLoaders.clear();
+        mLabelLoaders.clear();
+    }
+
+    private <T extends AsyncTask> void cancelTasks(Collection<T> tasks) {
+        for (T task: tasks) {
+            task.cancel(false);
+        }
     }
 
     private static ColorMatrixColorFilter getSuspendedColorMatrix() {
@@ -883,11 +919,9 @@
 
     protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
         private final DisplayResolveInfo mDisplayResolveInfo;
-        private final ViewHolder mHolder;
 
-        protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
+        protected LoadLabelTask(DisplayResolveInfo dri) {
             mDisplayResolveInfo = dri;
-            mHolder = holder;
         }
 
         @Override
@@ -925,21 +959,22 @@
 
         @Override
         protected void onPostExecute(CharSequence[] result) {
+            if (mDisplayResolveInfo.hasDisplayLabel()) {
+                return;
+            }
             mDisplayResolveInfo.setDisplayLabel(result[0]);
             mDisplayResolveInfo.setExtendedInfo(result[1]);
-            mHolder.bindLabel(result[0], result[1], alwaysShowSubLabel());
+            notifyDataSetChanged();
         }
     }
 
     class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
         protected final DisplayResolveInfo mDisplayResolveInfo;
         private final ResolveInfo mResolveInfo;
-        private ViewHolder mHolder;
 
-        LoadIconTask(DisplayResolveInfo dri, ViewHolder holder) {
+        LoadIconTask(DisplayResolveInfo dri) {
             mDisplayResolveInfo = dri;
             mResolveInfo = dri.getResolveInfo();
-            mHolder = holder;
         }
 
         @Override
@@ -953,17 +988,9 @@
                 mResolverListCommunicator.updateProfileViewButton();
             } else if (!mDisplayResolveInfo.hasDisplayIcon()) {
                 mDisplayResolveInfo.setDisplayIcon(d);
-                mHolder.bindIcon(mDisplayResolveInfo);
-                // Notify in case view is already bound to resolve the race conditions on
-                // low end devices
                 notifyDataSetChanged();
             }
         }
-
-        public void setViewHolder(ViewHolder holder) {
-            mHolder = holder;
-            mHolder.bindIcon(mDisplayResolveInfo);
-        }
     }
 
     /**
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a..d503904 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@
             Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
         }
 
-        mBrightnessSyncObserver.startObserving();
+        mBrightnessSyncObserver.startObserving(mHandler);
         mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
     }
 
@@ -482,30 +482,31 @@
             }
         };
 
-        private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                if (selfChange) {
-                    return;
+        private ContentObserver createBrightnessContentObserver(Handler handler) {
+            return new ContentObserver(handler) {
+                @Override
+                public void onChange(boolean selfChange, Uri uri) {
+                    if (selfChange) {
+                        return;
+                    }
+                    if (BRIGHTNESS_URI.equals(uri)) {
+                        handleBrightnessChangeInt(getScreenBrightnessInt());
+                    }
                 }
-                if (BRIGHTNESS_URI.equals(uri)) {
-                    handleBrightnessChangeInt(getScreenBrightnessInt());
-                }
-            }
-        };
+            };
+        }
 
         boolean isObserving() {
             return mIsObserving;
         }
 
-        void startObserving() {
+        void startObserving(Handler handler) {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
-                    UserHandle.USER_ALL);
-            mDisplayManager.registerDisplayListener(mListener, mHandler,
+            cr.registerContentObserver(BRIGHTNESS_URI, false,
+                    createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+            mDisplayManager.registerDisplayListener(mListener, handler,
                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
             mIsObserving = true;
         }
-
     }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a4da8de4..1235b60 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1522,8 +1522,7 @@
                         STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
                         STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
                         STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
-                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
-                        SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
+                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StrongAuthFlags {}
 
@@ -1576,12 +1575,6 @@
         public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
 
         /**
-         * Some authentication is required because the trustagent either timed out or was disabled
-         * manually.
-         */
-        public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
-
-        /**
          * Strong auth flags that do not prevent biometric methods from being accepted as auth.
          * If any other flags are set, biometric authentication is disabled.
          */
diff --git a/core/proto/android/os/processstarttime.proto b/core/proto/android/os/processstarttime.proto
deleted file mode 100644
index d0f8bae..0000000
--- a/core/proto/android/os/processstarttime.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-syntax = "proto2";
-package android.os;
-
-option java_multiple_files = true;
-
-// This message is used for statsd logging and should be kept in sync with
-// frameworks/proto_logging/stats/atoms.proto
-/**
- * Logs information about process start time.
- *
- * Logged from:
- *      frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message ProcessStartTime {
-  // The uid of the ProcessRecord.
-  optional int32 uid = 1;
-
-  // The process pid.
-  optional int32 pid = 2;
-
-  // The process name.
-  // Usually package name, "system" for system server.
-  // Provided by ActivityManagerService.
-  optional string process_name = 3;
-
-  enum StartType {
-    UNKNOWN = 0;
-    WARM = 1;
-    HOT = 2;
-    COLD = 3;
-  }
-
-  // The start type.
-  optional StartType type = 4;
-
-  // The elapsed realtime at the start of the process.
-  optional int64 process_start_time_millis = 5;
-
-  // Number of milliseconds it takes to reach bind application.
-  optional int32 bind_application_delay_millis = 6;
-
-  // Number of milliseconds it takes to finish start of the process.
-  optional int32 process_start_delay_millis = 7;
-
-  // hostingType field in ProcessRecord, the component type such as "activity",
-  // "service", "content provider", "broadcast" or other strings.
-  optional string hosting_type = 8;
-
-  // hostingNameStr field in ProcessRecord. The component class name that runs
-  // in this process.
-  optional string hosting_name = 9;
-
-  // Broadcast action name.
-  optional string broadcast_action_name = 10;
-
-  enum HostingTypeId {
-    HOSTING_TYPE_UNKNOWN = 0;
-    HOSTING_TYPE_ACTIVITY = 1;
-    HOSTING_TYPE_ADDED_APPLICATION = 2;
-    HOSTING_TYPE_BACKUP = 3;
-    HOSTING_TYPE_BROADCAST = 4;
-    HOSTING_TYPE_CONTENT_PROVIDER = 5;
-    HOSTING_TYPE_LINK_FAIL = 6;
-    HOSTING_TYPE_ON_HOLD = 7;
-    HOSTING_TYPE_NEXT_ACTIVITY = 8;
-    HOSTING_TYPE_NEXT_TOP_ACTIVITY = 9;
-    HOSTING_TYPE_RESTART = 10;
-    HOSTING_TYPE_SERVICE = 11;
-    HOSTING_TYPE_SYSTEM = 12;
-    HOSTING_TYPE_TOP_ACTIVITY = 13;
-    HOSTING_TYPE_EMPTY = 14;
-  }
-
-  optional HostingTypeId hosting_type_id = 11;
-}
-
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index f47cf3f..7c439008 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"የአንድ ጊዜ መዳረሻን ፍቀድ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"አትፍቀድ"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸውን መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የእርስዎ መሣሪያ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"የመሣሪያ ምዝግብ ማስታወሻዎች በመሣሪያዎ ላይ ምን እንደሚከሰት ይመዘግባሉ። መተግበሪያዎች ችግሮችን ለማግኘት እና ለማስተካከል እነዚህን ምዝግብ ማስታወሻዎች መጠቀም ይችላሉ።\n\nአንዳንድ ምዝግብ ማስታወሻዎች ሚስጥራዊነት ያለው መረጃ ሊይዙ ይችላሉ፣ ስለዚህ የሚያምኗቸው መተግበሪያዎች ብቻ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርሱ ይፍቀዱላቸው። \n\nይህ መተግበሪያ ሁሉንም የመሣሪያ ምዝግብ ማስታወሻዎች እንዲደርስ ካልፈቀዱለት አሁንም የራሱን ምዝግብ ማስታወሻዎች መድረስ ይችላል። የመሣሪያዎ አምራች አሁንም አንዳንድ ምዝግብ ማስታወሻዎችን ወይም መረጃዎችን በመሣሪያዎ ላይ ሊደርስ ይችላል።\n\ng.co/android/devicelogs ላይ የበለጠ ይወቁ።"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"ዳግም አታሳይ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን ማሳየት ይፈልጋል"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"አርትዕ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 8d47c17..1280485 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2055,8 +2055,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"السماح بالوصول إلى السجلّ لمرة واحدة"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"عدم السماح"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. ويظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏ترصد سجلّات الجهاز ما يحدث على جهازك. يمكن أن تستخدم التطبيقات هذه السجلّات لتحديد المشاكل وحلّها.\n\nقد تحتوي بعض السجلّات على معلومات حساسة، ولذلك يجب عدم السماح بالوصول إلى جميع سجلّات الجهاز إلا للتطبيقات التي تثق بها. \n\nإذا لم تسمح بوصول هذا التطبيق إلى جميع سجلّات الجهاز، يظل بإمكان التطبيق الوصول إلى سجلّاته. وقد يظل بإمكان الشركة المصنِّعة لجهازك الوصول إلى بعض السجلّات أو المعلومات المتوفّرة على جهازك.\n\nتعرَّف على مزيد من المعلومات على الرابط g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"عدم الإظهار مرة أخرى"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"يريد تطبيق <xliff:g id="APP_0">%1$s</xliff:g> عرض شرائح تطبيق <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"تعديل"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index f5fbba9..c9550c8 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -437,8 +437,8 @@
     <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"এই এপটোৱে আপোনাৰ টেবলেটত কেলেণ্ডাৰ কাৰ্যক্ৰম যোগ কৰিব, আঁতৰাব বা সলনি কৰিব পাৰে। ই এনে বাৰ্তা পঠিয়াব পাৰে যিবোৰ কেলেণ্ডাৰৰ গৰাকীৰ পৰা অহা যেন লাগিব বা ই গৰাকীক নজনোৱাকৈ কাৰ্যক্ৰম সলনি কৰিব পাৰে৷"</string>
     <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"এই এপ্‌টোৱে আপোনাৰ Android TV ডিভাইচত কেলেণ্ডাৰ অনুষ্ঠানবোৰ যোগ দিব, আঁতৰাব অথবা সলনি কৰিব পাৰে। এই এপ্‌টোৱে এনে বাৰ্তা পঠিয়াব পাৰে যিবোৰ কেলেণ্ডাৰৰ গৰাকীৰ পৰা অহা বুলি প্ৰদর্শিত হ’ব পাৰে অথবা এইটোৱে গৰাকীসকলক নজনোৱাকৈ অনুষ্ঠানবোৰ সলনি কৰিব পাৰে।"</string>
     <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"এই এপে আপোনাৰ ফ\'নৰ কেলেণ্ডাৰত কার্যক্ৰম যোগ দিব, আঁতৰাব বা সলনি কৰিব পাৰে। ই এনে বাৰ্তা পঠিয়াব পাৰে যিবোৰ কেলেণ্ডাৰৰ গৰাকীৰ পৰা অহা যেন লাগে বা ই গৰাকীক নজনোৱাকৈ কাৰ্যক্ৰম সলনি কৰিব পাৰে৷"</string>
-    <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"অতিৰিক্ত অৱস্থান দেখুওৱা নির্দেশত প্ৰৱেশ কৰক"</string>
-    <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"অৱস্থানৰ অতিৰিক্ত নির্দেশনাসমূহত প্ৰৱেশ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়ে এপটোক জিপিএছ বা অন্য অৱস্থান উৎসসমূহৰ কাৰ্যকলাপত হস্তক্ষেপ কৰাৰ সুযোগ দিব পাৰে।"</string>
+    <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"অতিৰিক্ত অৱস্থান প্ৰদানকাৰী নির্দেশসমূহ এক্সেছ কৰে"</string>
+    <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"অৱস্থানৰ অতিৰিক্ত নির্দেশনাসমূহ এক্সেছ কৰিবলৈ এপক অনুমতি দিয়ে। ই এপ্‌টোক জিপিএছ বা অন্য অৱস্থান উৎসসমূহৰ কাৰ্যকলাপত হস্তক্ষেপ কৰাৰ সুযোগ দিব পাৰে।"</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"কেৱল অগ্ৰভূমিত অৱস্থানৰ সঠিক তথ্য পাওক"</string>
     <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"এই এপ্‌টো ব্যৱহাৰ হৈ থকা অৱস্থাত ই অৱস্থান সেৱাসমূহৰ পৰা আপোনাৰ সঠিক অৱস্থান লাভ কৰিব পাৰে। এপ্‌টোৱে অৱস্থান লাভ কৰিবলৈ হ’লে আপোনাৰ ডিভাইচৰ অৱস্থান সেৱাসমূহ অন কৰি ৰাখিবই লাগিব। ইয়াৰ ফলত বেটাৰীৰ ব্যৱহাৰ বাঢ়িব পাৰে।"</string>
     <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"কেৱল অগ্ৰভূমিত আনুমানিক অৱস্থান এক্সেছ কৰক"</string>
@@ -722,7 +722,7 @@
     <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপটোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"নেটৱর্ক ব্যৱহাৰৰ হিচাপ সলনি কৰক"</string>
     <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"এপ অনুসুৰি নেটৱর্কৰ ব্যৱহাৰৰ হিচাপ সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। এয়া সাধাৰণ এপবোৰৰ ব্যৱহাৰৰ বাবে নহয়।"</string>
-    <string name="permlab_accessNotifications" msgid="7130360248191984741">"প্ৰৱেশ জাননীসমূহ"</string>
+    <string name="permlab_accessNotifications" msgid="7130360248191984741">"জাননীসমূহ এক্সেছ কৰে"</string>
     <string name="permdesc_accessNotifications" msgid="761730149268789668">"অন্য এপসমূহৰদ্বাৰা প\'ষ্ট কৰা জাননীসমূহকে ধৰি জাননী বিচাৰি উলিয়াবলৈ, পৰীক্ষা কৰিবলৈ আৰু মচিবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"এটা জাননী শুনা সেৱাৰ লগত সংযুক্ত হ\'ব পাৰে"</string>
     <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"এটা জাননী শ্ৰৱণ সেৱা প্ৰদানকাৰীৰ শীৰ্ষ স্তৰৰ ইণ্টাৰফেইচৰ লগত সংযুক্ত হ\'বলৈ ধাৰকক অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন নহয়।"</string>
@@ -736,7 +736,7 @@
     <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"এটা এপ্লিকেশ্বনক নেটৱৰ্ক অৱস্থাসমূহত নিৰীক্ষণৰ বাবে শুনিবলৈ অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন নহয়।"</string>
     <string name="permlab_setInputCalibration" msgid="932069700285223434">"ইনপুট ডিভাইচ কেলিব্ৰেশ্বন সলনি কৰিব পাৰে"</string>
     <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"টাচ্চ স্ক্ৰীনৰ কেলিব্ৰেশ্বন পেৰামিটাৰ সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে কেতিয়াও প্ৰয়োজন হোৱা উচিত নহয়।"</string>
-    <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"DRM প্ৰমাণপত্ৰসমূহলৈ প্ৰৱেশ"</string>
+    <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"DRM প্ৰমাণপত্ৰসমূহ এক্সেছ কৰে"</string>
     <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"এটা এপ্লিকেশ্বনক DRM প্ৰমাণপত্ৰ গোটাবলৈ আৰু ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন নহয়।"</string>
     <string name="permlab_handoverStatus" msgid="7620438488137057281">"Android বীম স্থানান্তৰণৰ স্থিতি লাভ কৰিব পাৰে"</string>
     <string name="permdesc_handoverStatus" msgid="3842269451732571070">"বৰ্তমানৰ Android Beam স্থানান্তৰণসমূহৰ বিষয়ে তথ্য পাবলৈ এই এপ্লিকেশ্বনক অনুমতি দিয়ে"</string>
@@ -1470,7 +1470,7 @@
     <string name="ime_action_default" msgid="8265027027659800121">"কার্য কৰক"</string>
     <string name="dial_number_using" msgid="6060769078933953531">"<xliff:g id="NUMBER">%s</xliff:g> ব্যৱহাৰ কৰি \n নম্বৰটো ডায়েল কৰক"</string>
     <string name="create_contact_using" msgid="6200708808003692594">"<xliff:g id="NUMBER">%s</xliff:g> ব্যৱহাৰ কৰি সম্পৰ্ক \n সৃষ্টি কৰক"</string>
-    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"বৰ্তমান আৰু ভৱিষ্যতে আপোনাৰ একাউণ্টত প্ৰৱেশ কৰিবলৈ তলৰ এটা বা অধিক এপে অনুমতি লাভৰ বাবে অনুৰোধ কৰিছে৷"</string>
+    <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"তলৰ এটা বা একাধিক এপে বৰ্তমান আৰু ভৱিষ্যতে আপোনাৰ একাউণ্ট এক্সেছ কৰিবলৈ অনুমতি বিচাৰি অনুৰোধ কৰিছে৷"</string>
     <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"আপুনি এই অনুৰোধক সন্মতি দিব বিচাৰেনে?"</string>
     <string name="grant_permissions_header_text" msgid="3420736827804657201">"ব্যৱহাৰ কৰাৰ অনুমতি বিচাৰি কৰা অনুৰোধ"</string>
     <string name="allow" msgid="6195617008611933762">"অনুমতি দিয়ক"</string>
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"কেৱল এবাৰ এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"অনুমতি নিদিব"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"আপোনাৰ ডিভাইচত কি কি ঘটে সেয়া ডিভাইচ লগে ৰেকৰ্ড কৰে। এপ্‌সমূহে সমস্যা বিচাৰিবলৈ আৰু সমাধান কৰিবলৈ এই লগসমূহ ব্যৱহাৰ কৰিব পাৰে।\n\nকিছুমান লগত সংবেদনশীল তথ্য থাকিব পাৰে, গতিকে কেৱল আপুনি বিশ্বাস কৰা এপকহে আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি দিয়ক। \n\nআপুনি যদি এই এপ্‌টোক আটাইবোৰ ডিভাইচ লগ এক্সেছ কৰাৰ অনুমতি নিদিয়ে, তথাপিও ই নিজৰ লগসমূহ এক্সেছ কৰিব পাৰিব। আপোনাৰ ডিভাইচৰ নিৰ্মাতাই তথাপিও হয়তো আপোনাৰ ডিভাইচটোত থকা কিছু লগ অথবা তথ্য এক্সেছ কৰিব পাৰিব।\n\ng.co/android/devicelogsত অধিক জানক।"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"পুনৰ নেদেখুৱাব"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>এ <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাব খুজিছে"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"সম্পাদনা কৰক"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index b3e4fc3..95968f1 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Дазволіць аднаразовы доступ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дазваляць"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналы прылад запісваюць усё, што адбываецца на вашай прыладзе. Праграмы выкарыстоўваюць гэтыя журналы для пошуку і выпраўлення памылак.\n\nУ некаторых журналах можа ўтрымлівацца канфідэнцыяльная інфармацыя, таму давайце доступ да ўсіх журналаў прылады толькі тым праграмам, якім вы давяраеце. \n\nКалі вы не дасце гэтай праграме доступу да ўсіх журналаў прылад, у яе ўсё роўна застанецца доступ да ўласных журналаў. Для вытворцы вашай прылады будуць даступнымі некаторыя журналы і інфармацыя на вашай прыладзе.\n\nДаведайцеся больш на старонцы g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больш не паказваць"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Праграма <xliff:g id="APP_0">%1$s</xliff:g> запытвае дазвол на паказ зрэзаў праграмы <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Рэдагаваць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 7d21ba3..f9d61bc 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешаване на еднократен достъп"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Забраняване"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството пак може да има достъп до някои регистрационни файлове или информация на устройството ви."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В регистрационните файлове за устройството се записва какво се извършва на него. Приложенията могат да използват тези регистрационни файлове, за да откриват и отстраняват проблеми.\n\nНякои регистрационни файлове за устройството може да съдържат поверителна информация, затова разрешавайте достъп до всички тях само на приложения, на които имате доверие. \n\nАко не разрешите на това приложение достъп до всички регистрационни файлове за устройството, то пак може да осъществява достъп до собствените си регистрационни файлове. Производителят на устройството може да има достъп до някои регистрационни файлове или информация на устройството ви.\n\nНаучете повече на адрес g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Да не се показва пак"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> иска да показва части от <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редактиране"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a5ed4e9..b52e57a 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1948,10 +1948,10 @@
     <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Play Store no està disponible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"La configuració d\'Android TV no està disponible"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"La configuració de la tauleta no està disponible"</string>
-    <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"La configuració del telèfon no està disponible"</string>
+    <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Configuració del telèfon no disponible"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No es pot accedir a aquesta aplicació al teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al dispositiu Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho a la tauleta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al telèfon."</string>
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permet l\'accés únic"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permetis"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació pugui accedir a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Els registres del dispositiu inclouen informació sobre tot allò que passa al teu dispositiu. Les aplicacions poden utilitzar aquests registres per detectar i corregir problemes.\n\nÉs possible que alguns registres continguin informació sensible; per això només has de donar-hi accés a les aplicacions de confiança. \n\nEncara que no permetis que aquesta aplicació accedeixi a tots els registres del dispositiu, podrà accedir als seus propis registres. És possible que el fabricant del dispositiu també tingui accés a alguns registres o a informació del teu dispositiu.\n\nObtén més informació a g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No tornis a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vol mostrar porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edita"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 21b147d..e7af3c0 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Povolit jednorázový přístup"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nepovolovat"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Do protokolů zařízení se zaznamenává, co se na zařízení děje. Aplikace tyto protokoly mohou používat k vyhledání a odstranění problémů.\n\nNěkteré protokoly mohou zahrnovat citlivé údaje. Přístup k protokolům zařízení proto povolte pouze aplikacím, kterým důvěřujete. \n\nPokud této aplikaci nepovolíte přístup ke všem protokolům zařízení, bude mít stále přístup ke svým vlastním protokolům. Výrobce zařízení může mít stále přístup k některým protokolům nebo informacím na vašem zařízení.\n\nDalší informace najdete na stránce g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Příště nezobrazovat"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikace <xliff:g id="APP_0">%1$s</xliff:g> chce zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Upravit"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index dc9d746..43ee44f 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Einmaligen Zugriff zulassen"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Nicht zulassen"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen, daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugriff auf einige Protokolle oder Informationen auf deinem Gerät."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"In Geräteprotokollen wird aufgezeichnet, welche Aktionen auf deinem Gerät ausgeführt werden. Apps können diese Protokolle verwenden, um Probleme zu finden und zu beheben.\n\nEinige Protokolle enthalten unter Umständen vertrauliche Informationen. Daher solltest du nur vertrauenswürdigen Apps den Zugriff auf alle Geräteprotokolle erlauben. \n\nWenn du dieser App keinen Zugriff auf alle Geräteprotokolle gewährst, kann sie trotzdem auf ihre eigenen Protokolle zugreifen. Dein Gerätehersteller hat möglicherweise auch Zugang zu einigen Protokollen oder Informationen auf deinem Gerät.\n\nWeitere Informationen findest du unter g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Nicht mehr anzeigen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> möchte Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzeigen"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Bearbeiten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 1fcf94e..9ee2e49 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -2290,7 +2290,7 @@
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται στο παρασκήνιο. Πατήστε για διαχείριση της χρήσης της μπαταρίας."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> μπορεί να επηρεάσει τη διάρκεια ζωής μπαταρίας. Πατήστε για έλεγχο των ενεργών εφαρμογών."</string>
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Έλεγχος ενεργών εφαρμογών"</string>
-    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Δεν είναι δυνατή η πρόσβαση στην κάμερα του τηλεφώνου από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Δεν είναι δυνατή η πρόσβαση στην κάμερα τηλεφώνου από το <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Δεν είναι δυνατή η πρόσβαση στην κάμερα του tablet από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Δεν είναι δυνατή η πρόσβαση σε αυτό το στοιχείο κατά τη ροή. Δοκιμάστε στο τηλέφωνό σας."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Προεπιλογή συστήματος"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 0969a5f..90f1006 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -2052,8 +2052,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir el acceso una vez"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"No permitir"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, aún podrá acceder a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Los registros del dispositivo documentan lo que sucede en tu dispositivo. Las aplicaciones pueden usar estos registros para encontrar y solucionar problemas.\n\nComo algunos registros pueden contener información sensible, es mejor que solo permitas que accedan a ellos las aplicaciones en las que confíes. \n\nAunque no permitas que esta aplicación acceda a todos los registros del dispositivo, podrá seguir accediendo a sus propios registros. El fabricante de tu dispositivo aún puede acceder a algunos registros o información de tu dispositivo.\n\nObtén más información en g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"No volver a mostrar"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quiere mostrar fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index e79cacb..f11e893 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Luba ühekordne juurdepääs"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ära luba"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Seadmelogid jäädvustavad, mis teie seadmes toimub. Rakendused saavad neid logisid kasutada probleemide tuvastamiseks ja lahendamiseks.\n\nMõned logid võivad sisaldada tundlikku teavet, seega lubage juurdepääs kõigile seadmelogidele ainult rakendustele, mida usaldate. \n\nKui te ei luba sellel rakendusel kõigile seadmelogidele juurde pääseda, pääseb see siiski juurde oma logidele. Teie seadme tootja võib teie seadmes siiski teatud logidele või teabele juurde pääseda.\n\nLugege lisateavet aadressil g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ära kuva uuesti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Rakendus <xliff:g id="APP_0">%1$s</xliff:g> soovib näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muuda"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 954c683..15b25ad 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -524,9 +524,9 @@
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Wi-Fi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen die aplikazioei multidifusio-helbideak erabilita, ez tableta soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Multidifusio-helbideak erabiliz wifi-sare bateko gailu guztiei (ez bakarrik Android TV gailuari) bidalitako paketeak jasotzeko baimena ematen die aplikazioei. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Wi-Fi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen die aplikazioei multidifusio-helbideak erabilita, ez telefonoa soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
-    <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"atzitu Bluetooth-aren ezarpenak"</string>
+    <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"atzitu Bluetootharen ezarpenak"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Tokiko Bluetooth tableta konfiguratzea eta urruneko gailuak detektatzea eta haiekin parekatzea baimentzen die aplikazioei."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Android TV gailuan Bluetooth-a konfiguratzeko eta urruneko gailuak hautemateko eta haiekin parekatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Android TV gailuan Bluetootha konfiguratzeko eta urruneko gailuak hautemateko eta haiekin parekatzeko baimena ematen die aplikazioei."</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Tokiko Bluetooth telefonoa konfiguratzea eta urruneko gailuak detektatzea eta haiekin parekatzea baimentzen die aplikazioei."</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAX sarera konektatzea eta deskonektatzea"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX gaituta dagoen zehazteko eta konektatutako WiMAX sareei buruzko informazioa ikusteko baimena ematen die aplikazioei."</string>
@@ -1310,7 +1310,7 @@
   <string-array name="network_switch_type_name">
     <item msgid="2255670471736226365">"datu-konexioa"</item>
     <item msgid="5520925862115353992">"Wifia"</item>
-    <item msgid="1055487873974272842">"Bluetooth-a"</item>
+    <item msgid="1055487873974272842">"Bluetootha"</item>
     <item msgid="1616528372438698248">"Ethernet"</item>
     <item msgid="9177085807664964627">"VPNa"</item>
   </string-array>
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eman behin erabiltzeko baimena"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ez eman baimenik"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea.\n\nLortu informazio gehiago g.co/android/devicelogs helbidean."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ez erakutsi berriro"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> aplikazioak <xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakutsi nahi ditu"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editatu"</string>
@@ -2110,7 +2109,7 @@
     <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> kalkulu-orria"</string>
     <string name="mime_type_presentation" msgid="1145384236788242075">"Aurkezpena"</string>
     <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> aurkezpena"</string>
-    <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth-ak aktibatuta jarraituko du hegaldi moduan"</string>
+    <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetoothak aktibatuta jarraituko du hegaldi moduan"</string>
     <string name="car_loading_profile" msgid="8219978381196748070">"Kargatzen"</string>
     <string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} eta beste # fitxategi}other{{file_name} eta beste # fitxategi}}"</string>
     <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Ez dago edukia partekatzeko pertsona gomendaturik"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 424efe1..e192475 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"مجاز کردن دسترسی یک‌باره"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازه ندادن"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏گزارش‌های دستگاه آنچه را در دستگاهتان رخ می‌دهد ثبت می‌کند. برنامه‌ها می‌توانند از این گزارش‌ها برای پیدا کردن مشکلات و رفع آن‌ها استفاده کنند.\n\nبرخی‌از گزارش‌ها ممکن است حاوی اطلاعات حساس باشند، بنابراین فقط به برنامه‌های مورداعتمادتان اجازه دسترسی به همه گزارش‌های دستگاه را بدهید. \n\nاگر به این برنامه اجازه ندهید به همه گزارش‌های دستگاه دسترسی داشته باشد، همچنان می‌تواند به گزارش‌های خودش دسترسی داشته باشد. سازنده دستگاه نیز ممکن است همچنان بتواند به برخی‌از گزارش‌ها یا اطلاعات دستگاهتان دسترسی داشته باشد.\n\nاطلاعات بیشتر: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوباره نشان داده نشود"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> می‌خواهد تکه‌های <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ویرایش"</string>
@@ -2291,7 +2290,7 @@
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> در پس‌زمینه درحال اجرا است. برای مدیریت مصرف باتری ضربه بزنید."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ممکن است بر عمر باتری تأثیر بگذارد. برای مرور برنامه‌های فعال، ضربه بزنید."</string>
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"بررسی برنامه‌های فعال"</string>
-    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"نمی‌توان از <xliff:g id="DEVICE">%1$s</xliff:g> شما به دوربین تلفن دسترسی داشت"</string>
+    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"از <xliff:g id="DEVICE">%1$s</xliff:g> به دوربین تلفن دسترسی ندارید"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"نمی‌توان از <xliff:g id="DEVICE">%1$s</xliff:g> شما به دوربین رایانه لوحی دسترسی داشت"</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"درحین جاری‌سازی، نمی‌توانید به آن دسترسی داشته باشید. دسترسی به آن را در تلفنتان امتحان کنید."</string>
     <string name="system_locale_title" msgid="711882686834677268">"پیش‌فرض سیستم"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 3f2b4ce..5c1335c 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Salli kertaluonteinen pääsy"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Älä salli"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Laitteen tapahtumat tallentuvat laitelokeihin. Niiden avulla sovellukset voivat löytää ja korjata ongelmia.\n\nJotkin lokit voivat sisältää arkaluontoista tietoa, joten salli pääsy kaikkiin laitelokeihin vain sovelluksille, joihin luotat. \n\nJos et salli tälle sovellukselle pääsyä kaikkiin laitelokeihin, sillä on kuitenkin pääsy sen omiin lokeihin. Laitteen valmistajalla voi olla pääsy joihinkin lokeihin tai tietoihin laitteella.\n\nLue lisää osoitteessa g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Älä näytä uudelleen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> haluaa näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Muokkaa"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 067388d..104808b 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1950,15 +1950,15 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Paramètres Android TV non accessibles"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Paramètres de la tablette non accessibles"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Paramètres du téléphone non accessibles"</string>
-    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre appareil Android TV à la place."</string>
-    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre tablette à la place."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre téléphone à la place."</string>
+    <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre appareil Android TV à la place."</string>
+    <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre tablette à la place."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre téléphone à la place."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Cette application demande une sécurité supplémentaire. Essayez sur votre appareil Android TV à la place."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Cette application demande une sécurité supplémentaire. Essayez sur votre tablette à la place."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Cette application demande une sécurité supplémentaire. Essayez sur votre téléphone à la place."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre appareil Android TV à la place."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre tablette à la place."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Impossible d\'accéder à ce contenu sur votre appareil <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre téléphone à la place."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre appareil Android TV à la place."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre tablette à la place."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez sur votre téléphone à la place."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et pourrait ne pas fonctionner correctement. Essayez de vérifier les mises à jour ou communiquez avec son développeur."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Vérifier la présence de mises à jour"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 204c658..7f099b0 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2052,8 +2052,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Autoriser un accès unique"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne pas autoriser"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre les problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Les journaux enregistrent ce qui se passe sur votre appareil. Les applis peuvent les utiliser pour rechercher et résoudre des problèmes.\n\nCertains journaux pouvant contenir des infos sensibles, autorisez uniquement les applis de confiance à accéder à tous les journaux de l\'appareil. \n\nSi vous refusez à cette appli l\'accès à tous les journaux de l\'appareil, elle a quand même accès aux siens. Le fabricant de l\'appareil peut accéder à certains journaux ou certaines infos sur votre appareil.\n\nEn savoir plus sur g.co/android/devicelogs"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne plus afficher"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> souhaite afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifier"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index b092453..9b68186 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Permitir acceso unha soa vez"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Non permitir"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Os rexistros do dispositivo dan conta do que ocorre neste. As aplicacións poden usalos para buscar problemas e solucionalos.\n\nAlgúns poden conter información confidencial, polo que che recomendamos que só permitas que accedan a todos os rexistros do dispositivo as aplicacións nas que confíes. \n\nEsta aplicación pode acceder aos seus propios rexistros aínda que non lle permitas acceder a todos. É posible que o fabricante do dispositivo teña acceso a algúns rexistros ou á información do teu dispositivo.\n\nConsulta máis información en g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Non amosar outra vez"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> quere mostrar fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Editar"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index cb0e30d..79faaac 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1948,7 +1948,7 @@
     <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Play Store उपलब्ध नहीं है"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Android TV की सेटिंग उपलब्ध नहीं हैं"</string>
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"टैबलेट की सेटिंग उपलब्ध नहीं हैं"</string>
-    <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"फ़ोन की सेटिंग उपलब्ध नहीं हैं"</string>
+    <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"फ़ोन की सेटिंग उपलब्ध नहीं है"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने Android TV डिवाइस पर कोशिश करें."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने टैबलेट पर कोशिश करें."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने फ़ोन पर कोशिश करें."</string>
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक बार ऐक्सेस करने की अनुमति दें"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति न दें"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिवाइस लॉग में, आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें ठीक करने के लिए करते हैं.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस लॉग का ऐक्सेस दें. \n\nअगर इस ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी यह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिवाइस लॉग में आपके डिवाइस पर की गई कार्रवाइयां रिकॉर्ड होती हैं. ऐप्लिकेशन, इन लॉग का इस्तेमाल गड़बड़ियां ढूंढने और उन्हें सही करने के लिए करता है.\n\nकुछ लॉग में संवेदनशील जानकारी हो सकती है. इसलिए, सिर्फ़ भरोसेमंद ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस दें. \n\nअगर किसी ऐप्लिकेशन को डिवाइस के सभी लॉग का ऐक्सेस नहीं दिया जाता है, तब भी वह डिवाइस पर अपने लॉग को ऐक्सेस कर सकता है. डिवाइस को बनाने वाली कंपनी अब भी डिवाइस के कुछ लॉग या जानकारी को ऐक्सेस कर सकती है.\n\nज़्यादा जानने के लिए, g.co/android/devicelogs पर जाएं."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फिर से न दिखाएं"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>, <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाना चाहता है"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"बदलाव करें"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index c335648..2e78dfd 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Թույլատրել մեկանգամյա մուտքը"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Չթույլատրել"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Այն, ինչ տեղի է ունենում ձեր սարքում, գրանցվում է սարքի մատյաններում։ Հավելվածները կարող են դրանք օգտագործել անսարքությունները հայտնաբերելու և վերացնելու նպատակով։\n\nՔանի որ որոշ մատյաններ անձնական տեղեկություններ են պարունակում, խորհուրդ ենք տալիս հասանելի դարձնել ձեր սարքի բոլոր մատյանները միայն այն հավելվածներին, որոնց վստահում եք։ \n\nԵթե այս հավելվածին նման թույլտվություն չեք տվել, դրան նախկինի պես հասանելի կլինեն իր մատյանները։ Հնարավոր է՝ ձեր սարքի արտադրողին ևս հասանելի լինեն սարքի որոշ մատյաններ և տեղեկություններ։\n\nՄանրամասների համար այցելեք g.co/android/devicelogs էջ։"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Այլևս ցույց չտալ"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> հավելվածն ուզում է ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Փոփոխել"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 405717c..41b3959 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Izinkan akses satu kali"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Jangan izinkan"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Log perangkat merekam hal-hal yang terjadi di perangkat Anda. Aplikasi dapat menggunakan log ini untuk menemukan dan memperbaiki masalah.\n\nBeberapa log mungkin berisi info sensitif, jadi hanya izinkan aplikasi yang Anda percayai untuk mengakses semua log perangkat. \n\nJika Anda tidak mengizinkan aplikasi ini mengakses semua log perangkat, aplikasi masih dapat mengakses log-nya sendiri. Produsen perangkat masih dapat mengakses beberapa log atau info di perangkat Anda.\n\nPelajari lebih lanjut di g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Jangan tampilkan lagi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ingin menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 743a215..9396d89 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leyfa aðgang í eitt skipti"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ekki leyfa"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Annálar tækisins skrá það sem gerist í tækinu. Forrit geta notað þessa annála til að finna og lagfæra vandamál.\n\nTilteknir annálar innihalda viðkvæmar upplýsingar og því skaltu einungis veita forritum sem þú treystir aðgang að öllum annálum tækisins. \n\nEf þú veitir þessu forriti ekki aðgang að öllum annálum tækisins hefur það áfram aðgang að eigin annálum. Framleiðandi tækisins getur þó hugsanlega opnað tiltekna annála eða upplýsingar í tækinu.\n\nNánar á g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ekki sýna aftur"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Breyta"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 0e32d33..c0d2526 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1942,7 +1942,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non disponibile"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"È necessaria l\'autorizzazione"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Autorizzazione necessaria"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Fotocamera non disponibile"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continua sul telefono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfono non disponibile"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 57176a6..59408a7 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"הרשאת גישה חד-פעמית"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"אין אישור"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל יומני המכשיר רק לאפליקציות מהימנות. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏ביומני המכשיר מתועדת הפעילות במכשיר. האפליקציות יכולות להשתמש ביומנים האלה כדי למצוא בעיות ולפתור אותן.\n\nהמידע בחלק מהיומנים יכול להיות רגיש, לכן יש לתת הרשאת גישה לכל היומנים של המכשיר רק לאפליקציות שסומכים עליהן. \n\nגם אם האפליקציה הזו לא תקבל הרשאת גישה לכל יומני המכשיר, היא תוכל לגשת ליומנים שלה. יכול להיות שליצרן המכשיר עדיין תהיה גישה לחלק מהיומנים או למידע במכשיר שלך.\n\nמידע נוסף זמין בכתובת g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"אין להציג שוב"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> רוצה להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"עריכה"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 4c5fe51..5db972e 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бір реттік пайдалану рұқсатын беру"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Рұқсат бермеу"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Журналдарға құрылғыда не болып жатқаны жазылады. Қолданбалар осы журналдарды қате тауып, түзету үшін пайдаланады.\n\nКейбір журналдарда құпия ақпарат болуы мүмкін. Сондықтан барлық құрылғының журналын пайдалану рұқсаты тек сенімді қолданбаларға берілуі керек. \n\nБұл қолданбаға барлық құрылғының журналын пайдалануға рұқсат бермесеңіз де, ол өзінің журналдарын пайдалана береді. Құрылғы өндірушісі де құрылғыдағы кейбір журналдарды немесе ақпаратты пайдалануы мүмкін.\n\nТолық ақпарат: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Қайта көрсетілмесін"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасы <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсеткісі келеді"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Өзгерту"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index eb8f7572..73f7d86 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"អនុញ្ញាតឱ្យចូលប្រើ​ម្ដង"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"មិនអនុញ្ញាត"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"កំណត់ហេតុឧបករណ៍កត់ត្រាអ្វីដែលកើតឡើងនៅលើឧបករណ៍របស់អ្នក។ កម្មវិធីអាចប្រើកំណត់ហេតុទាំងនេះដើម្បីស្វែងរក និងដោះស្រាយបញ្ហាបាន។\n\nកំណត់ហេតុមួយចំនួនអាចមានព័ត៌មានរសើប ដូច្នេះគួរអនុញ្ញាតឱ្យចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់សម្រាប់តែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះ។ \n\nប្រសិនបើអ្នកមិនអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើកំណត់ហេតុឧបករណ៍ទាំងអស់ទេ វានៅតែអាចចូលប្រើកំណត់ហេតុរបស់វាផ្ទាល់បាន។ ក្រុមហ៊ុន​ផលិត​ឧបករណ៍របស់អ្នក​ប្រហែលជា​នៅតែអាចចូលប្រើ​កំណត់ហេតុ ឬព័ត៌មានមួយចំនួន​នៅលើឧបករណ៍​របស់អ្នក​បានដដែល។\n\nស្វែងយល់បន្ថែមតាមរយៈ g.co/android/devicelogs។"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ចង់​បង្ហាញ​ស្ថិតិ​ប្រើប្រាស់​របស់ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"កែ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 82ab262..9ab64e6 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"일회성 액세스 허용"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"허용 안함"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"기기 로그에 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그는 민감한 정보를 포함할 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 일부 로그 또는 기기 내 정보에 액세스할 수도 있습니다."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"기기 로그에는 기기에서 발생한 상황이 기록됩니다. 앱은 문제를 찾고 해결하는 데 이 로그를 사용할 수 있습니다.\n\n일부 로그에 민감한 정보가 포함될 수 있으므로 신뢰할 수 있는 앱만 모든 기기 로그에 액세스하도록 허용하세요. \n\n앱에 전체 기기 로그에 대한 액세스 권한을 부여하지 않아도, 앱이 자체 로그에는 액세스할 수 있습니다. 기기 제조업체에서 기기 내 일부 로그 또는 정보에 액세스할 수도 있습니다.\n\ng.co/android/devicelogs에서 자세히 알아보세요."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"다시 표시 안함"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하려고 합니다"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"수정"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index cdbad18..29e4616 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Бир жолу жеткиликтүү кылуу"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Жок"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Түзмөктө аткарылган бардык аракеттер түзмөктүн таржымалдарында сакталып калат. Колдонмолор бул таржымалдарды колдонуп, маселелерди оңдошот.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан түзмөктөгү бардык таржымалдарды ишенимдүү колдонмолорго гана пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Түзмөктө жасалган нерселердин баары таржымалга сактала берет. Колдонмолор анын жардамы менен көйгөйлөрдү аныктап, оңдоп турушат.\n\nАйрым таржымалдарда купуя маалымат болушу мүмкүн, андыктан ишенимдүү колдонмолорго гана түзмөктөгү бардык таржымалдарды пайдаланууга уруксат бериңиз. \n\nЭгер бул колдонмого түзмөктөгү айрым таржымалдарга кирүүгө тыюу салсаңыз, ал өзүнүн таржымалдарын пайдалана берет. Түзмөктү өндүрүүчү түзмөгүңүздөгү айрым таржымалдарды же маалыматты көрө берет.\n\nКеңири маалымат: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Экинчи көрүнбөсүн"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосу <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөткөнү жатат"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Түзөтүү"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 004a0a8..ab0ba3c 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Leisti vienkartinę prieigą"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neleisti"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Įrenginyje įrašoma, kas įvyksta jūsų įrenginyje. Programos gali naudoti šiuos žurnalus, kad surastų ir išspręstų problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Įrenginyje įrašoma, kas jame įvyksta. Programos gali naudoti šiuos žurnalus, kai reikia surasti ir išspręsti problemas.\n\nKai kuriuose žurnaluose gali būti neskelbtinos informacijos, todėl visus įrenginio žurnalus leiskite pasiekti tik programoms, kuriomis pasitikite. \n\nJei neleisite šiai programai pasiekti visų įrenginio žurnalų, ji vis tiek galės pasiekti savo žurnalus. Įrenginio gamintojui vis tiek gali būti leidžiama pasiekti tam tikrus žurnalus ar informaciją jūsų įrenginyje.\n\nSužinokite daugiau adresu g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Daugiau neberodyti"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"„<xliff:g id="APP_0">%1$s</xliff:g>“ nori rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redaguoti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index ef1a95f..688d6b2 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2052,8 +2052,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Atļaut vienreizēju piekļuvi"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Neatļaut"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Ierīces žurnālos tiek reģistrēti ierīces procesi un notikumi. Lietotņu izstrādātāji var izmantot šos žurnālus, lai atrastu un izlabotu problēmas savās lietotnēs.\n\nDažos žurnālos var būt ietverta sensitīva informācija, tāpēc atļaujiet tikai uzticamām lietotnēm piekļūt visiem ierīces žurnāliem. \n\nJa neatļausiet šai lietotnei piekļūt visiem ierīces žurnāliem, lietotnes izstrādātājs joprojām varēs piekļūt pašas lietotnes žurnāliem. Iespējams, ierīces ražotājs joprojām varēs piekļūt noteiktiem žurnāliem vai informācijai jūsu ierīcē.\n\nŠeit varat uzzināt vairāk: g.co/android/devicelogs"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Vairs nerādīt"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Lietotne <xliff:g id="APP_0">%1$s</xliff:g> vēlas rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Rediģēt"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index abb2740..5027a7f 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"တစ်ခါသုံး ဝင်ခွင့်ပေးရန်"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ခွင့်မပြုပါ"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် စက်မှတ်တမ်းအားလုံးကို ယုံကြည်ရသည့် အက်ပ်များကိုသာ သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ၎င်း၏ကိုယ်ပိုင်မှတ်တမ်းကို သုံးနိုင်ဆဲဖြစ်သည်။ သင့်စက်ရှိ အချို့မှတ်တမ်းများ (သို့) အချက်အလက်များကို သင့်စက်ထုတ်လုပ်သူက သုံးနိုင်ပါသေးသည်။"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"သင့်စက်ရှိ အဖြစ်အပျက်များကို စက်မှတ်တမ်းများက မှတ်တမ်းတင်သည်။ အက်ပ်များက ပြဿနာများ ရှာဖွေပြီးဖြေရှင်းရန် ဤမှတ်တမ်းများကို သုံးနိုင်သည်။\n\nအချို့မှတ်တမ်းများတွင် သတိထားရမည့်အချက်အလက်များ ပါဝင်နိုင်သဖြင့် ယုံကြည်ရသည့် အက်ပ်များကိုသာ စက်မှတ်တမ်းအားလုံး သုံးခွင့်ပြုပါ။ \n\nဤအက်ပ်ကို စက်မှတ်တမ်းအားလုံး သုံးခွင့်မပြုသော်လည်း ၎င်းက ကိုယ်ပိုင်မှတ်တမ်းများ သုံးနိုင်သေးသည်။ သင့်စက်ရှိ မှတ်တမ်း (သို့) အချက်အလက်အချို့ကို စက်ထုတ်လုပ်သူက သုံးနိုင်သေးသည်။\n\ng.co/android/devicelogs တွင် ပိုမိုလေ့လာပါ။"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"နောက်ထပ်မပြပါနှင့်"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> သည် <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များကို ပြသလိုသည်"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"တည်းဖြတ်ရန်"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 60b0eea..3397643 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"एक पटक प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"अनुमति नदिनुहोस्"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"डिभाइसका लगले तपाईंको डिभाइसमा भएका विभिन्न गतिविधिको अभिलेख राख्छ। एपहरू यी लगका आधारमा समस्या पत्ता लगाउन र तिनको समाधान गर्न सक्छन्।\n\nकेही लगहरूमा संवेदनशील जानकारी समावेश हुन सक्ने भएकाले आफूले भरोसा गर्ने एपलाई मात्र डिभाइसका सबै लग हेर्ने अनुमति दिनुहोस्। \n\nतपाईंले यो एपलाई डिभाइसका सबै लग हेर्ने अनुमति दिनुभएन भने पनि यसले आफ्नै लग भने हेर्न सक्छ। तपाईंको डिभाइसको उत्पादकले पनि तपाईंको डिभाइसमा भएका केही लग वा जानकारी हेर्न सक्ने सम्भावना हुन्छ।\n\nतपाईं यस सम्बन्धमा थप जान्न चाहनुहुन्छ भने g.co/android/devicelogs मा जानुहोस्।"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"फेरि नदेखाइयोस्"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ले <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन चाहन्छ"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"सम्पादन गर्नुहोस्"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index d01e430..86115d5 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Разрешить разовый доступ"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Запретить"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Не исключено, что некоторые журналы или сведения на вашем устройстве будут по-прежнему доступны его производителю."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"В журналы записывается информация о том, что происходит на устройстве. Приложения могут использовать их, чтобы находить и устранять неполадки.\n\nТак как некоторые журналы могут содержать конфиденциальную информацию, доступ ко всем журналам следует предоставлять только тем приложениям, которым вы доверяете. \n\nЕсли вы не предоставите такой доступ этому приложению, оно по-прежнему сможет просматривать свои журналы. Также некоторые журналы или сведения на вашем устройстве могут быть доступны его производителю.\n\nПодробнее: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Больше не показывать"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Приложение \"<xliff:g id="APP_0">%1$s</xliff:g>\" запрашивает разрешение на показ фрагментов приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"."</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Изменить"</string>
@@ -2293,7 +2292,7 @@
     <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"\"<xliff:g id="APP">%1$s</xliff:g>\" работает в фоновом режиме. Нажмите, чтобы изменить настройки, связанные с расходом заряда батареи."</string>
     <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Приложение \"<xliff:g id="APP">%1$s</xliff:g>\" может влиять на время работы батареи. Нажмите, чтобы увидеть активные приложения."</string>
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Проверить активные приложения"</string>
-    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"У устройства \"<xliff:g id="DEVICE">%1$s</xliff:g>\" нет доступа к камере телефона."</string>
+    <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"У устройства <xliff:g id="DEVICE">%1$s</xliff:g> нет доступа к камере телефона."</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"У устройства \"<xliff:g id="DEVICE">%1$s</xliff:g>\" нет доступа к камере планшета."</string>
     <string name="vdm_secure_window" msgid="161700398158812314">"Этот контент недоступен во время трансляции. Используйте телефон."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Системные настройки по умолчанию"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index ac0a3f6..76f6608 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"එක් වරක් ප්‍රවේශය ඉඩ දෙන්න"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"ඉඩ නොදෙන්න"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"උපාංග ලොග ඔබේ උපාංගයෙහි සිදු වන දේ වාර්තා කරයි. ගැටලු සොයා ගැනීමට සහ නිරාකරණයට යෙදුම්වලට මෙම ලොග භාවිතා කළ හැක.\n\nසමහර ලොගවල සංවේදී තතු අඩංගු විය හැකි බැවින්, ඔබ විශ්වාස කරන යෙදුම්වලට පමණක් සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න. \n\nඔබ මෙම යෙදුමට සියලු උපාංග ලොග වෙත ප්‍රවේශ වීමට ඉඩ නොදෙන්නේ නම්, එයට තවමත් එහිම ලොග වෙත ප්‍රවේශ විය හැක. ඔබේ උපාංග නිෂ්පාදකයාට තවමත් ඔබේ උපාංගයෙහි සමහර ලොග හෝ තතු වෙත ප්‍රවේශ විය හැක.\n\ng.co/android/devicelogs හි දී තව දැන ගන්න."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"නැවත නොපෙන්වන්න"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට අවශ්‍යයි"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"සංස්කරණය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 5a9e0c1..898126e 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1953,13 +1953,13 @@
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Nastavenia telefónu nie sú k dispozícii"</string>
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste namiesto toho použiť zariadenie Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste namiesto toho použiť tablet."</string>
-    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste namiesto toho použiť telefón."</string>
+    <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste použiť telefón."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste namiesto toho použiť zariadenie Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste namiesto toho použiť tablet."</string>
-    <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste namiesto toho použiť telefón."</string>
+    <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste použiť telefón."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte prístup k tomuto obsahu. Skúste namiesto toho použiť zariadenie Android TV."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte prístup k tomuto obsahu. Skúste namiesto toho použiť tablet."</string>
-    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte prístup k tomuto obsahu. Skúste namiesto toho použiť telefón."</string>
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte prístup k tomuto obsahu. Skúste použiť telefón."</string>
     <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Táto aplikácia bola zostavená pre staršiu verziu Androidu a nemusí správne fungovať. Skúste skontrolovať dostupnosť aktualizácií alebo kontaktovať vývojára."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Skontrolovať dostupnosť aktualizácie"</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové správy."</string>
@@ -2294,6 +2294,6 @@
     <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Skontrolovať aktívne aplikácie"</string>
     <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere telefónu"</string>
     <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere tabletu"</string>
-    <string name="vdm_secure_window" msgid="161700398158812314">"K tomuto obsahu nie je počas streamovania prístup. Skúste namiesto toho použiť telefón."</string>
+    <string name="vdm_secure_window" msgid="161700398158812314">"K tomuto obsahu nie je počas streamovania prístup. Skúste použiť telefón."</string>
     <string name="system_locale_title" msgid="711882686834677268">"Predvolené systémom"</string>
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 56e1b5c..a5cd174 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Dovoli enkratni dostop"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ne dovoli"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"V dnevnikih naprave se beleži dogajanje v napravi. Aplikacije lahko te dnevnike uporabijo za iskanje in odpravljanje težav.\n\nNekateri dnevniki morda vsebujejo občutljive podatke, zato dostop do vseh dnevnikov naprave omogočite le aplikacijam, ki jim zaupate. \n\nČe tej aplikaciji ne dovolite dostopa do vseh dnevnikov naprave, bo aplikacija kljub temu lahko dostopala do svojih dnevnikov. Proizvajalec naprave bo morda lahko kljub temu dostopal do nekaterih dnevnikov ali podatkov v napravi.\n\nPreberite več o tem na g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ne prikaži več"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Uredi"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index bb55457..9ef7770 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Lejo qasjen vetëm për një herë"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Mos lejo"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Evidencat e pajisjes regjistrojnë çfarë ndodh në pajisjen tënde. Aplikacionet mund t\'i përdorin këto evidenca për të gjetur dhe rregulluar problemet.\n\nDisa evidenca mund të përmbajnë informacione delikate, ndaj lejo vetëm aplikacionet që u beson të kenë qasje te të gjitha evidencat e pajisjes. \n\nNëse nuk e lejon këtë aplikacion që të ketë qasje te të gjitha evidencat e pajisjes, ai mund të vazhdojë të ketë qasje tek evidencat e tij. Prodhuesi i pajisjes sate mund të jetë ende në gjendje që të ketë qasje te disa evidenca ose informacione në pajisjen tënde.\n\nMëso më shumë në g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Mos e shfaq më"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> dëshiron të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Modifiko"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 92e0cd6..d7675f5 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tillåt engångsåtkomst"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Tillåt inte"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"I enhetsloggar registreras vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Enhetsloggar registrerar vad som händer på enheten. Appar kan använda dessa loggar för att hitta och åtgärda problem.\n\nVissa loggar kan innehålla känsliga uppgifter, så du ska bara bevilja appar du litar på åtkomst till alla enhetsloggar. \n\nEn app har åtkomst till sina egna loggar även om du inte ger den åtkomst till alla enhetsloggar. Enhetens tillverkare kan fortfarande ha åtkomst till vissa loggar eller viss information på enheten.\n\nLäs mer på g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Visa inte igen"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> vill kunna visa bitar av <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Redigera"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index fae3b07..5a7538e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Ruhusu ufikiaji wa mara moja"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Usiruhusu"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Kumbukumbu za kifaa zinarekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Kumbukumbu za kifaa hurekodi kinachofanyika kwenye kifaa chako. Programu zinaweza kutumia kumbukumbu hizi ili kutambua na kurekebisha hitilafu.\n\nBaadhi ya kumbukumbu huenda zikawa na taarifa nyeti, hivyo ruhusu tu programu unazoziamini kufikia kumbukumbu zote za kifaa. \n\nIwapo hutaruhusu programu hii ifikie kumbukumbu zote za kifaa, bado inaweza kufikia kumbukumbu zake yenyewe. Huenda mtengenezaji wa kifaa chako bado akaweza kufikia baadhi ya kumbukumbu au taarifa zilizopo kwenye kifaa chako.\n\nPata maelezo zaidi kwenye g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Usionyeshe tena"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> inataka kuonyesha vipengee <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Badilisha"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index c946894..131ccd0 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"వన్-టైమ్ యాక్సెస్‌ను అనుమతించండి"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"అనుమతించవద్దు"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"మీ పరికరంలో జరిగే దాన్ని పరికర లాగ్‌లు రికార్డ్ చేస్తాయి. సమస్యలను కనుగొని, పరిష్కరించడానికి యాప్‌లు ఈ లాగ్‌లను ఉపయోగిస్తాయి.\n\nకొన్ని లాగ్‌లలో గోప్యమైన సమాచారం ఉండవచ్చు, కాబట్టి మీరు విశ్వసించే యాప్‌లను మాత్రమే అన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. \n\nఅన్ని పరికర లాగ్‌లను యాక్సెస్ చేయడానికి మీరు ఈ యాప్‌ను అనుమతించకపోతే, అది తన స్వంత లాగ్‌లను ఇప్పటికి యాక్సెస్ చేయగలదు. మీ పరికర తయారీదారు ఇప్పటికీ మీ పరికరంలో కొన్ని లాగ్‌లు లేదా సమాచారాన్ని యాక్సెస్ చేయగలరు.\n\ng.co/android/devicelogsలో దీని గురించి మరింత తెలుసుకోండి."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"మళ్లీ చూపవద్దు"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> స్లైస్‌లను చూపించాలనుకుంటోంది"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ఎడిట్ చేయండి"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index c9c2d11..27183f2 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Tek seferlik erişim izni ver"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"İzin verme"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Cihaz günlükleri, cihazınızda olanları kaydeder. Uygulamalar, sorunları bulup düzeltmek için bu günlükleri kullanabilir.\n\nBazı günlükler hassas bilgiler içerebileceği için yalnızca güvendiğiniz uygulamaların tüm cihaz günlüklerine erişmesine izin verin. \n\nBu uygulamanın tüm cihaz günlüklerine erişmesine izin vermeseniz de kendi günlüklerine erişmeye devam edebilir. Ayrıca, cihaz üreticiniz de cihazınızdaki bazı günlüklere veya bilgilere erişmeye devam edebilir.\n\nDaha fazla bilgiyi g.co/android/devicelogs sayfasında bulabilirsiniz."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Bir daha gösterme"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> uygulaması, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermek istiyor"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Düzenle"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 163c629..d2485c7 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -2053,8 +2053,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Надати доступ лише цього разу"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Не дозволяти"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"У журналах пристрою реєструється все, що відбувається на ньому. За допомогою цих журналів додатки можуть виявляти й усувати проблеми.\n\nДеякі журнали можуть містити конфіденційні дані, тому надавати доступ до всіх журналів пристрою слід лише надійним додаткам. \n\nЯкщо додаток не має доступу до всіх журналів пристрою, він усе одно може використовувати власні журнали. Виробник вашого пристрою все одно може використовувати деякі журнали чи інформацію на ньому.\n\nДокладніше: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Більше не показувати"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> хоче показати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Редагувати"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 94f5ab5..9d9bb1a 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"یک وقتی رسائی کی اجازت دیں"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"اجازت نہ دیں"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنے بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"‏آپ کے آلے پر جو ہوتا ہے آلے کے لاگز اسے ریکارڈ کر لیتے ہیں۔ ایپس ان لاگز کا استعمال مسائل کو تلاش کرنے اور ان کو حل کرنے کے لیے کر سکتی ہیں۔\n\nکچھ لاگز میں حساس معلومات شامل ہو سکتی ہیں، اس لیے صرف اپنی بھروسے مند ایپس کو ہی آلے کے تمام لاگز تک رسائی کی اجازت دیں۔ \n\nاگر آپ اس ایپ کو آلے کے تمام لاگز تک رسائی کی اجازت نہیں دیتے ہیں تب بھی یہ اپنے لاگز تک رسائی حاصل کر سکتی ہے۔ آپ کے آلے کا مینوفیکچرر اب بھی آپ کے آلے پر کچھ لاگز یا معلومات تک رسائی حاصل کر سکتا ہے۔\n\ng.co/android/devicelogs پر مزید جانیں۔"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"دوبارہ نہ دکھائیں"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانا چاہتی ہے"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"ترمیم کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 92b23152..bad432d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Bir matalik foydalanishga ruxsat berish"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Rad etish"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Qurilma jurnaliga qurilma bilan yuz bergan hodisalar qaydlari yoziladi. Ilovalar bu jurnal qaydlari yordamida muammolarni topishi va bartaraf qilishi mumkin.\n\nAyrim jurnal qaydlarida maxfiy axborotlar yozilishi mumkin, shu sababli qurilmadagi barcha jurnal qaydlariga ruxsatni faqat ishonchli ilovalarga bering. \n\nBu ilovaga qurilmadagi barcha jurnal qaydlariga ruxsat berilmasa ham, u oʻzining jurnalini ocha oladi. Qurilma ishlab chiqaruvchisi ham ayrim jurnallar yoki qurilma haqidagi axborotlarni ocha oladi.\n\nBatafsil: g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Boshqa chiqmasin"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasi <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatish uchun ruxsat so‘ramoqda"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Tahrirlash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index f78c64e..66f0bdd 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Cho phép truy cập một lần"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Không cho phép"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào toàn bộ nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Nhật ký thiết bị ghi lại những hoạt động diễn ra trên thiết bị. Các ứng dụng có thể dùng các nhật ký này để tìm và khắc phục sự cố.\n\nMột số nhật ký có thể chứa thông tin nhạy cảm, vì vậy, bạn chỉ nên cấp quyền truy cập vào mọi nhật ký thiết bị cho những ứng dụng mà mình tin cậy. \n\nNếu bạn không cho phép ứng dụng này truy cập vào toàn bộ nhật ký thiết bị, thì ứng dụng vẫn có thể truy cập vào nhật ký của chính nó. Nhà sản xuất thiết bị vẫn có thể truy cập vào một số nhật ký hoặc thông tin trên thiết bị của bạn.\n\nTìm hiểu thêm tại g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Không hiện lại"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> muốn hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Chỉnh sửa"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 023d9d1..d46bdc8 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允许访问一次"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允许"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"设备日志会记录设备上发生的活动。应用可以使用这些日志查找和修复问题。\n\n部分日志可能包含敏感信息,因此请仅允许您信任的应用访问所有设备日志。\n\n如果您不授予此应用访问所有设备日志的权限,它仍然可以访问自己的日志。您的设备制造商可能仍然能够访问设备上的部分日志或信息。\n\n如需了解详情,请访问 g.co/android/devicelogs。"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不再显示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"“<xliff:g id="APP_0">%1$s</xliff:g>”想要显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"编辑"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index c67f9fa..81a0413 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許存取一次"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"裝置記錄會記下裝置上的活動。應用程式可透過這些記錄找出並修正問題。\n\n部分記錄可能包含敏感資料,因此請只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄,且裝置製造商可能仍可存取裝置上的部分記錄或資料。"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可使用這些記錄找出並修正問題。\n\n有些記錄可能包含敏感資料,因此建議只允許信任的應用程式存取所有裝置記錄。\n\n如果不允許此應用程式存取所有裝置記錄,此應用程式仍能存取自己的記錄。您的裝置製造商可能仍可存取裝置上的一些記錄或資料。\n\n詳情請瀏覽 g.co/android/devicelogs。"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 3da603d..43de5ba 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"允許一次性存取"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"不允許"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"系統會透過裝置記錄記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n某些記錄可能含有機密資訊,因此請勿讓不信任的應用程式存取所有裝置記錄。\n\n即使你不允許這個應用程式存取所有裝置記錄,這個應用程式仍能存取自己的記錄,而且裝置製造商或許仍可存取裝置的某些記錄或資訊。"</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"裝置記錄會記下裝置上的活動。應用程式可以根據這些記錄找出問題並進行修正。\n\n由於某些記錄可能含有機密資訊,建議只讓信任的應用程式存取所有裝置記錄。\n\n如果你不允許這個應用程式存取所有裝置記錄,這個應用程式仍可存取屬於自己的記錄,而裝置製造商也或許還是可以存取裝置的某些記錄或資訊。\n\n請參閱以下網址瞭解詳情:g.co/android/devicelogs。"</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"不要再顯示"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"「<xliff:g id="APP_0">%1$s</xliff:g>」想要顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"編輯"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 9028854..22a7110 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2051,8 +2051,7 @@
     <string name="log_access_confirmation_allow" msgid="5302517782599389507">"Vumela ukufinyelela kwesikhathi esisodwa"</string>
     <string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ungavumeli"</string>
     <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho."</string>
-    <!-- no translation found for log_access_confirmation_body (7379536536425265262) -->
-    <skip />
+    <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Amalogu edivayisi arekhoda okwenzekayo kudivayisi yakho. Ama-app angasebenzisa lawa malogu ukuze athole futhi alungise izinkinga.\n\nAmanye amalogu angase aqukathe ulwazi olubucayi, ngakho vumela ama-app owathembayo kuphela ukuthi afinyelele wonke amalogu edivayisi. \n\nUma ungayivumeli le app ukuthi ifinyelele wonke amalogu wedivayisi, isengakwazi ukufinyelela amalogu wayo. Umkhiqizi wedivayisi yakho usengakwazi ukufinyelela amanye amalogu noma ulwazi kudivayisi yakho.\n\nFunda kabanzi ku-g.co/android/devicelogs."</string>
     <string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ungabonisi futhi"</string>
     <string name="slices_permission_request" msgid="3677129866636153406">"I-<xliff:g id="APP_0">%1$s</xliff:g> ifuna ukubonisa izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="screenshot_edit" msgid="7408934887203689207">"Hlela"</string>
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
new file mode 100644
index 0000000..e3b3ea7
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.input
+
+import android.hardware.BatteryState
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import com.android.server.testutils.any
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager.InputDeviceBatteryListener].
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:InputDeviceBatteryListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class InputDeviceBatteryListenerTest {
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var testLooper: TestLooper
+    private var registeredListener: IInputDeviceBatteryListener? = null
+    private val monitoredDevices = mutableListOf<Int>()
+    private lateinit var executor: Executor
+    private lateinit var inputManager: InputManager
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        testLooper = TestLooper()
+        executor = HandlerExecutor(Handler(testLooper.looper))
+        registeredListener = null
+        monitoredDevices.clear()
+        inputManager = InputManager.resetInstance(iInputManagerMock)
+
+        // Handle battery listener registration.
+        doAnswer {
+            val deviceId = it.getArgument(0) as Int
+            val listener = it.getArgument(1) as IInputDeviceBatteryListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered battery listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            if (monitoredDevices.contains(deviceId)) {
+                fail("Trying to start monitoring a device that was already being monitored")
+            }
+            monitoredDevices.add(deviceId)
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
+
+        // Handle battery listener being unregistered.
+        doAnswer {
+            val deviceId = it.getArgument(0) as Int
+            val listener = it.getArgument(1) as IInputDeviceBatteryListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            if (!monitoredDevices.remove(deviceId)) {
+                fail("Trying to stop monitoring a device that is not being monitored")
+            }
+            if (monitoredDevices.isEmpty()) {
+                registeredListener = null
+            }
+        }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
+    }
+
+    @After
+    fun tearDown() {
+        InputManager.clearInstance()
+    }
+
+    private fun notifyBatteryStateChanged(
+        deviceId: Int,
+        isPresent: Boolean = true,
+        status: Int = BatteryState.STATUS_FULL,
+        capacity: Float = 1.0f,
+        eventTime: Long = 12345L
+    ) {
+        registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
+    }
+
+    @Test
+    fun testListenerIsNotifiedCorrectly() {
+        var callbackCount = 0
+
+        // Add a battery listener to monitor battery changes.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
+                deviceId: Int, eventTime: Long, batteryState: BatteryState ->
+            callbackCount++
+            assertEquals(1, deviceId)
+            assertEquals(true, batteryState.isPresent)
+            assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
+            assertEquals(0.5f, batteryState.capacity)
+            assertEquals(8675309L, eventTime)
+        }
+
+        // Adding the listener should register the callback with InputManagerService.
+        assertNotNull(registeredListener)
+        assertTrue(monitoredDevices.contains(1))
+
+        // Notifying battery change for a different device should not trigger the listener.
+        notifyBatteryStateChanged(deviceId = 2)
+        testLooper.dispatchAll()
+        assertEquals(0, callbackCount)
+
+        // Notifying battery change for the registered device will notify the listener.
+        notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
+            BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+        // Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
+        // while the second callback monitors devices 2 and 3.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+        assertEquals(1, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
+        assertEquals(2, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
+        assertEquals(3, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
+        assertEquals(3, monitoredDevices.size)
+
+        // Notifying battery change for each of the devices should trigger the registered callbacks.
+        notifyBatteryStateChanged(deviceId = 1)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+        assertEquals(0, callbackCount2)
+
+        notifyBatteryStateChanged(deviceId = 2)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        notifyBatteryStateChanged(deviceId = 3)
+        testLooper.dispatchNext()
+        testLooper.dispatchNext()
+        assertEquals(2, callbackCount1)
+        assertEquals(2, callbackCount2)
+
+        // Stop monitoring devices 1 and 2.
+        inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
+        assertEquals(2, monitoredDevices.size)
+        inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
+        assertEquals(1, monitoredDevices.size)
+
+        // Ensure device 3 continues to be monitored.
+        notifyBatteryStateChanged(deviceId = 3)
+        testLooper.dispatchNext()
+        testLooper.dispatchNext()
+        assertEquals(3, callbackCount1)
+        assertEquals(3, callbackCount2)
+
+        // Stop monitoring all devices.
+        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
+        assertEquals(1, monitoredDevices.size)
+        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
+        assertEquals(0, monitoredDevices.size)
+    }
+
+    @Test
+    fun testAdditionalListenersNotifiedImmediately() {
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+        // Add a battery listener and send the latest battery state.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+        assertEquals(1, monitoredDevices.size)
+        notifyBatteryStateChanged(deviceId = 1)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+
+        // Add a second listener for the same device that already has the latest battery state.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
+        assertEquals(1, monitoredDevices.size)
+
+        // Ensure that this listener is notified immediately.
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
index 56a7070..2861428 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
@@ -46,14 +46,14 @@
     }
 
     @Override
-    protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
-        return new LoadLabelWrapperTask(info, holder);
+    protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+        return new LoadLabelWrapperTask(info);
     }
 
     class LoadLabelWrapperTask extends LoadLabelTask {
 
-        protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
-            super(dri, holder);
+        protected LoadLabelWrapperTask(DisplayResolveInfo dri) {
+            super(dri);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec..9ee52cc 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -100,6 +100,21 @@
     out: ["wm_shell_protolog.json"],
 }
 
+genrule {
+    name: "protolog.json.gz",
+    srcs: [":generate-wm_shell_protolog.json"],
+    out: ["wmshell.protolog.json.gz"],
+    cmd: "$(location minigzip) -c < $(in) > $(out)",
+    tools: ["minigzip"],
+}
+
+prebuilt_etc {
+    name: "wmshell.protolog.json.gz",
+    system_ext_specific: true,
+    src: ":protolog.json.gz",
+    filename_from_src: true,
+}
+
 // End ProtoLog
 
 java_library {
@@ -123,9 +138,6 @@
     resource_dirs: [
         "res",
     ],
-    java_resources: [
-        ":generate-wm_shell_protolog.json",
-    ],
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
new file mode 100644
index 0000000..e029358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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 static android.content.Intent.EXTRA_DOCK_STATE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Provides information about the docked state of the device.
+ */
+@WMSingleton
+public class DockStateReader {
+
+    private static final IntentFilter DOCK_INTENT_FILTER = new IntentFilter(
+            Intent.ACTION_DOCK_EVENT);
+
+    private final Context mContext;
+
+    @Inject
+    public DockStateReader(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * @return True if the device is docked and false otherwise.
+     */
+    public boolean isDocked() {
+        Intent dockStatus = mContext.registerReceiver(/* receiver */ null, DOCK_INTENT_FILTER);
+        if (dockStatus != null) {
+            int dockState = dockStatus.getIntExtra(EXTRA_DOCK_STATE,
+                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            return dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+        }
+        return false;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 235fd9c..6627de5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -37,6 +37,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
@@ -109,6 +110,7 @@
     private final SyncTransactionQueue mSyncQueue;
     private final ShellExecutor mMainExecutor;
     private final Lazy<Transitions> mTransitionsLazy;
+    private final DockStateReader mDockStateReader;
 
     private CompatUICallback mCallback;
 
@@ -127,7 +129,8 @@
             DisplayImeController imeController,
             SyncTransactionQueue syncQueue,
             ShellExecutor mainExecutor,
-            Lazy<Transitions> transitionsLazy) {
+            Lazy<Transitions> transitionsLazy,
+            DockStateReader dockStateReader) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -138,6 +141,7 @@
         mTransitionsLazy = transitionsLazy;
         mCompatUIHintsState = new CompatUIHintsState();
         shellInit.addInitCallback(this::onInit, this);
+        mDockStateReader = dockStateReader;
     }
 
     private void onInit() {
@@ -315,7 +319,8 @@
         return new LetterboxEduWindowManager(context, taskInfo,
                 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
                 mTransitionsLazy.get(),
-                this::onLetterboxEduDismissed);
+                this::onLetterboxEduDismissed,
+                mDockStateReader);
     }
 
     private void onLetterboxEduDismissed() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 35f1038..867d0ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -34,6 +34,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
 import com.android.wm.shell.transition.Transitions;
@@ -88,19 +89,21 @@
      */
     private final int mDialogVerticalMargin;
 
+    private final DockStateReader mDockStateReader;
+
     public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions,
-            Runnable onDismissCallback) {
+            Runnable onDismissCallback, DockStateReader dockStateReader) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
-                onDismissCallback, new LetterboxEduAnimationController(context));
+                onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader);
     }
 
     @VisibleForTesting
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
-            LetterboxEduAnimationController animationController) {
+            LetterboxEduAnimationController animationController, DockStateReader dockStateReader) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -111,6 +114,7 @@
                 Context.MODE_PRIVATE);
         mDialogVerticalMargin = (int) mContext.getResources().getDimension(
                 R.dimen.letterbox_education_dialog_margin);
+        mDockStateReader = dockStateReader;
     }
 
     @Override
@@ -130,13 +134,15 @@
 
     @Override
     protected boolean eligibleToShowLayout() {
+        // - The letterbox education should not be visible if the device is docked.
         // - If taskbar education is showing, the letterbox education shouldn't be shown for the
         //   given task until the taskbar education is dismissed and the compat info changes (then
         //   the controller will create a new instance of this class since this one isn't eligible).
         // - If the layout isn't null then it was previously showing, and we shouldn't check if the
         //   user has seen the letterbox education before.
-        return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
-                || !getHasSeenLetterboxEducation());
+        return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
+                && (mLayout != null || !getHasSeenLetterboxEducation())
+                && !mDockStateReader.isDocked();
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c25bbbf..7d0c4bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -46,6 +46,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -69,7 +70,6 @@
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -192,33 +192,16 @@
 
     @WMSingleton
     @Provides
-    static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
-            Context context,
-            ShellInit shellInit,
-            ShellCommandHandler shellCommandHandler,
-            SyncTransactionQueue syncTransactionQueue,
-            DisplayController displayController,
-            DisplayInsetsController displayInsetsController,
-            Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasksOptional,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler
-    ) {
-        return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
-                syncTransactionQueue, displayController, displayInsetsController,
-                unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
     static CompatUIController provideCompatUIController(Context context,
             ShellInit shellInit,
             ShellController shellController,
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
-            @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
+            @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
+            DockStateReader dockStateReader) {
         return new CompatUIController(context, shellInit, shellController, displayController,
-                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
+                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
+                dockStateReader);
     }
 
     @WMSingleton
@@ -309,17 +292,17 @@
     // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
     @BindsOptionalOf
     @DynamicOverride
-    abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
+    abstract FullscreenTaskListener optionalFullscreenTaskListener();
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener<?> provideFullscreenTaskListener(
-            @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
+    static FullscreenTaskListener provideFullscreenTaskListener(
+            @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
             Optional<RecentTasksController> recentTasksOptional,
-            Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
+            Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
         if (fullscreenTaskListener.isPresent()) {
             return fullscreenTaskListener.get();
         } else {
@@ -333,7 +316,7 @@
     //
 
     @BindsOptionalOf
-    abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+    abstract WindowDecorViewModel optionalWindowDecorViewModel();
 
     //
     // Unfold transition
@@ -781,12 +764,11 @@
             DisplayInsetsController displayInsetsController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
-            FullscreenTaskListener<?> fullscreenTaskListener,
+            FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
             Optional<FreeformComponents> freeformComponents,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 37a50b6..461d7dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -55,7 +55,7 @@
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
 import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -182,7 +182,7 @@
 
     @WMSingleton
     @Provides
-    static WindowDecorViewModel<?> provideWindowDecorViewModel(
+    static WindowDecorViewModel provideWindowDecorViewModel(
             Context context,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
@@ -208,7 +208,7 @@
     @Provides
     @DynamicOverride
     static FreeformComponents provideFreeformComponents(
-            FreeformTaskListener<?> taskListener,
+            FreeformTaskListener taskListener,
             FreeformTaskTransitionHandler transitionHandler,
             FreeformTaskTransitionObserver transitionObserver) {
         return new FreeformComponents(
@@ -217,18 +217,18 @@
 
     @WMSingleton
     @Provides
-    static FreeformTaskListener<?> provideFreeformTaskListener(
+    static FreeformTaskListener provideFreeformTaskListener(
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
         ShellInit init = FreeformComponents.isFreeformEnabled(context)
                 ? shellInit
                 : null;
-        return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
+        return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
                 windowDecorViewModel);
     }
 
@@ -237,7 +237,7 @@
     static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
     }
 
@@ -247,10 +247,9 @@
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            FullscreenTaskListener<?> fullscreenTaskListener,
-            FreeformTaskListener<?> freeformTaskListener) {
+            WindowDecorViewModel windowDecorViewModel) {
         return new FreeformTaskTransitionObserver(
-                context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
+                context, shellInit, transitions, windowDecorViewModel);
     }
 
     //
@@ -620,6 +619,28 @@
     }
 
     //
+    // Kids mode
+    //
+    @WMSingleton
+    @Provides
+    static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+            Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            SyncTransactionQueue syncTransactionQueue,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            Optional<UnfoldAnimationController> unfoldAnimationController,
+            Optional<RecentTasksController> recentTasksOptional,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler
+    ) {
+        return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+                syncTransactionQueue, displayController, displayInsetsController,
+                unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
+    }
+
+    //
     // Misc
     //
 
@@ -630,6 +651,7 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DefaultMixedHandler defaultMixedHandler,
+            KidsModeTaskOrganizer kidsModeTaskOrganizer,
             Optional<DesktopModeController> desktopModeController) {
         return new Object();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 99739c4..dc7ed15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
@@ -29,13 +30,18 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,7 +61,8 @@
 /**
  * Handles windowing changes when desktop mode system setting changes
  */
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+        Transitions.TransitionHandler {
 
     private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -89,6 +96,7 @@
         if (DesktopModeStatus.isActive(mContext)) {
             updateDesktopModeActive(true);
         }
+        mTransitions.addHandler(this);
     }
 
     @Override
@@ -157,7 +165,7 @@
     /**
      * Show apps on desktop
      */
-    public void showDesktopApps() {
+    WindowContainerTransaction showDesktopApps() {
         ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
         ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -173,7 +181,12 @@
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
-        mShellTaskOrganizer.applyTransaction(wct);
+
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mShellTaskOrganizer.applyTransaction(wct);
+        }
+
+        return wct;
     }
 
     /**
@@ -195,6 +208,35 @@
                 .configuration.windowConfiguration.getWindowingMode();
     }
 
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // This handler should never be the sole handler, so should not animate anything.
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+
+        // Only do anything if we are in desktop mode and opening a task/app
+        if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+            return null;
+        }
+
+        WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+        if (wct == null) {
+            wct = new WindowContainerTransaction();
+        }
+        wct.merge(showDesktopApps(), true /* transfer */);
+        wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+        return wct;
+    }
+
     /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index e2d5a49..f82a346 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -19,12 +19,8 @@
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
 
 import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -41,31 +37,26 @@
 /**
  * {@link ShellTaskOrganizer.TaskListener} for {@link
  * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
- *
- * @param <T> the type of window decoration instance
  */
-public class FreeformTaskListener<T extends AutoCloseable>
-        implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
-    private final WindowDecorViewModel<T> mWindowDecorationViewModel;
+    private final WindowDecorViewModel mWindowDecorationViewModel;
 
-    private final SparseArray<State<T>> mTasks = new SparseArray<>();
-    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+    private final SparseArray<State> mTasks = new SparseArray<>();
 
-    private static class State<T extends AutoCloseable> {
+    private static class State {
         RunningTaskInfo mTaskInfo;
         SurfaceControl mLeash;
-        T mWindowDecoration;
     }
 
     public FreeformTaskListener(
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
-            WindowDecorViewModel<T> windowDecorationViewModel) {
+            WindowDecorViewModel windowDecorationViewModel) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -80,13 +71,18 @@
 
     @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        if (mTasks.get(taskInfo.taskId) != null) {
+            throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
                 taskInfo.taskId);
-        final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+        final State state = new State();
+        state.mTaskInfo = taskInfo;
+        state.mLeash = leash;
+        mTasks.put(taskInfo.taskId, state);
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            state.mWindowDecoration =
-                    mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+            mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
             t.apply();
         }
 
@@ -97,28 +93,8 @@
         }
     }
 
-    private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
-        State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            updateTaskInfo(taskInfo);
-            return state;
-        }
-
-        state = new State<>();
-        state.mTaskInfo = taskInfo;
-        state.mLeash = leash;
-        mTasks.put(taskInfo.taskId, state);
-
-        return state;
-    }
-
     @Override
     public void onTaskVanished(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            // This is possible if the transition happens before this method.
-            return;
-        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
@@ -129,26 +105,18 @@
             mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
         }
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // Save window decorations of closing tasks so that we can hand them over to the
-            // transition system if this method happens before the transition. In case where the
-            // transition didn't happen, it'd be cleared when the next transition finished.
-            if (state.mWindowDecoration != null) {
-                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
-            }
-            return;
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
         }
-        releaseWindowDecor(state.mWindowDecoration);
     }
 
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
-        final State<T> state = updateTaskInfo(taskInfo);
+        final State state = mTasks.get(taskInfo.taskId);
+        state.mTaskInfo = taskInfo;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
                 taskInfo.taskId);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
-        }
+        mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
 
         if (DesktopModeStatus.IS_SUPPORTED) {
             if (taskInfo.isVisible) {
@@ -159,15 +127,6 @@
         }
     }
 
-    private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
-        }
-        state.mTaskInfo = taskInfo;
-        return state;
-    }
-
     @Override
     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
         b.setParent(findTaskSurface(taskId));
@@ -186,103 +145,6 @@
         return mTasks.get(taskId).mLeash;
     }
 
-    /**
-     * Creates a window decoration for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @return {@code true} if it creates the window decoration; {@code false} otherwise
-     */
-    boolean createWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        if (state.mWindowDecoration != null) {
-            return false;
-        }
-        state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
-                state.mTaskInfo, state.mLeash, startT, finishT);
-        return true;
-    }
-
-    /**
-     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
-     * left) this task listener. This is the transition system asking for the ownership.
-     *
-     * @param taskInfo the maximizing task
-     * @return the window decor of the maximizing task if any
-     */
-    T giveWindowDecoration(
-            RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        T windowDecor;
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            windowDecor = state.mWindowDecoration;
-            state.mWindowDecoration = null;
-        } else {
-            windowDecor =
-                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
-        }
-        if (windowDecor == null) {
-            return null;
-        }
-        mWindowDecorationViewModel.setupWindowDecorationForTransition(
-                taskInfo, startT, finishT, windowDecor);
-        return windowDecor;
-    }
-
-    /**
-     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @param startT the start transaction of this transition
-     * @param finishT the finish transaction of this transition
-     * @param windowDecor the window decoration to adopt
-     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
-     */
-    boolean adoptWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            @Nullable AutoCloseable windowDecor) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorationViewModel.setupWindowDecorationForTransition(
-                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
-            return true;
-        } else {
-            state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
-                    state.mTaskInfo, state.mLeash, startT, finishT);
-            return false;
-        }
-    }
-
-    void onTaskTransitionFinished() {
-        if (mWindowDecorOfVanishedTasks.size() == 0) {
-            return;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Clearing window decors of vanished tasks. There could be visual defects "
-                + "if any of them is used later in transitions.");
-        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
-            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
-        }
-        mWindowDecorOfVanishedTasks.clear();
-    }
-
-    private void releaseWindowDecor(T windowDecor) {
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
-        }
-    }
-
     @Override
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index fd4c85fa..04fc79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -46,14 +46,14 @@
         implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
 
     private final Transitions mTransitions;
-    private final WindowDecorViewModel<?> mWindowDecorViewModel;
+    private final WindowDecorViewModel mWindowDecorViewModel;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
     public FreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         mTransitions = transitions;
         mWindowDecorViewModel = windowDecorViewModel;
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 17d6067..f4888fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -16,13 +16,9 @@
 
 package com.android.wm.shell.freeform;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import android.app.ActivityManager;
 import android.content.Context;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -31,9 +27,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -47,23 +43,19 @@
  * be a part of transitions.
  */
 public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
-    private static final String TAG = "FreeformTO";
-
     private final Transitions mTransitions;
-    private final FreeformTaskListener<?> mFreeformTaskListener;
-    private final FullscreenTaskListener<?> mFullscreenTaskListener;
+    private final WindowDecorViewModel mWindowDecorViewModel;
 
-    private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();
+    private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
+            new HashMap<>();
 
     public FreeformTaskTransitionObserver(
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            FullscreenTaskListener<?> fullscreenTaskListener,
-            FreeformTaskListener<?> freeformTaskListener) {
+            WindowDecorViewModel windowDecorViewModel) {
         mTransitions = transitions;
-        mFreeformTaskListener = freeformTaskListener;
-        mFullscreenTaskListener = fullscreenTaskListener;
+        mWindowDecorViewModel = windowDecorViewModel;
         if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -80,7 +72,7 @@
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT) {
-        final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+        final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
         final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
@@ -110,92 +102,40 @@
                     onOpenTransitionReady(change, startT, finishT);
                     break;
                 case WindowManager.TRANSIT_CLOSE: {
-                    onCloseTransitionReady(change, windowDecors, startT, finishT);
+                    taskInfoList.add(change.getTaskInfo());
+                    onCloseTransitionReady(change, startT, finishT);
                     break;
                 }
                 case WindowManager.TRANSIT_CHANGE:
-                    onChangeTransitionReady(info.getType(), change, startT, finishT);
+                    onChangeTransitionReady(change, startT, finishT);
                     break;
             }
         }
-        if (!windowDecors.isEmpty()) {
-            mTransitionToWindowDecors.put(transition, windowDecors);
-        }
+        mTransitionToTaskInfo.put(transition, taskInfoList);
     }
 
     private void onOpenTransitionReady(
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        switch (change.getTaskInfo().getWindowingMode()){
-            case WINDOWING_MODE_FREEFORM:
-                mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
-                break;
-            case WINDOWING_MODE_FULLSCREEN:
-                mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
-                break;
-        }
+        mWindowDecorViewModel.createWindowDecoration(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
     private void onCloseTransitionReady(
             TransitionInfo.Change change,
-            ArrayList<AutoCloseable> windowDecors,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        final AutoCloseable windowDecor;
-        switch (change.getTaskInfo().getWindowingMode()) {
-            case WINDOWING_MODE_FREEFORM:
-                windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
-                        startT, finishT);
-                break;
-            case WINDOWING_MODE_FULLSCREEN:
-                windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
-                        startT, finishT);
-                break;
-            default:
-                windowDecor = null;
-        }
-        if (windowDecor != null) {
-            windowDecors.add(windowDecor);
-        }
+        mWindowDecorViewModel.setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     private void onChangeTransitionReady(
-            int type,
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        AutoCloseable windowDecor = null;
-
-        boolean adopted = false;
-        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-            windowDecor = mFreeformTaskListener.giveWindowDecoration(
-                    change.getTaskInfo(), startT, finishT);
-            if (windowDecor != null) {
-                adopted = mFullscreenTaskListener.adoptWindowDecoration(
-                        change, startT, finishT, windowDecor);
-            } else {
-                // will return false if it already has the window decor.
-                adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
-            }
-        }
-
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            windowDecor = mFullscreenTaskListener.giveWindowDecoration(
-                    change.getTaskInfo(), startT, finishT);
-            if (windowDecor != null) {
-                adopted = mFreeformTaskListener.adoptWindowDecoration(
-                        change, startT, finishT, windowDecor);
-            } else {
-                // will return false if it already has the window decor.
-                adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
-            }
-        }
-
-        if (!adopted) {
-            releaseWindowDecor(windowDecor);
-        }
+        mWindowDecorViewModel.setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     @Override
@@ -203,43 +143,32 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
-        if (windowDecorsOfMerged == null) {
+        final List<ActivityManager.RunningTaskInfo> infoOfMerged =
+                mTransitionToTaskInfo.get(merged);
+        if (infoOfMerged == null) {
             // We are adding window decorations of the merged transition to them of the playing
             // transition so if there is none of them there is nothing to do.
             return;
         }
-        mTransitionToWindowDecors.remove(merged);
+        mTransitionToTaskInfo.remove(merged);
 
-        final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
-        if (windowDecorsOfPlaying != null) {
-            windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
+        final List<ActivityManager.RunningTaskInfo> infoOfPlaying =
+                mTransitionToTaskInfo.get(playing);
+        if (infoOfPlaying != null) {
+            infoOfPlaying.addAll(infoOfMerged);
         } else {
-            mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
+            mTransitionToTaskInfo.put(playing, infoOfMerged);
         }
     }
 
     @Override
     public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
-        final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
-                transition, Collections.emptyList());
-        mTransitionToWindowDecors.remove(transition);
+        final List<ActivityManager.RunningTaskInfo> taskInfo =
+                mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
+        mTransitionToTaskInfo.remove(transition);
 
-        for (AutoCloseable windowDecor : windowDecors) {
-            releaseWindowDecor(windowDecor);
-        }
-        mFullscreenTaskListener.onTaskTransitionFinished();
-        mFreeformTaskListener.onTaskTransitionFinished();
-    }
-
-    private static void releaseWindowDecor(AutoCloseable windowDecor) {
-        if (windowDecor == null) {
-            return;
-        }
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
+        for (int i = 0; i < taskInfo.size(); ++i) {
+            mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 76e296b..75a4091 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -22,13 +22,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Point;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
-import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,23 +43,20 @@
   * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
  * @param <T> the type of window decoration instance
   */
-public class FullscreenTaskListener<T extends AutoCloseable>
-        implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FullscreenTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
 
-    private final SparseArray<State<T>> mTasks = new SparseArray<>();
-    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+    private final SparseArray<State> mTasks = new SparseArray<>();
 
-    private static class State<T extends AutoCloseable> {
+    private static class State {
         RunningTaskInfo mTaskInfo;
         SurfaceControl mLeash;
-        T mWindowDecoration;
     }
     private final SyncTransactionQueue mSyncQueue;
     private final Optional<RecentTasksController> mRecentTasksOptional;
-    private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
     /**
      * This constructor is used by downstream products.
      */
@@ -75,7 +69,7 @@
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
             Optional<RecentTasksController> recentTasksOptional,
-            Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
+            Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mRecentTasksOptional = recentTasksOptional;
@@ -98,21 +92,21 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                 taskInfo.taskId);
         final Point positionInParent = taskInfo.positionInParent;
-        final State<T> state = new State();
+        final State state = new State();
         state.mLeash = leash;
         state.mTaskInfo = taskInfo;
         mTasks.put(taskInfo.taskId, state);
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateRecentsForVisibleFullscreenTask(taskInfo);
+        boolean createdWindowDecor = false;
         if (mWindowDecorViewModelOptional.isPresent()) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            state.mWindowDecoration =
-                    mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
-                            leash, t, t);
+            createdWindowDecor = mWindowDecorViewModelOptional.get()
+                    .createWindowDecoration(taskInfo, leash, t, t);
             t.apply();
         }
-        if (state.mWindowDecoration == null) {
+        if (!createdWindowDecor) {
             mSyncQueue.runInSync(t -> {
                 // Reset several properties back to fullscreen (PiP, for example, leaves all these
                 // properties in a bad state).
@@ -127,12 +121,11 @@
 
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
+        final State state = mTasks.get(taskInfo.taskId);
         final Point oldPositionInParent = state.mTaskInfo.positionInParent;
         state.mTaskInfo = taskInfo;
-        if (state.mWindowDecoration != null) {
-            mWindowDecorViewModelOptional.get().onTaskInfoChanged(
-                    state.mTaskInfo, state.mWindowDecoration);
+        if (mWindowDecorViewModelOptional.isPresent()) {
+            mWindowDecorViewModelOptional.get().onTaskInfoChanged(state.mTaskInfo);
         }
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -147,160 +140,13 @@
 
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state == null) {
-            // This is possible if the transition happens before this method.
-            return;
-        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // Save window decorations of closing tasks so that we can hand them over to the
-            // transition system if this method happens before the transition. In case where the
-            // transition didn't happen, it'd be cleared when the next transition finished.
-            if (state.mWindowDecoration != null) {
-                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
-            }
-            return;
-        }
-        releaseWindowDecor(state.mWindowDecoration);
-    }
-
-    /**
-     * Creates a window decoration for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @return {@code true} if a decoration was actually created.
-     */
-    public boolean createWindowDecoration(TransitionInfo.Change change,
-            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        if (!mWindowDecorViewModelOptional.isPresent()) return false;
-        if (state.mWindowDecoration != null) {
-            // Already has a decoration.
-            return false;
-        }
-        T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
-                state.mTaskInfo, state.mLeash, startT, finishT);
-        if (newWindowDecor != null) {
-            state.mWindowDecoration = newWindowDecor;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
-     *
-     * @param change the change of this task transition that needs to have the task layer as the
-     *               leash
-     * @param startT the start transaction of this transition
-     * @param finishT the finish transaction of this transition
-     * @param windowDecor the window decoration to adopt
-     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
-     */
-    public boolean adoptWindowDecoration(
-            TransitionInfo.Change change,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            @Nullable AutoCloseable windowDecor) {
-        if (!mWindowDecorViewModelOptional.isPresent()) {
-            return false;
-        }
-        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
-        state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
-                windowDecor);
-        if (state.mWindowDecoration != null) {
-            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
-                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
-            return true;
-        } else {
-            T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
-                    state.mTaskInfo, state.mLeash, startT, finishT);
-            if (newWindowDecor != null) {
-                state.mWindowDecoration = newWindowDecor;
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Clear window decors of vanished tasks.
-     */
-    public void onTaskTransitionFinished() {
-        if (mWindowDecorOfVanishedTasks.size() == 0) {
-            return;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Clearing window decors of vanished tasks. There could be visual defects "
-                + "if any of them is used later in transitions.");
-        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
-            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
-        }
-        mWindowDecorOfVanishedTasks.clear();
-    }
-
-    /**
-     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
-     * left) this task listener. This is the transition system asking for the ownership.
-     *
-     * @param taskInfo the maximizing task
-     * @return the window decor of the maximizing task if any
-     */
-    public T giveWindowDecoration(
-            ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT) {
-        T windowDecor;
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            windowDecor = state.mWindowDecoration;
-            state.mWindowDecoration = null;
-        } else {
-            windowDecor =
-                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
-        }
-        if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
-            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
-                    taskInfo, startT, finishT, windowDecor);
-        }
-
-        return windowDecor;
-    }
-
-    private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl leash) {
-        State<T> state = mTasks.get(taskInfo.taskId);
-        if (state != null) {
-            updateTaskInfo(taskInfo);
-            return state;
-        }
-
-        state = new State<T>();
-        state.mTaskInfo = taskInfo;
-        state.mLeash = leash;
-        mTasks.put(taskInfo.taskId, state);
-
-        return state;
-    }
-
-    private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
-        final State<T> state = mTasks.get(taskInfo.taskId);
-        state.mTaskInfo = taskInfo;
-        return state;
-    }
-
-    private void releaseWindowDecor(T windowDecor) {
-        if (windowDecor == null) {
-            return;
-        }
-        try {
-            windowDecor.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to release window decoration.", e);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+        if (mWindowDecorViewModelOptional.isPresent()) {
+            mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
         }
     }
 
@@ -342,6 +188,4 @@
     public String toString() {
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
-
-
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 4def15d..2624ee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -59,10 +59,15 @@
     /**
      * Sets listener to get pinned stack animation callbacks.
      */
-    oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+    oneway void setPipAnimationListener(IPipAnimationListener listener) = 3;
 
     /**
      * Sets the shelf height and visibility.
      */
     oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+
+    /**
+     * Sets the next pip animation type to be the alpha animation.
+     */
+    oneway void setPipAnimationTypeToAlpha() = 5;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index c06881a..72b9dd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -51,15 +51,6 @@
     }
 
     /**
-     * Sets both shelf visibility and its height.
-     *
-     * @param visible visibility of shelf.
-     * @param height  to specify the height for shelf.
-     */
-    default void setShelfHeight(boolean visible, int height) {
-    }
-
-    /**
      * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
      *
      * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
@@ -68,14 +59,6 @@
     default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
 
     /**
-     * Set the pinned stack with {@link PipAnimationController.AnimationType}
-     *
-     * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
-     */
-    default void setPinnedStackAnimationType(int animationType) {
-    }
-
-    /**
      * Called when showing Pip menu.
      */
     default void showPictureInPictureMenu() {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index af47666..3345b1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -23,6 +23,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
@@ -1065,13 +1066,6 @@
         }
 
         @Override
-        public void setShelfHeight(boolean visible, int height) {
-            mMainExecutor.execute(() -> {
-                PipController.this.setShelfHeight(visible, height);
-            });
-        }
-
-        @Override
         public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
             mMainExecutor.execute(() -> {
                 PipController.this.setOnIsInPipStateChangedListener(callback);
@@ -1079,13 +1073,6 @@
         }
 
         @Override
-        public void setPinnedStackAnimationType(int animationType) {
-            mMainExecutor.execute(() -> {
-                PipController.this.setPinnedStackAnimationType(animationType);
-            });
-        }
-
-        @Override
         public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
             mMainExecutor.execute(() -> {
                 mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
@@ -1178,8 +1165,8 @@
         }
 
         @Override
-        public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
-            executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+        public void setPipAnimationListener(IPipAnimationListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
                     (controller) -> {
                         if (listener != null) {
                             mListener.register(listener);
@@ -1188,5 +1175,13 @@
                         }
                     });
         }
+
+        @Override
+        public void setPipAnimationTypeToAlpha() {
+            executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
+                    (controller) -> {
+                        controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
+                    });
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
 package com.android.wm.shell.protolog;
 
 import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.BaseProtoLogImpl;
 import com.android.internal.protolog.ProtoLogViewerConfigReader;
 import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
 
-import org.json.JSONException;
-
 
 /**
  * A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@
 public class ShellProtoLogImpl extends BaseProtoLogImpl {
     private static final String TAG = "ProtoLogImpl";
     private static final int BUFFER_CAPACITY = 1024 * 1024;
-    // TODO: Get the right path for the proto log file when we initialize the shell components
-    private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+    // TODO: find a proper location to save the protolog message file
+    private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+    private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
 
     private static ShellProtoLogImpl sServiceInstance = null;
 
@@ -111,18 +104,8 @@
     }
 
     public int startTextLogging(String[] groups, PrintWriter pw) {
-        try (InputStream is =
-                     getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
-            mViewerConfig.loadViewerConfig(is);
-            return setLogging(true /* setTextLogging */, true, pw, groups);
-        } catch (IOException e) {
-            Log.i(TAG, "Unable to load log definitions: IOException while reading "
-                    + "wm_shell_protolog. " + e);
-        } catch (JSONException e) {
-            Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
-                    + "wm_shell_protolog. " + e);
-        }
-        return -1;
+        mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+        return setLogging(true /* setTextLogging */, true, pw, groups);
     }
 
     public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@
     }
 
     private ShellProtoLogImpl() {
-        super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+        super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+                new ProtoLogViewerConfigReader());
     }
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 29d25bc..f251e9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -441,31 +442,34 @@
             return;
         }
 
-        // apply transfer starting window directly if there is no other task change. Since this
-        // is an activity->activity situation, we can detect it by selecting transitions with only
-        // 2 changes where neither are tasks and one is a starting-window recipient.
         final int changeSize = info.getChanges().size();
-        if (changeSize == 2) {
-            boolean nonTaskChange = true;
-            boolean transferStartingWindow = false;
-            for (int i = changeSize - 1; i >= 0; --i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                if (change.getTaskInfo() != null) {
-                    nonTaskChange = false;
-                    break;
-                }
-                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-                    transferStartingWindow = true;
-                }
+        boolean taskChange = false;
+        boolean transferStartingWindow = false;
+        boolean allOccluded = changeSize > 0;
+        for (int i = changeSize - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            taskChange |= change.getTaskInfo() != null;
+            transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+            if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
+                allOccluded = false;
             }
-            if (nonTaskChange && transferStartingWindow) {
-                t.apply();
-                finishT.apply();
-                // Treat this as an abort since we are bypassing any merge logic and effectively
-                // finishing immediately.
-                onAbort(transitionToken);
-                return;
-            }
+        }
+        // There does not need animation when:
+        // A. Transfer starting window. Apply transfer starting window directly if there is no other
+        // task change. Since this is an activity->activity situation, we can detect it by selecting
+        // transitions with only 2 changes where neither are tasks and one is a starting-window
+        // recipient.
+        if (!taskChange && transferStartingWindow && changeSize == 2
+                // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
+                // changes are underneath another change.
+                || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
+                && allOccluded)) {
+            t.apply();
+            finishT.apply();
+            // Treat this as an abort since we are bypassing any merge logic and effectively
+            // finishing immediately.
+            onAbort(transitionToken);
+            return;
         }
 
         final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -542,6 +546,22 @@
                 "This shouldn't happen, maybe the default handler is broken.");
     }
 
+    /**
+     * Gives every handler (in order) a chance to handle request until one consumes the transition.
+     * @return the WindowContainerTransaction given by the handler which consumed the transition.
+     */
+    public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+        for (int i = mHandlers.size() - 1; i >= 0; --i) {
+            if (mHandlers.get(i) == skip) continue;
+            WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+            if (wct != null) {
+                return wct;
+            }
+        }
+        return null;
+    }
+
     /** Special version of finish just for dealing with no-op/invalid transitions. */
     private void onAbort(IBinder transition) {
         onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -716,8 +736,8 @@
                         null /* newDisplayAreaInfo */);
             }
         }
-        active.mToken = mOrganizer.startTransition(
-                request.getType(), transitionToken, wct);
+        mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
+        active.mToken = transitionToken;
         mActiveTransitions.add(active);
     }
 
@@ -726,7 +746,7 @@
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
         final ActiveTransition active = new ActiveTransition();
         active.mHandler = handler;
-        active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
+        active.mToken = mOrganizer.startNewTransition(type, wct);
         mActiveTransitions.add(active);
         return active.mToken;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9e49b51..3df33f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,6 +26,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -46,7 +47,7 @@
  * View model for the window decoration with a caption and shadows. Works with
  * {@link CaptionWindowDecoration}.
  */
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -57,6 +58,8 @@
     private FreeformTaskTransitionStarter mTransitionStarter;
     private DesktopModeController mDesktopModeController;
 
+    private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+
     public CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
@@ -81,12 +84,12 @@
     }
 
     @Override
-    public CaptionWindowDecoration createWindowDecoration(
+    public boolean createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        if (!shouldShowWindowDecor(taskInfo)) return null;
+        if (!shouldShowWindowDecor(taskInfo)) return false;
         final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
                 mContext,
                 mDisplayController,
@@ -96,30 +99,24 @@
                 mMainHandler,
                 mMainChoreographer,
                 mSyncQueue);
+        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
         TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
         CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
-        setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
+        setupWindowDecorationForTransition(taskInfo, startT, finishT);
         setupCaptionColor(taskInfo, windowDecoration);
-        return windowDecoration;
+        return true;
     }
 
     @Override
-    public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
-        if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
-        final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
-        if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
-            return null;
-        }
-        return captionWindowDecor;
-    }
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) return;
 
-    @Override
-    public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
         decoration.relayout(taskInfo);
-
         setupCaptionColor(taskInfo, decoration);
     }
 
@@ -132,11 +129,22 @@
     public void setupWindowDecorationForTransition(
             RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            CaptionWindowDecoration decoration) {
+            SurfaceControl.Transaction finishT) {
+        final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) return;
+
         decoration.relayout(taskInfo, startT, finishT);
     }
 
+    @Override
+    public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+        final CaptionWindowDecoration decoration =
+                mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+        if (decoration == null) return;
+
+        decoration.close();
+    }
+
     private class CaptionTouchEventListener implements
             View.OnClickListener, View.OnTouchListener {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d9697d2..d7f71c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -19,8 +19,6 @@
 import android.app.ActivityManager;
 import android.view.SurfaceControl;
 
-import androidx.annotation.Nullable;
-
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 
 /**
@@ -28,10 +26,8 @@
  * customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
  * interactions with UI widgets in window decorations and send corresponding requests to system
  * servers.
- *
- * @param <T> The actual decoration type
  */
-public interface WindowDecorViewModel<T extends AutoCloseable> {
+public interface WindowDecorViewModel {
 
     /**
      * Sets the transition starter that starts freeform task transitions.
@@ -50,29 +46,19 @@
      * @param finishT the finish transaction to restore states after the transition
      * @return the window decoration object
      */
-    @Nullable T createWindowDecoration(
+    boolean createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT);
 
     /**
-     * Adopts the window decoration if possible.
-     * May be {@code null} if a window decor is not needed or the given one is incompatible.
-     *
-     * @param windowDecor the potential window decoration to adopt
-     * @return the window decoration if it can be adopted, or {@code null} otherwise.
-     */
-    @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
-
-    /**
      * Notifies a task info update on the given task, with the window decoration created previously
      * for this task by {@link #createWindowDecoration}.
      *
      * @param taskInfo the new task info of the task
-     * @param windowDecoration the window decoration created for the task
      */
-    void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+    void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
 
     /**
      * Notifies a transition is about to start about the given task to give the window decoration a
@@ -80,11 +66,16 @@
      *
      * @param startT the start transaction to be applied before the transition
      * @param finishT the finish transaction to restore states after the transition
-     * @param windowDecoration the window decoration created for the task
      */
     void setupWindowDecorationForTransition(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
-            SurfaceControl.Transaction finishT,
-            T windowDecoration);
+            SurfaceControl.Transaction finishT);
+
+    /**
+     * Destroys the window decoration of the give task.
+     *
+     * @param taskInfo the info of the task
+     */
+    void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 6292130..2fc0914 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
@@ -93,6 +94,7 @@
     private @Mock Lazy<Transitions> mMockTransitionsLazy;
     private @Mock CompatUIWindowManager mMockCompatLayout;
     private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+    private @Mock DockStateReader mDockStateReader;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -113,7 +115,7 @@
         mShellInit = spy(new ShellInit(mMockExecutor));
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
-                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
+                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index f3a8cf4..16517c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -54,6 +54,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.transition.Transitions;
 
@@ -103,6 +104,7 @@
     @Mock private SurfaceControlViewHost mViewHost;
     @Mock private Transitions mTransitions;
     @Mock private Runnable mOnDismissCallback;
+    @Mock private DockStateReader mDockStateReader;
 
     private SharedPreferences mSharedPreferences;
     @Nullable
@@ -153,6 +155,16 @@
     }
 
     @Test
+    public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
+        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+                true, /* isDocked */ true);
+
+        assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+        assertNull(windowManager.mLayout);
+    }
+
+    @Test
     public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
         LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
                 true, USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -382,17 +394,27 @@
         return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
     }
 
+    private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
+        return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
+                false, isDocked);
+    }
+
     private LetterboxEduWindowManager createWindowManager(boolean eligible,
             int userId, boolean isTaskbarEduShowing) {
+        return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
+    }
+
+    private LetterboxEduWindowManager createWindowManager(boolean eligible,
+            int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+        doReturn(isDocked).when(mDockStateReader).isDocked();
         LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
                 createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
                 createDisplayLayout(), mTransitions, mOnDismissCallback,
-                mAnimationController);
+                mAnimationController, mDockStateReader);
 
         spyOn(windowManager);
         doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
         doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
-
         return windowManager;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 0fd5cb0..7068a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,17 +17,10 @@
 package com.android.wm.shell.freeform;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
-
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
@@ -44,9 +37,9 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,9 +58,7 @@
     @Mock
     private Transitions mTransitions;
     @Mock
-    private FullscreenTaskListener<?> mFullscreenTaskListener;
-    @Mock
-    private FreeformTaskListener<?> mFreeformTaskListener;
+    private WindowDecorViewModel mWindowDecorViewModel;
 
     private FreeformTaskTransitionObserver mTransitionObserver;
 
@@ -82,7 +73,7 @@
         doReturn(pm).when(context).getPackageManager();
 
         mTransitionObserver = new FreeformTaskTransitionObserver(
-                context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener);
+                context, mShellInit, mTransitions, mWindowDecorViewModel);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
                     Runnable.class);
@@ -112,11 +103,12 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT);
+        verify(mWindowDecorViewModel).createWindowDecoration(
+                change.getTaskInfo(), change.getLeash(), startT, finishT);
     }
 
     @Test
-    public void testObtainsWindowDecorOnCloseTransition_freeform() {
+    public void testPreparesWindowDecorOnCloseTransition_freeform() {
         final TransitionInfo.Change change =
                 createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
         final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
@@ -128,7 +120,8 @@
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+        verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+                change.getTaskInfo(), startT, finishT);
     }
 
     @Test
@@ -138,17 +131,13 @@
         final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
         info.addChange(change);
 
-        final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
-
         final IBinder transition = mock(IBinder.class);
         final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
         mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
         mTransitionObserver.onTransitionStarting(transition);
 
-        verify(windowDecor, never()).close();
+        verify(mWindowDecorViewModel, never()).destroyWindowDecoration(change.getTaskInfo());
     }
 
     @Test
@@ -159,8 +148,6 @@
         info.addChange(change);
 
         final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
 
         final IBinder transition = mock(IBinder.class);
         final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -169,7 +156,7 @@
         mTransitionObserver.onTransitionStarting(transition);
         mTransitionObserver.onTransitionFinished(transition, false);
 
-        verify(windowDecor).close();
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change.getTaskInfo());
     }
 
     @Test
@@ -192,10 +179,6 @@
         final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info2.addChange(change2);
 
-        final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
-        doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change2.getTaskInfo()), any(), any());
-
         final IBinder transition2 = mock(IBinder.class);
         final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -204,7 +187,7 @@
 
         mTransitionObserver.onTransitionFinished(transition1, false);
 
-        verify(windowDecor2).close();
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
     }
 
     @Test
@@ -215,10 +198,6 @@
         final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info1.addChange(change1);
 
-        final AutoCloseable windowDecor1 = mock(AutoCloseable.class);
-        doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change1.getTaskInfo()), any(), any());
-
         final IBinder transition1 = mock(IBinder.class);
         final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
@@ -231,10 +210,6 @@
         final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
         info2.addChange(change2);
 
-        final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
-        doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change2.getTaskInfo()), any(), any());
-
         final IBinder transition2 = mock(IBinder.class);
         final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
         final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -243,48 +218,8 @@
 
         mTransitionObserver.onTransitionFinished(transition1, false);
 
-        verify(windowDecor1).close();
-        verify(windowDecor2).close();
-    }
-
-    @Test
-    public void testTransfersWindowDecorOnMaximize() {
-        final TransitionInfo.Change change =
-                createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN);
-        final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0);
-        info.addChange(change);
-
-        final AutoCloseable windowDecor = mock(AutoCloseable.class);
-        doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
-                eq(change.getTaskInfo()), any(), any());
-
-        final IBinder transition = mock(IBinder.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
-        mTransitionObserver.onTransitionStarting(transition);
-
-        verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
-        verify(mFullscreenTaskListener).adoptWindowDecoration(
-                eq(change), same(startT), same(finishT), any());
-    }
-
-    @Test
-    public void testTransfersWindowDecorOnRestoreFromMaximize() {
-        final TransitionInfo.Change change =
-                createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM);
-        final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0);
-        info.addChange(change);
-
-        final IBinder transition = mock(IBinder.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
-        mTransitionObserver.onTransitionStarting(transition);
-
-        verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
-        verify(mFreeformTaskListener).adoptWindowDecoration(
-                eq(change), same(startT), same(finishT), any());
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change1.getTaskInfo());
+        verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
     }
 
     private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c6492be..db9136d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -45,7 +45,6 @@
 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.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -67,10 +66,12 @@
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
+import android.window.IWindowContainerToken;
 import android.window.RemoteTransition;
 import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -117,7 +118,7 @@
     @Before
     public void setUp() {
         doAnswer(invocation -> invocation.getArguments()[1])
-                .when(mOrganizer).startTransition(anyInt(), any(), any());
+                .when(mOrganizer).startTransition(any(), any());
     }
 
     @Test
@@ -136,7 +137,7 @@
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -188,7 +189,7 @@
         // Make a request that will be rejected by the testhandler.
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
         transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
                 mock(SurfaceControl.Transaction.class));
         assertEquals(1, mDefaultHandler.activeCount());
@@ -199,10 +200,12 @@
         // Make a request that will be handled by testhandler but not animated by it.
         RunningTaskInfo mwTaskInfo =
                 createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        // Make the wct non-empty.
+        handlerWCT.setFocusable(new WindowContainerToken(mock(IWindowContainerToken.class)), true);
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
         verify(mOrganizer, times(1)).startTransition(
-                eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+                eq(transitToken), eq(handlerWCT));
         transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
                 mock(SurfaceControl.Transaction.class));
         assertEquals(1, mDefaultHandler.activeCount());
@@ -217,8 +220,8 @@
         transitions.addHandler(topHandler);
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(
-                eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT));
+        verify(mOrganizer, times(2)).startTransition(
+                eq(transitToken), eq(handlerWCT));
         TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
                 .addChange(TRANSIT_CHANGE).build();
         transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
@@ -256,7 +259,7 @@
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
                         new RemoteTransition(testRemote)));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -406,7 +409,7 @@
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index 0a5afe4..db54ae3 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,9 @@
         android:layout_width="24dp"
         android:layout_height="24dp"
         android:layout_marginStart="24dp"
-        android:tint="@android:color/system_accent1_600"/>
+        android:tint="@android:color/system_accent1_600"
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <TextView
         android:id="@android:id/text1"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 54916a2..a3d71b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -30,7 +30,8 @@
         android:layout_height="24dp"
         android:layout_marginTop="8dp"
         android:layout_marginEnd="12dp"
-        android:contentDescription="Permission Icon"/>
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index fc5ff08..5c9ab7b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -103,7 +103,7 @@
 
     private final Runnable mTimeoutRunnable = this::timeout;
 
-    private boolean mStopAfterFirstMatch;;
+    private boolean mStopAfterFirstMatch;
 
     /**
      * A state enum for devices' discovery.
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 022fc05..3750254 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -268,7 +268,7 @@
     <string name="keep_screen_on" msgid="1187161672348797558">"Mantendu aktibo"</string>
     <string name="keep_screen_on_summary" msgid="1510731514101925829">"Pantaila ez da ezarriko inoiz inaktibo kargatu bitartean"</string>
     <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Gaitu Bluetooth HCI miatze-erregistroa"</string>
-    <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Hauteman Bluetooth paketeak (aktibatu edo desaktibatu Bluetooth-a ezarpena aldatu ostean)"</string>
+    <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Hauteman Bluetooth paketeak (aktibatu edo desaktibatu Bluetootha ezarpena aldatu ostean)"</string>
     <string name="oem_unlock_enable" msgid="5334869171871566731">"OEM desblokeoa"</string>
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Onartu abiarazlea desblokeatzea"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM desblokeoa onartu nahi duzu?"</string>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index a6bfc408b..76cee7b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -44,8 +44,6 @@
         Settings.System.DIM_SCREEN,
         Settings.System.SCREEN_OFF_TIMEOUT,
         Settings.System.SCREEN_BRIGHTNESS_MODE,
-        Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-        Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
         Settings.System.ADAPTIVE_SLEEP,             // moved to secure
         Settings.System.APPLY_RAMPING_RINGER,
         Settings.System.VIBRATE_INPUT_DEVICES,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8e82b8b..8b9d118 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -102,7 +102,9 @@
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
+                    Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
                     Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific
                     Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED
                     );
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index df6f08d..2f5b5f4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -294,5 +294,6 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
+        "wmshell.protolog.json.gz",
     ],
 }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
new file mode 100644
index 0000000..4eb7c7d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/** Detects usage of Context.getSystemService() and suggests to use an injected instance instead. */
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("getSystemService")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        val evaluator = context.evaluator
+        if (
+            !evaluator.isStatic(method) &&
+                method.name == "getSystemService" &&
+                method.containingClass?.qualifiedName == "android.content.Context"
+        ) {
+            context.report(
+                ISSUE,
+                method,
+                context.getNameLocation(node),
+                "Use @Inject to get the handle to a system-level services instead of using " +
+                    "Context.getSystemService()"
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "NonInjectedService",
+                briefDescription =
+                    "System-level services should be retrieved using " +
+                        "@Inject instead of Context.getSystemService().",
+                explanation =
+                    "Context.getSystemService() should be avoided because it makes testing " +
+                        "difficult. Instead, use an injected service. For example, " +
+                        "instead of calling Context.getSystemService(UserManager.class), " +
+                        "use @Inject and add UserManager to the constructor",
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(NonInjectedServiceDetector::class.java, Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index b72d03d..eb71d32 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -27,6 +27,7 @@
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UCallExpression
 
+@Suppress("UnstableApiUsage")
 class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
 
     override fun getApplicableMethodNames(): List<String> {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 4879883..312810b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -35,6 +35,7 @@
                 GetMainLooperViaContextDetector.ISSUE,
                 RegisterReceiverViaContextDetector.ISSUE,
                 SoftwareBitmapDetector.ISSUE,
+                NonInjectedServiceDetector.ISSUE,
         )
 
     override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
new file mode 100644
index 0000000..26bd8d0
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+
+/*
+ * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
+ * stubs are not used in the lint detectors themselves.
+ */
+@Suppress("UnstableApiUsage")
+internal val androidStubs =
+    arrayOf(
+        java(
+            """
+package android.app;
+
+public class ActivityManager {
+    public static int getCurrentUser() {}
+}
+"""
+        ),
+        java(
+            """
+package android.os;
+import android.content.pm.UserInfo;
+import android.annotation.UserIdInt;
+
+public class UserManager {
+    public UserInfo getUserInfo(@UserIdInt int userId) {}
+}
+"""
+        ),
+        java("""
+package android.annotation;
+
+public @interface UserIdInt {}
+"""),
+        java("""
+package android.content.pm;
+
+public class UserInfo {}
+"""),
+        java("""
+package android.os;
+
+public class Looper {}
+"""),
+        java("""
+package android.os;
+
+public class Handler {}
+"""),
+        java("""
+package android.content;
+
+public class ServiceConnection {}
+"""),
+        java("""
+package android.os;
+
+public enum UserHandle {
+    ALL
+}
+"""),
+        java(
+            """
+package android.content;
+import android.os.UserHandle;
+import android.os.Handler;
+import android.os.Looper;
+import java.util.concurrent.Executor;
+
+public class Context {
+    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
+    public void registerReceiverAsUser(
+            BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {}
+    public void registerReceiverForAllUsers(
+            BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
+            Handler scheduler) {}
+    public void sendBroadcast(Intent intent) {}
+    public void sendBroadcast(Intent intent, String receiverPermission) {}
+    public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
+    public void bindService(Intent intent) {}
+    public void bindServiceAsUser(
+            Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
+    public void unbindService(ServiceConnection connection) {}
+    public Looper getMainLooper() { return null; }
+    public Executor getMainExecutor() { return null; }
+    public Handler getMainThreadHandler() { return null; }
+    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
+    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+}
+"""
+        ),
+        java(
+            """
+package android.app;
+import android.content.Context;
+
+public class Activity extends Context {}
+"""
+        ),
+        java(
+            """
+package android.graphics;
+
+public class Bitmap {
+    public enum Config {
+        ARGB_8888,
+        RGB_565,
+        HARDWARE
+    }
+    public static Bitmap createBitmap(int width, int height, Config config) {
+        return null;
+    }
+}
+"""
+        ),
+        java("""
+package android.content;
+
+public class BroadcastReceiver {}
+"""),
+        java("""
+package android.content;
+
+public class IntentFilter {}
+"""),
+        java(
+            """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+
+public interface UserTracker {
+    int getUserId();
+    UserInfo getUserInfo();
+}
+"""
+        ),
+    )
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
similarity index 61%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
index bf685f7..564afcb 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class BindServiceViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = BindServiceViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-            BindServiceViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE)
 
     private val explanation = "Binding or unbinding services are synchronous calls"
 
     @Test
     fun testBindService() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -49,17 +49,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testBindServiceAsUser() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -73,17 +76,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testUnbindService() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -96,45 +102,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(BindServiceViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(BindServiceViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void bindService(Intent intent) {};
-            public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
-                                          UserHandle userHandle) {};
-            public void unbindService(ServiceConnection connection) {};
-        }
-        """
-    )
-
-    private val serviceConnectionStub: TestFile = java(
-            """
-        package android.content;
-
-        public class ServiceConnection {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-            """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
similarity index 62%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index da010212f2..06aee8e 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -1,26 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = BroadcastSentViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-        BroadcastSentViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
 
     @Test
     fun testSendBroadcast() {
-        lint().files(
-            TestFiles.java(
-                """
+        println(stubs.size)
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
 
@@ -31,21 +48,25 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testSendBroadcastAsUser() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
                     import android.os.UserHandle;
@@ -56,21 +77,26 @@
                           context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
                         }
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testSendBroadcastInActivity() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.app.Activity;
                     import android.os.UserHandle;
@@ -82,21 +108,26 @@
                         }
 
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectWarningCount(1)
             .expectContains(
-            "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
-                    "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+                "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+                    "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+            )
     }
 
     @Test
     fun testNoopIfNoCall() {
-        lint().files(
-            TestFiles.java(
-                """
+        lint()
+            .files(
+                TestFiles.java(
+                        """
                     package test.pkg;
                     import android.content.Context;
 
@@ -106,45 +137,15 @@
                           context.startActivity(intent);
                         }
                     }
-                """).indented(),
-                *stubs)
+                """
+                    )
+                    .indented(),
+                *stubs
+            )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectClean()
     }
 
-    private val contextStub: TestFile = java(
-        """
-        package android.content;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void sendBroadcast(Intent intent) {};
-            public void sendBroadcast(Intent intent, String receiverPermission) {};
-            public void sendBroadcastAsUser(Intent intent, UserHandle userHandle,
-                                                String permission) {};
-        }
-        """
-    )
-
-    private val activityStub: TestFile = java(
-        """
-        package android.app;
-        import android.content.Context;
-
-        public class Activity extends Context {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-        """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, activityStub, userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
similarity index 64%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
index ec761cd..c55f399 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -17,13 +17,13 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = GetMainLooperViaContextDetector()
@@ -35,7 +35,8 @@
 
     @Test
     fun testGetMainThreadHandler() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -48,17 +49,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testGetMainLooper() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -71,17 +75,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testGetMainExecutor() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -94,42 +101,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(GetMainLooperViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(GetMainLooperViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
-
-        public class Context {
-            public Looper getMainLooper() { return null; };
-            public Executor getMainExecutor() { return null; };
-            public Handler getMainThreadHandler() { return null; };
-        }
-        """
-    )
-
-    private val looperStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Looper {}
-        """
-    )
-
-    private val handlerStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Handler {}
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
new file mode 100644
index 0000000..6b9f88f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = NonInjectedServiceDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+    override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
+
+    @Test
+    fun testGetServiceWithString() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.content.Context;
+
+                        public class TestClass1 {
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                context.getSystemService("user");
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedServiceDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains("Use @Inject to get the handle")
+    }
+
+    @Test
+    fun testGetServiceWithClass() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.content.Context;
+                        import android.os.UserManager;
+
+                        public class TestClass2 {
+                            public void getSystemServiceWithoutDagger(Context context) {
+                                context.getSystemService(UserManager.class);
+                            }
+                        }
+                        """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(NonInjectedServiceDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains("Use @Inject to get the handle")
+    }
+
+    private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
similarity index 60%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 76c0519..802ceba 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    override fun getIssues(): List<Issue> = listOf(
-            RegisterReceiverViaContextDetector.ISSUE)
+    override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
 
     private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
 
     @Test
     fun testRegisterReceiver() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -51,17 +51,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testRegisterReceiverAsUser() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -79,17 +82,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
     @Test
     fun testRegisterReceiverForAllUsers() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     package test.pkg;
@@ -107,65 +113,15 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(RegisterReceiverViaContextDetector.ISSUE)
-                .run()
-                .expectWarningCount(1)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(RegisterReceiverViaContextDetector.ISSUE)
+            .run()
+            .expectWarningCount(1)
+            .expectContains(explanation)
     }
 
-    private val contextStub: TestFile = java(
-            """
-        package android.content;
-        import android.os.Handler;
-        import android.os.UserHandle;
-
-        public class Context {
-            public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
-                int flags) {};
-            public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
-                IntentFilter filter, String broadcastPermission, Handler scheduler) {};
-            public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
-                String broadcastPermission, Handler scheduler) {};
-        }
-        """
-    )
-
-    private val broadcastReceiverStub: TestFile = java(
-            """
-        package android.content;
-
-        public class BroadcastReceiver {}
-        """
-    )
-
-    private val intentFilterStub: TestFile = java(
-            """
-        package android.content;
-
-        public class IntentFilter {}
-        """
-    )
-
-    private val handlerStub: TestFile = java(
-            """
-        package android.os;
-
-        public class Handler {}
-        """
-    )
-
-    private val userHandleStub: TestFile = java(
-            """
-        package android.os;
-
-        public enum UserHandle {
-            ALL
-        }
-        """
-    )
-
-    private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
-            userHandleStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
similarity index 74%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 2738f04..e265837 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -1,13 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class SlowUserQueryDetectorTest : LintDetectorTest() {
 
     override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -134,61 +150,5 @@
             .expectClean()
     }
 
-    private val activityManagerStub: TestFile =
-        java(
-            """
-            package android.app;
-
-            public class ActivityManager {
-                public static int getCurrentUser() {};
-            }
-            """
-        )
-
-    private val userManagerStub: TestFile =
-        java(
-            """
-            package android.os;
-            import android.content.pm.UserInfo;
-            import android.annotation.UserIdInt;
-
-            public class UserManager {
-                public UserInfo getUserInfo(@UserIdInt int userId) {};
-            }
-            """
-        )
-
-    private val userIdIntStub: TestFile =
-        java(
-            """
-            package android.annotation;
-
-            public @interface UserIdInt {}
-            """
-        )
-
-    private val userInfoStub: TestFile =
-        java(
-            """
-            package android.content.pm;
-
-            public class UserInfo {}
-            """
-        )
-
-    private val userTrackerStub: TestFile =
-        java(
-            """
-            package com.android.systemui.settings;
-            import android.content.pm.UserInfo;
-
-            public interface UserTracker {
-                public int getUserId();
-                public UserInfo getUserInfo();
-            }
-            """
-        )
-
-    private val stubs =
-        arrayOf(activityManagerStub, userManagerStub, userIdIntStub, userInfoStub, userTrackerStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
similarity index 70%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 890f2b8..fd6ab09 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -17,7 +17,6 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 import com.android.tools.lint.detector.api.Detector
@@ -36,7 +35,8 @@
 
     @Test
     fun testSoftwareBitmap() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     import android.graphics.Bitmap;
@@ -48,17 +48,20 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(SoftwareBitmapDetector.ISSUE)
-                .run()
-                .expectWarningCount(2)
-                .expectContains(explanation)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(SoftwareBitmapDetector.ISSUE)
+            .run()
+            .expectWarningCount(2)
+            .expectContains(explanation)
     }
 
     @Test
     fun testHardwareBitmap() {
-        lint().files(
+        lint()
+            .files(
                 TestFiles.java(
                         """
                     import android.graphics.Bitmap;
@@ -69,29 +72,14 @@
                         }
                     }
                 """
-                ).indented(),
-                *stubs)
-                .issues(SoftwareBitmapDetector.ISSUE)
-                .run()
-                .expectWarningCount(0)
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(SoftwareBitmapDetector.ISSUE)
+            .run()
+            .expectWarningCount(0)
     }
 
-    private val bitmapStub: TestFile = java(
-            """
-        package android.graphics;
-
-        public class Bitmap {
-            public enum Config {
-                ARGB_8888,
-                RGB_565,
-                HARDWARE
-            }
-            public static Bitmap createBitmap(int width, int height, Config config) {
-                return null;
-            }
-        }
-        """
-    )
-
-    private val stubs = arrayOf(bitmapStub)
+    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
index 3175dcf..4d94bab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -17,8 +17,6 @@
 
 package com.android.systemui.user.ui.compose
 
-import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.graphics.drawable.Drawable
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.foundation.Image
@@ -50,10 +48,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -62,6 +58,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.compose.SysUiOutlinedButton
 import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@
         remember(viewModel.iconResourceId) {
             val drawable =
                 checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+            val size = with(density) { 20.dp.toPx() }.toInt()
             drawable
                 .toBitmap(
-                    size = with(density) { 20.dp.toPx() }.toInt(),
-                    tintColor = Color.White,
+                    width = size,
+                    height = size,
                 )
                 .asImageBitmap()
         }
@@ -392,32 +390,3 @@
                 ),
     )
 }
-
-/**
- * Converts the [Drawable] to a [Bitmap].
- *
- * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
- * the `Drawable` onto it. Use sparingly and with care.
- */
-private fun Drawable.toBitmap(
-    size: Int? = null,
-    tintColor: Color? = null,
-): Bitmap {
-    val bitmap =
-        if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
-            Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
-        } else {
-            Bitmap.createBitmap(
-                size ?: intrinsicWidth,
-                size ?: intrinsicHeight,
-                Bitmap.Config.ARGB_8888
-            )
-        }
-    val canvas = Canvas(bitmap)
-    setBounds(0, 0, canvas.width, canvas.height)
-    if (tintColor != null) {
-        setTint(tintColor.toArgb())
-    }
-    draw(canvas)
-    return bitmap
-}
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 6986961..9d063e9 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -21,11 +21,12 @@
             android:paddingEnd="0dp">
     <item>
         <shape android:shape="rectangle">
-          <solid android:color="?androidprv:attr/colorSurface" />
+          <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
             <corners android:radius="32dp" />
         </shape>
     </item>
     <item
+        android:id="@+id/user_switcher_key_down"
         android:drawable="@drawable/ic_ksh_key_down"
         android:gravity="end|center_vertical"
         android:width="32dp"
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
new file mode 100644
index 0000000..29832a0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/mobile_group"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:orientation="horizontal" >
+
+        <FrameLayout
+            android:id="@+id/inout_container"
+            android:layout_height="17dp"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical">
+            <ImageView
+                android:id="@+id/mobile_in"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_down"
+                android:visibility="gone"
+                android:paddingEnd="2dp"
+                />
+            <ImageView
+                android:id="@+id/mobile_out"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_up"
+                android:paddingEnd="2dp"
+                android:visibility="gone"
+                />
+        </FrameLayout>
+        <ImageView
+            android:id="@+id/mobile_type"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:paddingStart="2.5dp"
+            android:paddingEnd="1dp"
+            android:visibility="gone" />
+        <Space
+            android:id="@+id/mobile_roaming_space"
+            android:layout_height="match_parent"
+            android:layout_width="@dimen/roaming_icon_start_padding"
+            android:visibility="gone"
+            />
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical">
+            <com.android.systemui.statusbar.AnimatedImageView
+                android:id="@+id/mobile_signal"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                systemui:hasOverlappingRendering="false"
+                />
+            <ImageView
+                android:id="@+id/mobile_roaming"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/stat_sys_roaming"
+                android:contentDescription="@string/data_connection_roaming"
+                android:visibility="gone" />
+        </FrameLayout>
+        <ImageView
+            android:id="@+id/mobile_roaming_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/stat_sys_roaming_large"
+            android:contentDescription="@string/data_connection_roaming"
+            android:visibility="gone" />
+    </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 0000000..1b38fd2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, 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.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mobile_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical" >
+
+    <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res-keyguard/values/ids.xml b/packages/SystemUI/res-keyguard/values/ids.xml
new file mode 100644
index 0000000..0dff4ff
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/ids.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2022 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>
+    <item type="id" name="header_footer_views_added_tag_key" />
+</resources>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index d886806..ae8e38e 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -16,7 +16,7 @@
 <!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
      is the root view of a window, we cannot change the root view's margins.) -->
 <!-- Alphas start as 0 because the view will be animated in. -->
-<FrameLayout
+<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
             />
 
     </LinearLayout>
-</FrameLayout>
+</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b3..d6c63eb 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
 -->
 <com.android.systemui.statusbar.StatusBarMobileView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/mobile_combo"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:gravity="center_vertical" >
 
-    <com.android.keyguard.AlphaOptimizedLinearLayout
-        android:id="@+id/mobile_group"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center_vertical"
-        android:orientation="horizontal" >
+    <include layout="@layout/status_bar_mobile_signal_group_inner" />
 
-        <FrameLayout
-            android:id="@+id/inout_container"
-            android:layout_height="17dp"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical">
-            <ImageView
-                android:id="@+id/mobile_in"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_down"
-                android:visibility="gone"
-                android:paddingEnd="2dp"
-            />
-            <ImageView
-                android:id="@+id/mobile_out"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2dp"
-                android:visibility="gone"
-            />
-        </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_type"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:paddingStart="2.5dp"
-            android:paddingEnd="1dp"
-            android:visibility="gone" />
-        <Space
-            android:id="@+id/mobile_roaming_space"
-            android:layout_height="match_parent"
-            android:layout_width="@dimen/roaming_icon_start_padding"
-            android:visibility="gone"
-        />
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical">
-            <com.android.systemui.statusbar.AnimatedImageView
-                android:id="@+id/mobile_signal"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                systemui:hasOverlappingRendering="false"
-            />
-            <ImageView
-                android:id="@+id/mobile_roaming"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:src="@drawable/stat_sys_roaming"
-                android:contentDescription="@string/data_connection_roaming"
-                android:visibility="gone" />
-        </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_roaming_large"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/stat_sys_roaming_large"
-            android:contentDescription="@string/data_connection_roaming"
-            android:visibility="gone" />
-    </com.android.keyguard.AlphaOptimizedLinearLayout>
 </com.android.systemui.statusbar.StatusBarMobileView>
 
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index bc4236c..5cc6d00 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -32,13 +32,13 @@
     <string name="battery_saver_start_action" msgid="8353766979886287140">"অন কৰক"</string>
     <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"নালাগে, ধন্যবাদ"</string>
     <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
-    <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ত প্ৰৱেশ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক অনুমতি দিবনে?"</string>
+    <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক অনুমতি দিবনে?"</string>
     <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ অনুমতি দিবনে?\nএই এপ্‌টোক ৰেকর্ড কৰাৰ অনুমতি দিয়া হোৱা নাই কিন্তু ই এই ইউএছবি ডিভাইচটোৰ জৰিয়তে অডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string>
     <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ অনুমতি দিবনে?"</string>
     <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> নিয়ন্ত্ৰণ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g> খুলিবনে?"</string>
     <string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"এই এপ্‌টোক ৰেকর্ড কৰাৰ অনুমতি দিয়া হোৱা নাই কিন্তু ই এই ইউএছবি ডিভাইচটোৰ জৰিয়তে অডিঅ\' ৰেকর্ড কৰিব পাৰে। এইটো ডিভাইচৰ সৈতে <xliff:g id="APPLICATION">%1$s</xliff:g> ব্যৱহাৰ কৰিলে কল, জাননী আৰু এলাৰ্ম শুনাটো অৱৰুদ্ধ হ’ব পাৰে।"</string>
     <string name="usb_audio_device_prompt" msgid="7944987408206252949">"এইটো ডিভাইচৰ সৈতে <xliff:g id="APPLICATION">%1$s</xliff:g> ব্যৱহাৰ কৰিলে কল, জাননী আৰু এলাৰ্ম শুনাটো অৱৰুদ্ধ হ’ব পাৰে।"</string>
-    <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>ত প্ৰৱেশ কৰিবলৈ অনুমতি দিবনে?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> এক্সেছ কৰিবলৈ অনুমতি দিবনে?"</string>
     <string name="usb_device_confirm_prompt" msgid="4091711472439910809">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ক ব্যৱহাৰ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক খোলেনে?"</string>
     <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ব্যৱহাৰ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক খুলিবনে?\nএই এপ্‌টোক ৰেকর্ড কৰাৰ অনুমতি দিয়া হোৱা নাই কিন্তু ই এই ইউএছবি ডিভাইচটোৰ জৰিয়তে অডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string>
     <string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"<xliff:g id="USB_ACCESSORY">%2$s</xliff:g>ক ব্যৱহাৰ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক খোলেনে?"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 9bc0051..6724aa5 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -163,7 +163,7 @@
     <skip />
     <string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
-    <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-a konektatuta."</string>
+    <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
     <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string>
     <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string>
     <string name="accessibility_cast_name" msgid="7344437925388773685">"Hona konektatuta: <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -189,7 +189,7 @@
     <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"isiltasun osoa"</string>
     <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"alarmak soilik"</string>
     <string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"Ez molestatzeko modua."</string>
-    <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth-a."</string>
+    <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetootha."</string>
     <string name="accessibility_quick_settings_bluetooth_on" msgid="3819082137684078013">"Bluetooth bidezko konexioa aktibatuta dago."</string>
     <string name="accessibility_quick_settings_alarm" msgid="558094529584082090">"Alarma ordu honetarako ezarri da: <xliff:g id="TIME">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_more_time" msgid="7646479831704665284">"Denbora gehiago."</string>
@@ -211,7 +211,7 @@
     <string name="start_dreams" msgid="9131802557946276718">"Pantaila-babeslea"</string>
     <string name="ethernet_label" msgid="2203544727007463351">"Ethernet"</string>
     <string name="quick_settings_dnd_label" msgid="7728690179108024338">"Ez molestatzeko modua"</string>
-    <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth-a"</string>
+    <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetootha"</string>
     <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Ez dago parekatutako gailurik erabilgarri"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
@@ -444,7 +444,7 @@
     <string name="stream_music" msgid="2188224742361847580">"Multimedia-edukia"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Alarma"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Jakinarazpena"</string>
-    <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth-a"</string>
+    <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetootha"</string>
     <string name="stream_dtmf" msgid="7322536356554673067">"Tonu anitzeko maiztasun duala"</string>
     <string name="stream_accessibility" msgid="3873610336741987152">"Erabilerraztasuna"</string>
     <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Jo tonua"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 876bf18..86e8f9e 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -323,7 +323,7 @@
     <item msgid="5640521437931460125">"Déplacer vers le haut"</item>
   </string-array>
     <string name="keyguard_retry" msgid="886802522584053523">"Balayez l\'écran vers le haut pour réessayer"</string>
-    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Déverrouillez l\'écran pour pouvoir utiliser la NFC"</string>
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Déverrouillez l\'écran pour pouvoir utiliser le NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Cet appareil appartient à votre organisation"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Cet appareil appartient à <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="do_financed_disclosure_with_name" msgid="6723004643314467864">"Cet appareil est fourni par <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
index bc7efe6..ebe67d8 100644
--- a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
@@ -138,8 +138,8 @@
   </string-array>
   <string-array name="tile_states_cameratoggle">
     <item msgid="6680671247180519913">"Indisponível"</item>
-    <item msgid="4765607635752003190">"Desativada"</item>
-    <item msgid="1697460731949649844">"Ativada"</item>
+    <item msgid="4765607635752003190">"Desligada"</item>
+    <item msgid="1697460731949649844">"Ligada"</item>
   </string-array>
   <string-array name="tile_states_mictoggle">
     <item msgid="6895831614067195493">"Indisponível"</item>
diff --git a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
index 0a01020..bda7473 100644
--- a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
@@ -138,8 +138,8 @@
   </string-array>
   <string-array name="tile_states_cameratoggle">
     <item msgid="6680671247180519913">"Indisponível"</item>
-    <item msgid="4765607635752003190">"Desligado"</item>
-    <item msgid="1697460731949649844">"Ligado"</item>
+    <item msgid="4765607635752003190">"Desligada"</item>
+    <item msgid="1697460731949649844">"Ligada"</item>
   </string-array>
   <string-array name="tile_states_mictoggle">
     <item msgid="6895831614067195493">"Indisponível"</item>
diff --git a/packages/SystemUI/res/values-pt/tiles_states_strings.xml b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
index bc7efe6..ebe67d8 100644
--- a/packages/SystemUI/res/values-pt/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
@@ -138,8 +138,8 @@
   </string-array>
   <string-array name="tile_states_cameratoggle">
     <item msgid="6680671247180519913">"Indisponível"</item>
-    <item msgid="4765607635752003190">"Desativada"</item>
-    <item msgid="1697460731949649844">"Ativada"</item>
+    <item msgid="4765607635752003190">"Desligada"</item>
+    <item msgid="1697460731949649844">"Ligada"</item>
   </string-array>
   <string-array name="tile_states_mictoggle">
     <item msgid="6895831614067195493">"Indisponível"</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53f1227..9b9111f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -246,6 +246,16 @@
     <string name="screenrecord_start_label">Start Recording?</string>
     <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]-->
     <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string>
+    <!-- Dropdown option to record the entire screen [CHAR_LIMIT=30]-->
+    <string name="screenrecord_option_entire_screen">Record entire screen</string>
+    <!-- Dropdown option to record a single app [CHAR_LIMIT=30]-->
+    <string name="screenrecord_option_single_app">Record a single app</string>
+    <!-- Message reminding the user that sensitive information may be captured during a full screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+    <string name="screenrecord_warning_entire_screen">While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+    <!-- Message reminding the user that sensitive information may be captured during a single app screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+    <string name="screenrecord_warning_single_app">While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+    <!-- Button to start a screen recording in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]-->
+    <string name="screenrecord_start_recording">Start recording</string>
     <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]-->
     <string name="screenrecord_audio_label">Record audio</string>
     <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]-->
@@ -958,7 +968,26 @@
     <!-- Media projection permission dialog warning title. [CHAR LIMIT=NONE] -->
     <string name="media_projection_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g>?</string>
 
-    <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
+    <!-- Media projection permission dialog title. [CHAR LIMIT=NONE] -->
+    <string name="media_projection_permission_dialog_title">Allow <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> to share or record?</string>
+
+    <!-- Media projection permission dropdown option for capturing the whole screen. [CHAR LIMIT=30] -->
+    <string name="media_projection_permission_dialog_option_entire_screen">Entire screen</string>
+
+    <!-- Media projection permission dropdown option for capturing single app. [CHAR LIMIT=30] -->
+    <string name="media_projection_permission_dialog_option_single_app">A single app</string>
+
+    <!-- Media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_warning_entire_screen">When you\'re sharing, recording, or casting, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_warning_single_app">When you\'re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
+    <string name="media_projection_permission_dialog_continue">Continue</string>
+
+    <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
+    <string name="media_projection_permission_app_selector_title">Share or record an app</string>
 
     <!-- The text to clear all notifications. [CHAR LIMIT=60] -->
     <string name="clear_all_notifications_text">Clear all</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index c7d5ffe..023ef31 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -217,12 +217,16 @@
                 unregisterSamplingListener();
                 mSamplingListenerRegistered = true;
                 SurfaceControl wrappedStopLayer = wrap(stopLayerControl);
+
+                // pass this to background thread to avoid empty Rect race condition
+                final Rect boundsCopy = new Rect(mSamplingRequestBounds);
+
                 mBackgroundExecutor.execute(() -> {
                     if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
                         return;
                     }
                     mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
-                            wrappedStopLayer, mSamplingRequestBounds);
+                            wrappedStopLayer, boundsCopy);
                 });
                 mRegisteredSamplingBounds.set(mSamplingRequestBounds);
                 mRegisteredStopLayer = stopLayerControl;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e77c650..2b2b05ce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -81,11 +81,6 @@
      */
     void stopScreenPinning() = 17;
 
-    /*
-     * Notifies that the swipe-to-home (recents animation) is finished.
-     */
-    void notifySwipeToHomeFinished() = 23;
-
     /**
      * Notifies that quickstep will switch to a new task
      * @param rotation indicates which Surface.Rotation the gesture was started in
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
new file mode 100644
index 0000000..72f8b7b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -0,0 +1,209 @@
+package com.android.systemui.shared.recents.utilities;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Utility class to position the thumbnail in the TaskView
+ */
+public class PreviewPositionHelper {
+
+    public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+
+    // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
+    private final RectF mClippedInsets = new RectF();
+    private final Matrix mMatrix = new Matrix();
+    private boolean mIsOrientationChanged;
+
+    public Matrix getMatrix() {
+        return mMatrix;
+    }
+
+    public void setOrientationChanged(boolean orientationChanged) {
+        mIsOrientationChanged = orientationChanged;
+    }
+
+    public boolean isOrientationChanged() {
+        return mIsOrientationChanged;
+    }
+
+    /**
+     * Updates the matrix based on the provided parameters
+     */
+    public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
+            int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+            int currentRotation, boolean isRtl) {
+        boolean isRotated = false;
+        boolean isOrientationDifferent;
+
+        int thumbnailRotation = thumbnailData.rotation;
+        int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+        RectF thumbnailClipHint = new RectF();
+        float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+        float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+        thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+
+        float scale = thumbnailData.scale;
+        final float thumbnailScale;
+
+        // Landscape vs portrait change.
+        // Note: Disable rotation in grid layout.
+        boolean windowingModeSupportsRotation =
+                thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+        isOrientationDifferent = isOrientationChange(deltaRotate)
+                && windowingModeSupportsRotation;
+        if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
+            // If we haven't measured , skip the thumbnail drawing and only draw the background
+            // color
+            thumbnailScale = 0f;
+        } else {
+            // Rotate the screenshot if not in multi-window mode
+            isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+
+            float surfaceWidth = thumbnailBounds.width() / scale;
+            float surfaceHeight = thumbnailBounds.height() / scale;
+            float availableWidth = surfaceWidth
+                    - (thumbnailClipHint.left + thumbnailClipHint.right);
+            float availableHeight = surfaceHeight
+                    - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+            float canvasAspect = canvasWidth / (float) canvasHeight;
+            float availableAspect = isRotated
+                    ? availableHeight / availableWidth
+                    : availableWidth / availableHeight;
+            boolean isAspectLargelyDifferent =
+                    Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+                            availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
+            if (isRotated && isAspectLargelyDifferent) {
+                // Do not rotate thumbnail if it would not improve fit
+                isRotated = false;
+                isOrientationDifferent = false;
+            }
+
+            if (isAspectLargelyDifferent) {
+                // Crop letterbox insets if insets isn't already clipped
+                thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
+                thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+                thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+                thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
+                availableWidth = surfaceWidth
+                        - (thumbnailClipHint.left + thumbnailClipHint.right);
+                availableHeight = surfaceHeight
+                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+            }
+
+            final float targetW, targetH;
+            if (isOrientationDifferent) {
+                targetW = canvasHeight;
+                targetH = canvasWidth;
+            } else {
+                targetW = canvasWidth;
+                targetH = canvasHeight;
+            }
+            float targetAspect = targetW / targetH;
+
+            // Update the clipHint such that
+            //   > the final clipped position has same aspect ratio as requested by canvas
+            //   > first fit the width and crop the extra height
+            //   > if that will leave empty space, fit the height and crop the width instead
+            float croppedWidth = availableWidth;
+            float croppedHeight = croppedWidth / targetAspect;
+            if (croppedHeight > availableHeight) {
+                croppedHeight = availableHeight;
+                if (croppedHeight < targetH) {
+                    croppedHeight = Math.min(targetH, surfaceHeight);
+                }
+                croppedWidth = croppedHeight * targetAspect;
+
+                // One last check in case the task aspect radio messed up something
+                if (croppedWidth > surfaceWidth) {
+                    croppedWidth = surfaceWidth;
+                    croppedHeight = croppedWidth / targetAspect;
+                }
+            }
+
+            // Update the clip hints. Align to 0,0, crop the remaining.
+            if (isRtl) {
+                thumbnailClipHint.left += availableWidth - croppedWidth;
+                if (thumbnailClipHint.right < 0) {
+                    thumbnailClipHint.left += thumbnailClipHint.right;
+                    thumbnailClipHint.right = 0;
+                }
+            } else {
+                thumbnailClipHint.right += availableWidth - croppedWidth;
+                if (thumbnailClipHint.left < 0) {
+                    thumbnailClipHint.right += thumbnailClipHint.left;
+                    thumbnailClipHint.left = 0;
+                }
+            }
+            thumbnailClipHint.bottom += availableHeight - croppedHeight;
+            if (thumbnailClipHint.top < 0) {
+                thumbnailClipHint.bottom += thumbnailClipHint.top;
+                thumbnailClipHint.top = 0;
+            } else if (thumbnailClipHint.bottom < 0) {
+                thumbnailClipHint.top += thumbnailClipHint.bottom;
+                thumbnailClipHint.bottom = 0;
+            }
+
+            thumbnailScale = targetW / (croppedWidth * scale);
+        }
+
+        if (!isRotated) {
+            mMatrix.setTranslate(
+                    -thumbnailClipHint.left * scale,
+                    -thumbnailClipHint.top * scale);
+        } else {
+            setThumbnailRotation(deltaRotate, thumbnailBounds);
+        }
+
+        mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
+
+        mMatrix.postScale(thumbnailScale, thumbnailScale);
+        mIsOrientationChanged = isOrientationDifferent;
+    }
+
+    private int getRotationDelta(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
+    /**
+     * @param deltaRotation the number of 90 degree turns from the current orientation
+     * @return {@code true} if the change in rotation results in a shift from landscape to
+     * portrait or vice versa, {@code false} otherwise
+     */
+    private boolean isOrientationChange(int deltaRotation) {
+        return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+    }
+
+    private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
+        float translateX = 0;
+        float translateY = 0;
+
+        mMatrix.setRotate(90 * deltaRotate);
+        switch (deltaRotate) { /* Counter-clockwise */
+            case Surface.ROTATION_90:
+                translateX = thumbnailPosition.height();
+                break;
+            case Surface.ROTATION_270:
+                translateY = thumbnailPosition.width();
+                break;
+            case Surface.ROTATION_180:
+                translateX = thumbnailPosition.width();
+                translateY = thumbnailPosition.height();
+                break;
+        }
+        mMatrix.postTranslate(translateX, translateY);
+    }
+
+    public RectF getClippedInsets() {
+        return mClippedInsets;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 56326e3..77a13bd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -62,6 +62,16 @@
         return false; // Default
     }
 
+    /**
+     * Compares the ratio of two quantities and returns whether that ratio is greater than the
+     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+     * of a percentage.
+     */
+    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+            float bound) {
+        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+    }
+
     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
     public static float computeContrastBetweenColors(int bg, int fg) {
         float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1e5c53d..2cc5ccdc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,7 +24,6 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.animation.Animator;
@@ -107,8 +106,6 @@
                 return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_password;
-            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
-                return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 5b22324..9871645 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,9 +330,6 @@
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
                 break;
-            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
-                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
             case PROMPT_REASON_NONE:
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..c46e33d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,7 +22,6 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.animation.Animator;
@@ -124,8 +123,6 @@
                 return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_pin;
-            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
-                return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bdb1b8..632fcd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1073,6 +1073,11 @@
                         android.R.attr.textColorPrimary));
                 header.setBackground(mView.getContext().getDrawable(
                         R.drawable.bouncer_user_switcher_header_bg));
+                Drawable keyDownDrawable =
+                        ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId(
+                                R.id.user_switcher_key_down);
+                keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(),
+                        android.R.attr.textColorPrimary));
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 9d0a8ac..ac00e94 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,12 +61,6 @@
     int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
 
     /**
-     * Some auth is required because the trustagent expired either from timeout or manually by
-     * the user
-     */
-    int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
-
-    /**
      * Reset the view and prepare to take input. This should do things like clearing the
      * password or pattern and clear error messages.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6eef3b3..d14666d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -69,8 +69,7 @@
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
@@ -113,7 +112,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
@@ -126,6 +124,9 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
@@ -281,9 +282,9 @@
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
-    private final StatusBarStateController mStatusBarStateController;
     private final UiEventLogger mUiEventLogger;
     private final Set<Integer> mFaceAcquiredInfoIgnoreList;
+    private final PackageManager mPackageManager;
     private int mStatusBarState;
     private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
             new StatusBarStateController.StateListener() {
@@ -304,7 +305,7 @@
     };
 
     HashMap<Integer, SimData> mSimDatas = new HashMap<>();
-    HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
+    HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
 
     private int mPhoneState;
     private boolean mKeyguardIsVisible;
@@ -336,34 +337,41 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
-    private ContentObserver mTimeFormatChangeObserver;
+    private final ContentObserver mTimeFormatChangeObserver;
 
     private boolean mSwitchingUser;
 
     private boolean mDeviceInteractive;
-    private SubscriptionManager mSubscriptionManager;
+    private final SubscriptionManager mSubscriptionManager;
     private final TelephonyListenerManager mTelephonyListenerManager;
-    private List<SubscriptionInfo> mSubscriptionInfo;
-    private TrustManager mTrustManager;
-    private UserManager mUserManager;
-    private KeyguardBypassController mKeyguardBypassController;
-    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-    private LockPatternUtils mLockPatternUtils;
-    private final IDreamManager mDreamManager;
-    private boolean mIsDreaming;
+    private final TrustManager mTrustManager;
+    private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
+    private final StatusBarStateController mStatusBarStateController;
+    private final Executor mBackgroundExecutor;
+    private final SensorPrivacyManager mSensorPrivacyManager;
+    private final ActiveUnlockConfig mActiveUnlockConfig;
+    private final PowerManager mPowerManager;
+    private final IDreamManager mDreamManager;
+    private final TelephonyManager mTelephonyManager;
+    @Nullable
+    private final FingerprintManager mFpm;
+    @Nullable
+    private final FaceManager mFaceManager;
+    private final LockPatternUtils mLockPatternUtils;
+    private final boolean mWakeOnFingerprintAcquiredStart;
+
+    private KeyguardBypassController mKeyguardBypassController;
+    private List<SubscriptionInfo> mSubscriptionInfo;
+    private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+    private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private boolean mIsFaceEnrolled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private final Executor mBackgroundExecutor;
-    private SensorPrivacyManager mSensorPrivacyManager;
-    private final ActiveUnlockConfig mActiveUnlockConfig;
-    private final PowerManager mPowerManager;
-    private final boolean mWakeOnFingerprintAcquiredStart;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -390,12 +398,10 @@
     }
     private final Handler mHandler;
 
-    private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
-    private BiometricManager mBiometricManager;
-    private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
+    private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
             new IBiometricEnabledOnKeyguardCallback.Stub() {
                 @Override
-                public void onChanged(boolean enabled, int userId) throws RemoteException {
+                public void onChanged(boolean enabled, int userId) {
                     mHandler.post(() -> {
                         mBiometricEnabledForUser.put(userId, enabled);
                         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -414,7 +420,7 @@
         }
     };
 
-    private OnSubscriptionsChangedListener mSubscriptionListener =
+    private final OnSubscriptionsChangedListener mSubscriptionListener =
             new OnSubscriptionsChangedListener() {
                 @Override
                 public void onSubscriptionsChanged() {
@@ -433,11 +439,12 @@
         }
     }
 
-    private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
-    private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
-    private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
-    private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
-    private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
+    private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
+    private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
+    private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
+    private final SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
+    private final SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
+    private final Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<>();
 
     @VisibleForTesting
     SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
@@ -598,7 +605,7 @@
         }
         if (sil == null) {
             // getCompleteActiveSubscriptionInfoList was null callers expect an empty list.
-            mSubscriptionInfo = new ArrayList<SubscriptionInfo>();
+            mSubscriptionInfo = new ArrayList<>();
         } else {
             mSubscriptionInfo = sil;
         }
@@ -733,7 +740,7 @@
      * If the device is dreaming, awakens the device
      */
     public void awakenFromDream() {
-        if (mIsDreaming && mDreamManager != null) {
+        if (mIsDreaming) {
             try {
                 mDreamManager.awaken();
             } catch (RemoteException e) {
@@ -778,12 +785,8 @@
     }
 
     private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
-        mBackgroundExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
-            }
-        });
+        mBackgroundExecutor.execute(
+                () -> mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId));
     }
 
     private void handleFingerprintAuthFailed() {
@@ -866,7 +869,8 @@
         }
     }
 
-    private Runnable mRetryFingerprintAuthentication = new Runnable() {
+    private final Runnable mRetryFingerprintAuthentication = new Runnable() {
+        @SuppressLint("MissingPermission")
         @Override
         public void run() {
             mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
@@ -910,7 +914,7 @@
 
         boolean lockedOutStateChanged = false;
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
-            lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
+            lockedOutStateChanged = !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             mLogger.d("Fingerprint locked out - requiring strong auth");
             mLockPatternUtils.requireStrongAuth(
@@ -955,9 +959,9 @@
             // that the events will arrive in a particular order. Add a delay here in case
             // an unlock is in progress. In this is a normal unlock the extra delay won't
             // be noticeable.
-            mHandler.postDelayed(() -> {
-                updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-            }, getBiometricLockoutDelay());
+            mHandler.postDelayed(
+                    () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
+                    getBiometricLockoutDelay());
         } else {
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
@@ -1091,7 +1095,7 @@
         }
     }
 
-    private Runnable mRetryFaceAuthentication = new Runnable() {
+    private final Runnable mRetryFaceAuthentication = new Runnable() {
         @Override
         public void run() {
             mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
@@ -1117,12 +1121,8 @@
 
         // Error is always the end of authentication lifecycle
         mFaceCancelSignal = null;
-        boolean cameraPrivacyEnabled = false;
-        if (mSensorPrivacyManager != null) {
-            cameraPrivacyEnabled = mSensorPrivacyManager
-                    .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
-                    SensorPrivacyManager.Sensors.CAMERA);
-        }
+        boolean cameraPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
+                SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA);
 
         if (msgId == FaceManager.FACE_ERROR_CANCELED
                 && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1173,10 +1173,8 @@
         mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
         final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
 
-        mHandler.postDelayed(() -> {
-            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET);
-        }, getBiometricLockoutDelay());
+        mHandler.postDelayed(() -> updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
 
         if (changed) {
             notifyLockedOutStateChanged(BiometricSourceType.FACE);
@@ -1215,28 +1213,24 @@
         return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
     }
 
-    private boolean isTrustDisabled(int userId) {
+    private boolean isTrustDisabled() {
         // Don't allow trust agent if device is secured with a SIM PIN. This is here
         // mainly because there's no other way to prompt the user to enter their SIM PIN
         // once they get past the keyguard screen.
-        final boolean disabledBySimPin = isSimPinSecure();
-        return disabledBySimPin;
+        return isSimPinSecure(); // Disabled by SIM PIN
     }
 
     private boolean isFingerprintDisabled(int userId) {
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
-                & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
+        return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+                        & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
                 || isSimPinSecure();
     }
 
     private boolean isFaceDisabled(int userId) {
-        final DevicePolicyManager dpm =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         // TODO(b/140035044)
-        return whitelistIpcs(() -> dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
-                & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
+        return whitelistIpcs(() ->
+                (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+                        & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
                 || isSimPinSecure());
     }
 
@@ -1258,7 +1252,7 @@
     }
 
     public boolean getUserHasTrust(int userId) {
-        return !isTrustDisabled(userId) && mUserHasTrust.get(userId);
+        return !isTrustDisabled() && mUserHasTrust.get(userId);
     }
 
     /**
@@ -1290,7 +1284,7 @@
     }
 
     public boolean getUserTrustIsManaged(int userId) {
-        return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
+        return mUserTrustIsManaged.get(userId) && !isTrustDisabled();
     }
 
     private void updateSecondaryLockscreenRequirement(int userId) {
@@ -1308,7 +1302,7 @@
                 Intent intent =
                         new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
                                 .setPackage(supervisorComponent.getPackageName());
-                ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
+                ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0);
                 if (resolveInfo != null && resolveInfo.serviceInfo != null) {
                     Intent launchIntent =
                             new Intent().setComponent(resolveInfo.serviceInfo.getComponentName());
@@ -1677,22 +1671,19 @@
     CancellationSignal mFingerprintCancelSignal;
     @VisibleForTesting
     CancellationSignal mFaceCancelSignal;
-    private FingerprintManager mFpm;
-    private FaceManager mFaceManager;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
     private boolean mFaceLockedOutPermanent;
-    private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
-    private TelephonyManager mTelephonyManager;
+    private final HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
 
     /**
      * When we receive a
      * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
      * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
      * we need a single object to pass to the handler.  This class helps decode
-     * the intent and provide a {@link SimCard.State} result.
+     * the intent and provide a {@link SimData} result.
      */
     private static class SimData {
         public int simState;
@@ -1913,9 +1904,20 @@
             UiEventLogger uiEventLogger,
             // This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
             Provider<SessionTracker> sessionTrackerProvider,
-            PowerManager powerManager) {
+            PowerManager powerManager,
+            TrustManager trustManager,
+            SubscriptionManager subscriptionManager,
+            UserManager userManager,
+            IDreamManager dreamManager,
+            DevicePolicyManager devicePolicyManager,
+            SensorPrivacyManager sensorPrivacyManager,
+            TelephonyManager telephonyManager,
+            PackageManager packageManager,
+            @Nullable FaceManager faceManager,
+            @Nullable FingerprintManager fingerprintManager,
+            @Nullable BiometricManager biometricManager) {
         mContext = context;
-        mSubscriptionManager = SubscriptionManager.from(context);
+        mSubscriptionManager = subscriptionManager;
         mTelephonyListenerManager = telephonyListenerManager;
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
@@ -1929,12 +1931,20 @@
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
-        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
         mLogger = logger;
         mUiEventLogger = uiEventLogger;
         mSessionTrackerProvider = sessionTrackerProvider;
         mPowerManager = powerManager;
+        mTrustManager = trustManager;
+        mUserManager = userManager;
+        mDreamManager = dreamManager;
+        mTelephonyManager = telephonyManager;
+        mDevicePolicyManager = devicePolicyManager;
+        mPackageManager = packageManager;
+        mFpm = fingerprintManager;
+        mFaceManager = faceManager;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
         mWakeOnFingerprintAcquiredStart = context.getResources()
                         .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
@@ -2079,8 +2089,7 @@
         // listener now with the service state from the default sub.
         mBackgroundExecutor.execute(() -> {
             int subId = SubscriptionManager.getDefaultSubscriptionId();
-            ServiceState serviceState = mContext.getSystemService(TelephonyManager.class)
-                    .getServiceStateForSubscriber(subId);
+            ServiceState serviceState = mTelephonyManager.getServiceStateForSubscriber(subId);
             mHandler.sendMessage(
                     mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
         });
@@ -2102,26 +2111,21 @@
             e.rethrowAsRuntimeException();
         }
 
-        mTrustManager = context.getSystemService(TrustManager.class);
         mTrustManager.registerTrustListener(this);
 
         setStrongAuthTracker(mStrongAuthTracker);
 
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.getService(DreamService.DREAM_SERVICE));
-
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-            mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+        if (mFpm != null) {
             mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
+            mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+        if (mFaceManager != null) {
             mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
+            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
         }
 
-        if (mFpm != null || mFaceManager != null) {
-            mBiometricManager = context.getSystemService(BiometricManager.class);
-            mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+        if (biometricManager != null) {
+            biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
         }
 
         // in case authenticators aren't registered yet at this point:
@@ -2139,19 +2143,11 @@
             }
         });
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
-        if (mFpm != null) {
-            mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
-        }
-        if (mFaceManager != null) {
-            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
-        }
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
-        mUserManager = context.getSystemService(UserManager.class);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
         int user = ActivityManager.getCurrentUser();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
-        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
         List<UserInfo> allUsers = mUserManager.getUsers();
@@ -2161,22 +2157,8 @@
         }
         updateAirplaneModeState();
 
-        mTelephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (mTelephonyManager != null) {
-            mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
-            // Set initial sim states values.
-            for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
-                int state = mTelephonyManager.getSimState(slot);
-                int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
-                if (subIds != null) {
-                    for (int subId : subIds) {
-                        mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
-                                .sendToTarget();
-                    }
-                }
-            }
-        }
+        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+        initializeSimState();
 
         mTimeFormatChangeObserver = new ContentObserver(mHandler) {
             @Override
@@ -2193,6 +2175,20 @@
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
     }
 
+    private void initializeSimState() {
+        // Set initial sim states values.
+        for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+            int state = mTelephonyManager.getSimState(slot);
+            int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+            if (subIds != null) {
+                for (int subId : subIds) {
+                    mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+                            .sendToTarget();
+                }
+            }
+        }
+    }
+
     private void updateFaceEnrolled(int userId) {
         mIsFaceEnrolled = whitelistIpcs(
                 () -> mFaceManager != null && mFaceManager.isHardwareDetected()
@@ -2235,7 +2231,7 @@
         }
 
         @Override
-        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+        public void onUserSwitchComplete(int newUserId) {
             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
                     newUserId, 0));
         }
@@ -2593,7 +2589,7 @@
         }
 
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
-        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive
                 && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2639,7 +2635,7 @@
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                (mBouncerFullyShown && !mGoingToSleep
+                (mBouncerFullyShown
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
                         || awakeKeyguard
@@ -2651,6 +2647,7 @@
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
                 && !faceAuthenticated
+                && !mGoingToSleep
                 && !fpOrFaceIsLockedOut;
 
         // Aggregate relevant fields for debug logging.
@@ -2794,6 +2791,7 @@
         return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
     }
 
+    @SuppressLint("MissingPermission")
     @VisibleForTesting
     boolean isUnlockWithFingerprintPossible(int userId) {
         // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
@@ -2924,6 +2922,7 @@
         try {
             reply.sendResult(null);
         } catch (RemoteException e) {
+            mLogger.logException(e, "Ignored exception while userSwitching");
         }
     }
 
@@ -3190,7 +3189,7 @@
             return false;
         }
         Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
-        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent,
+        ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
                 0 /* flags */, getCurrentUser());
 
         if (resolveInfo == null) {
@@ -3320,11 +3319,7 @@
         }
 
         // change in battery overheat
-        if (current.health != old.health) {
-            return true;
-        }
-
-        return false;
+        return current.health != old.health;
     }
 
     /**
@@ -3375,10 +3370,8 @@
     public void setSwitchingUser(boolean switching) {
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post if first
-        mHandler.post(() -> {
-            updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
-                    FACE_AUTH_UPDATED_USER_SWITCHING);
-        });
+        mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+                FACE_AUTH_UPDATED_USER_SWITCHING));
     }
 
     private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -3487,7 +3480,7 @@
     /**
      * If any SIM cards are currently secure.
      *
-     * @see #isSimPinSecure(State)
+     * @see #isSimPinSecure(int)
      */
     public boolean isSimPinSecure() {
         // True if any SIM is pin secure
@@ -3534,10 +3527,7 @@
      * @return true if and only if the state has changed for the specified {@code slotId}
      */
     private boolean refreshSimState(int subId, int slotId) {
-        final TelephonyManager tele =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        int state = (tele != null) ?
-                tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
+        int state = mTelephonyManager.getSimState(slotId);
         SimData data = mSimDatas.get(subId);
         final boolean changed;
         if (data == null) {
@@ -3680,13 +3670,8 @@
      * Unregister all listeners.
      */
     public void destroy() {
-        // TODO: inject these dependencies:
-        TelephonyManager telephony =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephony != null) {
-            mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
-        }
-
+        mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
+        mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
 
         if (mDeviceProvisionedObserver != null) {
@@ -3716,8 +3701,9 @@
         mHandler.removeCallbacksAndMessages(null);
     }
 
+    @SuppressLint("MissingPermission")
     @Override
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index efa5558..b793fd2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -66,10 +66,13 @@
         listView.setDividerHeight(mContext.getResources().getDimensionPixelSize(
                 R.dimen.bouncer_user_switcher_popup_divider_height));
 
-        int height  = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bouncer_user_switcher_popup_header_height);
-        listView.addHeaderView(createSpacer(height), null, false);
-        listView.addFooterView(createSpacer(height), null, false);
+        if (listView.getTag(R.id.header_footer_views_added_tag_key) == null) {
+            int height = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.bouncer_user_switcher_popup_header_height);
+            listView.addHeaderView(createSpacer(height), null, false);
+            listView.addFooterView(createSpacer(height), null, false);
+            listView.setTag(R.id.header_footer_views_added_tag_key, new Object());
+        }
 
         listView.setOnTouchListener((v, ev) -> {
             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
new file mode 100644
index 0000000..50012a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.keyguard.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.dagger.KeyguardLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardLog"
+
+/**
+ * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
+ * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
+ * an overkill.
+ */
+class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
+    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+    fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+
+    fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+    fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
+
+    private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
+        buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarCalculatedAlpha(alpha: Float) {
+        debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarExplicitAlpha(alpha: Float) {
+        debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+    }
+
+    // TODO: remove after b/237743330 is fixed
+    fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
+        debugLog(
+            {
+                int1 = visibility
+                double1 = alpha.toDouble()
+                str1 = state
+            },
+            { "changing visibility to $int1 with alpha $double1 in state: $str1" }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 7a00cd9..bf9f4c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
 
     fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
 
     fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
index 2ba2bb6..ed6fbec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -45,7 +45,7 @@
     private boolean mShouldSetTouchStart;
 
     @Nullable private MoveWindowTask mMoveWindowTask;
-    private PointF mLastDrag = new PointF();
+    private final PointF mLastDrag = new PointF();
     private final Handler mHandler;
 
     SimpleMirrorWindowControl(Context context, Handler handler) {
@@ -92,8 +92,7 @@
     }
 
     private Point findOffset(View v, int moveFrameAmount) {
-        final Point offset = mTmpPoint;
-        offset.set(0, 0);
+        mTmpPoint.set(0, 0);
         if (v.getId() == R.id.left_control) {
             mTmpPoint.x = -moveFrameAmount;
         } else if (v.getId() == R.id.up_control) {
@@ -184,7 +183,7 @@
         private final int mYOffset;
         private final Handler mHandler;
         /** Time in milliseconds between successive task executions.*/
-        private long mPeriod;
+        private final long mPeriod;
         private boolean mCancel;
 
         MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index ae73e34..8ded268 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -94,7 +94,7 @@
     private final Context mContext;
     private final Resources mResources;
     private final Handler mHandler;
-    private Rect mWindowBounds;
+    private final Rect mWindowBounds;
     private final int mDisplayId;
     @Surface.Rotation
     @VisibleForTesting
@@ -174,16 +174,16 @@
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private final MagnificationGestureDetector mGestureDetector;
     private final int mBounceEffectDuration;
-    private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
+    private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
     private Locale mLocale;
     private NumberFormat mPercentFormat;
     private float mBounceEffectAnimationScale;
-    private SysUiState mSysUiState;
+    private final SysUiState mSysUiState;
     // Set it to true when the view is overlapped with the gesture insets at the bottom.
     private boolean mOverlapWithGestureInsets;
 
     @Nullable
-    private MirrorWindowControl mMirrorWindowControl;
+    private final MirrorWindowControl mMirrorWindowControl;
 
     WindowMagnificationController(@UiContext Context context, @NonNull Handler handler,
             @NonNull WindowMagnificationAnimationController animationController,
@@ -489,9 +489,7 @@
     /** Returns the rotation degree change of two {@link Surface.Rotation} */
     private int getDegreeFromRotation(@Surface.Rotation int newRotation,
             @Surface.Rotation int oldRotation) {
-        final int rotationDiff = oldRotation - newRotation;
-        final int degree = (rotationDiff + 4) % 4 * 90;
-        return degree;
+        return (oldRotation - newRotation + 4) % 4 * 90;
     }
 
     private void createMirrorWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb7..46c12b2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -875,6 +875,7 @@
     static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
         final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 | WindowManager.LayoutParams.FLAG_SECURE
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index c292296..701df89 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -45,6 +45,7 @@
     public static final int QS_SWIPE_SIDE = 15;
     public static final int BACK_GESTURE = 16;
     public static final int QS_SWIPE_NESTED = 17;
+    public static final int MEDIA_SEEKBAR = 18;
 
     @IntDef({
             QUICK_SETTINGS,
@@ -65,7 +66,8 @@
             LOCK_ICON,
             QS_SWIPE_SIDE,
             QS_SWIPE_NESTED,
-            BACK_GESTURE
+            BACK_GESTURE,
+            MEDIA_SEEKBAR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InteractionType {}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 5e4f149..f8ee49a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -23,6 +23,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
@@ -153,6 +154,7 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == MEDIA_SEEKBAR
                 || interactionType == SHADE_DRAG
                 || interactionType == QS_COLLAPSE
                 || interactionType == Classifier.UDFPS_AUTHENTICATION
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 07f94e7..e8c83b1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -119,7 +120,8 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER
-                || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE) {
+                || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE
+                || interactionType == MEDIA_SEEKBAR) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index 776bc88..f576a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
 import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
@@ -93,6 +94,10 @@
             case QS_SWIPE_NESTED:
                 wrongDirection = !vertical;
                 break;
+            case MEDIA_SEEKBAR:
+                confidence = 0;
+                wrongDirection = vertical;
+                break;
             default:
                 wrongDirection = true;
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index de2bdf7..840982c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -22,6 +22,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
 import android.graphics.Point;
@@ -91,6 +92,7 @@
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
         if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == MEDIA_SEEKBAR
                 || interactionType == SHADE_DRAG
                 || interactionType == LOCK_ICON) {
             return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
deleted file mode 100644
index d6a059d..0000000
--- a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- *  Copyright (C) 2022 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.containeddrawable
-
-import android.graphics.drawable.Drawable
-import androidx.annotation.DrawableRes
-
-/** Convenience container for [Drawable] or a way to load it later. */
-sealed class ContainedDrawable {
-    data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
-    data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 4096ed4..139a8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -46,6 +46,7 @@
 import android.content.res.Resources;
 import android.hardware.SensorManager;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.camera2.CameraManager;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -237,22 +238,39 @@
     @Singleton
     static IDreamManager provideIDreamManager() {
         return IDreamManager.Stub.asInterface(
-                ServiceManager.checkService(DreamService.DREAM_SERVICE));
+                ServiceManager.getService(DreamService.DREAM_SERVICE));
     }
 
     @Provides
     @Singleton
     @Nullable
     static FaceManager provideFaceManager(Context context) {
-        return context.getSystemService(FaceManager.class);
-
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return context.getSystemService(FaceManager.class);
+        }
+        return null;
     }
 
     @Provides
     @Singleton
     @Nullable
     static FingerprintManager providesFingerprintManager(Context context) {
-        return context.getSystemService(FingerprintManager.class);
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return context.getSystemService(FingerprintManager.class);
+        }
+        return null;
+    }
+
+    /**
+     * @return null if both faceManager and fingerprintManager are null.
+     */
+    @Provides
+    @Singleton
+    @Nullable
+    static BiometricManager providesBiometricManager(Context context,
+            @Nullable FaceManager faceManager, @Nullable FingerprintManager fingerprintManager) {
+        return faceManager == null && fingerprintManager == null ? null :
+                context.getSystemService(BiometricManager.class);
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 443d277..06dbab9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -81,6 +81,7 @@
 import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
 import com.android.systemui.statusbar.window.StatusBarWindowModule;
+import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
 import com.android.systemui.tuner.dagger.TunerModule;
 import com.android.systemui.unfold.SysUIUnfoldModule;
 import com.android.systemui.user.UserModule;
@@ -145,6 +146,7 @@
             StatusBarWindowModule.class,
             SysUIConcurrencyModule.class,
             SysUIUnfoldModule.class,
+            TelephonyRepositoryModule.class,
             TunerModule.class,
             UserModule.class,
             UtilModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index b598554..4c4aa5c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -33,6 +33,18 @@
     boolean isProvisioned();
 
     /**
+     * Whether there's a pulse that's been requested but hasn't started transitioning to pulsing
+     * states yet.
+     */
+    boolean isPulsePending();
+
+    /**
+     * @param isPulsePending whether a pulse has been requested but hasn't started transitioning
+     *                       to the pulse state yet
+     */
+    void setPulsePending(boolean isPulsePending);
+
+    /**
      * Makes a current pulse last for twice as long.
      * @param reason why we're extending it.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4161cf6..8ae305b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -280,8 +280,8 @@
     /**
      * Appends pulse dropped event to logs
      */
-    public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
-        mLogger.logPulseDropped(pulsePending, state, blocked);
+    public void tracePulseDropped(String from, DozeMachine.State state) {
+        mLogger.logPulseDropped(from, state);
     }
 
     /**
@@ -292,6 +292,13 @@
     }
 
     /**
+     * Appends pulsing event to logs.
+     */
+    public void tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason) {
+        mLogger.logPulseEvent(pulseEvent, dozing, DozeLog.reasonToString(pulseReason));
+    }
+
+    /**
      * Appends pulse dropped event to logs
      * @param reason why the pulse was dropped
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4b279ec..21a2c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -224,13 +224,12 @@
         })
     }
 
-    fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
+    fun logPulseDropped(from: String, state: DozeMachine.State) {
         buffer.log(TAG, INFO, {
-            bool1 = pulsePending
-            str1 = state.name
-            bool2 = blocked
+            str1 = from
+            str2 = state.name
         }, {
-            "Pulse dropped, pulsePending=$bool1 state=$str1 blocked=$bool2"
+            "Pulse dropped, cannot pulse from=$str1 state=$str2"
         })
     }
 
@@ -243,6 +242,16 @@
         })
     }
 
+    fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = pulseEvent
+            bool1 = dozing
+            str2 = pulseReason
+        }, {
+            "Pulse-$str1 dozing=$bool1 pulseReason=$str2"
+        })
+    }
+
     fun logPulseDropped(reason: String) {
         buffer.log(TAG, INFO, {
             str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00ac8bc..ef454ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -102,7 +102,6 @@
     private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
-    private boolean mPulsePending;
     private Runnable mAodInterruptRunnable;
 
     /** see {@link #onProximityFar} prox for callback */
@@ -303,8 +302,8 @@
                         null /* onPulseSuppressedListener */);
             }
         } else {
-            proximityCheckThenCall((result) -> {
-                if (result != null && result) {
+            proximityCheckThenCall((isNear) -> {
+                if (isNear != null && isNear) {
                     // In pocket, drop event.
                     mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
                     return;
@@ -410,8 +409,8 @@
         sWakeDisplaySensorState = wake;
 
         if (wake) {
-            proximityCheckThenCall((result) -> {
-                if (result != null && result) {
+            proximityCheckThenCall((isNear) -> {
+                if (isNear != null && isNear) {
                     // In pocket, drop event.
                     return;
                 }
@@ -537,24 +536,44 @@
             return;
         }
 
-        if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
-            if (mAllowPulseTriggers) {
-                mDozeLog.tracePulseDropped(mPulsePending, dozeState, mDozeHost.isPulsingBlocked());
+        if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+            if (!mAllowPulseTriggers) {
+                mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
+            } else if (mDozeHost.isPulsePending()) {
+                mDozeLog.tracePulseDropped("requestPulse - pulsePending");
+            } else if (!canPulse()) {
+                mDozeLog.tracePulseDropped("requestPulse", dozeState);
             }
             runIfNotNull(onPulseSuppressedListener);
             return;
         }
 
-        mPulsePending = true;
-        proximityCheckThenCall((result) -> {
-            if (result != null && result) {
+        mDozeHost.setPulsePending(true);
+        proximityCheckThenCall((isNear) -> {
+            if (isNear != null && isNear) {
                 // in pocket, abort pulse
-                mDozeLog.tracePulseDropped("inPocket");
-                mPulsePending = false;
+                mDozeLog.tracePulseDropped("requestPulse - inPocket");
+                mDozeHost.setPulsePending(false);
                 runIfNotNull(onPulseSuppressedListener);
             } else {
                 // not in pocket, continue pulsing
-                continuePulseRequest(reason);
+                final boolean isPulsePending = mDozeHost.isPulsePending();
+                mDozeHost.setPulsePending(false);
+                if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+                    if (!isPulsePending) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+                                + " pending, pulse was cancelled before it could start"
+                                + " transitioning to pulsing state.");
+                    } else if (mDozeHost.isPulsingBlocked()) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
+                    } else if (!canPulse()) {
+                        mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+                    }
+                    runIfNotNull(onPulseSuppressedListener);
+                    return;
+                }
+
+                mMachine.requestPulse(reason);
             }
         }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
 
@@ -569,16 +588,6 @@
                 || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
     }
 
-    private void continuePulseRequest(int reason) {
-        mPulsePending = false;
-        if (mDozeHost.isPulsingBlocked() || !canPulse()) {
-            mDozeLog.tracePulseDropped(mPulsePending, mMachine.getState(),
-                    mDozeHost.isPulsingBlocked());
-            return;
-        }
-        mMachine.requestPulse(reason);
-    }
-
     @Nullable
     private InstanceId getKeyguardSessionId() {
         return mSessionTracker.getSessionId(SESSION_KEYGUARD);
@@ -591,7 +600,7 @@
         pw.print(" notificationPulseTime=");
         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
 
-        pw.println(" pulsePending=" + mPulsePending);
+        pw.println(" DozeHost#isPulsePending=" + mDozeHost.isPulsePending());
         pw.println("DozeSensors:");
         IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
         idpw.increaseIndent();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 2503d3c..821e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -28,6 +28,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -158,17 +161,38 @@
         private final Context mContext;
         private final ControlsComponent mControlsComponent;
 
+        private final UiEventLogger mUiEventLogger;
+
+        @VisibleForTesting
+        public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+            @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+            DREAM_HOME_CONTROLS_TAPPED(1212);
+
+            private final int mId;
+
+            DreamOverlayEvent(int id) {
+                mId = id;
+            }
+
+            @Override
+            public int getId() {
+                return mId;
+            }
+        }
+
         @Inject
         DreamHomeControlsChipViewController(
                 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
-                ControlsComponent controlsComponent) {
+                ControlsComponent controlsComponent,
+                UiEventLogger uiEventLogger) {
             super(view);
 
             mActivityStarter = activityStarter;
             mContext = context;
             mControlsComponent = controlsComponent;
+            mUiEventLogger = uiEventLogger;
         }
 
         @Override
@@ -184,6 +208,8 @@
         private void onClickHomeControls(View v) {
             if (DEBUG) Log.d(TAG, "home controls complication tapped");
 
+            mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+
             final Intent intent = new Intent(mContext, ControlsActivity.class)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
                     .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 93f13eb..978aaa0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -68,6 +68,9 @@
 
     public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
 
+    public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
+            false);
+
     // next id: 112
 
     /***************************************/
@@ -104,9 +107,26 @@
     public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
             new ReleasedFlag(209, true);
 
-    /** Whether the new implementation of UserSwitcherController should be used. */
-    public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
-            new UnreleasedFlag(210, false);
+    /**
+     * Whether the user interactor and repository should use `UserSwitcherController`.
+     *
+     * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
+     * the framework APIs.
+     */
+    public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
+            new UnreleasedFlag(210, true);
+
+    /**
+     * Whether `UserSwitcherController` should use the user interactor.
+     *
+     * <p>When this is {@code true}, the controller does not directly access framework APIs.
+     * Instead, it goes through the interactor.
+     *
+     * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
+     * {@code true} as it would created a cycle between controller -> interactor -> controller.
+     */
+    public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
+            new UnreleasedFlag(211, false);
 
     /***************************************/
     // 300 - power menu
@@ -247,6 +267,17 @@
     public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
             new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
 
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
+            new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
+            new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
+
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+            new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
     // 1200 - predictive back
     @Keep
     public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 38b98eb..12101d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -804,9 +803,6 @@
             } else if (trustAgentsEnabled
                     && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
-            } else if (trustAgentsEnabled
-                    && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
-                return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
             } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
                     || mUpdateMonitor.isFingerprintLockedOut())) {
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 840a4b2..45b668e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -85,6 +85,15 @@
      */
     val dozeAmount: Flow<Float>
 
+    /**
+     * Returns `true` if the keyguard is showing; `false` otherwise.
+     *
+     * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+     * the z-order (which is not really above the system UI window, but rather - the lock-screen
+     * becomes invisible to reveal the "occluding activity").
+     */
+    fun isKeyguardShowing(): Boolean
+
     /** Sets whether the bottom area UI should animate the transition out of doze state. */
     fun setAnimateDozingTransitions(animate: Boolean)
 
@@ -103,7 +112,7 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    keyguardStateController: KeyguardStateController,
+    private val keyguardStateController: KeyguardStateController,
     dozeHost: DozeHost,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
@@ -148,7 +157,11 @@
                         }
                     }
                 dozeHost.addCallback(callback)
-                trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+                trySendWithFailureLogging(
+                    statusBarStateController.isDozing,
+                    TAG,
+                    "initial isDozing",
+                )
 
                 awaitClose { dozeHost.removeCallback(callback) }
             }
@@ -168,6 +181,10 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override fun isKeyguardShowing(): Boolean {
+        return keyguardStateController.isShowing
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index dccc941..192919e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -29,7 +29,7 @@
 class KeyguardInteractor
 @Inject
 constructor(
-    repository: KeyguardRepository,
+    private val repository: KeyguardRepository,
 ) {
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -40,4 +40,8 @@
     val isDozing: Flow<Boolean> = repository.isDozing
     /** Whether the keyguard is showing ot not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+
+    fun isKeyguardShowing(): Boolean {
+        return repository.isKeyguardShowing()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 95acc0b..01cd3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -104,7 +104,6 @@
                 KeyguardQuickAffordanceModel.Visible(
                     configKey = configs[index]::class,
                     icon = visibleState.icon,
-                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
                 )
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eff1469..eb6bb92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -17,8 +17,7 @@
 
 package com.android.systemui.keyguard.domain.model
 
-import androidx.annotation.StringRes
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
@@ -35,11 +34,6 @@
         /** Identifier for the affordance this is modeling. */
         val configKey: KClass<out KeyguardQuickAffordanceConfig>,
         /** An icon for the affordance. */
-        val icon: ContainedDrawable,
-        /**
-         * Resource ID for a string to use for the accessibility content description text of the
-         * affordance.
-         */
-        @StringRes val contentDescriptionResourceId: Int,
+        val icon: Icon,
     ) : KeyguardQuickAffordanceModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index ac2c9b1..89604f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.dagger.ControlsComponent
@@ -122,8 +123,14 @@
                 visibility == ControlsComponent.Visibility.AVAILABLE
         ) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithResource(iconResourceId),
-                contentDescriptionResourceId = component.getTileTitleId(),
+                icon =
+                    Icon.Resource(
+                        res = iconResourceId,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = component.getTileTitleId(),
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8fb952c..8e1c6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,9 +18,8 @@
 package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
-import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -44,12 +43,7 @@
         /** An affordance is visible. */
         data class Visible(
             /** An icon for the affordance. */
-            val icon: ContainedDrawable,
-            /**
-             * Resource ID for a string to use for the accessibility content description text of the
-             * affordance.
-             */
-            @StringRes val contentDescriptionResourceId: Int,
+            val icon: Icon,
         ) : State()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index c8e5e4a..d97deaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import javax.inject.Inject
@@ -76,8 +77,14 @@
     private fun state(): KeyguardQuickAffordanceConfig.State {
         return if (controller.isEnabledForLockScreenButton) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
-                contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+                icon =
+                    Icon.Resource(
+                        res = R.drawable.ic_qr_code_scanner,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_qr_code_scanner_button,
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 885af33..9196b09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -26,7 +26,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
@@ -100,8 +101,14 @@
     ): KeyguardQuickAffordanceConfig.State {
         return if (isFeatureEnabled && hasCard && tileIcon != null) {
             KeyguardQuickAffordanceConfig.State.Visible(
-                icon = ContainedDrawable.WithDrawable(tileIcon),
-                contentDescriptionResourceId = R.string.accessibility_wallet_button,
+                icon =
+                    Icon.Loaded(
+                        drawable = tileIcon,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_wallet_button,
+                            ),
+                    ),
             )
         } else {
             KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c4e3d4e..65b85c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -236,10 +236,7 @@
             }
         }
 
-        when (viewModel.icon) {
-            is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
-            is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
-        }
+        IconViewBinder.bind(viewModel.icon, view)
 
         view.drawable.setTint(
             Utils.getColorAttrDefaultColor(
@@ -250,7 +247,6 @@
         view.backgroundTintList =
             Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
 
-        view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             view.setOnClickListener(OnClickListener(viewModel, falsingManager))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index e3ebac6..970ee4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -116,7 +116,6 @@
                     isVisible = true,
                     animateReveal = animateReveal,
                     icon = icon,
-                    contentDescriptionResourceId = contentDescriptionResourceId,
                     onClicked = { parameters ->
                         quickAffordanceInteractor.onQuickAffordanceClicked(
                             configKey = parameters.configKey,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index b1de27d..0971f13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,9 +16,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
@@ -28,8 +27,7 @@
     val isVisible: Boolean = false,
     /** Whether to animate the transition of the quick affordance from invisible to visible. */
     val animateReveal: Boolean = false,
-    val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
-    @StringRes val contentDescriptionResourceId: Int = 0,
+    val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
     val onClicked: (OnClickedParameters) -> Unit = {},
     val isClickable: Boolean = false,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
new file mode 100644
index 0000000..aef3471
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A [com.android.systemui.log.LogBuffer] for keyguard-related stuff. Should be used mostly for
+ * adding temporary logs or logging from smaller classes when creating new separate log class might
+ * be an overkill.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class KeyguardLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 29e2c1c..d298ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -43,7 +43,7 @@
     @SysUISingleton
     @DozeLog
     public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
-        return factory.create("DozeLog", 100);
+        return factory.create("DozeLog", 120);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -334,4 +334,14 @@
     public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
         return factory.create("BluetoothLog", 50);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for general keyguard-related logs.
+     */
+    @Provides
+    @SysUISingleton
+    @KeyguardLog
+    public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyguardLog", 250);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 8368792..2cd564f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -449,7 +449,7 @@
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
                 .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
-        val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying
+        val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
         if (existingPlayer == null) {
             val newPlayer = mediaControlPanelFactory.get()
             newPlayer.attachPlayer(MediaViewHolder.create(
@@ -1042,15 +1042,6 @@
         }
     }
 
-    fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? {
-        mediaData.forEach { (key, value) ->
-            if (value == mediaSortKey) {
-                return mediaData[key]?.data
-            }
-        }
-        return null
-    }
-
     fun getMediaPlayer(key: String): MediaControlPanel? {
         return mediaData.get(key)?.let { mediaPlayers.get(it) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 0359c63..17ebfec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -30,7 +30,10 @@
 import androidx.core.view.GestureDetectorCompat
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
+import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.util.concurrency.RepeatableExecutor
 import javax.inject.Inject
@@ -72,7 +75,8 @@
 
 /** ViewModel for seek bar in QS media player. */
 class SeekBarViewModel @Inject constructor(
-    @Background private val bgExecutor: RepeatableExecutor
+    @Background private val bgExecutor: RepeatableExecutor,
+    private val falsingManager: FalsingManager,
 ) {
     private var _data = Progress(false, false, false, false, null, 0)
         set(value) {
@@ -275,7 +279,7 @@
     /** Gets a listener to attach to the seek bar to handle seeking. */
     val seekBarListener: SeekBar.OnSeekBarChangeListener
         get() {
-            return SeekBarChangeListener(this)
+            return SeekBarChangeListener(this, falsingManager)
         }
 
     /** Attach touch handlers to the seek bar view. */
@@ -315,7 +319,8 @@
     }
 
     private class SeekBarChangeListener(
-        val viewModel: SeekBarViewModel
+        val viewModel: SeekBarViewModel,
+        val falsingManager: FalsingManager,
     ) : SeekBar.OnSeekBarChangeListener {
         override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
             if (fromUser) {
@@ -328,6 +333,13 @@
         }
 
         override fun onStopTrackingTouch(bar: SeekBar) {
+            // in addition to the normal functionality of both functions.
+            // isFalseTouch returns true if there is a real/false tap since it is not a move.
+            // isFalseTap returns true if there is a real/false move since it is not a tap.
+            if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
+                    falsingManager.isFalseTap(LOW_PENALTY)) {
+                viewModel.onSeekFalse()
+            }
             viewModel.onSeek(bar.progress.toLong())
         }
     }
@@ -340,7 +352,7 @@
      */
     private class SeekBarTouchListener(
         private val viewModel: SeekBarViewModel,
-        private val bar: SeekBar
+        private val bar: SeekBar,
     ) : View.OnTouchListener, GestureDetector.OnGestureListener {
 
         // Gesture detector helps decide which touch events to intercept.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 4379d25..aae973d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -25,6 +25,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
 
 /**
@@ -107,12 +108,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
                 )
@@ -143,12 +147,15 @@
             controllerSender: MediaTttChipControllerSender,
             routeInfo: MediaRoute2Info,
             undoCallback: IUndoMediaTransferCallback?,
-            uiEventLogger: MediaTttSenderUiEventLogger
+            uiEventLogger: MediaTttSenderUiEventLogger,
+            falsingManager: FalsingManager,
         ): View.OnClickListener? {
             if (undoCallback == null) {
                 return null
             }
             return View.OnClickListener {
+                if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
                 uiEventLogger.logUndoClicked(
                     MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
                 )
@@ -215,7 +222,8 @@
         controllerSender: MediaTttChipControllerSender,
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?,
-        uiEventLogger: MediaTttSenderUiEventLogger
+        uiEventLogger: MediaTttSenderUiEventLogger,
+        falsingManager: FalsingManager,
     ): View.OnClickListener? = null
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index e539f3f..007eb8f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -22,25 +22,30 @@
 import android.os.PowerManager
 import android.util.Log
 import android.view.Gravity
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.widget.TextView
 import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
 import javax.inject.Inject
 
 /**
@@ -57,7 +62,11 @@
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
-        private val uiEventLogger: MediaTttSenderUiEventLogger
+        private val uiEventLogger: MediaTttSenderUiEventLogger,
+        // Added Lazy<> to delay the time we create Falsing instances.
+        // And overcome performance issue, check [b/247817628] for details.
+        private val falsingManager: Lazy<FalsingManager>,
+        private val falsingCollector: Lazy<FalsingCollector>,
 ) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
         context,
         logger,
@@ -70,6 +79,9 @@
         MediaTttUtils.WINDOW_TITLE,
         MediaTttUtils.WAKE_REASON,
 ) {
+
+    private lateinit var parent: MediaTttChipRootView
+
     override val windowLayoutParams = commonWindowLayoutParams.apply {
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
@@ -120,6 +132,15 @@
 
         val chipState = newInfo.state
 
+        // Detect falsing touches on the chip.
+        parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
+        parent.touchHandler = object : Gefingerpoken {
+            override fun onTouchEvent(ev: MotionEvent?): Boolean {
+                falsingCollector.get().onTouchEvent(ev)
+                return false
+            }
+        }
+
         // App icon
         val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context, newInfo.routeInfo.clientPackageName, logger
@@ -142,7 +163,11 @@
         // Undo
         val undoView = currentView.requireViewById<View>(R.id.undo)
         val undoClickListener = chipState.undoClickListener(
-                this, newInfo.routeInfo, newInfo.undoCallback, uiEventLogger
+                this,
+                newInfo.routeInfo,
+                newInfo.undoCallback,
+                uiEventLogger,
+                falsingManager.get(),
         )
         undoView.setOnClickListener(undoClickListener)
         undoView.visibility = (undoClickListener != null).visibleIfTrue()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
new file mode 100644
index 0000000..3373159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.sender
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events on chip. */
+class MediaTttChipRootView(
+        context: Context,
+        attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+    /** Assign this field to observe touch events. */
+    var touchHandler: Gefingerpoken? = null
+
+    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+        touchHandler?.onTouchEvent(ev)
+        return super.dispatchTouchEvent(ev)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0f1338e..b26b42c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -57,7 +57,6 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.policy.GestureNavigationSettingsObserver;
-import com.android.internal.util.LatencyTracker;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -199,7 +198,7 @@
     private final Rect mNavBarOverlayExcludedBounds = new Rect();
     private final Region mExcludeRegion = new Region();
     private final Region mUnrestrictedExcludeRegion = new Region();
-    private final LatencyTracker mLatencyTracker;
+    private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
     private final Provider<BackGestureTfClassifierProvider>
             mBackGestureTfClassifierProviderProvider;
     private final FeatureFlags mFeatureFlags;
@@ -339,7 +338,7 @@
             IWindowManager windowManagerService,
             Optional<Pip> pipOptional,
             FalsingManager falsingManager,
-            LatencyTracker latencyTracker,
+            Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
             FeatureFlags featureFlags) {
         super(broadcastDispatcher);
@@ -358,7 +357,7 @@
         mWindowManagerService = windowManagerService;
         mPipOptional = pipOptional;
         mFalsingManager = falsingManager;
-        mLatencyTracker = latencyTracker;
+        mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
         mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
         mFeatureFlags = featureFlags;
         mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
@@ -583,8 +582,7 @@
             setEdgeBackPlugin(
                     mBackPanelControllerFactory.create(mContext));
         } else {
-            setEdgeBackPlugin(
-                    new NavigationBarEdgePanel(mContext, mLatencyTracker));
+            setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
         }
     }
 
@@ -1091,7 +1089,7 @@
         private final IWindowManager mWindowManagerService;
         private final Optional<Pip> mPipOptional;
         private final FalsingManager mFalsingManager;
-        private final LatencyTracker mLatencyTracker;
+        private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
         private final Provider<BackGestureTfClassifierProvider>
                 mBackGestureTfClassifierProviderProvider;
         private final FeatureFlags mFeatureFlags;
@@ -1111,7 +1109,7 @@
                        IWindowManager windowManagerService,
                        Optional<Pip> pipOptional,
                        FalsingManager falsingManager,
-                       LatencyTracker latencyTracker,
+                       Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
                        Provider<BackGestureTfClassifierProvider>
                                backGestureTfClassifierProviderProvider,
                        FeatureFlags featureFlags) {
@@ -1129,7 +1127,7 @@
             mWindowManagerService = windowManagerService;
             mPipOptional = pipOptional;
             mFalsingManager = falsingManager;
-            mLatencyTracker = latencyTracker;
+            mNavBarEdgePanelProvider = navBarEdgePanelProvider;
             mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
             mFeatureFlags = featureFlags;
         }
@@ -1152,7 +1150,7 @@
                     mWindowManagerService,
                     mPipOptional,
                     mFalsingManager,
-                    mLatencyTracker,
+                    mNavBarEdgePanelProvider,
                     mBackGestureTfClassifierProviderProvider,
                     mFeatureFlags);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 24efc76..1230708 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -52,9 +52,9 @@
 
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +62,8 @@
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
 
     private static final String TAG = "NavigationBarEdgePanel";
@@ -282,11 +284,16 @@
             };
     private BackCallback mBackCallback;
 
-    public NavigationBarEdgePanel(Context context, LatencyTracker latencyTracker) {
+    @Inject
+    public NavigationBarEdgePanel(
+            Context context,
+            LatencyTracker latencyTracker,
+            VibratorHelper vibratorHelper,
+            @Background Executor backgroundExecutor) {
         super(context);
 
         mWindowManager = context.getSystemService(WindowManager.class);
-        mVibratorHelper = Dependency.get(VibratorHelper.class);
+        mVibratorHelper = vibratorHelper;
 
         mDensity = context.getResources().getDisplayMetrics().density;
 
@@ -358,7 +365,6 @@
 
         setVisibility(GONE);
 
-        Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 7b27cf4..1ef6426 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -60,6 +60,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -82,7 +83,7 @@
     private static final String EXTRA_VISIBLE = "visible";
 
     private final Rect mQsBounds = new Rect();
-    private final StatusBarStateController mStatusBarStateController;
+    private final SysuiStatusBarStateController mStatusBarStateController;
     private final FalsingManager mFalsingManager;
     private final KeyguardBypassController mBypassController;
     private boolean mQsExpanded;
@@ -159,7 +160,7 @@
      * Progress of pull down from the center of the lock screen.
      * @see com.android.systemui.statusbar.LockscreenShadeTransitionController
      */
-    private float mFullShadeProgress;
+    private float mLockscreenToShadeProgress;
 
     private boolean mOverScrolling;
 
@@ -177,7 +178,7 @@
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             QSTileHost qsTileHost,
-            StatusBarStateController statusBarStateController, CommandQueue commandQueue,
+            SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
             @Named(QS_PANEL) MediaHost qsMediaHost,
             @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
             KeyguardBypassController keyguardBypassController,
@@ -442,20 +443,19 @@
     }
 
     private void updateQsState() {
-        final boolean expanded = mQsExpanded || mInSplitShade;
-        final boolean expandVisually = expanded || mStackScrollerOverscrolling
+        final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
                 || mHeaderAnimating;
-        mQSPanelController.setExpanded(expanded);
+        mQSPanelController.setExpanded(mQsExpanded);
         boolean keyguardShowing = isKeyguardState();
-        mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
+        mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
                 ? View.VISIBLE
                 : View.INVISIBLE);
         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
+                || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
         boolean qsPanelVisible = !mQsDisabled && expandVisually;
-        boolean footerVisible = qsPanelVisible &&  (expanded || !keyguardShowing || mHeaderAnimating
-                || mShowCollapsedOnKeyguard);
+        boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
+                || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
         if (mQSFooterActionController != null) {
             mQSFooterActionController.setVisible(footerVisible);
@@ -463,7 +463,7 @@
             mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
         }
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (expanded && !mStackScrollerOverscrolling));
+                || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
         if (DEBUG) {
             Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible);
@@ -586,7 +586,7 @@
             mTransitioningToFullShade = isTransitioningToFullShade;
             updateShowCollapsedOnKeyguard();
         }
-        mFullShadeProgress = qsTransitionFraction;
+        mLockscreenToShadeProgress = qsTransitionFraction;
         setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation,
                 isTransitioningToFullShade ? qsSquishinessFraction : mSquishinessFraction);
     }
@@ -710,10 +710,13 @@
         }
         if (mInSplitShade) {
             // Large screens in landscape.
-            if (mTransitioningToFullShade || isKeyguardState()) {
+            // Need to check upcoming state as for unlocked -> AOD transition current state is
+            // not updated yet, but we're transitioning and UI should already follow KEYGUARD state
+            if (mTransitioningToFullShade || mStatusBarStateController.getCurrentOrUpcomingState()
+                    == StatusBarState.KEYGUARD) {
                 // Always use "mFullShadeProgress" on keyguard, because
                 // "panelExpansionFractions" is always 1 on keyguard split shade.
-                return mFullShadeProgress;
+                return mLockscreenToShadeProgress;
             } else {
                 return panelExpansionFraction;
             }
@@ -722,7 +725,7 @@
         if (mTransitioningToFullShade) {
             // Only use this value during the standard lock screen shade expansion. During the
             // "quick" expansion from top, this value is 0.
-            return mFullShadeProgress;
+            return mLockscreenToShadeProgress;
         } else {
             return panelExpansionFraction;
         }
@@ -930,7 +933,7 @@
         indentingPw.println("mLastHeaderTranslation: " + mLastHeaderTranslation);
         indentingPw.println("mInSplitShade: " + mInSplitShade);
         indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade);
-        indentingPw.println("mFullShadeProgress: " + mFullShadeProgress);
+        indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress);
         indentingPw.println("mOverScrolling: " + mOverScrolling);
         indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing());
         View view = getView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 264edb1..84d7e65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -410,9 +410,9 @@
         // If forceExpanded (we are opening QS from lockscreen), the animators have been set to
         // position = 1f.
         if (forceExpanded) {
-            setTranslationY(panelTranslationY);
+            setAlpha(expansionFraction);
         } else {
-            setTranslationY(0);
+            setAlpha(1);
         }
 
         mKeyguardExpansionFraction = keyguardExpansionFraction;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 97476b2..d2d5063 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -134,7 +134,7 @@
                 v.bind(name, drawable, item.info.id);
             }
             v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
+            v.setDisabledByAdmin(item.isDisabledByAdmin());
             v.setEnabled(item.isSwitchToEnabled);
             UserSwitcherController.setSelectableAlpha(v);
 
@@ -173,16 +173,16 @@
             Trace.beginSection("UserDetailView.Adapter#onClick");
             UserRecord userRecord =
                     (UserRecord) view.getTag();
-            if (mController.isDisabledByAdmin(userRecord)) {
+            if (userRecord.isDisabledByAdmin()) {
                 final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
-                        mContext, mController.getEnforcedAdmin(userRecord));
+                        mContext, userRecord.enforcedAdmin);
                 mController.startActivity(intent);
             } else if (userRecord.isSwitchToEnabled) {
                 MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
                 if (!userRecord.isAddUser
                         && !userRecord.isRestricted
-                        && !mController.isDisabledByAdmin(userRecord)) {
+                        && !userRecord.isDisabledByAdmin()) {
                     if (mCurrentUserView != null) {
                         mCurrentUserView.setActivated(false);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 7e2a5c5..899e57d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -342,14 +342,6 @@
         }
 
         @Override
-        public void notifySwipeToHomeFinished() {
-            verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
-                    mPipOptional.ifPresent(
-                            pip -> pip.setPinnedStackAnimationType(
-                                    PipAnimationController.ANIM_TYPE_ALPHA)));
-        }
-
-        @Override
         public void notifySwipeUpGestureStarted() {
             verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
                     notifySwipeUpGestureStartedInternal());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 55602a9..e3658de 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -19,6 +19,7 @@
 import static android.os.FileUtils.closeQuietly;
 
 import android.annotation.IntRange;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.graphics.Bitmap;
@@ -29,6 +30,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.util.Log;
 
@@ -142,8 +144,9 @@
      *
      * @return a listenable future result
      */
-    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) {
-        return export(executor, requestId, bitmap, ZonedDateTime.now());
+    ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+            UserHandle owner) {
+        return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
     }
 
     /**
@@ -155,10 +158,10 @@
      * @return a listenable future result
      */
     ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
-            ZonedDateTime captureTime) {
+            ZonedDateTime captureTime, UserHandle owner) {
 
         final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true);
+                mQuality, /* publish */ true, owner);
 
         return CallbackToFutureAdapter.getFuture(
                 (completer) -> {
@@ -174,28 +177,6 @@
         );
     }
 
-    /**
-     * Delete the entry.
-     *
-     * @param executor the thread for execution
-     * @param uri the uri of the image to publish
-     *
-     * @return a listenable future result
-     */
-    ListenableFuture<Result> delete(Executor executor, Uri uri) {
-        return CallbackToFutureAdapter.getFuture((completer) -> {
-            executor.execute(() -> {
-                mResolver.delete(uri, null);
-
-                Result result = new Result();
-                result.uri = uri;
-                result.deleted = true;
-                completer.set(result);
-            });
-            return "ContentResolver#delete";
-        });
-    }
-
     static class Result {
         Uri uri;
         UUID requestId;
@@ -203,7 +184,6 @@
         long timestamp;
         CompressFormat format;
         boolean published;
-        boolean deleted;
 
         @Override
         public String toString() {
@@ -214,7 +194,6 @@
             sb.append(", timestamp=").append(timestamp);
             sb.append(", format=").append(format);
             sb.append(", published=").append(published);
-            sb.append(", deleted=").append(deleted);
             sb.append('}');
             return sb.toString();
         }
@@ -227,17 +206,19 @@
         private final ZonedDateTime mCaptureTime;
         private final CompressFormat mFormat;
         private final int mQuality;
+        private final UserHandle mOwner;
         private final String mFileName;
         private final boolean mPublish;
 
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
-                CompressFormat format, int quality, boolean publish) {
+                CompressFormat format, int quality, boolean publish, UserHandle owner) {
             mResolver = resolver;
             mRequestId = requestId;
             mBitmap = bitmap;
             mCaptureTime = captureTime;
             mFormat = format;
             mQuality = quality;
+            mOwner = owner;
             mFileName = createFilename(mCaptureTime, mFormat);
             mPublish = publish;
         }
@@ -253,7 +234,7 @@
                     start = Instant.now();
                 }
 
-                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName);
+                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner);
                 throwIfInterrupted();
 
                 writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -297,15 +278,20 @@
     }
 
     private static Uri createEntry(ContentResolver resolver, CompressFormat format,
-            ZonedDateTime time, String fileName) throws ImageExportException {
+            ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException {
         Trace.beginSection("ImageExporter_createEntry");
         try {
             final ContentValues values = createMetadata(time, format, fileName);
 
-            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+            if (UserHandle.myUserId() != owner.getIdentifier()) {
+                baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+            }
+            Uri uri = resolver.insert(baseUri, values);
             if (uri == null) {
                 throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
             }
+            Log.d(TAG, "Inserted new URI: " + uri);
             return uri;
         } finally {
             Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ba6e98e..8bf956b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -387,7 +388,9 @@
 
         mOutputBitmap = renderBitmap(drawable, bounds);
         ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
-                mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now());
+                mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
+                // TODO: Owner must match the owner of the captured window.
+                Process.myUserHandle());
         exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index f248d69..077ad35 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -48,6 +48,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -71,6 +73,7 @@
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
     private final Context mContext;
+    private FeatureFlags mFlags;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final ScreenshotController.SaveImageInBackgroundData mParams;
     private final ScreenshotController.SavedImageData mImageData;
@@ -84,7 +87,10 @@
     private final ImageExporter mImageExporter;
     private long mImageTime;
 
-    SaveImageInBackgroundTask(Context context, ImageExporter exporter,
+    SaveImageInBackgroundTask(
+            Context context,
+            FeatureFlags flags,
+            ImageExporter exporter,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotController.SaveImageInBackgroundData data,
             Supplier<ActionTransition> sharedElementTransition,
@@ -92,6 +98,7 @@
                     screenshotNotificationSmartActionsProvider
     ) {
         mContext = context;
+        mFlags = flags;
         mScreenshotSmartActions = screenshotSmartActions;
         mImageData = new ScreenshotController.SavedImageData();
         mQuickShareData = new ScreenshotController.QuickShareData();
@@ -117,7 +124,8 @@
         }
         // TODO: move to constructor / from ScreenshotRequest
         final UUID requestId = UUID.randomUUID();
-        final UserHandle user = getUserHandleOfForegroundApplication(mContext);
+        final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+                ? mParams.owner : getUserHandleOfForegroundApplication(mContext);
 
         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
 
@@ -133,8 +141,9 @@
 
             // Call synchronously here since already on a background thread.
             ListenableFuture<ImageExporter.Result> future =
-                    mImageExporter.export(Runnable::run, requestId, image);
+                    mImageExporter.export(Runnable::run, requestId, image, mParams.owner);
             ImageExporter.Result result = future.get();
+            Log.d(TAG, "Saved screenshot: " + result);
             final Uri uri = result.uri;
             mImageTime = result.timestamp;
 
@@ -157,6 +166,7 @@
             }
 
             mImageData.uri = uri;
+            mImageData.owner = user;
             mImageData.smartActions = smartActions;
             mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
             mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 3fee232..df32d20 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,6 +34,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -57,7 +58,9 @@
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -90,6 +93,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.util.Assert;
@@ -151,6 +155,7 @@
         public Consumer<Uri> finisher;
         public ScreenshotController.ActionsReadyListener mActionsReadyListener;
         public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
+        public UserHandle owner;
 
         void clearImage() {
             image = null;
@@ -167,6 +172,8 @@
         public Notification.Action deleteAction;
         public List<Notification.Action> smartActions;
         public Notification.Action quickShareAction;
+        public UserHandle owner;
+
 
         /**
          * POD for shared element transition.
@@ -242,6 +249,7 @@
     private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
 
     private final WindowContext mContext;
+    private final FeatureFlags mFlags;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final UiEventLogger mUiEventLogger;
@@ -288,6 +296,7 @@
     @Inject
     ScreenshotController(
             Context context,
+            FeatureFlags flags,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController screenshotNotificationsController,
             ScrollCaptureClient scrollCaptureClient,
@@ -331,6 +340,7 @@
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mWindowManager = mContext.getSystemService(WindowManager.class);
+        mFlags = flags;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -377,7 +387,6 @@
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
             Consumer<Uri> finisher, RequestCallback requestCallback) {
-        // TODO: use task Id, userId, topComponent for smart handler
         Assert.isMainThread();
         if (screenshot == null) {
             Log.e(TAG, "Got null bitmap from screenshot message");
@@ -395,7 +404,7 @@
         }
         mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
-                showFlash);
+                showFlash, UserHandle.of(userId));
     }
 
     /**
@@ -543,14 +552,15 @@
             return;
         }
 
-        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
+        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
+                Process.myUserHandle());
 
         mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
                 ClipboardOverlayController.SELF_PERMISSION);
     }
 
     private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
-            Insets screenInsets, ComponentName topComponent, boolean showFlash) {
+            Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
         withWindowAttached(() ->
                 mScreenshotView.announceForAccessibility(
                         mContext.getResources().getString(R.string.screenshot_saving_title)));
@@ -575,11 +585,11 @@
 
         mScreenBitmap = screenshot;
 
-        if (!isUserSetupComplete()) {
+        if (!isUserSetupComplete(owner)) {
             Log.w(TAG, "User setup not complete, displaying toast only");
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
             // and sharing shouldn't be exposed to the user.
-            saveScreenshotAndToast(finisher);
+            saveScreenshotAndToast(owner, finisher);
             return;
         }
 
@@ -587,7 +597,7 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
-        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
+        saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
                 this::showUiOnQuickShareActionReady);
 
         // The window is focusable by default
@@ -853,11 +863,12 @@
      * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
      * failure).
      */
-    private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+    private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSound();
 
         saveScreenshotInWorkerThread(
+                owner,
                 /* onComplete */ finisher,
                 /* actionsReadyListener */ imageData -> {
                     if (DEBUG_CALLBACK) {
@@ -925,9 +936,11 @@
     /**
      * Creates a new worker thread and saves the screenshot to the media store.
      */
-    private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
-            @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
-            @Nullable ScreenshotController.QuickShareActionReadyListener
+    private void saveScreenshotInWorkerThread(
+            UserHandle owner,
+            @NonNull Consumer<Uri> finisher,
+            @Nullable ActionsReadyListener actionsReadyListener,
+            @Nullable QuickShareActionReadyListener
                     quickShareActionsReadyListener) {
         ScreenshotController.SaveImageInBackgroundData
                 data = new ScreenshotController.SaveImageInBackgroundData();
@@ -935,13 +948,14 @@
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
         data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
+        data.owner = owner;
 
         if (mSaveInBgTask != null) {
             // just log success/failure for the pre-existing screenshot
             mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
         }
 
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
                 mScreenshotSmartActions, data, getActionTransitionSupplier(),
                 mScreenshotNotificationSmartActionsProvider);
         mSaveInBgTask.execute();
@@ -960,6 +974,15 @@
         mScreenshotHandler.resetTimeout();
 
         if (imageData.uri != null) {
+            if (!imageData.owner.equals(Process.myUserHandle())) {
+                // TODO: Handle non-primary user ownership (e.g. Work Profile)
+                // This image is owned by another user. Special treatment will be
+                // required in the UI (badging) as well as sending intents which can
+                // correctly forward those URIs on to be read (actions).
+
+                Log.d(TAG, "*** Screenshot saved to a non-primary user ("
+                        + imageData.owner + ") as " + imageData.uri);
+            }
             mScreenshotHandler.post(() -> {
                 if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                     mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@@ -1033,9 +1056,9 @@
         }
     }
 
-    private boolean isUserSetupComplete() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    private boolean isUserSetupComplete(UserHandle owner) {
+        return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+                        .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index c2a5060..3a35286 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -68,7 +68,9 @@
     }
 
     override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
-        return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+        val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+        Log.d(TAG, "isManagedProfile: $managed")
+        return managed
     }
 
     private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 83b60fb..30a0b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -78,6 +78,7 @@
     static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
         private final Session mSession;
+        // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
 
         LongScreenshot(Session session, ImageTileSet imageTileSet) {
             mSession = session;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index a22fda7..6e9f859 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -76,6 +77,12 @@
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
         // The brightness mirror container is INVISIBLE by default.
         frame.setVisibility(View.VISIBLE);
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) frame.getLayoutParams();
+        int horizontalMargin =
+                getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+        lp.leftMargin = horizontalMargin;
+        lp.rightMargin = horizontalMargin;
+        frame.setLayoutParams(lp);
 
         BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
         controller.init();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d7e86b6..1110386 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -425,11 +425,10 @@
             new KeyguardClockPositionAlgorithm.Result();
     private boolean mIsExpanding;
 
-    private boolean mBlockTouches;
-
     /**
      * Determines if QS should be already expanded when expanding shade.
      * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
+     * It needs to be set when movement starts as it resets at the end of expansion/collapse.
      */
     @VisibleForTesting
     boolean mQsExpandImmediate;
@@ -1692,7 +1691,6 @@
 
     public void resetViews(boolean animate) {
         mIsLaunchTransitionFinished = false;
-        mBlockTouches = false;
         mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         if (animate && !isFullyCollapsed()) {
@@ -1737,8 +1735,10 @@
     }
 
     private void setQsExpandImmediate(boolean expandImmediate) {
-        mQsExpandImmediate = expandImmediate;
-        mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+        if (expandImmediate != mQsExpandImmediate) {
+            mQsExpandImmediate = expandImmediate;
+            mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+        }
     }
 
     private void setShowShelfOnly(boolean shelfOnly) {
@@ -2479,17 +2479,23 @@
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
         mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
 
-        // updateQsExpansion will get called whenever mTransitionToFullShadeProgress or
-        // mLockscreenShadeTransitionController.getDragProgress change.
-        // When in lockscreen, getDragProgress indicates the true expanded fraction of QS
-        float shadeExpandedFraction = mTransitioningToFullShadeProgress > 0
-                ? mLockscreenShadeTransitionController.getQSDragProgress()
+        float shadeExpandedFraction = isOnKeyguard()
+                ? getLockscreenShadeDragProgress()
                 : getExpandedFraction();
         mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
         mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
     }
 
+    private float getLockscreenShadeDragProgress() {
+        // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
+        // transition. If that's not the case we should follow QS expansion fraction for when
+        // user is pulling from the same top to go directly to expanded QS
+        return mTransitioningToFullShadeProgress > 0
+                ? mLockscreenShadeTransitionController.getQSDragProgress()
+                : computeQsExpansionFraction();
+    }
+
     private void onStackYChanged(boolean shouldAnimate) {
         if (mQs != null) {
             if (shouldAnimate) {
@@ -3124,26 +3130,24 @@
         }
         if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
                 && !mQsExpansionFromOverscroll)) {
-            float t;
-            if (mKeyguardShowing) {
-
+            float qsExpansionFraction;
+            if (mSplitShadeEnabled) {
+                qsExpansionFraction = 1;
+            } else if (mKeyguardShowing) {
                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / (getMaxPanelHeight());
+                qsExpansionFraction = expandedHeight / (getMaxPanelHeight());
             } else {
                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
                 // minimum QS expansion + minStackHeight
-                float
-                        panelHeightQsCollapsed =
+                float panelHeightQsCollapsed =
                         mNotificationStackScrollLayoutController.getIntrinsicPadding()
                                 + mNotificationStackScrollLayoutController.getLayoutMinHeight();
                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
-                t =
-                        (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
-                                - panelHeightQsCollapsed);
+                qsExpansionFraction = (expandedHeight - panelHeightQsCollapsed)
+                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
             }
-            float
-                    targetHeight =
-                    mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+            float targetHeight = mQsMinExpansionHeight
+                    + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
             setQsExpansion(targetHeight);
         }
         updateExpandedHeight(expandedHeight);
@@ -3329,7 +3333,11 @@
         } else {
             setListening(true);
         }
-        setQsExpandImmediate(false);
+        if (mBarState != SHADE) {
+            // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
+            // on keyguard panel state is always OPEN so we need to have that extra update
+            setQsExpandImmediate(false);
+        }
         setShowShelfOnly(false);
         mTwoFingerQsExpandPossible = false;
         updateTrackingHeadsUp(null);
@@ -4187,7 +4195,7 @@
                             "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
                                     + "," + event.getY() + ")");
                 }
-                if (mBlockTouches || mQs.disallowPanelTouches()) {
+                if (mQs.disallowPanelTouches()) {
                     return false;
                 }
                 initDownStates(event);
@@ -4230,8 +4238,7 @@
                 }
 
 
-                if (mBlockTouches || (mQsFullyExpanded && mQs != null
-                        && mQs.disallowPanelTouches())) {
+                if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
                     return false;
                 }
 
@@ -4678,12 +4685,19 @@
                     }
                 }
             } else {
+                // this else branch means we are doing one of:
+                //  - from KEYGUARD and SHADE (but not expanded shade)
+                //  - from SHADE to KEYGUARD
+                //  - from SHADE_LOCKED to SHADE
+                //  - getting notified again about the current SHADE or KEYGUARD state
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
                 if (!animatingUnlockedShadeToKeyguard) {
                     // Only make the status bar visible if we're not animating the screen off, since
                     // we only want to be showing the clock/notifications during the animation.
+                    mShadeLog.v("Updating keyguard status bar state to "
+                            + (keyguardShowing ? "visible" : "invisible"));
                     mKeyguardStatusBarViewController.updateViewState(
                             /* alpha= */ 1f,
                             keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -4749,9 +4763,7 @@
 
                 @Override
                 public float getLockscreenShadeDragProgress() {
-                    return mTransitioningToFullShadeProgress > 0
-                            ? mLockscreenShadeTransitionController.getQSDragProgress()
-                            : computeQsExpansionFraction();
+                    return NotificationPanelViewController.this.getLockscreenShadeDragProgress();
                 }
             };
 
@@ -4988,6 +5000,7 @@
         updateQSExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
+            setQsExpandImmediate(false);
             mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         }
         if (state == STATE_OPENING) {
@@ -5000,6 +5013,7 @@
             mCentralSurfaces.makeExpandedVisible(false);
         }
         if (state == STATE_CLOSED) {
+            setQsExpandImmediate(false);
             // Close the status bar in the next frame so we can show the end of the
             // animation.
             mView.post(mMaybeHideExpandedRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064..ce730ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@
 import android.widget.FrameLayout
 
 /**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * A temporary base class that's shared between our old status bar connectivity view implementations
+ * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
+ * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
  */
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8699441..c290ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -42,7 +42,6 @@
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -97,6 +96,7 @@
     private final List<UserChangedListener> mListeners = new ArrayList<>();
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final NotificationClickNotifier mClickNotifier;
+    private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
 
     private boolean mShowLockscreenNotifications;
     private boolean mAllowLockscreenRemoteInput;
@@ -157,7 +157,7 @@
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
                     // Start the overview connection to the launcher service
-                    Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
                     break;
                 case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
                     final IntentSender intentSender = intent.getParcelableExtra(
@@ -199,6 +199,7 @@
             Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
             Lazy<CommonNotifCollection> commonNotifCollectionLazy,
             NotificationClickNotifier clickNotifier,
+            Lazy<OverviewProxyService> overviewProxyServiceLazy,
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
             @Main Handler mainHandler,
@@ -214,6 +215,7 @@
         mVisibilityProviderLazy = visibilityProviderLazy;
         mCommonNotifCollectionLazy = commonNotifCollectionLazy;
         mClickNotifier = clickNotifier;
+        mOverviewProxyServiceLazy = overviewProxyServiceLazy;
         statusBarStateController.addCallback(this);
         mLockPatternUtils = new LockPatternUtils(context);
         mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c900c5a..4be5a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -43,7 +43,6 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -89,11 +88,9 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final StatusBarStateController mStatusBarStateController
-            = Dependency.get(StatusBarStateController.class);
-    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
-    private final KeyguardStateController mKeyguardStateController = Dependency.get(
-            KeyguardStateController.class);
+    private final StatusBarStateController mStatusBarStateController;
+    private final SysuiColorExtractor mColorExtractor;
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
@@ -179,6 +176,9 @@
             NotifCollection notifCollection,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor,
+            KeyguardStateController keyguardStateController,
             DumpManager dumpManager) {
         mContext = context;
         mMediaArtworkProcessor = mediaArtworkProcessor;
@@ -192,6 +192,9 @@
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
+        mStatusBarStateController = statusBarStateController;
+        mColorExtractor = colorExtractor;
+        mKeyguardStateController = keyguardStateController;
 
         setupNotifPipeline();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 48c6e27..fdad101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
@@ -43,7 +42,10 @@
 
 import java.util.ArrayList;
 
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
         StatusIconDisplayable {
     private static final String TAG = "StatusBarMobileView";
 
@@ -101,11 +103,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     @Override
     public void getDrawingRect(Rect outRect) {
         super.getDrawingRect(outRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3e74d9..decc70d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@
 /**
  * Start small: StatusBarWifiView will be able to layout from a WifiIconState
  */
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
     private static final String TAG = "StatusBarWifiView";
 
     /// Used to show etc dots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7cd79ca..11e3d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,6 +26,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -130,6 +131,9 @@
             NotifCollection notifCollection,
             @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
+            StatusBarStateController statusBarStateController,
+            SysuiColorExtractor colorExtractor,
+            KeyguardStateController keyguardStateController,
             DumpManager dumpManager) {
         return new NotificationMediaManager(
                 context,
@@ -142,6 +146,9 @@
                 notifCollection,
                 mainExecutor,
                 mediaDataManager,
+                statusBarStateController,
+                colorExtractor,
+                keyguardStateController,
                 dumpManager);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3eaa988..e129ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1398,7 +1398,7 @@
             throw exception;
         }
 
-        Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+        Log.wtf(TAG, "Allowing " + mConsecutiveReentrantRebuilds
                 + " consecutive reentrant notification pipeline rebuild(s).", exception);
         mChoreographer.schedule();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8278b54..ccf6fec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -393,7 +393,7 @@
             val posted = mPostedEntries.compute(entry.key) { _, value ->
                 value?.also { update ->
                     update.wasUpdated = true
-                    update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+                    update.shouldHeadsUpEver = shouldHeadsUpEver
                     update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
                     update.isAlerting = isAlerting
                     update.isBinding = isBinding
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 1aa0295..8e646a3 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
@@ -19,6 +19,8 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
@@ -36,6 +38,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -68,6 +71,8 @@
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
     private val fgsNotifListener: ForegroundServiceNotificationListener,
+    private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
+    private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
     override fun initialize(
@@ -107,6 +112,9 @@
 
         peopleSpaceWidgetManager.attach(notificationListener)
         fgsNotifListener.init()
+        if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
+            memoryMonitor.get().init()
+        }
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
new file mode 100644
index 0000000..832a739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+/** Describes usage of a notification. */
+data class NotificationMemoryUsage(
+    val packageName: String,
+    val notificationId: String,
+    val objectUsage: NotificationObjectUsage,
+)
+
+/**
+ * Describes current memory usage of a [android.app.Notification] object.
+ *
+ * The values are in bytes.
+ */
+data class NotificationObjectUsage(
+    val smallIcon: Int,
+    val largeIcon: Int,
+    val extras: Int,
+    val style: String?,
+    val styleIcon: Int,
+    val bigPicture: Int,
+    val extender: Int,
+    val hasCustomView: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
new file mode 100644
index 0000000..958978e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -0,0 +1,243 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.core.util.contains
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** This class monitors and logs current Notification memory use. */
+@SysUISingleton
+class NotificationMemoryMonitor
+@Inject
+constructor(
+    val notificationPipeline: NotifPipeline,
+    val dumpManager: DumpManager,
+) : Dumpable {
+
+    companion object {
+        private const val TAG = "NotificationMemMonitor"
+        private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+        private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+        private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+        private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+        private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+    }
+
+    fun init() {
+        Log.d(TAG, "NotificationMemoryMonitor initialized.")
+        dumpManager.registerDumpable(javaClass.simpleName, this)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+    }
+
+    @WorkerThread
+    fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
+        return notificationMemoryUse(notificationPipeline.allNotifs)
+    }
+
+    /** Returns a list of memory use entries for currently shown notifications. */
+    @WorkerThread
+    fun notificationMemoryUse(
+        notifications: Collection<NotificationEntry>
+    ): List<NotificationMemoryUsage> {
+        return notifications
+            .asSequence()
+            .map { entry ->
+                val packageName = entry.sbn.packageName
+                val notificationObjectUsage =
+                    computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
+                NotificationMemoryUsage(
+                    packageName,
+                    NotificationUtils.logKey(entry.sbn.key),
+                    notificationObjectUsage
+                )
+            }
+            .toList()
+    }
+
+    /**
+     * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+     * inspect Bitmaps in the object and provide summary of memory usage.
+     */
+    private fun computeNotificationObjectUse(
+        notification: Notification,
+        seenBitmaps: HashSet<Int>
+    ): NotificationObjectUsage {
+        val extras = notification.extras
+        val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+        val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+        // Collect memory usage of extra styles
+
+        // Big Picture
+        val bigPictureIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
+                computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+        val bigPictureUse =
+            computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+                computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+        // People
+        val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+        val peopleUse =
+            peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+        // Calling
+        val callingPersonUse =
+            computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+        val verificationIconUse =
+            computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+        // Messages
+        val messages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+            )
+        val messagesUse =
+            messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+        val historicMessages =
+            Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+            )
+        val historyicMessagesUse =
+            historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+        // Extenders
+        val carExtender = extras.getBundle(CAR_EXTENSIONS)
+        val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+        val carExtenderIcon =
+            computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+        val tvExtender = extras.getBundle(TV_EXTENSIONS)
+        val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+        val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+        val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+        val wearExtenderBackground =
+            computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+        val style = notification.notificationStyle
+        val hasCustomView = notification.contentView != null || notification.bigContentView != null
+        val extrasSize = computeBundleSize(extras)
+
+        return NotificationObjectUsage(
+            smallIconUse,
+            largeIconUse,
+            extrasSize,
+            style?.simpleName,
+            bigPictureIconUse +
+                peopleUse +
+                callingPersonUse +
+                verificationIconUse +
+                messagesUse +
+                historyicMessagesUse,
+            bigPictureUse,
+            carExtenderSize +
+                carExtenderIcon +
+                tvExtenderSize +
+                wearExtenderSize +
+                wearExtenderBackground,
+            hasCustomView
+        )
+    }
+
+    /**
+     * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+     * bitmaps). Can be slow.
+     */
+    private fun computeBundleSize(extras: Bundle): Int {
+        val parcel = Parcel.obtain()
+        try {
+            extras.writeToParcel(parcel, 0)
+            return parcel.dataSize()
+        } finally {
+            parcel.recycle()
+        }
+    }
+
+    /**
+     * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+     * if the key does not exist in extras.
+     */
+    private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+        return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+            is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+            is Icon -> computeIconUse(parcelable, seenBitmaps)
+            is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+            else -> 0
+        }
+    }
+
+    /**
+     * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+     * defined via Uri or a resource.
+     *
+     * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+     */
+    private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+        when (icon?.type) {
+            Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+            Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+            else -> 0
+        }
+
+    /**
+     * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+     * seenBitmaps set, this method returns 0 to avoid double counting.
+     *
+     * @return memory usage of the bitmap in bytes
+     */
+    private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+        val refId = System.identityHashCode(bitmap)
+        if (seenBitmaps?.contains(refId) == true) {
+            return 0
+        }
+
+        seenBitmaps?.add(refId)
+        return bitmap.allocationByteCount
+    }
+
+    private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+        val refId = System.identityHashCode(icon.dataBytes)
+        if (seenBitmaps.contains(refId)) {
+            return 0
+        }
+
+        seenBitmaps.add(refId)
+        return icon.dataLength
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 9faef1b..5ca13c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -45,11 +45,21 @@
     void logPanelShown(boolean isLockscreen,
             @Nullable List<NotificationEntry> visibleNotifications);
 
+    /**
+     * Log a NOTIFICATION_PANEL_REPORTED statsd event, with
+     * {@link NotificationPanelEvent#NOTIFICATION_DRAG} as the eventID.
+     *
+     * @param draggedNotification the notification that is being dragged
+     */
+    void logNotificationDrag(NotificationEntry draggedNotification);
+
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
         @UiEvent(doc = "Notification panel shown from status bar.")
         NOTIFICATION_PANEL_OPEN_STATUS_BAR(200),
         @UiEvent(doc = "Notification panel shown from lockscreen.")
-        NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201);
+        NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201),
+        @UiEvent(doc = "Notification was dragged")
+        NOTIFICATION_DRAG(1226);
 
         private final int mId;
         NotificationPanelEvent(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 75a6019..9a63228 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
+import static com.android.systemui.statusbar.notification.logging.NotificationPanelLogger.NotificationPanelEvent.NOTIFICATION_DRAG;
+
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 
 import com.google.protobuf.nano.MessageNano;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -38,4 +41,14 @@
                 /* int num_notifications*/ proto.notifications.length,
                 /* byte[] notifications*/ MessageNano.toByteArray(proto));
     }
+
+    @Override
+    public void logNotificationDrag(NotificationEntry draggedNotification) {
+        final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
+                Collections.singletonList(draggedNotification));
+        SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+                /* int event_id */ NOTIFICATION_DRAG.getId(),
+                /* int num_notifications*/ proto.notifications.length,
+                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 4939a9c..64f87ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,12 +45,17 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
+import java.util.Collections;
+
 import javax.inject.Inject;
 
 /**
@@ -63,14 +68,17 @@
     private final Context mContext;
     private final HeadsUpManager mHeadsUpManager;
     private final ShadeController mShadeController;
+    private NotificationPanelLogger mNotificationPanelLogger;
 
     @Inject
     public ExpandableNotificationRowDragController(Context context,
             HeadsUpManager headsUpManager,
-            ShadeController shadeController) {
+            ShadeController shadeController,
+            NotificationPanelLogger notificationPanelLogger) {
         mContext = context;
         mHeadsUpManager = headsUpManager;
         mShadeController = shadeController;
+        mNotificationPanelLogger = notificationPanelLogger;
 
         init();
     }
@@ -120,12 +128,16 @@
         dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent);
         dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
         ClipData.Item item = new ClipData.Item(dragIntent);
+        InstanceId instanceId = new InstanceIdSequence(Integer.MAX_VALUE).newInstanceId();
+        item.getIntent().putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, instanceId);
         ClipData dragData = new ClipData(clipDescription, item);
         View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
         view.setOnDragListener(getDraggedViewDragListener());
         boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL
                 | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
         if (result) {
+            // Log notification drag only if it succeeds
+            mNotificationPanelLogger.logNotificationDrag(enr.getEntry());
             view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
             if (enr.isPinned()) {
                 mHeadsUpManager.releaseAllImmediately();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index bc172ce..0b435fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -35,12 +35,12 @@
  * bounds change.
  */
 public class NotificationSection {
-    private @PriorityBucket int mBucket;
-    private View mOwningView;
-    private Rect mBounds = new Rect();
-    private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
-    private Rect mStartAnimationRect = new Rect();
-    private Rect mEndAnimationRect = new Rect();
+    private @PriorityBucket final int mBucket;
+    private final View mOwningView;
+    private final Rect mBounds = new Rect();
+    private final Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
+    private final Rect mStartAnimationRect = new Rect();
+    private final Rect mEndAnimationRect = new Rect();
     private ObjectAnimator mTopAnimator = null;
     private ObjectAnimator mBottomAnimator = null;
     private ExpandableView mFirstVisibleChild;
@@ -277,7 +277,6 @@
                 }
             }
         }
-        top = Math.max(minTopPosition, top);
         ExpandableView lastView = getLastVisibleChild();
         if (lastView != null) {
             float finalTranslationY = ViewState.getFinalTranslationY(lastView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5fbaa51..6821b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -135,7 +135,7 @@
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
 
     // Delay in milli-seconds before shade closes for clear all.
-    private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+    private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
     private boolean mShadeNeedsToClose = false;
 
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
@@ -152,7 +152,7 @@
     private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
     private boolean mKeyguardBypassEnabled;
 
-    private ExpandHelper mExpandHelper;
+    private final ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
@@ -165,12 +165,7 @@
 
     private VelocityTracker mVelocityTracker;
     private OverScroller mScroller;
-    /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
-    private int mLastScrollerY;
-    /**
-     * True if the max position was set to a known position on the last call to {@link #mScroller}.
-     */
-    private boolean mIsScrollerBoundSet;
+
     private Runnable mFinishScrollingCallback;
     private int mTouchSlop;
     private float mSlopMultiplier;
@@ -194,7 +189,6 @@
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
-    private int mCollapsedSize;
     private int mPaddingBetweenElements;
     private int mMaxTopPadding;
     private int mTopPadding;
@@ -210,15 +204,15 @@
     private final StackScrollAlgorithm mStackScrollAlgorithm;
     private final AmbientState mAmbientState;
 
-    private GroupMembershipManager mGroupMembershipManager;
-    private GroupExpansionManager mGroupExpansionManager;
-    private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
-    private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
-    private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
-    private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
-    private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
-    private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
-    private ArrayList<View> mSwipedOutViews = new ArrayList<>();
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final GroupExpansionManager mGroupExpansionManager;
+    private final HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
+    private final ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+    private final ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
+    private final ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
+    private final HashSet<View> mFromMoreCardAdditions = new HashSet<>();
+    private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+    private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
     private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
     private boolean mAnimationsEnabled;
@@ -296,7 +290,7 @@
     private boolean mDisallowDismissInThisMotion;
     private boolean mDisallowScrollingInThisMotion;
     private long mGoToFullShadeDelay;
-    private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
+    private final ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
@@ -309,17 +303,16 @@
     };
     private NotificationStackScrollLogger mLogger;
     private CentralSurfaces mCentralSurfaces;
-    private int[] mTempInt2 = new int[2];
+    private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
-    private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
-    private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
-    private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
+    private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
+    private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
+    private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
             = new HashSet<>();
-    private boolean mTrackingHeadsUp;
     private boolean mForceNoOverlappingRendering;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
     private boolean mAnimationRunning;
-    private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
+    private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
@@ -327,21 +320,21 @@
             return true;
         }
     };
-    private NotificationSection[] mSections;
+    private final NotificationSection[] mSections;
     private boolean mAnimateNextBackgroundTop;
     private boolean mAnimateNextBackgroundBottom;
     private boolean mAnimateNextSectionBoundsChange;
     private int mBgColor;
     private float mDimAmount;
     private ValueAnimator mDimAnimator;
-    private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
+    private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
     private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
             mDimAnimator = null;
         }
     };
-    private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
+    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
             = new ValueAnimator.AnimatorUpdateListener() {
 
         @Override
@@ -351,29 +344,23 @@
     };
     protected ViewGroup mQsHeader;
     // Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
-    private Rect mQsHeaderBound = new Rect();
+    private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
-    private ViewTreeObserver.OnPreDrawListener mShadowUpdater
+    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
             = () -> {
                 updateViewShadows();
                 return true;
             };
-    private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
+    private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
                 updateBackground();
                 return true;
             };
-    private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
+    private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
-        if (endY < otherEndY) {
-            return -1;
-        } else if (endY > otherEndY) {
-            return 1;
-        } else {
-            // The two notifications end at the same location
-            return 0;
-        }
+        // Return zero when the two notifications end at the same location
+        return Float.compare(endY, otherEndY);
     };
     private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
         @Override
@@ -435,16 +422,14 @@
     private int mUpcomingStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
-    private Runnable mReflingAndAnimateScroll = () -> {
-        animateScroll();
-    };
+    private final Runnable mReflingAndAnimateScroll = this::animateScroll;
     private int mCornerRadius;
     private int mMinimumPaddings;
     private int mQsTilePadding;
     private boolean mSkinnyNotifsInLandscape;
     private int mSidePaddings;
     private final Rect mBackgroundAnimationRect = new Rect();
-    private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
+    private final ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
     private int mHeadsUpInset;
 
     /**
@@ -479,8 +464,6 @@
     private int mWaterfallTopInset;
     private NotificationStackScrollLayoutController mController;
 
-    private boolean mKeyguardMediaControllorVisible;
-
     /**
      * The clip path used to clip the view in a rounded way.
      */
@@ -501,7 +484,7 @@
     private int mRoundedRectClippingTop;
     private int mRoundedRectClippingBottom;
     private int mRoundedRectClippingRight;
-    private float[] mBgCornerRadii = new float[8];
+    private final float[] mBgCornerRadii = new float[8];
 
     /**
      * Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -527,7 +510,7 @@
     /**
      * Corner radii of the launched notification if it's clipped
      */
-    private float[] mLaunchedNotificationRadii = new float[8];
+    private final float[] mLaunchedNotificationRadii = new float[8];
 
     /**
      * The notification that is being launched currently.
@@ -779,7 +762,7 @@
         y = getLayoutHeight();
         drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y);
 
-        y = (int) mMaxLayoutHeight;
+        y = mMaxLayoutHeight;
         drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight = " + y);
 
         // The space between mTopPadding and mKeyguardBottomPadding determines the available space
@@ -997,7 +980,6 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
 
         Resources res = context.getResources();
-        mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mAmbientState.reload(context);
@@ -1256,12 +1238,9 @@
     private void clampScrollPosition() {
         int scrollRange = getScrollRange();
         if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
-            boolean animateStackY = false;
-            if (scrollRange < getScrollAmountToScrollBoundary()
-                    && mAnimateStackYForContentHeightChange) {
-                // if the scroll boundary updates the position of the stack,
-                animateStackY = true;
-            }
+            // if the scroll boundary updates the position of the stack,
+            boolean animateStackY = scrollRange < getScrollAmountToScrollBoundary()
+                    && mAnimateStackYForContentHeightChange;
             setOwnScrollY(scrollRange, animateStackY);
         }
     }
@@ -1318,7 +1297,9 @@
                 + mAmbientState.getOverExpansion()
                 - getCurrentOverScrollAmount(false /* top */);
         float fraction = mAmbientState.getExpansionFraction();
-        if (mAmbientState.isBouncerInTransit()) {
+        // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
+        // overlap. Otherwise, we maintain the normal fraction for smoothness.
+        if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
@@ -1504,7 +1485,6 @@
         }
 
         if (mAmbientState.isHiddenAtAll()) {
-            clipToOutline = false;
             invalidateOutline();
             if (isFullyHidden()) {
                 setClipBounds(null);
@@ -1782,7 +1762,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private Runnable mReclamp = new Runnable() {
+    private final Runnable mReclamp = new Runnable() {
         @Override
         public void run() {
             int range = getScrollRange();
@@ -3084,11 +3064,8 @@
         int currentIndex = indexOfChild(child);
 
         if (currentIndex == -1) {
-            boolean isTransient = false;
-            if (child instanceof ExpandableNotificationRow
-                    && child.getTransientContainer() != null) {
-                isTransient = true;
-            }
+            boolean isTransient = child instanceof ExpandableNotificationRow
+                    && child.getTransientContainer() != null;
             Log.e(TAG, "Attempting to re-position "
                     + (isTransient ? "transient" : "")
                     + " view {"
@@ -3149,7 +3126,6 @@
     private void generateHeadsUpAnimationEvents() {
         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
             ExpandableNotificationRow row = eventPair.first;
-            String key = row.getEntry().getKey();
             boolean isHeadsUp = eventPair.second;
             if (isHeadsUp != row.isHeadsUp()) {
                 // For cases where we have a heads up showing and appearing again we shouldn't
@@ -3212,10 +3188,8 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
-        if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
-            return false;
-        }
-        return true;
+        return viewState.yTranslation + viewState.height
+                >= mAmbientState.getMaxHeadsUpTranslation();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4790,7 +4764,6 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
         mAmbientState.setTrackedHeadsUpRow(row);
-        mTrackingHeadsUp = row != null;
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6176,7 +6149,7 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
+    private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
         @Override
         public ExpandableView getChildAtPosition(float touchX, float touchY) {
             return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0b63bbf..f736047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -4453,10 +4453,11 @@
                     Trace.beginSection("CentralSurfaces#updateDozing");
                     mDozing = isDozing;
 
-                    // Collapse the notification panel if open
                     boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
                             && mDozeParameters.shouldControlScreenOff();
-                    mNotificationPanelViewController.resetViews(dozingAnimated);
+                    // resetting views is already done when going into doze, there's no need to
+                    // reset them again when we're waking up
+                    mNotificationPanelViewController.resetViews(dozingAnimated && isDozing);
 
                     updateQsExpansionEnabled();
                     mKeyguardViewMediator.setDozing(mDozing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 7de4668..0067316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.os.Handler;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
@@ -34,9 +33,6 @@
  */
 @SysUISingleton
 public class DozeScrimController implements StateListener {
-    private static final String TAG = "DozeScrimController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
     private final DozeLog mDozeLog;
     private final DozeParameters mDozeParameters;
     private final Handler mHandler = new Handler();
@@ -44,28 +40,26 @@
     private boolean mDozing;
     private DozeHost.PulseCallback mPulseCallback;
     private int mPulseReason;
-    private boolean mFullyPulsing;
 
     private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
         @Override
         public void onDisplayBlanked() {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
-                        + DozeLog.reasonToString(mPulseReason));
-            }
             if (!mDozing) {
+                mDozeLog.tracePulseDropped("onDisplayBlanked - not dozing");
                 return;
             }
 
-            // Signal that the pulse is ready to turn the screen on and draw.
-            pulseStarted();
+            if (mPulseCallback != null) {
+                // Signal that the pulse is ready to turn the screen on and draw.
+                mDozeLog.tracePulseStart(mPulseReason);
+                mPulseCallback.onPulseStarted();
+            }
         }
 
         @Override
         public void onFinished() {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
-            }
+            mDozeLog.tracePulseEvent("scrimCallback-onFinished", mDozing, mPulseReason);
+
             if (!mDozing) {
                 return;
             }
@@ -78,7 +72,6 @@
                 mHandler.postDelayed(mPulseOutExtended,
                         mDozeParameters.getPulseVisibleDurationExtended());
             }
-            mFullyPulsing = true;
         }
 
         /**
@@ -118,19 +111,14 @@
         }
 
         if (!mDozing || mPulseCallback != null) {
-            if (DEBUG) {
-                Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
-                        + (mPulseCallback != null));
-            }
             // Pulse suppressed.
             callback.onPulseFinished();
             if (!mDozing) {
-                mDozeLog.tracePulseDropped("device isn't dozing");
+                mDozeLog.tracePulseDropped("pulse - device isn't dozing");
             } else {
-                mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+                mDozeLog.tracePulseDropped("pulse - already has pulse callback mPulseCallback="
                         + mPulseCallback);
             }
-
             return;
         }
 
@@ -141,9 +129,7 @@
     }
 
     public void pulseOutNow() {
-        if (mPulseCallback != null && mFullyPulsing) {
-            mPulseOut.run();
-        }
+        mPulseOut.run();
     }
 
     public boolean isPulsing() {
@@ -168,24 +154,16 @@
 
     private void cancelPulsing() {
         if (mPulseCallback != null) {
-            if (DEBUG) Log.d(TAG, "Cancel pulsing");
-            mFullyPulsing = false;
+            mDozeLog.tracePulseEvent("cancel", mDozing, mPulseReason);
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
             pulseFinished();
         }
     }
 
-    private void pulseStarted() {
-        mDozeLog.tracePulseStart(mPulseReason);
-        if (mPulseCallback != null) {
-            mPulseCallback.onPulseStarted();
-        }
-    }
-
     private void pulseFinished() {
-        mDozeLog.tracePulseFinish();
         if (mPulseCallback != null) {
+            mDozeLog.tracePulseFinish();
             mPulseCallback.onPulseFinished();
             mPulseCallback = null;
         }
@@ -202,10 +180,9 @@
     private final Runnable mPulseOut = new Runnable() {
         @Override
         public void run() {
-            mFullyPulsing = false;
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
-            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
+            mDozeLog.tracePulseEvent("out", mDozing, mPulseReason);
             if (!mDozing) return;
             pulseFinished();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 24ce5e9..5196e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -36,7 +36,6 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -48,6 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
@@ -80,7 +80,6 @@
     private final BatteryController mBatteryController;
     private final ScrimController mScrimController;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
-    private final KeyguardViewMediator mKeyguardViewMediator;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final DozeScrimController mDozeScrimController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -95,6 +94,7 @@
     private View mAmbientIndicationContainer;
     private CentralSurfaces mCentralSurfaces;
     private boolean mAlwaysOnSuppressed;
+    private boolean mPulsePending;
 
     @Inject
     public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -104,7 +104,6 @@
             HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
             ScrimController scrimController,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
-            KeyguardViewMediator keyguardViewMediator,
             Lazy<AssistManager> assistManagerLazy,
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
             PulseExpansionHandler pulseExpansionHandler,
@@ -122,7 +121,6 @@
         mBatteryController = batteryController;
         mScrimController = scrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
-        mKeyguardViewMediator = keyguardViewMediator;
         mAssistManagerLazy = assistManagerLazy;
         mDozeScrimController = dozeScrimController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -131,6 +129,7 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -246,7 +245,7 @@
         mDozeScrimController.pulse(new PulseCallback() {
             @Override
             public void onPulseStarted() {
-                callback.onPulseStarted();
+                callback.onPulseStarted(); // requestState(DozeMachine.State.DOZE_PULSING)
                 mCentralSurfaces.updateNotificationPanelTouchState();
                 setPulsing(true);
             }
@@ -254,7 +253,7 @@
             @Override
             public void onPulseFinished() {
                 mPulsing = false;
-                callback.onPulseFinished();
+                callback.onPulseFinished(); // requestState(DozeMachine.State.DOZE_PULSE_DONE)
                 mCentralSurfaces.updateNotificationPanelTouchState();
                 mScrimController.setWakeLockScreenSensorActive(false);
                 setPulsing(false);
@@ -338,9 +337,8 @@
 
     @Override
     public void stopPulsing() {
-        if (mDozeScrimController.isPulsing()) {
-            mDozeScrimController.pulseOutNow();
-        }
+        setPulsePending(false); // prevent any pending pulses from continuing
+        mDozeScrimController.pulseOutNow();
     }
 
     @Override
@@ -451,6 +449,16 @@
         }
     }
 
+    @Override
+    public boolean isPulsePending() {
+        return mPulsePending;
+    }
+
+    @Override
+    public void setPulsePending(boolean isPulsePending) {
+        mPulsePending = isPulsePending;
+    }
+
     /**
      * Whether always-on-display is being suppressed. This does not affect wakeup gestures like
      * pickup and tap.
@@ -458,4 +466,22 @@
     public boolean isAlwaysOnSuppressed() {
         return mAlwaysOnSuppressed;
     }
+
+    final OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
+        @Override
+        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+            if (mStatusBarStateController.isDozing() && isHeadsUp) {
+                entry.setPulseSuppressed(false);
+                fireNotificationPulse(entry);
+                if (isPulsing()) {
+                    mDozeScrimController.cancelPendingPulseTimeout();
+                }
+            }
+            if (!isHeadsUp && !mHeadsUpManagerPhone.hasNotifications()) {
+                // There are no longer any notifications to show.  We should end the
+                // pulse now.
+                stopPulsing();
+            }
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 0026b71..054bd28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -40,6 +40,7 @@
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterViewController;
@@ -116,6 +117,7 @@
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
     private final Object mLock = new Object();
+    private final KeyguardLogger mLogger;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -279,7 +281,8 @@
             StatusBarUserInfoTracker statusBarUserInfoTracker,
             SecureSettings secureSettings,
             CommandQueue commandQueue,
-            @Main Executor mainExecutor
+            @Main Executor mainExecutor,
+            KeyguardLogger logger
     ) {
         super(view);
         mCarrierTextController = carrierTextController;
@@ -304,6 +307,7 @@
         mSecureSettings = secureSettings;
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
+        mLogger = logger;
 
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
         mKeyguardStateController.addCallback(
@@ -430,6 +434,7 @@
 
     /** Animate the keyguard status bar in. */
     public void animateKeyguardStatusBarIn() {
+        mLogger.d("animating status bar in");
         if (mDisableStateTracker.isDisabled()) {
             // If our view is disabled, don't allow us to animate in.
             return;
@@ -445,6 +450,7 @@
 
     /** Animate the keyguard status bar out. */
     public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+        mLogger.d("animating status bar out");
         ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
         anim.addUpdateListener(mAnimatorUpdateListener);
         anim.setStartDelay(startDelay);
@@ -481,6 +487,9 @@
             newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                     * mKeyguardStatusBarAnimateAlpha
                     * (1.0f - mKeyguardHeadsUpShowingAmount);
+            if (newAlpha != mView.getAlpha() && (newAlpha == 0 || newAlpha == 1)) {
+                mLogger.logStatusBarCalculatedAlpha(newAlpha);
+            }
         }
 
         boolean hideForBypass =
@@ -503,6 +512,10 @@
         if (mDisableStateTracker.isDisabled()) {
             visibility = View.INVISIBLE;
         }
+        if (visibility != mView.getVisibility()) {
+            mLogger.logStatusBarAlphaVisibility(visibility, alpha,
+                    StatusBarState.toString(mStatusBarState));
+        }
         mView.setAlpha(alpha);
         mView.setVisibility(visibility);
     }
@@ -596,6 +609,8 @@
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mExplicitAlpha: " + mExplicitAlpha);
+        pw.println("  alpha: " + mView.getAlpha());
+        pw.println("  visibility: " + mView.getVisibility());
         mView.dump(pw, args);
     }
 
@@ -605,6 +620,10 @@
      * @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
      */
     public void setAlpha(float alpha) {
+        if (mExplicitAlpha != alpha && (mExplicitAlpha == -1 || alpha == -1)) {
+            // logged if value changed to ignored or from ignored
+            mLogger.logStatusBarExplicitAlpha(alpha);
+        }
         mExplicitAlpha = alpha;
         updateViewState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index ae201e3..5512bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,8 +21,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -41,9 +39,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
-    private final NotificationsController mNotificationsController;
-    private final DozeServiceHost mDozeServiceHost;
-    private final DozeScrimController mDozeScrimController;
 
     @Inject
     StatusBarHeadsUpChangeListener(
@@ -53,10 +48,7 @@
             KeyguardBypassController keyguardBypassController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController statusBarStateController,
-            NotificationRemoteInputManager notificationRemoteInputManager,
-            NotificationsController notificationsController,
-            DozeServiceHost dozeServiceHost,
-            DozeScrimController dozeScrimController) {
+            NotificationRemoteInputManager notificationRemoteInputManager) {
 
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
@@ -65,9 +57,6 @@
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
-        mNotificationsController = notificationsController;
-        mDozeServiceHost = dozeServiceHost;
-        mDozeScrimController = dozeScrimController;
     }
 
     @Override
@@ -117,20 +106,4 @@
             }
         }
     }
-
-    @Override
-    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        if (mStatusBarStateController.isDozing() && isHeadsUp) {
-            entry.setPulseSuppressed(false);
-            mDozeServiceHost.fireNotificationPulse(entry);
-            if (mDozeServiceHost.isPulsing()) {
-                mDozeScrimController.cancelPendingPulseTimeout();
-            }
-        }
-        if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
-            // There are no longer any notifications to show.  We should end the
-            //pulse now.
-            mDozeScrimController.pulseOutNow();
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d6d021f..ece7ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
 import android.annotation.Nullable;
@@ -38,7 +39,7 @@
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
 import com.android.systemui.util.Assert;
@@ -84,6 +89,12 @@
     void setMobileIcons(String slot, List<MobileIconState> states);
 
     /**
+     * This method completely replaces {@link #setMobileIcons} with the information from the new
+     * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+     * to worry about funneling MobileIconState objects through anymore.
+     */
+    void setNewMobileIconSubIds(List<Integer> subIds);
+    /**
      * Display the no calling & SMS icons.
      */
     void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -141,12 +152,14 @@
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
                     statusBarPipelineFlags,
                     wifiViewModel,
+                    mobileUiAdapter,
                     mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
@@ -207,6 +220,7 @@
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
+            private final MobileUiAdapter mMobileUiAdapter;
             private final DarkIconDispatcher mDarkIconDispatcher;
 
             @Inject
@@ -214,10 +228,12 @@
                     StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiViewModel wifiViewModel,
                     MobileContextProvider mobileContextProvider,
+                    MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiViewModel = wifiViewModel;
                 mMobileContextProvider = mobileContextProvider;
+                mMobileUiAdapter = mobileUiAdapter;
                 mDarkIconDispatcher = darkIconDispatcher;
             }
 
@@ -227,6 +243,7 @@
                         location,
                         mStatusBarPipelineFlags,
                         mWifiViewModel,
+                        mMobileUiAdapter,
                         mMobileContextProvider,
                         mDarkIconDispatcher);
             }
@@ -244,11 +261,14 @@
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiViewModel wifiViewModel,
-                MobileContextProvider mobileContextProvider) {
+                MobileUiAdapter mobileUiAdapter,
+                MobileContextProvider mobileContextProvider
+        ) {
             super(group,
                     location,
                     statusBarPipelineFlags,
                     wifiViewModel,
+                    mobileUiAdapter,
                     mobileContextProvider);
         }
 
@@ -284,14 +304,18 @@
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiViewModel mWifiViewModel;
             private final MobileContextProvider mMobileContextProvider;
+            private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiViewModel wifiViewModel,
-                    MobileContextProvider mobileContextProvider) {
+                    MobileUiAdapter mobileUiAdapter,
+                    MobileContextProvider mobileContextProvider
+            ) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiViewModel = wifiViewModel;
+                mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
             }
 
@@ -301,6 +325,7 @@
                         location,
                         mStatusBarPipelineFlags,
                         mWifiViewModel,
+                        mMobileUiAdapter,
                         mMobileContextProvider);
             }
         }
@@ -315,6 +340,8 @@
         private final StatusBarPipelineFlags mStatusBarPipelineFlags;
         private final WifiViewModel mWifiViewModel;
         private final MobileContextProvider mMobileContextProvider;
+        private final MobileIconsViewModel mMobileIconsViewModel;
+
         protected final Context mContext;
         protected final int mIconSize;
         // Whether or not these icons show up in dumpsys
@@ -333,7 +360,9 @@
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiViewModel wifiViewModel,
-                MobileContextProvider mobileContextProvider) {
+                MobileUiAdapter mobileUiAdapter,
+                MobileContextProvider mobileContextProvider
+        ) {
             mGroup = group;
             mLocation = location;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
@@ -342,6 +371,14 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+
+            if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                // This starts the flow for the new pipeline, and will notify us of changes
+                mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+            } else {
+                mMobileIconsViewModel = null;
+            }
         }
 
         public boolean isDemoable() {
@@ -394,6 +431,9 @@
 
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
+
+                case TYPE_MOBILE_NEW:
+                    return addNewMobileIcon(index, slot, holder.getTag());
             }
 
             return null;
@@ -410,7 +450,7 @@
 
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            final BaseStatusBarWifiView view;
+            final BaseStatusBarFrameLayout view;
             if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
                 view = onCreateModernStatusBarWifiView(slot);
                 // When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -429,17 +469,47 @@
         }
 
         @VisibleForTesting
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+        protected StatusIconDisplayable addMobileIcon(
+                int index,
+                String slot,
+                MobileIconState state
+        ) {
+            if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                throw new IllegalStateException("Attempting to add a mobile icon while the new "
+                        + "pipeline is enabled is not supported");
+            }
+
             // Use the `subId` field as a key to query for the correct context
-            StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
-            view.applyMobileState(state);
-            mGroup.addView(view, index, onCreateLayoutParams());
+            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+            mobileView.applyMobileState(state);
+            mGroup.addView(mobileView, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
                 Context mobileContext = mMobileContextProvider
                         .getMobileContextForSub(state.subId, mContext);
                 mDemoStatusIcons.addMobileView(state, mobileContext);
             }
+            return mobileView;
+        }
+
+        protected StatusIconDisplayable addNewMobileIcon(
+                int index,
+                String slot,
+                int subId
+        ) {
+            if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                throw new IllegalStateException("Attempting to add a mobile icon using the new"
+                        + "pipeline, but the enabled flag is false.");
+            }
+
+            BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+            mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                // TODO (b/249790009): demo mode should be handled at the data layer in the
+                //  new pipeline
+            }
+
             return view;
         }
 
@@ -464,6 +534,15 @@
             return view;
         }
 
+        private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+                String slot, int subId) {
+            return ModernStatusBarMobileView
+                    .constructAndBind(
+                            mContext,
+                            slot,
+                            mMobileIconsViewModel.viewModelForSub(subId));
+        }
+
         protected LinearLayout.LayoutParams onCreateLayoutParams() {
             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
         }
@@ -519,6 +598,10 @@
                     return;
                 case TYPE_MOBILE:
                     onSetMobileIcon(viewIndex, holder.getMobileState());
+                    return;
+                case TYPE_MOBILE_NEW:
+                    // Nothing, the icon updates itself now
+                    return;
                 default:
                     break;
             }
@@ -542,9 +625,13 @@
         }
 
         public void onSetMobileIcon(int viewIndex, MobileIconState state) {
-            StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
-            if (view != null) {
-                view.applyMobileState(state);
+            View view = mGroup.getChildAt(viewIndex);
+            if (view instanceof StatusBarMobileView) {
+                ((StatusBarMobileView) view).applyMobileState(state);
+            } else {
+                // ModernStatusBarMobileView automatically updates via the ViewModel
+                throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+                        + "the new pipeline");
             }
 
             if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 7c31366..e106b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@
     private final StatusBarIconList mStatusBarIconList;
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconHideList = new ArraySet<>();
-
-    private Context mContext;
+    private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+    private final Context mContext;
 
     /** */
     @Inject
@@ -78,9 +79,12 @@
             ConfigurationController configurationController,
             TunerService tunerService,
             DumpManager dumpManager,
-            StatusBarIconList statusBarIconList) {
+            StatusBarIconList statusBarIconList,
+            StatusBarPipelineFlags statusBarPipelineFlags
+    ) {
         mStatusBarIconList = statusBarIconList;
         mContext = context;
+        mStatusBarPipelineFlags = statusBarPipelineFlags;
 
         configurationController.addCallback(this);
         commandQueue.addCallback(this);
@@ -220,6 +224,11 @@
      */
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+        if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+                    + "pipeline frontend is enabled");
+            return;
+        }
         Slot mobileSlot = mStatusBarIconList.getSlot(slot);
 
         // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@
         Collections.reverse(iconStates);
 
         for (MobileIconState state : iconStates) {
-
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@
         }
     }
 
+    @Override
+    public void setNewMobileIconSubIds(List<Integer> subIds) {
+        if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+            Log.d(TAG, "ignoring new pipeline callback, "
+                    + "since the frontend is disabled");
+            return;
+        }
+        Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+        Collections.reverse(subIds);
+
+        for (Integer subId : subIds) {
+            StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+            if (holder == null) {
+                holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+                setIcon("mobile", holder);
+            } else {
+                // Don't have to do anything in the new world
+            }
+        }
+    }
+
     /**
      * Accept a list of CallIndicatorIconStates, and show the call strength icons.
      * @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@
         }
     }
 
-
-
     private void handleSet(String slotName, StatusBarIconHolder holder) {
         int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
         mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd..68a203e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@
     public static final int TYPE_ICON = 0;
     public static final int TYPE_WIFI = 1;
     public static final int TYPE_MOBILE = 2;
+    /**
+     * TODO (b/249790733): address this once the new pipeline is in place
+     * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+     * to inform the old view system about changes to the data set (the list of mobile icons). The
+     * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+     * the need for this entire class.
+     *
+     * @deprecated This field only exists so the new status bar pipeline can interface with the
+     * view holder system.
+     */
+    @Deprecated
+    public static final int TYPE_MOBILE_NEW = 3;
+
+    @IntDef({
+            TYPE_ICON,
+            TYPE_WIFI,
+            TYPE_MOBILE,
+            TYPE_MOBILE_NEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface IconType {}
 
     private StatusBarIcon mIcon;
     private WifiIconState mWifiState;
     private MobileIconState mMobileState;
-    private int mType = TYPE_ICON;
+    private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
 
     private StatusBarIconHolder() {
-
     }
 
     public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@
     }
 
     /**
+     * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+     * determine icon ordering and building the correct view model
+     */
+    public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mType = TYPE_MOBILE_NEW;
+        holder.mTag = subId;
+
+        return holder;
+    }
+
+    /**
      * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
      */
     public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@
         return holder;
     }
 
-    public int getType() {
+    public @IconType int getType() {
         return mType;
     }
 
@@ -134,8 +171,12 @@
                 return mWifiState.visible;
             case TYPE_MOBILE:
                 return mMobileState.visible;
+            case TYPE_MOBILE_NEW:
+                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+                return true;
 
-            default: return true;
+            default:
+                return true;
         }
     }
 
@@ -156,6 +197,10 @@
             case TYPE_MOBILE:
                 mMobileState.visible = visible;
                 break;
+
+            case TYPE_MOBILE_NEW:
+                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+                break;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 9d5392a..a0578fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -733,6 +733,11 @@
             if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
                 reset(dozing /* hideBouncerWhenShowing */);
             }
+
+            if (bouncerIsOrWillBeShowing()) {
+                // Ensure bouncer is not shown when dozing state changes.
+                hideBouncer(false);
+            }
             updateStates();
 
             if (!dozing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 9a7c3fa..06d5542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@
 
     @Binds
     abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+    @Binds
+    abstract fun mobileSubscriptionRepository(
+        impl: MobileSubscriptionRepositoryImpl
+    ): MobileSubscriptionRepository
+
+    @Binds
+    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 0000000..46ccf32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.annotation.IntRange
+import android.telephony.Annotation.DataActivityType
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Data class containing all of the relevant information for a particular line of service, known as
+ * a Subscription in the telephony world. These models are the result of a single telephony listener
+ * which has many callbacks which each modify some particular field on this object.
+ *
+ * The design goal here is to de-normalize fields from the system into our model fields below. So
+ * any new field that needs to be tracked should be copied into this data class rather than
+ * threading complex system objects through the pipeline.
+ */
+data class MobileSubscriptionModel(
+    /** From [ServiceStateListener.onServiceStateChanged] */
+    val isEmergencyOnly: Boolean = false,
+
+    /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+    val isGsm: Boolean = false,
+    @IntRange(from = 0, to = 4)
+    val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+    @IntRange(from = 0, to = 4)
+    val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+
+    /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+    val dataConnectionState: Int? = null,
+
+    /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+    @DataActivityType val dataActivityDirection: Int? = null,
+
+    /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+    val carrierNetworkChangeActive: Boolean? = null,
+
+    /** From [DisplayInfoListener.onDisplayInfoChanged] */
+    val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 0000000..36de2a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+    /** Observable list of current mobile subscriptions */
+    val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+    /** Observable for the subscriptionId of the current mobile data connection */
+    val activeMobileDataSubscriptionId: Flow<Int>
+
+    /** Get or create an observable for the given subscription ID */
+    fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+    private val subscriptionManager: SubscriptionManager,
+    private val telephonyManager: TelephonyManager,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+    private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+    /**
+     * State flow that emits the set of mobile data subscriptions, each represented by its own
+     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+     * info object, but for now we keep track of the infos themselves.
+     */
+    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                        override fun onSubscriptionsChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                    bgDispatcher.asExecutor(),
+                    callback,
+                )
+
+                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+            }
+            .mapLatest { fetchSubscriptionsList() }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+    /** StateFlow that keeps track of the current active mobile data subscription */
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+                            trySend(subId)
+                        }
+                    }
+
+                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+            }
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID
+            )
+
+    /**
+     * Each mobile subscription needs its own flow, which comes from registering listeners on the
+     * system. Use this method to create those flows and cache them for reuse
+     */
+    override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+        return subIdFlowCache[subId]
+            ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+    }
+
+    @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+    private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+        var state = MobileSubscriptionModel()
+        conflatedCallbackFlow {
+                val phony = telephonyManager.createForSubscriptionId(subId)
+                // TODO (b/240569788): log all of these into the connectivity logger
+                val callback =
+                    object :
+                        TelephonyCallback(),
+                        ServiceStateListener,
+                        SignalStrengthsListener,
+                        DataConnectionStateListener,
+                        DataActivityListener,
+                        CarrierNetworkListener,
+                        DisplayInfoListener {
+                        override fun onServiceStateChanged(serviceState: ServiceState) {
+                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            trySend(state)
+                        }
+                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            val cdmaLevel =
+                                signalStrength
+                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+                                    .let { strengths ->
+                                        if (!strengths.isEmpty()) {
+                                            strengths[0].level
+                                        } else {
+                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                                        }
+                                    }
+
+                            val primaryLevel = signalStrength.level
+
+                            state =
+                                state.copy(
+                                    cdmaLevel = cdmaLevel,
+                                    primaryLevel = primaryLevel,
+                                    isGsm = signalStrength.isGsm,
+                                )
+                            trySend(state)
+                        }
+                        override fun onDataConnectionStateChanged(
+                            dataState: Int,
+                            networkType: Int
+                        ) {
+                            state = state.copy(dataConnectionState = dataState)
+                            trySend(state)
+                        }
+                        override fun onDataActivity(direction: Int) {
+                            state = state.copy(dataActivityDirection = direction)
+                            trySend(state)
+                        }
+                        override fun onCarrierNetworkChange(active: Boolean) {
+                            state = state.copy(carrierNetworkChangeActive = active)
+                            trySend(state)
+                        }
+                        override fun onDisplayInfoChanged(
+                            telephonyDisplayInfo: TelephonyDisplayInfo
+                        ) {
+                            state = state.copy(displayInfo = telephonyDisplayInfo)
+                            trySend(state)
+                        }
+                    }
+                phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose {
+                    phony.unregisterTelephonyCallback(callback)
+                    // Release the cached flow
+                    subIdFlowCache.remove(subId)
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+    }
+
+    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..77de849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+    /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+    val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+    private val deviceProvisionedController: DeviceProvisionedController,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application scope: CoroutineScope,
+) : UserSetupRepository {
+    /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+    override val isUserSetupFlow: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DeviceProvisionedController.DeviceProvisionedListener {
+                        override fun onUserSetupChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                deviceProvisionedController.addCallback(callback)
+
+                awaitClose { deviceProvisionedController.removeCallback(callback) }
+            }
+            .onStart { emit(Unit) }
+            .mapLatest { fetchUserSetupState() }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    private suspend fun fetchUserSetupState(): Boolean =
+        withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
new file mode 100644
index 0000000..40fe0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+    /** Identifier for RAT type indicator */
+    val iconGroup: Flow<SignalIcon.MobileIconGroup>
+    /** True if this line of service is emergency-only */
+    val isEmergencyOnly: Flow<Boolean>
+    /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+    val level: Flow<Int>
+    /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+    val numberOfLevels: Flow<Int>
+    /** True when we want to draw an icon that makes room for the exclamation mark */
+    val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+    mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+    override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+    override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+    override val level: Flow<Int> =
+        mobileStatusInfo.map { mobileModel ->
+            // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+            if (mobileModel.isGsm) {
+                mobileModel.primaryLevel
+            } else {
+                mobileModel.cdmaLevel
+            }
+        }
+
+    /**
+     * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+     * once it's wired up inside of [CarrierConfigTracker]
+     */
+    override val numberOfLevels: Flow<Int> = flowOf(4)
+
+    /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+    // TODO: find a better name for this?
+    override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 0000000..8e67e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+    private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+    private val carrierConfigTracker: CarrierConfigTracker,
+    userSetupRepo: UserSetupRepository,
+) {
+    private val activeMobileDataSubscriptionId =
+        mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+    private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+        mobileSubscriptionRepo.subscriptionsFlow
+
+    /**
+     * Generally, SystemUI wants to show iconography for each subscription that is listed by
+     * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+     * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+     *
+     * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+     * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+     *
+     * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+     * subscriptions based on
+     * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+     * and by checking which subscription is opportunistic, or which one is active.
+     */
+    val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+        combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+            ->
+            // Based on the old logic,
+            if (unfilteredSubs.size != 2) {
+                return@combine unfilteredSubs
+            }
+
+            val info1 = unfilteredSubs[0]
+            val info2 = unfilteredSubs[1]
+            // If both subscriptions are primary, show both
+            if (!info1.isOpportunistic && !info2.isOpportunistic) {
+                return@combine unfilteredSubs
+            }
+
+            // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+            // If carrier required, always show the icon of the primary subscription.
+            // Otherwise, show whichever subscription is currently active for internet.
+            if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+                // return the non-opportunistic info
+                return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+            } else {
+                return@combine if (info1.subscriptionId == activeId) {
+                    listOf(info1)
+                } else {
+                    listOf(info2)
+                }
+            }
+        }
+
+    val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+    /** Vends out new [MobileIconInteractor] for a particular subId */
+    fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+        MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+    /**
+     * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+     */
+    private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+        mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 0000000..380017c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+    interactor: MobileIconsInteractor,
+    private val iconController: StatusBarIconController,
+    private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    @Application scope: CoroutineScope,
+) {
+    private val mobileSubIds: Flow<List<Int>> =
+        interactor.filteredSubscriptions.mapLatest { infos ->
+            infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+        }
+
+    /**
+     * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+     * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+     * house the mobile infos.
+     *
+     * NOTE: this should go away as the view presenter learns more about this data pipeline
+     */
+    private val mobileSubIdsState: StateFlow<List<Int>> =
+        mobileSubIds
+            .onEach {
+                // Notify the icon controller here so that it knows to add icons
+                iconController.setNewMobileIconSubIds(it)
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    /**
+     * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+     * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+     * the old view system.
+     */
+    fun createMobileIconsViewModel(): MobileIconsViewModel =
+        iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 0000000..1405b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+    /** Binds the view to the view-model, continuing to update the former based on the latter */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: MobileIconViewModel,
+    ) {
+        val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+        val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+        view.isVisible = true
+        iconView.isVisible = true
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                // Set the icon for the triangle
+                launch {
+                    viewModel.iconId.distinctUntilChanged().collect { iconId ->
+                        mobileDrawable.level = iconId
+                    }
+                }
+
+                // Set the tint
+                launch {
+                    viewModel.tint.collect { tint ->
+                        iconView.imageTintList = ColorStateList.valueOf(tint)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 0000000..e7d5ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+    /**
+     * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+     * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+     * collecting, [MobileUiAdapter] will send updates to the icon manager.
+     */
+    @JvmStatic
+    fun bind(view: View, viewModel: MobileIconsViewModel) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.subscriptionIdsFlow.collect {
+                        // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+                        //  sets up a side-effect in this flow to trigger the methods on
+                        // [StatusBarIconController] which allows for this pipeline to be a data
+                        // source for the mobile icons.
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 0000000..ec4fa9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+    context: Context,
+    attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+    private lateinit var slot: String
+    override fun getSlot() = slot
+
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        // TODO
+    }
+
+    override fun setStaticDrawableColor(color: Int) {
+        // TODO
+    }
+
+    override fun setDecorColor(color: Int) {
+        // TODO
+    }
+
+    override fun setVisibleState(state: Int, animate: Boolean) {
+        // TODO
+    }
+
+    override fun getVisibleState(): Int {
+        return STATE_ICON
+    }
+
+    override fun isIconVisible(): Boolean {
+        return true
+    }
+
+    companion object {
+
+        /**
+         * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+         * returns it.
+         */
+        @JvmStatic
+        fun constructAndBind(
+            context: Context,
+            slot: String,
+            viewModel: MobileIconViewModel,
+        ): ModernStatusBarMobileView {
+            return (LayoutInflater.from(context)
+                    .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+                    as ModernStatusBarMobileView)
+                .also {
+                    it.slot = slot
+                    MobileIconBinder.bind(it, viewModel)
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 0000000..cfabeba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+    val subscriptionId: Int,
+    iconInteractor: MobileIconInteractor,
+    logger: ConnectivityPipelineLogger,
+) {
+    /** An int consumable by [SignalDrawable] for display */
+    var iconId: Flow<Int> =
+        combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+                level,
+                numberOfLevels,
+                cutOut ->
+                SignalDrawable.getState(level, numberOfLevels, cutOut)
+            }
+            .distinctUntilChanged()
+            .logOutputChange(logger, "iconId($subscriptionId)")
+
+    var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 0000000..24c1db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+@file:OptIn(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+    val subscriptionIdsFlow: Flow<List<Int>>,
+    private val interactor: MobileIconsInteractor,
+    private val logger: ConnectivityPipelineLogger,
+) {
+    /** TODO: do we need to cache these? */
+    fun viewModelForSub(subId: Int): MobileIconViewModel =
+        MobileIconViewModel(
+            subId,
+            interactor.createMobileConnectionInteractorForSubId(subId),
+            logger
+        )
+
+    class Factory
+    @Inject
+    constructor(
+        private val interactor: MobileIconsInteractor,
+        private val logger: ConnectivityPipelineLogger,
+    ) {
+        fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+            return MobileIconsViewModel(
+                subscriptionIdsFlow,
+                interactor,
+                logger,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 6c616ac..0cd9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,7 +22,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -37,7 +37,7 @@
 class ModernStatusBarWifiView(
     context: Context,
     attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
 
     private lateinit var slot: String
     private lateinit var binding: WifiViewBinder.Binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 0995a00..712953e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -505,7 +505,7 @@
                 v.bind(name, drawable, item.info.id);
             }
             v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(getController().isDisabledByAdmin(item));
+            v.setDisabledByAdmin(item.isDisabledByAdmin());
             v.setEnabled(item.isSwitchToEnabled);
             UserSwitcherController.setSelectableAlpha(v);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a33603..da6d455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -221,8 +221,10 @@
 
         mEditText.setTextColor(textColor);
         mEditText.setHintTextColor(hintColor);
-        mEditText.getTextCursorDrawable().setColorFilter(
-                accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        if (mEditText.getTextCursorDrawable() != null) {
+            mEditText.getTextCursorDrawable().setColorFilter(
+                    accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        }
         mContentBackground.setColor(editBgColor);
         mContentBackground.setStroke(stroke, accentColor);
         mDelete.setImageTintList(ColorStateList.valueOf(deleteFgColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 843c232..146b222 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -19,7 +19,6 @@
 import android.annotation.UserIdInt
 import android.content.Intent
 import android.view.View
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.systemui.Dumpable
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
 import com.android.systemui.user.data.source.UserRecord
@@ -130,12 +129,6 @@
     /** Whether keyguard is showing. */
     val isKeyguardShowing: Boolean
 
-    /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */
-    fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin?
-
-    /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */
-    fun isDisabledByAdmin(record: UserRecord): Boolean
-
     /** Starts an activity with the given [Intent]. */
     fun startActivity(intent: Intent)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
index 12834f6..1692656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -17,13 +17,21 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.content.Context
 import android.content.Intent
 import android.view.View
-import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import dagger.Lazy
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
@@ -31,58 +39,76 @@
 import kotlinx.coroutines.flow.Flow
 
 /** Implementation of [UserSwitcherController]. */
+@SysUISingleton
 class UserSwitcherControllerImpl
 @Inject
 constructor(
-    private val flags: FeatureFlags,
+    @Application private val applicationContext: Context,
+    flags: FeatureFlags,
     @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
+    private val userInteractorLazy: Lazy<UserInteractor>,
+    private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val activityStarter: ActivityStarter,
 ) : UserSwitcherController {
 
-    private val isNewImpl: Boolean
-        get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)
+    private val useInteractor: Boolean =
+        flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
+            !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
     private val _oldImpl: UserSwitcherControllerOldImpl
         get() = oldImpl.get()
+    private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+    private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+    private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
 
-    private fun notYetImplemented(): Nothing {
-        error("Not yet implemented!")
+    private val callbackCompatMap =
+        mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
+
+    private fun notSupported(): Nothing {
+        error("Not supported in the new implementation!")
     }
 
     override val users: ArrayList<UserRecord>
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                userInteractor.userRecords.value
             } else {
                 _oldImpl.users
             }
 
     override val isSimpleUserSwitcher: Boolean
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                userInteractor.isSimpleUserSwitcher
             } else {
                 _oldImpl.isSimpleUserSwitcher
             }
 
     override fun init(view: View) {
-        if (isNewImpl) {
-            notYetImplemented()
-        } else {
+        if (!useInteractor) {
             _oldImpl.init(view)
         }
     }
 
     override val currentUserRecord: UserRecord?
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                userInteractor.selectedUserRecord.value
             } else {
                 _oldImpl.currentUserRecord
             }
 
     override val currentUserName: String?
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                currentUserRecord?.let {
+                    LegacyUserUiHelper.getUserRecordName(
+                        context = applicationContext,
+                        record = it,
+                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+                        isGuestUserResetting = userInteractor.isGuestUserResetting,
+                    )
+                }
             } else {
                 _oldImpl.currentUserName
             }
@@ -91,8 +117,8 @@
         userId: Int,
         dialogShower: UserSwitchDialogController.DialogShower?
     ) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.selectUser(userId)
         } else {
             _oldImpl.onUserSelected(userId, dialogShower)
         }
@@ -100,24 +126,24 @@
 
     override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                notSupported()
             } else {
                 _oldImpl.isAddUsersFromLockScreenEnabled
             }
 
     override val isGuestUserAutoCreated: Boolean
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                userInteractor.isGuestUserAutoCreated
             } else {
                 _oldImpl.isGuestUserAutoCreated
             }
 
     override val isGuestUserResetting: Boolean
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                userInteractor.isGuestUserResetting
             } else {
                 _oldImpl.isGuestUserResetting
             }
@@ -125,40 +151,48 @@
     override fun createAndSwitchToGuestUser(
         dialogShower: UserSwitchDialogController.DialogShower?,
     ) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            notSupported()
         } else {
             _oldImpl.createAndSwitchToGuestUser(dialogShower)
         }
     }
 
     override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            notSupported()
         } else {
             _oldImpl.showAddUserDialog(dialogShower)
         }
     }
 
     override fun startSupervisedUserActivity() {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            notSupported()
         } else {
             _oldImpl.startSupervisedUserActivity()
         }
     }
 
     override fun onDensityOrFontScaleChanged() {
-        if (isNewImpl) {
-            notYetImplemented()
-        } else {
+        if (!useInteractor) {
             _oldImpl.onDensityOrFontScaleChanged()
         }
     }
 
     override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.addCallback(
+                object : UserInteractor.UserCallback {
+                    override fun isEvictable(): Boolean {
+                        return adapter.get() == null
+                    }
+
+                    override fun onUserStateChanged() {
+                        adapter.get()?.notifyDataSetChanged()
+                    }
+                }
+            )
         } else {
             _oldImpl.addAdapter(adapter)
         }
@@ -168,16 +202,23 @@
         record: UserRecord,
         dialogShower: UserSwitchDialogController.DialogShower?,
     ) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            if (LegacyUserDataHelper.isUser(record)) {
+                userInteractor.selectUser(record.resolveId())
+            } else {
+                userInteractor.executeAction(LegacyUserDataHelper.toUserActionModel(record))
+            }
         } else {
             _oldImpl.onUserListItemClicked(record, dialogShower)
         }
     }
 
     override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.removeGuestUser(
+                guestUserId = guestUserId,
+                targetUserId = targetUserId,
+            )
         } else {
             _oldImpl.removeGuestUser(guestUserId, targetUserId)
         }
@@ -188,16 +229,16 @@
         targetUserId: Int,
         forceRemoveGuestOnExit: Boolean
     ) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
         } else {
             _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
         }
     }
 
     override fun schedulePostBootGuestCreation() {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            guestUserInteractor.onDeviceBootCompleted()
         } else {
             _oldImpl.schedulePostBootGuestCreation()
         }
@@ -205,63 +246,57 @@
 
     override val isKeyguardShowing: Boolean
         get() =
-            if (isNewImpl) {
-                notYetImplemented()
+            if (useInteractor) {
+                keyguardInteractor.isKeyguardShowing()
             } else {
                 _oldImpl.isKeyguardShowing
             }
 
-    override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? {
-        return if (isNewImpl) {
-            notYetImplemented()
-        } else {
-            _oldImpl.getEnforcedAdmin(record)
-        }
-    }
-
-    override fun isDisabledByAdmin(record: UserRecord): Boolean {
-        return if (isNewImpl) {
-            notYetImplemented()
-        } else {
-            _oldImpl.isDisabledByAdmin(record)
-        }
-    }
-
     override fun startActivity(intent: Intent) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            activityStarter.startActivity(intent, /* dismissShade= */ false)
         } else {
             _oldImpl.startActivity(intent)
         }
     }
 
     override fun refreshUsers(forcePictureLoadForId: Int) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.refreshUsers()
         } else {
             _oldImpl.refreshUsers(forcePictureLoadForId)
         }
     }
 
     override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            val interactorCallback =
+                object : UserInteractor.UserCallback {
+                    override fun onUserStateChanged() {
+                        callback.onUserSwitched()
+                    }
+                }
+            callbackCompatMap[callback] = interactorCallback
+            userInteractor.addCallback(interactorCallback)
         } else {
             _oldImpl.addUserSwitchCallback(callback)
         }
     }
 
     override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            val interactorCallback = callbackCompatMap.remove(callback)
+            if (interactorCallback != null) {
+                userInteractor.removeCallback(interactorCallback)
+            }
         } else {
             _oldImpl.removeUserSwitchCallback(callback)
         }
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        if (isNewImpl) {
-            notYetImplemented()
+        if (useInteractor) {
+            userInteractor.dump(pw)
         } else {
             _oldImpl.dump(pw, args)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index d365aa6..46d2f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -17,17 +17,13 @@
 
 import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
 
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
@@ -40,7 +36,6 @@
 import android.provider.Settings;
 import android.telephony.TelephonyCallback;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -49,17 +44,14 @@
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
-import androidx.collection.SimpleArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.users.UserCreatingDialog;
 import com.android.systemui.GuestResetOrExitSessionReceiver;
 import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.R;
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -75,10 +67,12 @@
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.CreateUserActivity;
 import com.android.systemui.user.data.source.UserRecord;
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
+import com.android.systemui.user.shared.model.UserActionModel;
+import com.android.systemui.user.ui.dialog.AddUserDialog;
+import com.android.systemui.user.ui.dialog.ExitGuestDialog;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -139,9 +133,6 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
-    private final SimpleArrayMap<UserRecord, EnforcedAdmin> mEnforcedAdminByUserRecord =
-            new SimpleArrayMap<>();
-    private final ArraySet<UserRecord> mDisabledByAdmin = new ArraySet<>();
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     @VisibleForTesting
@@ -334,7 +325,6 @@
 
             for (UserInfo info : infos) {
                 boolean isCurrent = currentId == info.id;
-                boolean switchToEnabled = canSwitchUsers || isCurrent;
                 if (!mUserSwitcherEnabled && !info.isPrimary()) {
                     continue;
                 }
@@ -343,25 +333,22 @@
                     if (info.isGuest()) {
                         // Tapping guest icon triggers remove and a user switch therefore
                         // the icon shouldn't be enabled even if the user is current
-                        guestRecord = new UserRecord(info, null /* picture */,
-                                true /* isGuest */, isCurrent, false /* isAddUser */,
-                                false /* isRestricted */, canSwitchUsers,
-                                false /* isAddSupervisedUser */);
+                        guestRecord = LegacyUserDataHelper.createRecord(
+                                mContext,
+                                mUserManager,
+                                null /* picture */,
+                                info,
+                                isCurrent,
+                                canSwitchUsers);
                     } else if (info.supportsSwitchToByUser()) {
-                        Bitmap picture = bitmaps.get(info.id);
-                        if (picture == null) {
-                            picture = mUserManager.getUserIcon(info.id);
-
-                            if (picture != null) {
-                                int avatarSize = mContext.getResources()
-                                        .getDimensionPixelSize(R.dimen.max_avatar_size);
-                                picture = Bitmap.createScaledBitmap(
-                                        picture, avatarSize, avatarSize, true);
-                            }
-                        }
-                        records.add(new UserRecord(info, picture, false /* isGuest */,
-                                isCurrent, false /* isAddUser */, false /* isRestricted */,
-                                switchToEnabled, false /* isAddSupervisedUser */));
+                        records.add(
+                                LegacyUserDataHelper.createRecord(
+                                        mContext,
+                                        mUserManager,
+                                        bitmaps.get(info.id),
+                                        info,
+                                        isCurrent,
+                                        canSwitchUsers));
                     }
                 }
             }
@@ -372,18 +359,20 @@
                     // we will just use it as an indicator for "Resetting guest...".
                     // Otherwise, default to canSwitchUsers.
                     boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
-                    guestRecord = new UserRecord(null /* info */, null /* picture */,
-                            true /* isGuest */, false /* isCurrent */,
-                            false /* isAddUser */, false /* isRestricted */,
-                            isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
-                    checkIfAddUserDisallowedByAdminOnly(guestRecord);
+                    guestRecord = LegacyUserDataHelper.createRecord(
+                            mContext,
+                            currentId,
+                            UserActionModel.ENTER_GUEST_MODE,
+                            false /* isRestricted */,
+                            isSwitchToGuestEnabled);
                     records.add(guestRecord);
                 } else if (canCreateGuest(guestRecord != null)) {
-                    guestRecord = new UserRecord(null /* info */, null /* picture */,
-                            true /* isGuest */, false /* isCurrent */,
-                            false /* isAddUser */, createIsRestricted(), canSwitchUsers,
-                            false /* isAddSupervisedUser */);
-                    checkIfAddUserDisallowedByAdminOnly(guestRecord);
+                    guestRecord = LegacyUserDataHelper.createRecord(
+                            mContext,
+                            currentId,
+                            UserActionModel.ENTER_GUEST_MODE,
+                            false /* isRestricted */,
+                            canSwitchUsers);
                     records.add(guestRecord);
                 }
             } else {
@@ -391,20 +380,23 @@
             }
 
             if (canCreateUser()) {
-                UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
-                        false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
-                        createIsRestricted(), canSwitchUsers,
-                        false /* isAddSupervisedUser */);
-                checkIfAddUserDisallowedByAdminOnly(addUserRecord);
-                records.add(addUserRecord);
+                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+                        mContext,
+                        currentId,
+                        UserActionModel.ADD_USER,
+                        createIsRestricted(),
+                        canSwitchUsers);
+                records.add(userRecord);
             }
 
             if (canCreateSupervisedUser()) {
-                UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
-                        false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
-                        createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
-                checkIfAddUserDisallowedByAdminOnly(addUserRecord);
-                records.add(addUserRecord);
+                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+                        mContext,
+                        currentId,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        createIsRestricted(),
+                        canSwitchUsers);
+                records.add(userRecord);
             }
 
             mUiExecutor.execute(() -> {
@@ -591,12 +583,23 @@
         showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
     }
 
-    private void showExitGuestDialog(int id, boolean isGuestEphemeral,
-                        int targetId, DialogShower dialogShower) {
+    private void showExitGuestDialog(
+            int id,
+            boolean isGuestEphemeral,
+            int targetId,
+            DialogShower dialogShower) {
         if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
             mExitGuestDialog.cancel();
         }
-        mExitGuestDialog = new ExitGuestDialog(mContext, id, isGuestEphemeral, targetId);
+        mExitGuestDialog = new ExitGuestDialog(
+                mContext,
+                id,
+                isGuestEphemeral,
+                targetId,
+                mKeyguardStateController.isShowing(),
+                mFalsingManager,
+                mDialogLaunchAnimator,
+                this::exitGuestUser);
         if (dialogShower != null) {
             dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
                     InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
@@ -622,7 +625,15 @@
         if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
             mAddUserDialog.cancel();
         }
-        mAddUserDialog = new AddUserDialog(mContext);
+        final UserInfo currentUser = mUserTracker.getUserInfo();
+        mAddUserDialog = new AddUserDialog(
+                mContext,
+                currentUser.getUserHandle(),
+                mKeyguardStateController.isShowing(),
+                /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
+                mFalsingManager,
+                mBroadcastSender,
+                mDialogLaunchAnimator);
         if (dialogShower != null) {
             dialogShower.showDialog(mAddUserDialog,
                     new DialogCuj(
@@ -964,30 +975,6 @@
         return mKeyguardStateController.isShowing();
     }
 
-    @Override
-    @Nullable
-    public EnforcedAdmin getEnforcedAdmin(UserRecord record) {
-        return mEnforcedAdminByUserRecord.get(record);
-    }
-
-    @Override
-    public boolean isDisabledByAdmin(UserRecord record) {
-        return mDisabledByAdmin.contains(record);
-    }
-
-    private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
-        EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
-                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
-        if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) {
-            mDisabledByAdmin.add(record);
-            mEnforcedAdminByUserRecord.put(record, admin);
-        } else {
-            mDisabledByAdmin.remove(record);
-            mEnforcedAdminByUserRecord.put(record, null);
-        }
-    }
-
     private boolean shouldUseSimpleUserSwitcher() {
         int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
@@ -1052,133 +1039,4 @@
                     }
                 }
             };
-
-
-    private final class ExitGuestDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
-
-        private final int mGuestId;
-        private final int mTargetId;
-        private final boolean mIsGuestEphemeral;
-
-        ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
-                    int targetId) {
-            super(context);
-            if (isGuestEphemeral) {
-                setTitle(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_title));
-                setMessage(context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_message));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_dialog_button), this);
-            } else {
-                setTitle(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_title_non_ephemeral));
-                setMessage(context.getString(
-                            com.android.settingslib
-                                .R.string.guest_exit_dialog_message_non_ephemeral));
-                setButton(DialogInterface.BUTTON_NEUTRAL,
-                        context.getString(android.R.string.cancel), this);
-                setButton(DialogInterface.BUTTON_NEGATIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_clear_data_button),
-                        this);
-                setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(
-                            com.android.settingslib.R.string.guest_exit_save_data_button),
-                        this);
-            }
-            SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
-            setCanceledOnTouchOutside(false);
-            mGuestId = guestId;
-            mTargetId = targetId;
-            mIsGuestEphemeral = isGuestEphemeral;
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
-                    : FalsingManager.HIGH_PENALTY;
-            if (mFalsingManager.isFalseTap(penalty)) {
-                return;
-            }
-            if (mIsGuestEphemeral) {
-                if (which == DialogInterface.BUTTON_POSITIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Ephemeral guest: exit guest, guest is removed by the system
-                    // on exit, since its marked ephemeral
-                    exitGuestUser(mGuestId, mTargetId, false);
-                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                    // Cancel clicked, do nothing
-                    cancel();
-                }
-            } else {
-                if (which == DialogInterface.BUTTON_POSITIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Non-ephemeral guest: exit guest, guest is not removed by the system
-                    // on exit, since its marked non-ephemeral
-                    exitGuestUser(mGuestId, mTargetId, false);
-                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                    mDialogLaunchAnimator.dismissStack(this);
-                    // Non-ephemeral guest: remove guest and then exit
-                    exitGuestUser(mGuestId, mTargetId, true);
-                } else if (which == DialogInterface.BUTTON_NEUTRAL) {
-                    // Cancel clicked, do nothing
-                    cancel();
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    final class AddUserDialog extends SystemUIDialog implements
-            DialogInterface.OnClickListener {
-
-        AddUserDialog(Context context) {
-            super(context);
-
-            setTitle(com.android.settingslib.R.string.user_add_user_title);
-            String message = context.getString(
-                                com.android.settingslib.R.string.user_add_user_message_short);
-            UserInfo currentUser = mUserTracker.getUserInfo();
-            if (currentUser != null && currentUser.isGuest() && currentUser.isEphemeral()) {
-                message += context.getString(R.string.user_add_user_message_guest_remove);
-            }
-            setMessage(message);
-            setButton(DialogInterface.BUTTON_NEUTRAL,
-                    context.getString(android.R.string.cancel), this);
-            setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(android.R.string.ok), this);
-            SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
-                    : FalsingManager.MODERATE_PENALTY;
-            if (mFalsingManager.isFalseTap(penalty)) {
-                return;
-            }
-            if (which == BUTTON_NEUTRAL) {
-                cancel();
-            } else {
-                mDialogLaunchAnimator.dismissStack(this);
-                if (ActivityManager.isUserAMonkey()) {
-                    return;
-                }
-                // Use broadcast instead of ShadeController, as this dialog may have started in
-                // another process and normal dagger bindings are not available
-                mBroadcastSender.sendBroadcastAsUser(
-                        new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
-                getContext().startActivityAsUser(
-                        CreateUserActivity.createIntentForStart(getContext()),
-                        mUserTracker.getUserHandle());
-            }
-        }
-    }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
new file mode 100644
index 0000000..9c38dc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.telephony.data.repository
+
+import android.telephony.Annotation
+import android.telephony.TelephonyCallback
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.TelephonyListenerManager
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that encapsulate _some_ telephony-related state. */
+interface TelephonyRepository {
+    /** The state of the current call. */
+    @Annotation.CallState val callState: Flow<Int>
+}
+
+/**
+ * NOTE: This repository tracks only telephony-related state regarding the default mobile
+ * subscription. `TelephonyListenerManager` does not create new instances of `TelephonyManager` on a
+ * per-subscription basis and thus will always be tracking telephony information regarding
+ * `SubscriptionManager.getDefaultSubscriptionId`. See `TelephonyManager` and `SubscriptionManager`
+ * for more documentation.
+ */
+@SysUISingleton
+class TelephonyRepositoryImpl
+@Inject
+constructor(
+    private val manager: TelephonyListenerManager,
+) : TelephonyRepository {
+    @Annotation.CallState
+    override val callState: Flow<Int> = conflatedCallbackFlow {
+        val listener = TelephonyCallback.CallStateListener { state -> trySend(state) }
+
+        manager.addCallStateListener(listener)
+
+        awaitClose { manager.removeCallStateListener(listener) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
new file mode 100644
index 0000000..630fbf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.telephony.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface TelephonyRepositoryModule {
+    @Binds fun repository(impl: TelephonyRepositoryImpl): TelephonyRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
new file mode 100644
index 0000000..86ca33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.telephony.domain.interactor
+
+import android.telephony.Annotation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.data.repository.TelephonyRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Hosts business logic related to telephony. */
+@SysUISingleton
+class TelephonyInteractor
+@Inject
+constructor(
+    repository: TelephonyRepository,
+) {
+    @Annotation.CallState val callState: Flow<Int> = repository.callState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 5b522dc..0c72b78 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -20,6 +20,7 @@
 
 import com.android.settingslib.users.EditUserInfoController;
 import com.android.systemui.user.data.repository.UserRepositoryModule;
+import com.android.systemui.user.ui.dialog.UserDialogModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -32,6 +33,7 @@
  */
 @Module(
         includes = {
+                UserDialogModule.class,
                 UserRepositoryModule.class,
         }
 )
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
new file mode 100644
index 0000000..4fd55c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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.user.data.model
+
+/** Encapsulates the state of settings related to user switching. */
+data class UserSwitcherSettingsModel(
+    val isSimpleUserSwitcher: Boolean = false,
+    val isAddUsersFromLockscreen: Boolean = false,
+    val isUserSwitcherEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 0356388..3014f39 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -18,9 +18,13 @@
 package com.android.systemui.user.data.repository
 
 import android.content.Context
+import android.content.pm.UserInfo
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
 import androidx.appcompat.content.res.AppCompatResources
 import com.android.internal.util.UserIcons
 import com.android.systemui.R
@@ -29,15 +33,36 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Acts as source of truth for user related data.
@@ -55,6 +80,18 @@
     /** List of available user-related actions. */
     val actions: Flow<List<UserActionModel>>
 
+    /** User switcher related settings. */
+    val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
+
+    /** List of all users on the device. */
+    val userInfos: Flow<List<UserInfo>>
+
+    /** [UserInfo] of the currently-selected user. */
+    val selectedUserInfo: Flow<UserInfo>
+
+    /** User ID of the last non-guest selected user. */
+    val lastSelectedNonGuestUserId: Int
+
     /** Whether actions are available even when locked. */
     val isActionableWhenLocked: Flow<Boolean>
 
@@ -62,7 +99,23 @@
     val isGuestUserAutoCreated: Boolean
 
     /** Whether the guest user is currently being reset. */
-    val isGuestUserResetting: Boolean
+    var isGuestUserResetting: Boolean
+
+    /** Whether we've scheduled the creation of a guest user. */
+    val isGuestUserCreationScheduled: AtomicBoolean
+
+    /** The user of the secondary service. */
+    var secondaryUserId: Int
+
+    /** Whether refresh users should be paused. */
+    var isRefreshUsersPaused: Boolean
+
+    /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
+    fun refreshUsers()
+
+    fun getSelectedUserInfo(): UserInfo
+
+    fun isSimpleUserSwitcher(): Boolean
 }
 
 @SysUISingleton
@@ -71,9 +124,31 @@
 constructor(
     @Application private val appContext: Context,
     private val manager: UserManager,
-    controller: UserSwitcherController,
+    private val controller: UserSwitcherController,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val globalSettings: GlobalSettings,
+    private val tracker: UserTracker,
+    private val featureFlags: FeatureFlags,
 ) : UserRepository {
 
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+    private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null)
+    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+        _userSwitcherSettings.asStateFlow().filterNotNull()
+
+    private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
+    override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
+
+    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
+    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+
+    override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+        private set
+
     private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
         fun send() {
             trySendWithFailureLogging(
@@ -99,11 +174,148 @@
     override val actions: Flow<List<UserActionModel>> =
         userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
 
-    override val isActionableWhenLocked: Flow<Boolean> = controller.isAddUsersFromLockScreenEnabled
+    override val isActionableWhenLocked: Flow<Boolean> =
+        if (isNewImpl) {
+            emptyFlow()
+        } else {
+            controller.isAddUsersFromLockScreenEnabled
+        }
 
-    override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated
+    override val isGuestUserAutoCreated: Boolean =
+        if (isNewImpl) {
+            appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
+        } else {
+            controller.isGuestUserAutoCreated
+        }
 
-    override val isGuestUserResetting: Boolean = controller.isGuestUserResetting
+    private var _isGuestUserResetting: Boolean = false
+    override var isGuestUserResetting: Boolean =
+        if (isNewImpl) {
+            _isGuestUserResetting
+        } else {
+            controller.isGuestUserResetting
+        }
+        set(value) =
+            if (isNewImpl) {
+                _isGuestUserResetting = value
+            } else {
+                error("Not supported in the old implementation!")
+            }
+
+    override val isGuestUserCreationScheduled = AtomicBoolean()
+
+    override var secondaryUserId: Int = UserHandle.USER_NULL
+
+    override var isRefreshUsersPaused: Boolean = false
+
+    init {
+        if (isNewImpl) {
+            observeSelectedUser()
+            observeUserSettings()
+        }
+    }
+
+    override fun refreshUsers() {
+        applicationScope.launch {
+            val result = withContext(backgroundDispatcher) { manager.aliveUsers }
+
+            if (result != null) {
+                _userInfos.value = result
+            }
+        }
+    }
+
+    override fun getSelectedUserInfo(): UserInfo {
+        return checkNotNull(_selectedUserInfo.value)
+    }
+
+    override fun isSimpleUserSwitcher(): Boolean {
+        return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+    }
+
+    private fun observeSelectedUser() {
+        conflatedCallbackFlow {
+                fun send() {
+                    trySendWithFailureLogging(tracker.userInfo, TAG)
+                }
+
+                val callback =
+                    object : UserTracker.Callback {
+                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                            send()
+                        }
+                    }
+
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                send()
+
+                awaitClose { tracker.removeCallback(callback) }
+            }
+            .onEach {
+                if (!it.isGuest) {
+                    lastSelectedNonGuestUserId = it.id
+                }
+
+                _selectedUserInfo.value = it
+            }
+            .launchIn(applicationScope)
+    }
+
+    private fun observeUserSettings() {
+        globalSettings
+            .observerFlow(
+                names =
+                    arrayOf(
+                        SETTING_SIMPLE_USER_SWITCHER,
+                        Settings.Global.ADD_USERS_WHEN_LOCKED,
+                        Settings.Global.USER_SWITCHER_ENABLED,
+                    ),
+                userId = UserHandle.USER_SYSTEM,
+            )
+            .onStart { emit(Unit) } // Forces an initial update.
+            .map { getSettings() }
+            .onEach { _userSwitcherSettings.value = it }
+            .launchIn(applicationScope)
+    }
+
+    private suspend fun getSettings(): UserSwitcherSettingsModel {
+        return withContext(backgroundDispatcher) {
+            val isSimpleUserSwitcher =
+                globalSettings.getIntForUser(
+                    SETTING_SIMPLE_USER_SWITCHER,
+                    if (
+                        appContext.resources.getBoolean(
+                            com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+                        )
+                    ) {
+                        1
+                    } else {
+                        0
+                    },
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            val isAddUsersFromLockscreen =
+                globalSettings.getIntForUser(
+                    Settings.Global.ADD_USERS_WHEN_LOCKED,
+                    0,
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            val isUserSwitcherEnabled =
+                globalSettings.getIntForUser(
+                    Settings.Global.USER_SWITCHER_ENABLED,
+                    0,
+                    UserHandle.USER_SYSTEM,
+                ) != 0
+
+            UserSwitcherSettingsModel(
+                isSimpleUserSwitcher = isSimpleUserSwitcher,
+                isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+                isUserSwitcherEnabled = isUserSwitcherEnabled,
+            )
+        }
+    }
 
     private fun UserRecord.isUser(): Boolean {
         return when {
@@ -125,6 +337,7 @@
             image = getUserImage(this),
             isSelected = isCurrent,
             isSelectable = isSwitchToEnabled || isGuest,
+            isGuest = isGuest,
         )
     }
 
@@ -162,5 +375,6 @@
 
     companion object {
         private const val TAG = "UserRepository"
+        @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index cf6da9a..9370286 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -19,6 +19,7 @@
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.os.UserHandle
+import com.android.settingslib.RestrictedLockUtils
 
 /** Encapsulates raw data for a user or an option item related to managing users on the device. */
 data class UserRecord(
@@ -41,6 +42,11 @@
     @JvmField val isSwitchToEnabled: Boolean = false,
     /** Whether this record represents an option to add another supervised user to the device. */
     @JvmField val isAddSupervisedUser: Boolean = false,
+    /**
+     * An enforcing admin, if the user action represented by this record is disabled by the admin.
+     * If not disabled, this is `null`.
+     */
+    @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
 ) {
     /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
     fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
@@ -59,6 +65,14 @@
         }
     }
 
+    /**
+     * Returns `true` if the user action represented by this record has been disabled by an admin;
+     * `false` otherwise.
+     */
+    fun isDisabledByAdmin(): Boolean {
+        return enforcedAdmin != null
+    }
+
     companion object {
         @JvmStatic
         fun createForGuest(): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
new file mode 100644
index 0000000..07e5cf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import android.annotation.UserIdInt
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import android.view.WindowManagerGlobal
+import android.widget.Toast
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+/** Encapsulates business logic to interact with guest user data and systems. */
+@SysUISingleton
+class GuestUserInteractor
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val manager: UserManager,
+    private val repository: UserRepository,
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val refreshUsersScheduler: RefreshUsersScheduler,
+    private val uiEventLogger: UiEventLogger,
+) {
+    /** Whether the device is configured to always have a guest user available. */
+    val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+
+    /** Whether the guest user is currently being reset. */
+    val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+
+    /** Notifies that the device has finished booting. */
+    fun onDeviceBootCompleted() {
+        applicationScope.launch {
+            if (isDeviceAllowedToAddGuest()) {
+                guaranteePresent()
+                return@launch
+            }
+
+            suspendCancellableCoroutine<Unit> { continuation ->
+                val callback =
+                    object : DeviceProvisionedController.DeviceProvisionedListener {
+                        override fun onDeviceProvisionedChanged() {
+                            continuation.resumeWith(Result.success(Unit))
+                            deviceProvisionedController.removeCallback(this)
+                        }
+                    }
+
+                deviceProvisionedController.addCallback(callback)
+            }
+
+            if (isDeviceAllowedToAddGuest()) {
+                guaranteePresent()
+            }
+        }
+    }
+
+    /** Creates a guest user and switches to it. */
+    fun createAndSwitchTo(
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        selectUser: (userId: Int) -> Unit,
+    ) {
+        applicationScope.launch {
+            val newGuestUserId = create(showDialog, dismissDialog)
+            if (newGuestUserId != UserHandle.USER_NULL) {
+                selectUser(newGuestUserId)
+            }
+        }
+    }
+
+    /** Exits the guest user, switching back to the last non-guest user or to the default user. */
+    fun exit(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean,
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        switchUser: (userId: Int) -> Unit,
+    ) {
+        val currentUserInfo = repository.getSelectedUserInfo()
+        if (currentUserInfo.id != guestUserId) {
+            Log.w(
+                TAG,
+                "User requesting to start a new session ($guestUserId) is not current user" +
+                    " (${currentUserInfo.id})"
+            )
+            return
+        }
+
+        if (!currentUserInfo.isGuest) {
+            Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+            return
+        }
+
+        applicationScope.launch {
+            var newUserId = UserHandle.USER_SYSTEM
+            if (targetUserId == UserHandle.USER_NULL) {
+                // When a target user is not specified switch to last non guest user:
+                val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
+                if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
+                    val info =
+                        withContext(backgroundDispatcher) {
+                            manager.getUserInfo(lastSelectedNonGuestUserHandle)
+                        }
+                    if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
+                        newUserId = info.id
+                    }
+                }
+            } else {
+                newUserId = targetUserId
+            }
+
+            if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
+                remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
+            } else {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
+                switchUser(newUserId)
+            }
+        }
+    }
+
+    /**
+     * Guarantees that the guest user is present on the device, creating it if needed and if allowed
+     * to.
+     */
+    suspend fun guaranteePresent() {
+        if (!isDeviceAllowedToAddGuest()) {
+            return
+        }
+
+        val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
+        if (guestUser == null) {
+            scheduleCreation()
+        }
+    }
+
+    /** Removes the guest user from the device. */
+    suspend fun remove(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+        switchUser: (userId: Int) -> Unit,
+    ) {
+        val currentUser: UserInfo = repository.getSelectedUserInfo()
+        if (currentUser.id != guestUserId) {
+            Log.w(
+                TAG,
+                "User requesting to start a new session ($guestUserId) is not current user" +
+                    " ($currentUser.id)"
+            )
+            return
+        }
+
+        if (!currentUser.isGuest) {
+            Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+            return
+        }
+
+        val marked =
+            withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
+        if (!marked) {
+            Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
+            return
+        }
+
+        if (targetUserId == UserHandle.USER_NULL) {
+            // Create a new guest in the foreground, and then immediately switch to it
+            val newGuestId = create(showDialog, dismissDialog)
+            if (newGuestId == UserHandle.USER_NULL) {
+                Log.e(TAG, "Could not create new guest, switching back to system user")
+                switchUser(UserHandle.USER_SYSTEM)
+                withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+                try {
+                    WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+                } catch (e: RemoteException) {
+                    Log.e(
+                        TAG,
+                        "Couldn't remove guest because ActivityManager or WindowManager is dead"
+                    )
+                }
+                return
+            }
+
+            switchUser(newGuestId)
+
+            withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+        } else {
+            if (repository.isGuestUserAutoCreated) {
+                repository.isGuestUserResetting = true
+            }
+            switchUser(targetUserId)
+            manager.removeUser(currentUser.id)
+        }
+    }
+
+    /**
+     * Creates the guest user and adds it to the device.
+     *
+     * @param showDialog A function to invoke to show a dialog.
+     * @param dismissDialog A function to invoke to dismiss a dialog.
+     * @return The user ID of the newly-created guest user.
+     */
+    private suspend fun create(
+        showDialog: (ShowDialogRequestModel) -> Unit,
+        dismissDialog: () -> Unit,
+    ): Int {
+        return withContext(mainDispatcher) {
+            showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            val guestUserId = createInBackground()
+            dismissDialog()
+            if (guestUserId != UserHandle.USER_NULL) {
+                uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
+            } else {
+                Toast.makeText(
+                        applicationContext,
+                        com.android.settingslib.R.string.add_guest_failed,
+                        Toast.LENGTH_SHORT,
+                    )
+                    .show()
+            }
+
+            guestUserId
+        }
+    }
+
+    /** Schedules the creation of the guest user. */
+    private suspend fun scheduleCreation() {
+        if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
+            return
+        }
+
+        withContext(backgroundDispatcher) {
+            val newGuestUserId = createInBackground()
+            repository.isGuestUserCreationScheduled.set(false)
+            repository.isGuestUserResetting = false
+            if (newGuestUserId == UserHandle.USER_NULL) {
+                Log.w(TAG, "Could not create new guest while exiting existing guest")
+                // Refresh users so that we still display "Guest" if
+                // config_guestUserAutoCreated=true
+                refreshUsersScheduler.refreshIfNotPaused()
+            }
+        }
+    }
+
+    /**
+     * Creates a guest user and return its multi-user user ID.
+     *
+     * This method does not check if a guest already exists before it makes a call to [UserManager]
+     * to create a new one.
+     *
+     * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
+     * the guest couldn't be created.
+     */
+    @UserIdInt
+    private suspend fun createInBackground(): Int {
+        return withContext(backgroundDispatcher) {
+            try {
+                val guestUser = manager.createGuest(applicationContext)
+                if (guestUser != null) {
+                    guestUser.id
+                } else {
+                    Log.e(
+                        TAG,
+                        "Couldn't create guest, most likely because there already exists one!"
+                    )
+                    UserHandle.USER_NULL
+                }
+            } catch (e: UserManager.UserOperationException) {
+                Log.e(TAG, "Couldn't create guest user!", e)
+                UserHandle.USER_NULL
+            }
+        }
+    }
+
+    private fun isDeviceAllowedToAddGuest(): Boolean {
+        return deviceProvisionedController.isDeviceProvisioned &&
+            !devicePolicyManager.isDeviceManaged
+    }
+
+    companion object {
+        private const val TAG = "GuestUserInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
new file mode 100644
index 0000000..8f36821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Encapsulates logic for pausing, unpausing, and scheduling a delayed job. */
+@SysUISingleton
+class RefreshUsersScheduler
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val repository: UserRepository,
+) {
+    private var scheduledUnpauseJob: Job? = null
+    private var isPaused = false
+
+    fun pause() {
+        applicationScope.launch(mainDispatcher) {
+            isPaused = true
+            scheduledUnpauseJob?.cancel()
+            scheduledUnpauseJob =
+                applicationScope.launch {
+                    delay(PAUSE_REFRESH_USERS_TIMEOUT_MS)
+                    unpauseAndRefresh()
+                }
+        }
+    }
+
+    fun unpauseAndRefresh() {
+        applicationScope.launch(mainDispatcher) {
+            isPaused = false
+            refreshIfNotPaused()
+        }
+    }
+
+    fun refreshIfNotPaused() {
+        applicationScope.launch(mainDispatcher) {
+            if (isPaused) {
+                return@launch
+            }
+
+            repository.refreshUsers()
+        }
+    }
+
+    companion object {
+        private const val PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
new file mode 100644
index 0000000..1b4746a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.systemui.user.data.repository.UserRepository
+
+/** Utilities related to user management actions. */
+object UserActionsUtil {
+
+    /** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
+    fun canCreateGuest(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        if (!isUserSwitcherEnabled) {
+            return false
+        }
+
+        return currentUserCanCreateUsers(manager, repository) ||
+            anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+    }
+
+    /** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
+    fun canCreateUser(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        if (!isUserSwitcherEnabled) {
+            return false
+        }
+
+        if (
+            !currentUserCanCreateUsers(manager, repository) &&
+                !anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+        ) {
+            return false
+        }
+
+        return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
+    }
+
+    /**
+     * Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
+     */
+    fun canCreateSupervisedUser(
+        manager: UserManager,
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+        supervisedUserPackageName: String?
+    ): Boolean {
+        if (supervisedUserPackageName.isNullOrEmpty()) {
+            return false
+        }
+
+        return canCreateUser(
+            manager,
+            repository,
+            isUserSwitcherEnabled,
+            isAddUsersFromLockScreenEnabled
+        )
+    }
+
+    /**
+     * Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
+     */
+    private fun currentUserCanCreateUsers(
+        manager: UserManager,
+        repository: UserRepository,
+    ): Boolean {
+        val currentUser = repository.getSelectedUserInfo()
+        if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
+            return false
+        }
+
+        return systemCanCreateUsers(manager)
+    }
+
+    /** Returns `true` if the system can add users to the device; `false` otherwise. */
+    private fun systemCanCreateUsers(
+        manager: UserManager,
+    ): Boolean {
+        return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
+    }
+
+    /** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
+    private fun anyoneCanCreateUsers(
+        manager: UserManager,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 3c5b969..a84238c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -17,94 +17,725 @@
 
 package com.android.systemui.user.domain.interactor
 
+import android.annotation.SuppressLint
+import android.annotation.UserIdInt
+import android.app.ActivityManager
+import android.content.Context
 import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
 import android.provider.Settings
+import android.util.Log
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
+import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
 
 /** Encapsulates business logic to interact with user data and systems. */
 @SysUISingleton
 class UserInteractor
 @Inject
 constructor(
-    repository: UserRepository,
+    @Application private val applicationContext: Context,
+    private val repository: UserRepository,
     private val controller: UserSwitcherController,
     private val activityStarter: ActivityStarter,
-    keyguardInteractor: KeyguardInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val featureFlags: FeatureFlags,
+    private val manager: UserManager,
+    @Application private val applicationScope: CoroutineScope,
+    telephonyInteractor: TelephonyInteractor,
+    broadcastDispatcher: BroadcastDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val activityManager: ActivityManager,
+    private val refreshUsersScheduler: RefreshUsersScheduler,
+    private val guestUserInteractor: GuestUserInteractor,
 ) {
+    /**
+     * Defines interface for classes that can be notified when the state of users on the device is
+     * changed.
+     */
+    interface UserCallback {
+        /** Returns `true` if this callback can be cleaned-up. */
+        fun isEvictable(): Boolean = false
+        /** Notifies that the state of users on the device has changed. */
+        fun onUserStateChanged()
+    }
+
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+    private val supervisedUserPackageName: String?
+        get() =
+            applicationContext.getString(
+                com.android.internal.R.string.config_supervisedUserCreationPackage
+            )
+
+    private val callbackMutex = Mutex()
+    private val callbacks = mutableSetOf<UserCallback>()
+
     /** List of current on-device users to select from. */
-    val users: Flow<List<UserModel>> = repository.users
+    val users: Flow<List<UserModel>>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.userInfos,
+                    repository.selectedUserInfo,
+                    repository.userSwitcherSettings,
+                ) { userInfos, selectedUserInfo, settings ->
+                    toUserModels(
+                        userInfos = userInfos,
+                        selectedUserId = selectedUserInfo.id,
+                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                    )
+                }
+            } else {
+                repository.users
+            }
 
     /** The currently-selected user. */
-    val selectedUser: Flow<UserModel> = repository.selectedUser
+    val selectedUser: Flow<UserModel>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.selectedUserInfo,
+                    repository.userSwitcherSettings,
+                ) { selectedUserInfo, settings ->
+                    val selectedUserId = selectedUserInfo.id
+                    checkNotNull(
+                        toUserModel(
+                            userInfo = selectedUserInfo,
+                            selectedUserId = selectedUserId,
+                            canSwitchUsers = canSwitchUsers(selectedUserId),
+                            isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+                        )
+                    )
+                }
+            } else {
+                repository.selectedUser
+            }
 
     /** List of user-switcher related actions that are available. */
-    val actions: Flow<List<UserActionModel>> =
-        combine(
-                repository.isActionableWhenLocked,
-                keyguardInteractor.isKeyguardShowing,
-            ) { isActionableWhenLocked, isLocked ->
-                isActionableWhenLocked || !isLocked
-            }
-            .flatMapLatest { isActionable ->
-                if (isActionable) {
-                    repository.actions.map { actions ->
-                        actions +
-                            if (actions.isNotEmpty()) {
-                                // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because
-                                // that's a user
-                                // switcher specific action that is not known to the our data source
-                                // or other
-                                // features.
-                                listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-                            } else {
-                                // If no actions, don't add the navigate action.
-                                emptyList()
-                            }
+    val actions: Flow<List<UserActionModel>>
+        get() =
+            if (isNewImpl) {
+                combine(
+                    repository.userInfos,
+                    repository.userSwitcherSettings,
+                    keyguardInteractor.isKeyguardShowing,
+                ) { userInfos, settings, isDeviceLocked ->
+                    buildList {
+                        val hasGuestUser = userInfos.any { it.isGuest }
+                        if (
+                            !hasGuestUser &&
+                                (guestUserInteractor.isGuestUserAutoCreated ||
+                                    UserActionsUtil.canCreateGuest(
+                                        manager,
+                                        repository,
+                                        settings.isUserSwitcherEnabled,
+                                        settings.isAddUsersFromLockscreen,
+                                    ))
+                        ) {
+                            add(UserActionModel.ENTER_GUEST_MODE)
+                        }
+
+                        if (isDeviceLocked && !settings.isAddUsersFromLockscreen) {
+                            // The device is locked and our setting to allow actions that add users
+                            // from the lock-screen is not enabled. The guest action from above is
+                            // always allowed, even when the device is locked, but the various "add
+                            // user" actions below are not. We can finish building the list here.
+                            return@buildList
+                        }
+
+                        if (
+                            UserActionsUtil.canCreateUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_USER)
+                        }
+
+                        if (
+                            UserActionsUtil.canCreateSupervisedUser(
+                                manager,
+                                repository,
+                                settings.isUserSwitcherEnabled,
+                                settings.isAddUsersFromLockscreen,
+                                supervisedUserPackageName,
+                            )
+                        ) {
+                            add(UserActionModel.ADD_SUPERVISED_USER)
+                        }
                     }
-                } else {
-                    // If not actionable it means that we're not allowed to show actions when locked
-                    // and we
-                    // are locked. Therefore, we should show no actions.
-                    flowOf(emptyList())
                 }
+            } else {
+                combine(
+                        repository.isActionableWhenLocked,
+                        keyguardInteractor.isKeyguardShowing,
+                    ) { isActionableWhenLocked, isLocked ->
+                        isActionableWhenLocked || !isLocked
+                    }
+                    .flatMapLatest { isActionable ->
+                        if (isActionable) {
+                            repository.actions.map { actions ->
+                                actions +
+                                    if (actions.isNotEmpty()) {
+                                        // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
+                                        // because that's a user switcher specific action that is
+                                        // not known to the our data source or other features.
+                                        listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+                                    } else {
+                                        // If no actions, don't add the navigate action.
+                                        emptyList()
+                                    }
+                            }
+                        } else {
+                            // If not actionable it means that we're not allowed to show actions
+                            // when
+                            // locked and we are locked. Therefore, we should show no actions.
+                            flowOf(emptyList())
+                        }
+                    }
             }
 
+    val userRecords: StateFlow<ArrayList<UserRecord>> =
+        if (isNewImpl) {
+            combine(
+                    repository.userInfos,
+                    repository.selectedUserInfo,
+                    actions,
+                    repository.userSwitcherSettings,
+                ) { userInfos, selectedUserInfo, actionModels, settings ->
+                    ArrayList(
+                        userInfos.map {
+                            toRecord(
+                                userInfo = it,
+                                selectedUserId = selectedUserInfo.id,
+                            )
+                        } +
+                            actionModels.map {
+                                toRecord(
+                                    action = it,
+                                    selectedUserId = selectedUserInfo.id,
+                                    isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+                                )
+                            }
+                    )
+                }
+                .onEach { notifyCallbacks() }
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = ArrayList(),
+                )
+        } else {
+            MutableStateFlow(ArrayList())
+        }
+
+    val selectedUserRecord: StateFlow<UserRecord?> =
+        if (isNewImpl) {
+            repository.selectedUserInfo
+                .map { selectedUserInfo ->
+                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+                }
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = null,
+                )
+        } else {
+            MutableStateFlow(null)
+        }
+
     /** Whether the device is configured to always have a guest user available. */
-    val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+    val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
 
     /** Whether the guest user is currently being reset. */
-    val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+    val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+
+    private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
+    val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
+
+    private val _dialogDismissRequests = MutableStateFlow<Unit?>(null)
+    val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
+
+    val isSimpleUserSwitcher: Boolean
+        get() =
+            if (isNewImpl) {
+                repository.isSimpleUserSwitcher()
+            } else {
+                error("Not supported in the old implementation!")
+            }
+
+    init {
+        if (isNewImpl) {
+            refreshUsersScheduler.refreshIfNotPaused()
+            telephonyInteractor.callState
+                .distinctUntilChanged()
+                .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+                .launchIn(applicationScope)
+
+            combine(
+                    broadcastDispatcher.broadcastFlow(
+                        filter =
+                            IntentFilter().apply {
+                                addAction(Intent.ACTION_USER_ADDED)
+                                addAction(Intent.ACTION_USER_REMOVED)
+                                addAction(Intent.ACTION_USER_INFO_CHANGED)
+                                addAction(Intent.ACTION_USER_SWITCHED)
+                                addAction(Intent.ACTION_USER_STOPPED)
+                                addAction(Intent.ACTION_USER_UNLOCKED)
+                            },
+                        user = UserHandle.SYSTEM,
+                        map = { intent, _ -> intent },
+                    ),
+                    repository.selectedUserInfo.pairwise(null),
+                ) { intent, selectedUserChange ->
+                    Pair(intent, selectedUserChange.previousValue)
+                }
+                .onEach { (intent, previousSelectedUser) ->
+                    onBroadcastReceived(intent, previousSelectedUser)
+                }
+                .launchIn(applicationScope)
+        }
+    }
+
+    fun addCallback(callback: UserCallback) {
+        applicationScope.launch { callbackMutex.withLock { callbacks.add(callback) } }
+    }
+
+    fun removeCallback(callback: UserCallback) {
+        applicationScope.launch { callbackMutex.withLock { callbacks.remove(callback) } }
+    }
+
+    fun refreshUsers() {
+        refreshUsersScheduler.refreshIfNotPaused()
+    }
+
+    fun onDialogShown() {
+        _dialogShowRequests.value = null
+    }
+
+    fun onDialogDismissed() {
+        _dialogDismissRequests.value = null
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("UserInteractor state:")
+        pw.println("  lastSelectedNonGuestUserId=${repository.lastSelectedNonGuestUserId}")
+
+        val users = userRecords.value.filter { it.info != null }
+        pw.println("  userCount=${userRecords.value.count { LegacyUserDataHelper.isUser(it) }}")
+        for (i in users.indices) {
+            pw.println("    ${users[i]}")
+        }
+
+        val actions = userRecords.value.filter { it.info == null }
+        pw.println("  actionCount=${userRecords.value.count { !LegacyUserDataHelper.isUser(it) }}")
+        for (i in actions.indices) {
+            pw.println("    ${actions[i]}")
+        }
+
+        pw.println("isSimpleUserSwitcher=$isSimpleUserSwitcher")
+        pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
+    }
+
+    fun onDeviceBootCompleted() {
+        guestUserInteractor.onDeviceBootCompleted()
+    }
 
     /** Switches to the user with the given user ID. */
     fun selectUser(
-        userId: Int,
+        newlySelectedUserId: Int,
     ) {
-        controller.onUserSelected(userId, /* dialogShower= */ null)
+        if (isNewImpl) {
+            val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+            if (
+                newlySelectedUserId == currentlySelectedUserInfo.id &&
+                    currentlySelectedUserInfo.isGuest
+            ) {
+                // Here when clicking on the currently-selected guest user to leave guest mode
+                // and return to the previously-selected non-guest user.
+                showDialog(
+                    ShowDialogRequestModel.ShowExitGuestDialog(
+                        guestUserId = currentlySelectedUserInfo.id,
+                        targetUserId = repository.lastSelectedNonGuestUserId,
+                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        onExitGuestUser = this::exitGuestUser,
+                    )
+                )
+                return
+            }
+
+            if (currentlySelectedUserInfo.isGuest) {
+                // Here when switching from guest to a non-guest user.
+                showDialog(
+                    ShowDialogRequestModel.ShowExitGuestDialog(
+                        guestUserId = currentlySelectedUserInfo.id,
+                        targetUserId = newlySelectedUserId,
+                        isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+                        isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                        onExitGuestUser = this::exitGuestUser,
+                    )
+                )
+                return
+            }
+
+            switchUser(newlySelectedUserId)
+        } else {
+            controller.onUserSelected(newlySelectedUserId, /* dialogShower= */ null)
+        }
     }
 
     /** Executes the given action. */
     fun executeAction(action: UserActionModel) {
-        when (action) {
-            UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
-            UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
-            UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
-            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
-                activityStarter.startActivity(
-                    Intent(Settings.ACTION_USER_SETTINGS),
-                    /* dismissShade= */ false,
-                )
+        if (isNewImpl) {
+            when (action) {
+                UserActionModel.ENTER_GUEST_MODE ->
+                    guestUserInteractor.createAndSwitchTo(
+                        this::showDialog,
+                        this::dismissDialog,
+                        this::selectUser,
+                    )
+                UserActionModel.ADD_USER -> {
+                    val currentUser = repository.getSelectedUserInfo()
+                    showDialog(
+                        ShowDialogRequestModel.ShowAddUserDialog(
+                            userHandle = currentUser.userHandle,
+                            isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+                            showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+                        )
+                    )
+                }
+                UserActionModel.ADD_SUPERVISED_USER ->
+                    activityStarter.startActivity(
+                        Intent()
+                            .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+                            .setPackage(supervisedUserPackageName)
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                        /* dismissShade= */ false,
+                    )
+                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                    activityStarter.startActivity(
+                        Intent(Settings.ACTION_USER_SETTINGS),
+                        /* dismissShade= */ false,
+                    )
+            }
+        } else {
+            when (action) {
+                UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
+                UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
+                UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
+                UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+                    activityStarter.startActivity(
+                        Intent(Settings.ACTION_USER_SETTINGS),
+                        /* dismissShade= */ false,
+                    )
+            }
         }
     }
+
+    fun exitGuestUser(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+        forceRemoveGuestOnExit: Boolean,
+    ) {
+        guestUserInteractor.exit(
+            guestUserId = guestUserId,
+            targetUserId = targetUserId,
+            forceRemoveGuestOnExit = forceRemoveGuestOnExit,
+            showDialog = this::showDialog,
+            dismissDialog = this::dismissDialog,
+            switchUser = this::switchUser,
+        )
+    }
+
+    fun removeGuestUser(
+        @UserIdInt guestUserId: Int,
+        @UserIdInt targetUserId: Int,
+    ) {
+        applicationScope.launch {
+            guestUserInteractor.remove(
+                guestUserId = guestUserId,
+                targetUserId = targetUserId,
+                ::showDialog,
+                ::dismissDialog,
+                ::selectUser,
+            )
+        }
+    }
+
+    private fun showDialog(request: ShowDialogRequestModel) {
+        _dialogShowRequests.value = request
+    }
+
+    private fun dismissDialog() {
+        _dialogDismissRequests.value = Unit
+    }
+
+    private fun notifyCallbacks() {
+        applicationScope.launch {
+            callbackMutex.withLock {
+                val iterator = callbacks.iterator()
+                while (iterator.hasNext()) {
+                    val callback = iterator.next()
+                    if (!callback.isEvictable()) {
+                        callback.onUserStateChanged()
+                    } else {
+                        iterator.remove()
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun toRecord(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+    ): UserRecord {
+        return LegacyUserDataHelper.createRecord(
+            context = applicationContext,
+            manager = manager,
+            userInfo = userInfo,
+            picture = null,
+            isCurrent = userInfo.id == selectedUserId,
+            canSwitchUsers = canSwitchUsers(selectedUserId),
+        )
+    }
+
+    private suspend fun toRecord(
+        action: UserActionModel,
+        selectedUserId: Int,
+        isAddFromLockscreenEnabled: Boolean,
+    ): UserRecord {
+        return LegacyUserDataHelper.createRecord(
+            context = applicationContext,
+            selectedUserId = selectedUserId,
+            actionType = action,
+            isRestricted =
+                if (action == UserActionModel.ENTER_GUEST_MODE) {
+                    // Entering guest mode is never restricted, so it's allowed to happen from the
+                    // lockscreen even if the "add from lockscreen" system setting is off.
+                    false
+                } else {
+                    !isAddFromLockscreenEnabled
+                },
+            isSwitchToEnabled =
+                canSwitchUsers(selectedUserId) &&
+                    // If the user is auto-created is must not be currently resetting.
+                    !(isGuestUserAutoCreated && isGuestUserResetting),
+        )
+    }
+
+    private fun switchUser(userId: Int) {
+        // TODO(b/246631653): track jank and lantecy like in the old impl.
+        refreshUsersScheduler.pause()
+        try {
+            activityManager.switchUser(userId)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Couldn't switch user.", e)
+        }
+    }
+
+    private suspend fun onBroadcastReceived(
+        intent: Intent,
+        previousUserInfo: UserInfo?,
+    ) {
+        val shouldRefreshAllUsers =
+            when (intent.action) {
+                Intent.ACTION_USER_SWITCHED -> {
+                    dismissDialog()
+                    val selectedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+                    if (previousUserInfo?.id != selectedUserId) {
+                        notifyCallbacks()
+                        restartSecondaryService(selectedUserId)
+                    }
+                    if (guestUserInteractor.isGuestUserAutoCreated) {
+                        guestUserInteractor.guaranteePresent()
+                    }
+                    true
+                }
+                Intent.ACTION_USER_INFO_CHANGED -> true
+                Intent.ACTION_USER_UNLOCKED -> {
+                    // If we unlocked the system user, we should refresh all users.
+                    intent.getIntExtra(
+                        Intent.EXTRA_USER_HANDLE,
+                        UserHandle.USER_NULL,
+                    ) == UserHandle.USER_SYSTEM
+                }
+                else -> true
+            }
+
+        if (shouldRefreshAllUsers) {
+            refreshUsersScheduler.unpauseAndRefresh()
+        }
+    }
+
+    private fun restartSecondaryService(@UserIdInt userId: Int) {
+        val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java)
+        // Disconnect from the old secondary user's service
+        val secondaryUserId = repository.secondaryUserId
+        if (secondaryUserId != UserHandle.USER_NULL) {
+            applicationContext.stopServiceAsUser(
+                intent,
+                UserHandle.of(secondaryUserId),
+            )
+            repository.secondaryUserId = UserHandle.USER_NULL
+        }
+
+        // Connect to the new secondary user's service (purely to ensure that a persistent
+        // SystemUI application is created for that user)
+        if (userId != UserHandle.USER_SYSTEM) {
+            applicationContext.startServiceAsUser(
+                intent,
+                UserHandle.of(userId),
+            )
+            repository.secondaryUserId = userId
+        }
+    }
+
+    private suspend fun toUserModels(
+        userInfos: List<UserInfo>,
+        selectedUserId: Int,
+        isUserSwitcherEnabled: Boolean,
+    ): List<UserModel> {
+        val canSwitchUsers = canSwitchUsers(selectedUserId)
+
+        return userInfos
+            // The guest user should go in the last position.
+            .sortedBy { it.isGuest }
+            .mapNotNull { userInfo ->
+                toUserModel(
+                    userInfo = userInfo,
+                    selectedUserId = selectedUserId,
+                    canSwitchUsers = canSwitchUsers,
+                    isUserSwitcherEnabled = isUserSwitcherEnabled,
+                )
+            }
+    }
+
+    private suspend fun toUserModel(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+        canSwitchUsers: Boolean,
+        isUserSwitcherEnabled: Boolean,
+    ): UserModel? {
+        val userId = userInfo.id
+        val isSelected = userId == selectedUserId
+
+        return when {
+            // When the user switcher is not enabled in settings, we only show the primary user.
+            !isUserSwitcherEnabled && !userInfo.isPrimary -> null
+
+            // We avoid showing disabled users.
+            !userInfo.isEnabled -> null
+            userInfo.isGuest ->
+                UserModel(
+                    id = userId,
+                    name = Text.Loaded(userInfo.name),
+                    image =
+                        getUserImage(
+                            isGuest = true,
+                            userId = userId,
+                        ),
+                    isSelected = isSelected,
+                    isSelectable = canSwitchUsers,
+                    isGuest = true,
+                )
+            userInfo.supportsSwitchToByUser() ->
+                UserModel(
+                    id = userId,
+                    name = Text.Loaded(userInfo.name),
+                    image =
+                        getUserImage(
+                            isGuest = false,
+                            userId = userId,
+                        ),
+                    isSelected = isSelected,
+                    isSelectable = canSwitchUsers || isSelected,
+                    isGuest = false,
+                )
+            else -> null
+        }
+    }
+
+    private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
+        return withContext(backgroundDispatcher) {
+            manager.getUserSwitchability(UserHandle.of(selectedUserId))
+        } == UserManager.SWITCHABILITY_STATUS_OK
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private suspend fun getUserImage(
+        isGuest: Boolean,
+        userId: Int,
+    ): Drawable {
+        if (isGuest) {
+            return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle))
+        }
+
+        // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
+        // TODO(b/246631653): downscale the bitmaps to R.dimen.max_avatar_size if requested.
+        val userIcon = withContext(backgroundDispatcher) { manager.getUserIcon(userId) }
+        if (userIcon != null) {
+            return BitmapDrawable(userIcon)
+        }
+
+        return UserIcons.getDefaultUserIcon(
+            applicationContext.resources,
+            userId,
+            /* light= */ false
+        )
+    }
+
+    companion object {
+        private const val TAG = "UserInteractor"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
new file mode 100644
index 0000000..08d7c5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.user.domain.model
+
+import android.os.UserHandle
+
+/** Encapsulates a request to show a dialog. */
+sealed class ShowDialogRequestModel {
+    data class ShowAddUserDialog(
+        val userHandle: UserHandle,
+        val isKeyguardShowing: Boolean,
+        val showEphemeralMessage: Boolean,
+    ) : ShowDialogRequestModel()
+
+    data class ShowUserCreationDialog(
+        val isGuest: Boolean,
+    ) : ShowDialogRequestModel()
+
+    data class ShowExitGuestDialog(
+        val guestUserId: Int,
+        val targetUserId: Int,
+        val isGuestEphemeral: Boolean,
+        val isKeyguardShowing: Boolean,
+        val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
+    ) : ShowDialogRequestModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
new file mode 100644
index 0000000..137de15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 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.user.legacyhelper.data
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.R
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+
+/**
+ * Defines utility functions for helping with legacy data code for users.
+ *
+ * We need these to avoid code duplication between logic inside the UserSwitcherController and in
+ * modern architecture classes such as repositories, interactors, and view-models. If we ever
+ * simplify UserSwitcherController (or delete it), the code here could be moved into its call-sites.
+ */
+object LegacyUserDataHelper {
+
+    @JvmStatic
+    fun createRecord(
+        context: Context,
+        manager: UserManager,
+        picture: Bitmap?,
+        userInfo: UserInfo,
+        isCurrent: Boolean,
+        canSwitchUsers: Boolean,
+    ): UserRecord {
+        val isGuest = userInfo.isGuest
+        return UserRecord(
+            info = userInfo,
+            picture =
+                getPicture(
+                    manager = manager,
+                    context = context,
+                    userInfo = userInfo,
+                    picture = picture,
+                ),
+            isGuest = isGuest,
+            isCurrent = isCurrent,
+            isSwitchToEnabled = canSwitchUsers || (isCurrent && !isGuest),
+        )
+    }
+
+    @JvmStatic
+    fun createRecord(
+        context: Context,
+        selectedUserId: Int,
+        actionType: UserActionModel,
+        isRestricted: Boolean,
+        isSwitchToEnabled: Boolean,
+    ): UserRecord {
+        return UserRecord(
+            isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
+            isAddUser = actionType == UserActionModel.ADD_USER,
+            isAddSupervisedUser = actionType == UserActionModel.ADD_SUPERVISED_USER,
+            isRestricted = isRestricted,
+            isSwitchToEnabled = isSwitchToEnabled,
+            enforcedAdmin =
+                getEnforcedAdmin(
+                    context = context,
+                    selectedUserId = selectedUserId,
+                ),
+        )
+    }
+
+    fun toUserActionModel(record: UserRecord): UserActionModel {
+        check(!isUser(record))
+
+        return when {
+            record.isAddUser -> UserActionModel.ADD_USER
+            record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
+            record.isGuest -> UserActionModel.ENTER_GUEST_MODE
+            else -> error("Not a known action: $record")
+        }
+    }
+
+    fun isUser(record: UserRecord): Boolean {
+        return record.info != null
+    }
+
+    private fun getEnforcedAdmin(
+        context: Context,
+        selectedUserId: Int,
+    ): EnforcedAdmin? {
+        val admin =
+            RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                context,
+                UserManager.DISALLOW_ADD_USER,
+                selectedUserId,
+            )
+                ?: return null
+
+        return if (
+            !RestrictedLockUtilsInternal.hasBaseUserRestriction(
+                context,
+                UserManager.DISALLOW_ADD_USER,
+                selectedUserId,
+            )
+        ) {
+            admin
+        } else {
+            null
+        }
+    }
+
+    private fun getPicture(
+        context: Context,
+        manager: UserManager,
+        userInfo: UserInfo,
+        picture: Bitmap?,
+    ): Bitmap? {
+        if (userInfo.isGuest) {
+            return null
+        }
+
+        if (picture != null) {
+            return picture
+        }
+
+        val unscaledOrNull = manager.getUserIcon(userInfo.id) ?: return null
+
+        val avatarSize = context.resources.getDimensionPixelSize(R.dimen.max_avatar_size)
+        return Bitmap.createScaledBitmap(
+            unscaledOrNull,
+            avatarSize,
+            avatarSize,
+            /* filter= */ true,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
index bf7977a..2095683 100644
--- a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
@@ -32,4 +32,6 @@
     val isSelected: Boolean,
     /** Whether this use is selectable. A non-selectable user cannot be switched to. */
     val isSelectable: Boolean,
+    /** Whether this model represents the guest user. */
+    val isGuest: Boolean,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
new file mode 100644
index 0000000..a9d66de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.user.ui.dialog
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.UserHandle
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.CreateUserActivity
+
+/** Dialog for adding a new user to the device. */
+class AddUserDialog(
+    context: Context,
+    userHandle: UserHandle,
+    isKeyguardShowing: Boolean,
+    showEphemeralMessage: Boolean,
+    private val falsingManager: FalsingManager,
+    private val broadcastSender: BroadcastSender,
+    private val dialogLaunchAnimator: DialogLaunchAnimator
+) : SystemUIDialog(context) {
+
+    private val onClickListener =
+        object : DialogInterface.OnClickListener {
+            override fun onClick(dialog: DialogInterface, which: Int) {
+                val penalty =
+                    if (which == BUTTON_NEGATIVE) {
+                        FalsingManager.NO_PENALTY
+                    } else {
+                        FalsingManager.MODERATE_PENALTY
+                    }
+                if (falsingManager.isFalseTap(penalty)) {
+                    return
+                }
+
+                if (which == BUTTON_NEUTRAL) {
+                    cancel()
+                    return
+                }
+
+                dialogLaunchAnimator.dismissStack(this@AddUserDialog)
+                if (ActivityManager.isUserAMonkey()) {
+                    return
+                }
+
+                // Use broadcast instead of ShadeController, as this dialog may have started in
+                // another
+                // process where normal dagger bindings are not available.
+                broadcastSender.sendBroadcastAsUser(
+                    Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+                    UserHandle.CURRENT
+                )
+
+                context.startActivityAsUser(
+                    CreateUserActivity.createIntentForStart(context),
+                    userHandle,
+                )
+            }
+        }
+
+    init {
+        setTitle(R.string.user_add_user_title)
+        val message =
+            context.getString(R.string.user_add_user_message_short) +
+                if (showEphemeralMessage) {
+                    context.getString(
+                        com.android.systemui.R.string.user_add_user_message_guest_remove
+                    )
+                } else {
+                    ""
+                }
+        setMessage(message)
+
+        setButton(
+            BUTTON_NEUTRAL,
+            context.getString(android.R.string.cancel),
+            onClickListener,
+        )
+
+        setButton(
+            BUTTON_POSITIVE,
+            context.getString(android.R.string.ok),
+            onClickListener,
+        )
+
+        setWindowOnTop(this, isKeyguardShowing)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
new file mode 100644
index 0000000..19ad44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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.user.ui.dialog
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.DialogInterface
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog for exiting the guest user. */
+class ExitGuestDialog(
+    context: Context,
+    private val guestUserId: Int,
+    private val isGuestEphemeral: Boolean,
+    private val targetUserId: Int,
+    isKeyguardShowing: Boolean,
+    private val falsingManager: FalsingManager,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val onExitGuestUserListener: OnExitGuestUserListener,
+) : SystemUIDialog(context) {
+
+    fun interface OnExitGuestUserListener {
+        fun onExitGuestUser(
+            @UserIdInt guestId: Int,
+            @UserIdInt targetId: Int,
+            forceRemoveGuest: Boolean,
+        )
+    }
+
+    private val onClickListener =
+        object : DialogInterface.OnClickListener {
+            override fun onClick(dialog: DialogInterface, which: Int) {
+                val penalty =
+                    if (which == BUTTON_NEGATIVE) {
+                        FalsingManager.NO_PENALTY
+                    } else {
+                        FalsingManager.MODERATE_PENALTY
+                    }
+                if (falsingManager.isFalseTap(penalty)) {
+                    return
+                }
+
+                if (isGuestEphemeral) {
+                    if (which == BUTTON_POSITIVE) {
+                        dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                        // Ephemeral guest: exit guest, guest is removed by the system
+                        // on exit, since its marked ephemeral
+                        onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, false)
+                    } else if (which == BUTTON_NEGATIVE) {
+                        // Cancel clicked, do nothing
+                        cancel()
+                    }
+                } else {
+                    when (which) {
+                        BUTTON_POSITIVE -> {
+                            dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                            // Non-ephemeral guest: exit guest, guest is not removed by the system
+                            // on exit, since its marked non-ephemeral
+                            onExitGuestUserListener.onExitGuestUser(
+                                guestUserId,
+                                targetUserId,
+                                false
+                            )
+                        }
+                        BUTTON_NEGATIVE -> {
+                            dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+                            // Non-ephemeral guest: remove guest and then exit
+                            onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, true)
+                        }
+                        BUTTON_NEUTRAL -> {
+                            // Cancel clicked, do nothing
+                            cancel()
+                        }
+                    }
+                }
+            }
+        }
+
+    init {
+        if (isGuestEphemeral) {
+            setTitle(context.getString(R.string.guest_exit_dialog_title))
+            setMessage(context.getString(R.string.guest_exit_dialog_message))
+            setButton(
+                BUTTON_NEUTRAL,
+                context.getString(android.R.string.cancel),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_POSITIVE,
+                context.getString(R.string.guest_exit_dialog_button),
+                onClickListener,
+            )
+        } else {
+            setTitle(context.getString(R.string.guest_exit_dialog_title_non_ephemeral))
+            setMessage(context.getString(R.string.guest_exit_dialog_message_non_ephemeral))
+            setButton(
+                BUTTON_NEUTRAL,
+                context.getString(android.R.string.cancel),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_NEGATIVE,
+                context.getString(R.string.guest_exit_clear_data_button),
+                onClickListener,
+            )
+            setButton(
+                BUTTON_POSITIVE,
+                context.getString(R.string.guest_exit_save_data_button),
+                onClickListener,
+            )
+        }
+        setWindowOnTop(this, isKeyguardShowing)
+        setCanceledOnTouchOutside(false)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
new file mode 100644
index 0000000..c1d2f47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.user.ui.dialog
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface UserDialogModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(UserSwitcherDialogCoordinator::class)
+    fun bindFeature(impl: UserSwitcherDialogCoordinator): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
new file mode 100644
index 0000000..6e7b523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 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.user.ui.dialog
+
+import android.app.Dialog
+import android.content.Context
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Coordinates dialogs for user switcher logic. */
+@SysUISingleton
+class UserSwitcherDialogCoordinator
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Application private val applicationScope: CoroutineScope,
+    private val falsingManager: FalsingManager,
+    private val broadcastSender: BroadcastSender,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val interactor: UserInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable(context) {
+
+    private var currentDialog: Dialog? = null
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+            return
+        }
+
+        startHandlingDialogShowRequests()
+        startHandlingDialogDismissRequests()
+    }
+
+    private fun startHandlingDialogShowRequests() {
+        applicationScope.launch {
+            interactor.dialogShowRequests.filterNotNull().collect { request ->
+                currentDialog?.let {
+                    if (it.isShowing) {
+                        it.cancel()
+                    }
+                }
+
+                currentDialog =
+                    when (request) {
+                        is ShowDialogRequestModel.ShowAddUserDialog ->
+                            AddUserDialog(
+                                context = context,
+                                userHandle = request.userHandle,
+                                isKeyguardShowing = request.isKeyguardShowing,
+                                showEphemeralMessage = request.showEphemeralMessage,
+                                falsingManager = falsingManager,
+                                broadcastSender = broadcastSender,
+                                dialogLaunchAnimator = dialogLaunchAnimator,
+                            )
+                        is ShowDialogRequestModel.ShowUserCreationDialog ->
+                            UserCreatingDialog(
+                                context,
+                                request.isGuest,
+                            )
+                        is ShowDialogRequestModel.ShowExitGuestDialog ->
+                            ExitGuestDialog(
+                                context = context,
+                                guestUserId = request.guestUserId,
+                                isGuestEphemeral = request.isGuestEphemeral,
+                                targetUserId = request.targetUserId,
+                                isKeyguardShowing = request.isKeyguardShowing,
+                                falsingManager = falsingManager,
+                                dialogLaunchAnimator = dialogLaunchAnimator,
+                                onExitGuestUserListener = request.onExitGuestUser,
+                            )
+                    }
+
+                currentDialog?.show()
+                interactor.onDialogShown()
+            }
+        }
+    }
+
+    private fun startHandlingDialogDismissRequests() {
+        applicationScope.launch {
+            interactor.dialogDismissRequests.filterNotNull().collect {
+                currentDialog?.let {
+                    if (it.isShowing) {
+                        it.cancel()
+                    }
+                }
+
+                interactor.onDialogDismissed()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 398341d..5b83df7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -21,7 +21,10 @@
 import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.R
 import com.android.systemui.common.ui.drawable.CircularDrawable
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
@@ -36,9 +39,14 @@
 class UserSwitcherViewModel
 private constructor(
     private val userInteractor: UserInteractor,
+    private val guestUserInteractor: GuestUserInteractor,
     private val powerInteractor: PowerInteractor,
+    private val featureFlags: FeatureFlags,
 ) : ViewModel() {
 
+    private val isNewImpl: Boolean
+        get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -47,9 +55,6 @@
     val maximumUserColumns: Flow<Int> =
         users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
 
-    /** Whether the button to open the user action menu is visible. */
-    val isOpenMenuButtonVisible: Flow<Boolean> = userInteractor.actions.map { it.isNotEmpty() }
-
     private val _isMenuVisible = MutableStateFlow(false)
     /**
      * Whether the user action menu should be shown. Once the action menu is dismissed/closed, the
@@ -58,9 +63,23 @@
     val isMenuVisible: Flow<Boolean> = _isMenuVisible
     /** The user action menu. */
     val menu: Flow<List<UserActionViewModel>> =
-        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+        userInteractor.actions.map { actions ->
+            if (isNewImpl && actions.isNotEmpty()) {
+                    // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
+                    // switcher specific action that is not known to the our data source or other
+                    // features.
+                    actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+                } else {
+                    actions
+                }
+                .map { action -> toViewModel(action) }
+        }
+
+    /** Whether the button to open the user action menu is visible. */
+    val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
 
     private val hasCancelButtonBeenClicked = MutableStateFlow(false)
+    private val isFinishRequiredDueToExecutedAction = MutableStateFlow(false)
 
     /**
      * Whether the observer should finish the experience. Once consumed, [onFinished] must be called
@@ -81,6 +100,7 @@
      */
     fun onFinished() {
         hasCancelButtonBeenClicked.value = false
+        isFinishRequiredDueToExecutedAction.value = false
     }
 
     /** Notifies that the user has clicked the "open menu" button. */
@@ -120,8 +140,10 @@
             },
             // When the cancel button is clicked, we should finish.
             hasCancelButtonBeenClicked,
-        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked ->
-            selectedUserChanged || screenTurnedOff || cancelButtonClicked
+            // If an executed action told us to finish, we should finish,
+            isFinishRequiredDueToExecutedAction,
+        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
+            selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
         }
     }
 
@@ -164,13 +186,25 @@
                 } else {
                     LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
                         isGuest = model == UserActionModel.ENTER_GUEST_MODE,
-                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = userInteractor.isGuestUserResetting,
+                        isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
+                        isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
                         isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
                         isAddUser = model == UserActionModel.ADD_USER,
                     )
                 },
-            onClicked = { userInteractor.executeAction(action = model) },
+            onClicked = {
+                userInteractor.executeAction(action = model)
+                // We don't finish because we want to show a dialog over the full-screen UI and
+                // that dialog can be dismissed in case the user changes their mind and decides not
+                // to add a user.
+                //
+                // We finish for all other actions because they navigate us away from the
+                // full-screen experience or are destructive (like changing to the guest user).
+                val shouldFinish = model != UserActionModel.ADD_USER
+                if (shouldFinish) {
+                    isFinishRequiredDueToExecutedAction.value = true
+                }
+            },
         )
     }
 
@@ -186,13 +220,17 @@
     @Inject
     constructor(
         private val userInteractor: UserInteractor,
+        private val guestUserInteractor: GuestUserInteractor,
         private val powerInteractor: PowerInteractor,
+        private val featureFlags: FeatureFlags,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
             return UserSwitcherViewModel(
                 userInteractor = userInteractor,
+                guestUserInteractor = guestUserInteractor,
                 powerInteractor = powerInteractor,
+                featureFlags = featureFlags,
             )
                 as T
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d745..a925e38 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@
     private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
     private boolean mDefaultShowOperatorNameConfigLoaded;
     private boolean mDefaultShowOperatorNameConfig;
+    private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+    private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
 
     @Inject
     public CarrierConfigTracker(
@@ -207,6 +209,22 @@
     }
 
     /**
+     * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+     * the default carrier config.
+     */
+    public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+        if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+            mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+                    .getDefaultConfig().getBoolean(CarrierConfigManager
+                            .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+                    );
+            mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+        }
+
+        return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+    }
+
+    /**
      * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
      * default value if no override exists
      *
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
new file mode 100644
index 0000000..0b8257d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.util.settings
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Kotlin extension functions for [SettingsProxy]. */
+object SettingsProxyExt {
+
+    /** Returns a flow of [Unit] that is invoked each time that content is updated. */
+    fun SettingsProxy.observerFlow(
+        vararg names: String,
+        @UserIdInt userId: Int = UserHandle.USER_CURRENT,
+    ): Flow<Unit> {
+        return conflatedCallbackFlow {
+            val observer =
+                object : ContentObserver(null) {
+                    override fun onChange(selfChange: Boolean) {
+                        trySend(Unit)
+                    }
+                }
+
+            names.forEach { name -> registerContentObserverForUser(name, observer, userId) }
+
+            awaitClose { unregisterContentObserver(observer) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index bf7c459..7c022eb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -65,6 +65,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
@@ -178,7 +179,8 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             CaptioningManager captioningManager,
             KeyguardManager keyguardManager,
-            ActivityManager activityManager
+            ActivityManager activityManager,
+            DumpManager dumpManager
     ) {
         mContext = context.getApplicationContext();
         mPackageManager = packageManager;
@@ -207,7 +209,7 @@
         mCaptioningManager = captioningManager;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
-
+        dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
                 .isAccessibilityVolumeStreamActive();
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 0f7e143..6b3beeb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,21 +16,17 @@
 
 package com.android.systemui.wallpapers;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
 
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -40,8 +36,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
-import android.view.Display;
-import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
@@ -49,8 +43,11 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
 import com.android.systemui.wallpapers.gl.EglHelper;
 import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
@@ -59,6 +56,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -78,15 +76,28 @@
     private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
     private final ArraySet<RectF> mColorAreas = new ArraySet<>();
     private volatile int mPages = 1;
+    private boolean mPagesComputed = false;
     private HandlerThread mWorker;
     // scaled down version
     private Bitmap mMiniBitmap;
     private final FeatureFlags mFeatureFlags;
 
+    // used in canvasEngine to load/unload the bitmap and extract the colors
+    @Background
+    private final DelayableExecutor mBackgroundExecutor;
+    private static final int DELAY_UNLOAD_BITMAP = 2000;
+
+    @Main
+    private final Executor mMainExecutor;
+
     @Inject
-    public ImageWallpaper(FeatureFlags featureFlags) {
+    public ImageWallpaper(FeatureFlags featureFlags,
+            @Background DelayableExecutor backgroundExecutor,
+            @Main Executor mainExecutor) {
         super();
         mFeatureFlags = featureFlags;
+        mBackgroundExecutor = backgroundExecutor;
+        mMainExecutor = mainExecutor;
     }
 
     @Override
@@ -339,7 +350,6 @@
                 imgArea.left = 0;
                 imgArea.right = 1;
             }
-
             return imgArea;
         }
 
@@ -510,69 +520,84 @@
 
 
     class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
-
-        // time [ms] before unloading the wallpaper after it is loaded
-        private static final int DELAY_FORGET_WALLPAPER = 5000;
-
-        private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper;
-
         private WallpaperManager mWallpaperManager;
-        private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer;
+        private final WallpaperColorExtractor mWallpaperColorExtractor;
+        private SurfaceHolder mSurfaceHolder;
+        @VisibleForTesting
+        static final int MIN_SURFACE_WIDTH = 128;
+        @VisibleForTesting
+        static final int MIN_SURFACE_HEIGHT = 128;
         private Bitmap mBitmap;
 
-        private Display mDisplay;
-        private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
-
-        private AsyncTask<Void, Void, Bitmap> mLoader;
-        private boolean mNeedsDrawAfterLoadingWallpaper = false;
+        /*
+         * Counter to unload the bitmap as soon as possible.
+         * Before any bitmap operation, this is incremented.
+         * After an operation completion, this is decremented (synchronously),
+         * and if the count is 0, unload the bitmap
+         */
+        private int mBitmapUsages = 0;
+        private final Object mLock = new Object();
 
         CanvasEngine() {
             super();
             setFixedSizeAllowed(true);
             setShowForAllUsers(true);
-        }
+            mWallpaperColorExtractor = new WallpaperColorExtractor(
+                    mBackgroundExecutor,
+                    new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+                        @Override
+                        public void onColorsProcessed(List<RectF> regions,
+                                List<WallpaperColors> colors) {
+                            CanvasEngine.this.onColorsProcessed(regions, colors);
+                        }
 
-        void trimMemory(int level) {
-            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
-                    && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
-                    && isBitmapLoaded()) {
-                if (DEBUG) {
-                    Log.d(TAG, "trimMemory");
-                }
-                unloadWallpaper();
+                        @Override
+                        public void onMiniBitmapUpdated() {
+                            CanvasEngine.this.onMiniBitmapUpdated();
+                        }
+
+                        @Override
+                        public void onActivated() {
+                            setOffsetNotificationsEnabled(true);
+                        }
+
+                        @Override
+                        public void onDeactivated() {
+                            setOffsetNotificationsEnabled(false);
+                        }
+                    });
+
+            // if the number of pages is already computed, transmit it to the color extractor
+            if (mPagesComputed) {
+                mWallpaperColorExtractor.onPageChanged(mPages);
             }
         }
 
         @Override
         public void onCreate(SurfaceHolder surfaceHolder) {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
             if (DEBUG) {
                 Log.d(TAG, "onCreate");
             }
+            mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+            mSurfaceHolder = surfaceHolder;
+            Rect dimensions = mWallpaperManager.peekBitmapDimensions();
+            int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
+            int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
+            mSurfaceHolder.setFixedSize(width, height);
 
-            mWallpaperManager = getSystemService(WallpaperManager.class);
-            super.onCreate(surfaceHolder);
-
-            final Context displayContext = getDisplayContext();
-            final int displayId = displayContext == null ? DEFAULT_DISPLAY :
-                    displayContext.getDisplayId();
-            DisplayManager dm = getSystemService(DisplayManager.class);
-            if (dm != null) {
-                mDisplay = dm.getDisplay(displayId);
-                if (mDisplay == null) {
-                    Log.e(TAG, "Cannot find display! Fallback to default.");
-                    mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
-                }
-            }
-            setOffsetNotificationsEnabled(false);
-
-            mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder);
-            loadWallpaper(false);
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .registerDisplayListener(this, null);
+            getDisplaySizeAndUpdateColorExtractor();
+            Trace.endSection();
         }
 
         @Override
         public void onDestroy() {
-            super.onDestroy();
-            unloadWallpaper();
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .unregisterDisplayListener(this);
+            mWallpaperColorExtractor.cleanUp();
+            unloadBitmap();
         }
 
         @Override
@@ -581,31 +606,30 @@
         }
 
         @Override
+        public boolean shouldWaitForEngineShown() {
+            return true;
+        }
+
+        @Override
         public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
             if (DEBUG) {
                 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
             }
-            super.onSurfaceChanged(holder, format, width, height);
-            mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
-            drawFrame(false);
         }
 
         @Override
         public void onSurfaceDestroyed(SurfaceHolder holder) {
-            super.onSurfaceDestroyed(holder);
             if (DEBUG) {
                 Log.i(TAG, "onSurfaceDestroyed");
             }
-            mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
+            mSurfaceHolder = null;
         }
 
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
-            super.onSurfaceCreated(holder);
             if (DEBUG) {
                 Log.i(TAG, "onSurfaceCreated");
             }
-            mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
         }
 
         @Override
@@ -613,135 +637,88 @@
             if (DEBUG) {
                 Log.d(TAG, "onSurfaceRedrawNeeded");
             }
-            super.onSurfaceRedrawNeeded(holder);
-            // At the end of this method we should have drawn into the surface.
-            // This means that the bitmap should be loaded synchronously if
-            // it was already unloaded.
-            if (!isBitmapLoaded()) {
-                setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+            drawFrame();
+        }
+
+        private void drawFrame() {
+            mBackgroundExecutor.execute(this::drawFrameSynchronized);
+        }
+
+        private void drawFrameSynchronized() {
+            synchronized (mLock) {
+                drawFrameInternal();
             }
-            drawFrame(true);
         }
 
-        private DisplayInfo getDisplayInfo() {
-            mDisplay.getDisplayInfo(mTmpDisplayInfo);
-            return mTmpDisplayInfo;
-        }
-
-        private void drawFrame(boolean forceRedraw) {
-            if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
+        private void drawFrameInternal() {
+            if (mSurfaceHolder == null) {
                 Log.e(TAG, "attempt to draw a frame without a valid surface");
                 return;
             }
 
+            // load the wallpaper if not already done
             if (!isBitmapLoaded()) {
-                // ensure that we load the wallpaper.
-                // if the wallpaper is currently loading, this call will have no effect.
-                loadWallpaper(true);
-                return;
-            }
-            mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw);
-        }
-
-        private void setBitmap(Bitmap bitmap) {
-            if (bitmap == null) {
-                Log.e(TAG, "Attempt to set a null bitmap");
-            } else if (mBitmap == bitmap) {
-                Log.e(TAG, "The value of bitmap is the same");
-            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
-                Log.e(TAG, "Attempt to set an invalid wallpaper of length "
-                        + bitmap.getWidth() + "x" + bitmap.getHeight());
+                loadWallpaperAndDrawFrameInternal();
             } else {
-                if (mBitmap != null) {
-                    mBitmap.recycle();
-                }
-                mBitmap = bitmap;
+                mBitmapUsages++;
+
+                // drawing is done on the main thread
+                mMainExecutor.execute(() -> {
+                    drawFrameOnCanvas(mBitmap);
+                    reportEngineShown(false);
+                    unloadBitmapIfNotUsed();
+                });
             }
         }
 
-        private boolean isBitmapLoaded() {
+        @VisibleForTesting
+        void drawFrameOnCanvas(Bitmap bitmap) {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
+            // TODO change SurfaceHolder API to add wcg support
+            Canvas c = mSurfaceHolder.lockHardwareCanvas();
+            if (c != null) {
+                Rect dest = mSurfaceHolder.getSurfaceFrame();
+                try {
+                    c.drawBitmap(bitmap, null, dest, null);
+                } finally {
+                    mSurfaceHolder.unlockCanvasAndPost(c);
+                }
+            }
+            Trace.endSection();
+        }
+
+        @VisibleForTesting
+        boolean isBitmapLoaded() {
             return mBitmap != null && !mBitmap.isRecycled();
         }
 
-        /**
-         * Loads the wallpaper on background thread and schedules updating the surface frame,
-         * and if {@code needsDraw} is set also draws a frame.
-         *
-         * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
-         * the active request).
-         *
-         */
-        private void loadWallpaper(boolean needsDraw) {
-            mNeedsDrawAfterLoadingWallpaper |= needsDraw;
-            if (mLoader != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
-                }
-                return;
-            }
-            mLoader = new AsyncTask<Void, Void, Bitmap>() {
-                @Override
-                protected Bitmap doInBackground(Void... params) {
-                    Throwable exception;
-                    try {
-                        Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */);
-                        if (wallpaper != null
-                                && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
-                            throw new RuntimeException("Wallpaper is too large to draw!");
-                        }
-                        return wallpaper;
-                    } catch (RuntimeException | OutOfMemoryError e) {
-                        exception = e;
-                    }
-
-                    if (isCancelled()) {
-                        return null;
-                    }
-
-                    // Note that if we do fail at this, and the default wallpaper can't
-                    // be loaded, we will go into a cycle.  Don't do a build where the
-                    // default wallpaper can't be loaded.
-                    Log.w(TAG, "Unable to load wallpaper!", exception);
-                    try {
-                        mWallpaperManager.clear();
-                    } catch (IOException ex) {
-                        // now we're really screwed.
-                        Log.w(TAG, "Unable reset to default wallpaper!", ex);
-                    }
-
-                    if (isCancelled()) {
-                        return null;
-                    }
-
-                    try {
-                        return mWallpaperManager.getBitmap(true /* hardware */);
-                    } catch (RuntimeException | OutOfMemoryError e) {
-                        Log.w(TAG, "Unable to load default wallpaper!", e);
-                    }
-                    return null;
-                }
-
-                @Override
-                protected void onPostExecute(Bitmap bitmap) {
-                    setBitmap(bitmap);
-
-                    if (mNeedsDrawAfterLoadingWallpaper) {
-                        drawFrame(true);
-                    }
-
-                    mLoader = null;
-                    mNeedsDrawAfterLoadingWallpaper = false;
-                    scheduleUnloadWallpaper();
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        private void unloadBitmapIfNotUsed() {
+            mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
         }
 
-        private void unloadWallpaper() {
-            if (mLoader != null) {
-                mLoader.cancel(false);
-                mLoader = null;
+        private void unloadBitmapIfNotUsedSynchronized() {
+            synchronized (mLock) {
+                mBitmapUsages -= 1;
+                if (mBitmapUsages <= 0) {
+                    mBitmapUsages = 0;
+                    unloadBitmapInternal();
+                }
             }
+        }
 
+        private void unloadBitmap() {
+            mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
+        }
+
+        private void unloadBitmapSynchronized() {
+            synchronized (mLock) {
+                mBitmapUsages = 0;
+                unloadBitmapInternal();
+            }
+        }
+
+        private void unloadBitmapInternal() {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
             if (mBitmap != null) {
                 mBitmap.recycle();
             }
@@ -750,12 +727,133 @@
             final Surface surface = getSurfaceHolder().getSurface();
             surface.hwuiDestroy();
             mWallpaperManager.forgetLoadedWallpaper();
+            Trace.endSection();
         }
 
-        private void scheduleUnloadWallpaper() {
-            Handler handler = getMainThreadHandler();
-            handler.removeCallbacks(mUnloadWallpaperCallback);
-            handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
+        private void loadWallpaperAndDrawFrameInternal() {
+            Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+            boolean loadSuccess = false;
+            Bitmap bitmap;
+            try {
+                bitmap = mWallpaperManager.getBitmap(false);
+                if (bitmap != null
+                        && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+                    throw new RuntimeException("Wallpaper is too large to draw!");
+                }
+            } catch (RuntimeException | OutOfMemoryError exception) {
+
+                // Note that if we do fail at this, and the default wallpaper can't
+                // be loaded, we will go into a cycle. Don't do a build where the
+                // default wallpaper can't be loaded.
+                Log.w(TAG, "Unable to load wallpaper!", exception);
+                try {
+                    mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
+                } catch (IOException ex) {
+                    // now we're really screwed.
+                    Log.w(TAG, "Unable reset to default wallpaper!", ex);
+                }
+
+                try {
+                    bitmap = mWallpaperManager.getBitmap(false);
+                } catch (RuntimeException | OutOfMemoryError e) {
+                    Log.w(TAG, "Unable to load default wallpaper!", e);
+                    bitmap = null;
+                }
+            }
+
+            if (bitmap == null) {
+                Log.w(TAG, "Could not load bitmap");
+            } else if (bitmap.isRecycled()) {
+                Log.e(TAG, "Attempt to load a recycled bitmap");
+            } else if (mBitmap == bitmap) {
+                Log.e(TAG, "Loaded a bitmap that was already loaded");
+            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
+                Log.e(TAG, "Attempt to load an invalid wallpaper of length "
+                        + bitmap.getWidth() + "x" + bitmap.getHeight());
+            } else {
+                // at this point, loading is done correctly.
+                loadSuccess = true;
+                // recycle the previously loaded bitmap
+                if (mBitmap != null) {
+                    mBitmap.recycle();
+                }
+                mBitmap = bitmap;
+
+                // +2 usages for the color extraction and the delayed unload.
+                mBitmapUsages += 2;
+                recomputeColorExtractorMiniBitmap();
+                drawFrameInternal();
+
+                /*
+                 * after loading, the bitmap will be unloaded after all these conditions:
+                 *   - the frame is redrawn
+                 *   - the mini bitmap from color extractor is recomputed
+                 *   - the DELAY_UNLOAD_BITMAP has passed
+                 */
+                mBackgroundExecutor.executeDelayed(
+                        this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
+            }
+            // even if the bitmap cannot be loaded, call reportEngineShown
+            if (!loadSuccess) reportEngineShown(false);
+            Trace.endSection();
+        }
+
+        private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+            try {
+                notifyLocalColorsChanged(regions, colors);
+            } catch (RuntimeException e) {
+                Log.e(TAG, e.getMessage(), e);
+            }
+        }
+
+        @VisibleForTesting
+        void recomputeColorExtractorMiniBitmap() {
+            mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+        }
+
+        @VisibleForTesting
+        void onMiniBitmapUpdated() {
+            unloadBitmapIfNotUsed();
+        }
+
+        @Override
+        public boolean supportsLocalColorExtraction() {
+            return true;
+        }
+
+        @Override
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            // this call will activate the offset notifications
+            // if no colors were being processed before
+            mWallpaperColorExtractor.addLocalColorsAreas(regions);
+        }
+
+        @Override
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            // this call will deactivate the offset notifications
+            // if we are no longer processing colors
+            mWallpaperColorExtractor.removeLocalColorAreas(regions);
+        }
+
+        @Override
+        public void onOffsetsChanged(float xOffset, float yOffset,
+                float xOffsetStep, float yOffsetStep,
+                int xPixelOffset, int yPixelOffset) {
+            /*
+             * TODO check this formula. mPages is always >= 4, even when launcher is single-paged
+             * this formula is also used in the GL engine
+             */
+            final int pages;
+            if (xOffsetStep > 0 && xOffsetStep <= 1) {
+                pages = Math.round(1 / xOffsetStep) + 1;
+            } else {
+                pages = 1;
+            }
+            if (pages != mPages || !mPagesComputed) {
+                mPages = pages;
+                mPagesComputed = true;
+                mWallpaperColorExtractor.onPageChanged(mPages);
+            }
         }
 
         @Override
@@ -764,13 +862,46 @@
         }
 
         @Override
-        public void onDisplayChanged(int displayId) {
+        public void onDisplayRemoved(int displayId) {
 
         }
 
         @Override
-        public void onDisplayRemoved(int displayId) {
+        public void onDisplayChanged(int displayId) {
+            // changes the display in the color extractor
+            // the new display dimensions will be used in the next color computation
+            if (displayId == getDisplayContext().getDisplayId()) {
+                getDisplaySizeAndUpdateColorExtractor();
+            }
+        }
 
+        private void getDisplaySizeAndUpdateColorExtractor() {
+            Rect window = getDisplayContext()
+                    .getSystemService(WindowManager.class)
+                    .getCurrentWindowMetrics()
+                    .getBounds();
+            mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+        }
+
+
+        @Override
+        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+            super.dump(prefix, fd, out, args);
+            out.print(prefix); out.print("Engine="); out.println(this);
+            out.print(prefix); out.print("valid surface=");
+            out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
+                    ? getSurfaceHolder().getSurface().isValid()
+                    : "null");
+
+            out.print(prefix); out.print("surface frame=");
+            out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
+
+            out.print(prefix); out.print("bitmap=");
+            out.println(mBitmap == null ? "null"
+                    : mBitmap.isRecycled() ? "recycled"
+                    : mBitmap.getWidth() + "x" + mBitmap.getHeight());
+
+            mWallpaperColorExtractor.dump(prefix, fd, out, args);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
deleted file mode 100644
index fdba16e..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2022 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.wallpapers.canvas;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to draw a wallpaper on a surface.
- * It handles the geometry regarding the dimensions of the display and the wallpaper,
- * and rescales the surface and the wallpaper accordingly.
- */
-public class ImageCanvasWallpaperRenderer {
-
-    private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private SurfaceHolder mSurfaceHolder;
-    //private Bitmap mBitmap = null;
-
-    @VisibleForTesting
-    static final int MIN_SURFACE_WIDTH = 128;
-    @VisibleForTesting
-    static final int MIN_SURFACE_HEIGHT = 128;
-
-    private boolean mSurfaceRedrawNeeded;
-
-    private int mLastSurfaceWidth = -1;
-    private int mLastSurfaceHeight = -1;
-
-    public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) {
-        mSurfaceHolder = surfaceHolder;
-    }
-
-    /**
-     * Set the surface holder on which to draw.
-     * Should be called when the surface holder is created or changed
-     * @param surfaceHolder the surface on which to draw the wallpaper
-     */
-    public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
-        mSurfaceHolder = surfaceHolder;
-    }
-
-    /**
-     * Check if a surface holder is loaded
-     * @return true if a valid surfaceHolder has been set.
-     */
-    public boolean isSurfaceHolderLoaded() {
-        return mSurfaceHolder != null;
-    }
-
-    /**
-     * Computes and set the surface dimensions, by using the play and the bitmap dimensions.
-     * The Bitmap must be loaded before any call to this function
-     */
-    private boolean updateSurfaceSize(Bitmap bitmap) {
-        int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth());
-        int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight());
-        boolean surfaceChanged =
-                surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight;
-        if (surfaceChanged) {
-            /*
-             Used a fixed size surface, because we are special.  We can do
-             this because we know the current design of window animations doesn't
-             cause this to break.
-            */
-            mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
-            mLastSurfaceWidth = surfaceWidth;
-            mLastSurfaceHeight = surfaceHeight;
-        }
-        return surfaceChanged;
-    }
-
-    /**
-     * Draw a the wallpaper on the surface.
-     * The bitmap and the surface must be loaded before calling
-     * this function.
-     * @param forceRedraw redraw the wallpaper even if no changes are detected
-     */
-    public void drawFrame(Bitmap bitmap, boolean forceRedraw) {
-
-        if (bitmap == null || bitmap.isRecycled()) {
-            Log.e(TAG, "Attempt to draw frame before background is loaded:");
-            return;
-        }
-
-        if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
-            Log.e(TAG, "Attempt to set an invalid wallpaper of length "
-                    + bitmap.getWidth() + "x" + bitmap.getHeight());
-            return;
-        }
-
-        mSurfaceRedrawNeeded |= forceRedraw;
-        boolean surfaceChanged = updateSurfaceSize(bitmap);
-
-        boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded;
-        mSurfaceRedrawNeeded = false;
-
-        if (!redrawNeeded) {
-            if (DEBUG) {
-                Log.d(TAG, "Suppressed drawFrame since redraw is not needed ");
-            }
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Redrawing wallpaper");
-        }
-        drawWallpaperWithCanvas(bitmap);
-    }
-
-    @VisibleForTesting
-    void drawWallpaperWithCanvas(Bitmap bitmap) {
-        Canvas c = mSurfaceHolder.lockHardwareCanvas();
-        if (c != null) {
-            Rect dest = mSurfaceHolder.getSurfaceFrame();
-            Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
-                    + mLastSurfaceWidth + "x" + mLastSurfaceHeight);
-            try {
-                c.drawBitmap(bitmap, null, dest, null);
-            } finally {
-                mSurfaceHolder.unlockCanvasAndPost(c);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 0000000..e2e4555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2022 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.wallpapers.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses  a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+    private Bitmap mMiniBitmap;
+
+    @VisibleForTesting
+    static final int SMALL_SIDE = 128;
+
+    private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
+
+    private int mDisplayWidth = -1;
+    private int mDisplayHeight = -1;
+    private int mPages = -1;
+    private int mBitmapWidth = -1;
+    private int mBitmapHeight = -1;
+
+    private final Object mLock = new Object();
+
+    private final List<RectF> mPendingRegions = new ArrayList<>();
+    private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+    @Background
+    private final Executor mBackgroundExecutor;
+
+    private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+    /**
+     * Interface to handle the callbacks after the different steps of the color extraction
+     */
+    public interface WallpaperColorExtractorCallback {
+        /**
+         * Callback after the colors of new regions have been extracted
+         * @param regions the list of new regions that have been processed
+         * @param colors the resulting colors for these regions, in the same order as the regions
+         */
+        void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+        /**
+         * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+         * no longer used by the color extractor and can be safely recycled
+         */
+        void onMiniBitmapUpdated();
+
+        /**
+         * Callback to inform that the extractor has started processing colors
+         */
+        void onActivated();
+
+        /**
+         * Callback to inform that no more colors are being processed
+         */
+        void onDeactivated();
+    }
+
+    /**
+     * Creates a new color extractor.
+     * @param backgroundExecutor the executor on which the color extraction will be performed
+     * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+     *                                        the color extractor.
+     */
+    public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+            WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+        mBackgroundExecutor = backgroundExecutor;
+        mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+    }
+
+    /**
+     * Used by the outside to inform that the display size has changed.
+     * The new display size will be used in the next computations, but the current colors are
+     * not recomputed.
+     */
+    public void setDisplayDimensions(int displayWidth, int displayHeight) {
+        mBackgroundExecutor.execute(() ->
+                setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+    }
+
+    private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+        synchronized (mLock) {
+            if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            processColorsInternal();
+        }
+    }
+
+    /**
+     * @return whether color extraction is currently in use
+     */
+    private boolean isActive() {
+        return mPendingRegions.size() + mProcessedRegions.size() > 0;
+    }
+
+    /**
+     * Should be called when the wallpaper is changed.
+     * This will recompute the mini bitmap
+     * and restart the extraction of all areas
+     * @param bitmap the new wallpaper
+     */
+    public void onBitmapChanged(@NonNull Bitmap bitmap) {
+        mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+    }
+
+    private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+        synchronized (mLock) {
+            if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+                Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+                return;
+            }
+            mBitmapWidth = bitmap.getWidth();
+            mBitmapHeight = bitmap.getHeight();
+            mMiniBitmap = createMiniBitmap(bitmap);
+            mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+            recomputeColors();
+        }
+    }
+
+    /**
+     * Should be called when the number of pages is changed
+     * This will restart the extraction of all areas
+     * @param pages the total number of pages of the launcher
+     */
+    public void onPageChanged(int pages) {
+        mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+    }
+
+    private void onPageChangedSynchronized(int pages) {
+        synchronized (mLock) {
+            if (mPages == pages) return;
+            mPages = pages;
+            if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+                recomputeColors();
+            }
+        }
+    }
+
+    // helper to recompute colors, to be called in synchronized methods
+    private void recomputeColors() {
+        mPendingRegions.addAll(mProcessedRegions);
+        mProcessedRegions.clear();
+        processColorsInternal();
+    }
+
+    /**
+     * Add new regions to extract
+     * This will trigger the color extraction and call the callback only for these new regions
+     * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+     */
+    public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+        if (regions.size() > 0) {
+            mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+        } else {
+            Log.w(TAG, "Attempt to add colors with an empty list");
+        }
+    }
+
+    private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+        synchronized (mLock) {
+            boolean wasActive = isActive();
+            mPendingRegions.addAll(regions);
+            if (!wasActive && isActive()) {
+                mWallpaperColorExtractorCallback.onActivated();
+            }
+            processColorsInternal();
+        }
+    }
+
+    /**
+     * Remove regions to extract. If a color extraction is ongoing does not stop it.
+     * But if there are subsequent changes that restart the extraction, the removed regions
+     * will not be recomputed.
+     * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+     */
+    public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+        mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+    }
+
+    private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+        synchronized (mLock) {
+            boolean wasActive = isActive();
+            mPendingRegions.removeAll(regions);
+            regions.forEach(mProcessedRegions::remove);
+            if (wasActive && !isActive()) {
+                mWallpaperColorExtractorCallback.onDeactivated();
+            }
+        }
+    }
+
+    /**
+     * Clean up the memory (in particular, the mini bitmap) used by this class.
+     */
+    public void cleanUp() {
+        mBackgroundExecutor.execute(this::cleanUpSynchronized);
+    }
+
+    private void cleanUpSynchronized() {
+        synchronized (mLock) {
+            if (mMiniBitmap != null) {
+                mMiniBitmap.recycle();
+                mMiniBitmap = null;
+            }
+            mProcessedRegions.clear();
+            mPendingRegions.clear();
+        }
+    }
+
+    private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+        Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+        // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+        int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+        float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+        Bitmap result = createMiniBitmap(bitmap,
+                (int) (scale * bitmap.getWidth()),
+                (int) (scale * bitmap.getHeight()));
+        Trace.endSection();
+        return result;
+    }
+
+    @VisibleForTesting
+    Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+        return Bitmap.createScaledBitmap(bitmap, width, height, false);
+    }
+
+    private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+        RectF imageArea = pageToImgRect(area);
+        if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+            return null;
+        }
+        Rect subImage = new Rect(
+                (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+                (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+                (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+                (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+        if (subImage.isEmpty()) {
+            // Do not notify client. treat it as too small to sample
+            return null;
+        }
+        return getLocalWallpaperColors(subImage);
+    }
+
+    @VisibleForTesting
+    WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+        Assert.isNotMainThread();
+        Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+                subImage.left, subImage.top, subImage.width(), subImage.height());
+        return WallpaperColors.fromBitmap(colorImg);
+    }
+
+    /**
+     * Transform the logical coordinates into wallpaper coordinates.
+     *
+     * Logical coordinates are organised such that the various pages are non-overlapping. So,
+     * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+     *
+     * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+     * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+     * pages increase.
+     * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+     * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+     * (1-Wr)].
+     */
+    private RectF pageToImgRect(RectF area) {
+        // Width of a page for the caller of this API.
+        float virtualPageWidth = 1f / (float) mPages;
+        float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+        float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+        int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+        if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+            Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+            return null;
+        }
+
+        RectF imgArea = new RectF();
+        imgArea.bottom = area.bottom;
+        imgArea.top = area.top;
+
+        float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+        float mappedScreenWidth = mDisplayWidth * imageScale;
+        float pageWidth = Math.min(1.0f,
+                mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+        float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+        imgArea.left = MathUtils.constrain(
+                leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+        imgArea.right = MathUtils.constrain(
+                rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+        if (imgArea.left > imgArea.right) {
+            // take full page
+            imgArea.left = 0;
+            imgArea.right = 1;
+        }
+        return imgArea;
+    }
+
+    /**
+     * Extract the colors from the pending regions,
+     * then notify the callback with the resulting colors for these regions
+     * This method should only be called synchronously
+     */
+    private void processColorsInternal() {
+        /*
+         * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+         * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+         * will be called again when the bitmap is loaded and the miniBitmap is computed.
+         */
+        if (mMiniBitmap == null || mMiniBitmap.isRecycled())  return;
+
+        /*
+         * if the screen size or number of pages is not yet known, exit
+         * the function will be called again once the screen size and page are known
+         */
+        if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+        Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+        List<WallpaperColors> processedColors = new ArrayList<>();
+        for (int i = 0; i < mPendingRegions.size(); i++) {
+            RectF nextArea = mPendingRegions.get(i);
+            WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+            mProcessedRegions.add(nextArea);
+            processedColors.add(colors);
+        }
+        List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+        mPendingRegions.clear();
+        Trace.endSection();
+
+        mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+    }
+
+    /**
+     * Called to dump current state.
+     * @param prefix prefix.
+     * @param fd fd.
+     * @param out out.
+     * @param args args.
+     */
+    public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+        out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+        out.print(prefix); out.print("mPages="); out.println(mPages);
+
+        out.print(prefix); out.print("bitmap dimensions=");
+        out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+        out.print(prefix); out.print("bitmap=");
+        out.println(mMiniBitmap == null ? "null"
+                : mMiniBitmap.isRecycled() ? "recycled"
+                : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+        out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+        out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 43f6f1a..c1036e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -411,7 +411,7 @@
                     0 /* flags */);
             users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
                     false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
-                    false /* isAddSupervisedUser */));
+                    false /* isAddSupervisedUser */, null /* enforcedAdmin */));
         }
         return users;
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 9c64c1b..9aa71e9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -59,6 +59,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -79,13 +80,13 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.dreams.IDreamManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -170,6 +171,8 @@
     @Mock
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
+    private IDreamManager mDreamManager;
+    @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private SubscriptionManager mSubscriptionManager;
@@ -178,6 +181,8 @@
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
+    private SensorPrivacyManager mSensorPrivacyManager;
+    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private AuthController mAuthController;
@@ -219,7 +224,6 @@
     private TestableLooper mTestableLooper;
     private Handler mHandler;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private TestableContext mSpiedContext;
     private MockitoSession mMockitoSession;
     private StatusBarStateController.StateListener mStatusBarStateListener;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
@@ -228,9 +232,6 @@
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mSpiedContext = spy(mContext);
-        when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
-        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
         when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
         when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -279,14 +280,6 @@
                 .thenReturn(new ServiceState());
         when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
-        mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
-        mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
-        mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
-        mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager);
-        mSpiedContext.addMockSystemService(UserManager.class, mUserManager);
-        mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
-        mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
-        mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
 
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(SubscriptionManager.class)
@@ -301,7 +294,7 @@
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         verify(mBiometricManager)
                 .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -356,7 +349,7 @@
         when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
         when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
 
-        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mContext);
 
         mTestableLooper.processAllMessages();
 
@@ -1202,9 +1195,9 @@
     @Test
     public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.addMockSystemService(FaceManager.class, null);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mFaceManager = null;
+
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
@@ -1258,7 +1251,7 @@
         // This disables face auth
         when(mUserManager.isPrimaryUser()).thenReturn(false);
         mKeyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(mSpiedContext);
+                new TestableKeyguardUpdateMonitor(mContext);
 
         // Face auth should run when the following is true.
         keyguardNotGoingAway();
@@ -1482,6 +1475,27 @@
     }
 
     @Test
+    public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+            throws RemoteException {
+        // Preconditions for face auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        deviceNotGoingToSleep();
+        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        deviceGoingToSleep();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    @Test
     public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
@@ -1527,15 +1541,16 @@
         verify(mHandler, times(1)).removeCallbacks(mKeyguardUpdateMonitor.mFpCancelNotReceived);
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
     }
 
     @Test
     public void testFingerAcquired_wakesUpPowerManager() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.getOrCreateTestableResources().addOverride(
+        mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, true);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         fingerprintAcquireStart();
 
         verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
@@ -1544,9 +1559,9 @@
     @Test
     public void testFingerAcquired_doesNotWakeUpPowerManager() {
         cleanupKeyguardUpdateMonitor();
-        mSpiedContext.getOrCreateTestableResources().addOverride(
+        mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.kg_wake_on_acquire_start, false);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         fingerprintAcquireStart();
 
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -1667,6 +1682,10 @@
         mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
     }
 
+    private void deviceGoingToSleep() {
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
+    }
+
     private void deviceIsInteractive() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
     }
@@ -1716,7 +1735,9 @@
                     mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
                     mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
-                    mPowerManager);
+                    mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
+                    mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
+                    mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 4a5b23c..d52612b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -322,6 +322,13 @@
     }
 
     @Test
+    fun testLayoutParams_hasShowWhenLockedFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
+                .isTrue()
+    }
+
+    @Test
     fun testLayoutParams_hasDimbehindWindowFlag() {
         val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
         val lpFlags = layoutParams.flags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6436981..781dc15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -144,6 +144,12 @@
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
+        ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doAnswer(invocation ->
+                when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+        ).when(mHost).setPulsePending(boolCaptor.capture());
+
+        when(mHost.isPulsingBlocked()).thenReturn(false);
         mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
         captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
         mProximitySensor.alertListeners();
@@ -160,6 +166,29 @@
     }
 
     @Test
+    public void testOnNotification_noPulseIfPulseIsNotPendingAnymore() {
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+        ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+        doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
+
+        mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
+        mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+        clearInvocations(mMachine);
+        when(mHost.isPulsingBlocked()).thenReturn(false);
+
+        // GIVEN pulsePending = false
+        when(mHost.isPulsePending()).thenReturn(false);
+
+        // WHEN prox check returns FAR
+        mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
+        captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
+        mProximitySensor.alertListeners();
+
+        // THEN don't request pulse because the pending pulse was abandoned early
+        verify(mMachine, never()).requestPulse(anyInt());
+    }
+
+    @Test
     public void testTransitionTo_disablesAndEnablesTouchSensors() {
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
 
@@ -237,6 +266,11 @@
         when(mSessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD))
                 .thenReturn(keyguardSessionId);
 
+        ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doAnswer(invocation ->
+                when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+        ).when(mHost).setPulsePending(boolCaptor.capture());
+
         // WHEN quick pick up is triggered
         mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 04ff7ae..db6082d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -29,9 +29,12 @@
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
@@ -40,6 +43,7 @@
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,6 +83,15 @@
     @Captor
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
+    @Mock
+    private ImageView mView;
+
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    @Mock
+    UiEventLogger mUiEventLogger;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -151,6 +164,30 @@
         verify(mDreamOverlayStateController).addComplication(mComplication);
     }
 
+    /**
+     * Ensures clicking home controls chip logs UiEvent.
+     */
+    @Test
+    public void testClick_logsUiEvent() {
+        final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+                new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+                        mView,
+                        mActivityStarter,
+                        mContext,
+                        mControlsComponent,
+                        mUiEventLogger);
+        viewController.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mUiEventLogger).log(
+                DreamHomeControlsComplication.DreamHomeControlsChipViewController
+                        .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+    }
+
     private void setHaveFavorites(boolean value) {
         final List<StructureInfo> favorites = mock(List.class);
         when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836..7f55d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
 
@@ -148,8 +149,12 @@
         return false;
     }
 
-    private static void executeShellCommand(String cmd) {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+    private void executeShellCommand(String cmd) {
+        try {
+            runShellCommand(cmd);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index ba1e168..7a15680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@
 import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -116,6 +116,7 @@
         val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
 
         assertThat(latest).isFalse()
+        assertThat(underTest.isKeyguardShowing()).isFalse()
 
         val captor = argumentCaptor<KeyguardStateController.Callback>()
         verify(keyguardStateController).addCallback(captor.capture())
@@ -123,10 +124,12 @@
         whenever(keyguardStateController.isShowing).thenReturn(true)
         captor.value.onKeyguardShowingChanged()
         assertThat(latest).isTrue()
+        assertThat(underTest.isKeyguardShowing()).isTrue()
 
         whenever(keyguardStateController.isShowing).thenReturn(false)
         captor.value.onKeyguardShowingChanged()
         assertThat(latest).isFalse()
+        assertThat(underTest.isKeyguardShowing()).isFalse()
 
         job.cancel()
     }
@@ -150,6 +153,21 @@
     }
 
     @Test
+    fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+        var latest: Boolean? = null
+
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+        assertThat(latest).isTrue()
+        job.cancel()
+
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+        assertThat(latest).isFalse()
+        job.cancel()
+    }
+
+    @Test
     fun dozeAmount() = runBlockingTest {
         val values = mutableListOf<Float>()
         val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index d4fba41..329c4db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -138,7 +138,7 @@
         assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
         val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
         assertThat(visibleState.icon).isNotNull()
-        assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+        assertThat(visibleState.icon.contentDescription).isNotNull()
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 5a3a78e..0a4478f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -21,9 +21,11 @@
 import android.service.quickaccesswallet.GetWalletCardsResponse
 import android.service.quickaccesswallet.QuickAccessWalletClient
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -69,8 +71,16 @@
         val job = underTest.state.onEach { latest = it }.launchIn(this)
 
         val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
-        assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
-        assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+        assertThat(visibleModel.icon)
+            .isEqualTo(
+                Icon.Loaded(
+                    drawable = ICON,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            res = R.string.accessibility_wallet_button,
+                        ),
+                )
+            )
         job.cancel()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c5e828e..b6d7559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -21,7 +21,8 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -34,6 +35,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Test
@@ -46,7 +48,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -55,7 +56,15 @@
 
     companion object {
         private val INTENT = Intent("some.intent.action")
-        private val DRAWABLE = mock<ContainedDrawable>()
+        private val DRAWABLE =
+            mock<Icon> {
+                whenever(this.contentDescription)
+                    .thenReturn(
+                        ContentDescription.Resource(
+                            res = CONTENT_DESCRIPTION_RESOURCE_ID,
+                        )
+                    )
+            }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
 
         @Parameters(
@@ -236,7 +245,6 @@
             state =
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = DRAWABLE,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
         )
         homeControls.onClickedResult =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 19d8412..1dd919a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,7 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -101,7 +103,6 @@
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -120,8 +121,8 @@
         val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
         assertThat(visibleModel.configKey).isEqualTo(configKey)
         assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        assertThat(visibleModel.icon.contentDescription)
+            .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
         job.cancel()
     }
 
@@ -131,7 +132,6 @@
         quickAccessWallet.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -150,8 +150,8 @@
         val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
         assertThat(visibleModel.configKey).isEqualTo(configKey)
         assertThat(visibleModel.icon).isEqualTo(ICON)
-        assertThat(visibleModel.contentDescriptionResourceId)
-            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        assertThat(visibleModel.icon.contentDescription)
+            .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
         job.cancel()
     }
 
@@ -161,7 +161,6 @@
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
-                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
             )
         )
 
@@ -182,7 +181,6 @@
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = ICON,
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
             )
 
@@ -197,7 +195,14 @@
         }
 
     companion object {
-        private val ICON: ContainedDrawable = mock()
+        private val ICON: Icon = mock {
+            whenever(this.contentDescription)
+                .thenReturn(
+                    ContentDescription.Resource(
+                        res = CONTENT_DESCRIPTION_RESOURCE_ID,
+                    )
+                )
+        }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index c612091..96544e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -505,7 +505,6 @@
                 }
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
-                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
                 )
             } else {
                 KeyguardQuickAffordanceConfig.State.Hidden
@@ -543,7 +542,7 @@
     private data class TestConfig(
         val isVisible: Boolean,
         val isClickable: Boolean = false,
-        val icon: ContainedDrawable? = null,
+        val icon: Icon? = null,
         val canShowWhileLocked: Boolean = false,
         val intent: Intent? = null,
     ) {
@@ -555,6 +554,5 @@
     companion object {
         private const val DEFAULT_BURN_IN_OFFSET = 5
         private const val RETURNED_BURN_IN_OFFSET = 3
-        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 82aa612..5973340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -26,13 +26,13 @@
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.TaskExecutor
 import androidx.test.filters.SmallTest
-
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.concurrency.FakeRepeatableExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-
 import org.junit.After
 import org.junit.Before
 import org.junit.Ignore
@@ -47,8 +47,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
     }
     @Mock private lateinit var mockController: MediaController
     @Mock private lateinit var mockTransport: MediaController.TransportControls
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var mockBar: SeekBar
     private val token1 = MediaSession.Token(1, null)
     private val token2 = MediaSession.Token(2, null)
 
@@ -78,9 +80,10 @@
     @Before
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
+        viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
         viewModel.logSeek = { }
         whenever(mockController.sessionToken).thenReturn(token1)
+        whenever(mockBar.context).thenReturn(context)
 
         // LiveData to run synchronously
         ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -454,6 +457,25 @@
     }
 
     @Test
+    fun onFalseTapOrTouch() {
+        whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+        whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
+        whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+        viewModel.updateController(mockController)
+        val pos = 169
+
+        viewModel.attachTouchHandlers(mockBar)
+        with(viewModel.seekBarListener) {
+            onStartTrackingTouch(mockBar)
+            onProgressChanged(mockBar, pos, true)
+            onStopTrackingTouch(mockBar)
+        }
+
+        // THEN transport controls should not be used
+        verify(mockTransport, never()).seekTo(pos.toLong())
+    }
+
+    @Test
     fun queuePollTaskWhenPlaying() {
         // GIVEN that the track is playing
         val state = PlaybackState.Builder().run {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ff0faf9..098086a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -35,7 +35,9 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -43,10 +45,12 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -75,6 +79,14 @@
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var commandQueue: CommandQueue
+    @Mock
+    private lateinit var lazyFalsingManager: Lazy<FalsingManager>
+    @Mock
+    private lateinit var falsingManager: FalsingManager
+    @Mock
+    private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
+    @Mock
+    private lateinit var falsingCollector: FalsingCollector
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var fakeClock: FakeSystemClock
@@ -101,6 +113,8 @@
         senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+        whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
+        whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
 
         controllerSender = MediaTttChipControllerSender(
             commandQueue,
@@ -111,7 +125,9 @@
             accessibilityManager,
             configurationController,
             powerManager,
-            senderUiEventLogger
+            senderUiEventLogger,
+            lazyFalsingManager,
+            lazyFalsingCollector
         )
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -417,6 +433,38 @@
     }
 
     @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isFalse()
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+        whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isTrue()
+    }
+
+    @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         val undoCallback = object : IUndoMediaTransferCallback.Stub() {
             override fun onUndoTriggered() {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 5d5918d..d2c2d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.qs;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
@@ -49,13 +52,13 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -93,7 +96,7 @@
     @Mock private QSPanel.QSTileLayout mQsTileLayout;
     @Mock private QSPanel.QSTileLayout mQQsTileLayout;
     @Mock private QSAnimator mQSAnimator;
-    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private QSSquishinessController mSquishinessController;
     private View mQsFragmentView;
 
@@ -158,7 +161,7 @@
     public void
             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
-        setStatusBarState(StatusBarState.KEYGUARD);
+        setStatusBarState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
@@ -174,7 +177,7 @@
     public void
             transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
-        setStatusBarState(StatusBarState.KEYGUARD);
+        setStatusBarState(KEYGUARD);
         when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
@@ -262,6 +265,27 @@
     }
 
     @Test
+    public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+        boolean isTransitioningToFullShade = false;
+        float transitionProgress = 0;
+        float squishinessFraction = 0f;
+
+        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+                squishinessFraction);
+
+        // trigger alpha refresh with non-zero expansion and fraction values
+        fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
+                /* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
+
+        // alpha should follow lockscreen to shade progress, not panel expansion fraction
+        assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+    }
+
+    @Test
     public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
         QSFragment fragment = resumeAndGetFragment();
         disableSplitShade();
@@ -402,6 +426,19 @@
         verify(mQSPanelController).setListening(eq(true), anyBoolean());
     }
 
+    @Test
+    public void passCorrectExpansionState_inSplitShade() {
+        QSFragment fragment = resumeAndGetFragment();
+        enableSplitShade();
+        clearInvocations(mQSPanelController);
+
+        fragment.setExpanded(true);
+        verify(mQSPanelController).setExpanded(true);
+
+        fragment.setExpanded(false);
+        verify(mQSPanelController).setExpanded(false);
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 7d56339..4c44dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -33,6 +33,7 @@
 import android.graphics.Paint;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.provider.MediaStore;
 import android.testing.AndroidTestingRunner;
 
@@ -97,7 +98,8 @@
         Bitmap original = createCheckerBitmap(10, 10, 10);
 
         ListenableFuture<ImageExporter.Result> direct =
-                exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME);
+                exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME,
+                        Process.myUserHandle());
         assertTrue("future should be done", direct.isDone());
         assertFalse("future should not be canceled", direct.isCancelled());
         ImageExporter.Result result = direct.get();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 69b7b88..8c9404e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -180,7 +180,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
@@ -208,7 +208,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
@@ -236,7 +236,7 @@
         data.finisher = null;
         data.mActionsReadyListener = null;
         SaveImageInBackgroundTask task =
-                new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+                new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
                         ActionTransition::new, mSmartActionsProvider);
 
         Notification.Action deleteAction = task.createDeleteAction(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index b40d5ac..0c60d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -23,6 +23,9 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -1249,14 +1252,10 @@
     @Test
     public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
         enableSplitShade(/* enabled= */ true);
-        // set panel state to CLOSED
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
-                /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        mPanelExpansionStateManager.updateState(STATE_CLOSED);
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
 
-        // change panel state to OPENING
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0.5f,
-                /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 100);
+        mPanelExpansionStateManager.updateState(STATE_OPENING);
 
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
     }
@@ -1264,19 +1263,27 @@
     @Test
     public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
         enableSplitShade(/* enabled= */ true);
-        // set panel state to CLOSED
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
-                /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+        mPanelExpansionStateManager.updateState(STATE_CLOSED);
 
-        // go to lockscreen, which also sets fraction to 1.0f and makes shade "expanded"
         mStatusBarStateController.setState(KEYGUARD);
-        mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 0);
+        // going to lockscreen would trigger STATE_OPENING
+        mPanelExpansionStateManager.updateState(STATE_OPENING);
 
         assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
     }
 
     @Test
+    public void testQsImmediateResetsWhenPanelOpensOrCloses() {
+        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mPanelExpansionStateManager.updateState(STATE_OPEN);
+        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+
+        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mPanelExpansionStateManager.updateState(STATE_CLOSED);
+        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+    }
+
+    @Test
     public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
         // to make sure shade is in expanded state
         mNotificationPanelViewController.startWaitingForOpenPanelGesture();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
index 8bc438b..5fc09c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
@@ -15,6 +15,7 @@
  */
 
 package com.android.systemui.shared.navigationbar
+
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -24,15 +25,23 @@
 import androidx.concurrent.futures.DirectExecutor
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.*
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -99,4 +108,39 @@
         regionSamplingHelper.stopAndDestroy()
         verify(compositionListener).unregister(any())
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testCompositionSamplingListener_has_nonEmptyRect() {
+        // simulate race condition
+        val fakeExecutor = FakeExecutor(FakeSystemClock()) // pass in as backgroundExecutor
+        val fakeSamplingCallback = mock(RegionSamplingHelper.SamplingCallback::class.java)
+
+        whenever(fakeSamplingCallback.isSamplingEnabled).thenReturn(true)
+        whenever(wrappedSurfaceControl.isValid).thenReturn(true)
+
+        regionSamplingHelper = object : RegionSamplingHelper(sampledView, fakeSamplingCallback,
+                DirectExecutor.INSTANCE, fakeExecutor, compositionListener) {
+            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                return wrappedSurfaceControl
+            }
+        }
+        regionSamplingHelper.setWindowVisible(true)
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+
+        // make sure background task is enqueued
+        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+
+        // make sure regionSamplingHelper will have empty Rect
+        whenever(fakeSamplingCallback.getSampledRegion(any())).thenReturn(Rect(0, 0, 0, 0))
+        regionSamplingHelper.onLayoutChange(sampledView, 0, 0, 0, 0, 0, 0, 0, 0)
+
+        // resume running of background thread
+        fakeExecutor.runAllReady()
+
+        // grab Rect passed into compositionSamplingListener and make sure it's not empty
+        val argumentGrabber = argumentCaptor<Rect>()
+        verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl),
+                argumentGrabber.capture())
+        assertThat(argumentGrabber.value.isEmpty).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
deleted file mode 100644
index 5432a74..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Verifies that particular sets of dependencies don't have dependencies on others. For example,
- * code managing notifications shouldn't directly depend on CentralSurfaces, since there are
- * platforms which want to manage notifications, but don't use CentralSurfaces.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NonPhoneDependencyTest extends SysuiTestCase {
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationListContainer mListContainer;
-    @Mock private RemoteInputController.Delegate mDelegate;
-    @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
-    @Mock private OnSettingsClickListener mOnSettingsClickListener;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-               new Handler(TestableLooper.get(this).getLooper()));
-    }
-
-    @Ignore("Causes binder calls which fail")
-    @Test
-    public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
-        mDependency.injectMockDependency(ShadeController.class);
-        NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
-        NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
-        NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
-        NotificationRemoteInputManager remoteInputManager =
-                Dependency.get(NotificationRemoteInputManager.class);
-        NotificationLockscreenUserManager lockscreenUserManager =
-                Dependency.get(NotificationLockscreenUserManager.class);
-        gutsManager.setUpWithPresenter(mPresenter, mListContainer,
-                mOnSettingsClickListener);
-        notificationLogger.setUpWithContainer(mListContainer);
-        mediaManager.setUpWithPresenter(mPresenter);
-        remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
-                mDelegate);
-        lockscreenUserManager.setUpWithPresenter(mPresenter);
-
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 853d1df..bdafa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -88,6 +89,8 @@
     @Mock
     private NotificationClickNotifier mClickNotifier;
     @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
     private KeyguardManager mKeyguardManager;
     @Mock
     private DeviceProvisionedController mDeviceProvisionedController;
@@ -344,6 +347,7 @@
                     (() -> mVisibilityProvider),
                     (() -> mNotifCollection),
                     mClickNotifier,
+                    (() -> mOverviewProxyService),
                     NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                     mStatusBarStateController,
                     Handler.createAsync(Looper.myLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 9f21409..82e32b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection;
 
 import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
+import static com.android.systemui.statusbar.notification.collection.ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,6 +46,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -125,6 +127,10 @@
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
 
+    private Log.TerribleFailureHandler mOldWtfHandler = null;
+    private Log.TerribleFailure mLastWtf = null;
+    private int mWtfCount = 0;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -1748,14 +1754,17 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the filter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1767,18 +1776,24 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the filter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
 
     @Test
-    public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() {
+    public void testNonConsecutiveOutOfOrderInvalidationsDontThrowAfterTooManyRuns() {
         // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
         CountingInvalidator invalidator = new CountingInvalidator(filter);
@@ -1786,17 +1801,22 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        // WHEN we try to run the pipeline and the filter is invalidated at least
-        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        interceptWtfs();
 
-        // THEN an exception is NOT thrown.
+        // WHEN we try to run the pipeline and the filter is invalidated
+        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason,
+        // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again,
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        dispatchBuild();
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
+        dispatchBuild();
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+        // THEN an exception is NOT thrown, but WTFs ARE logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2);
     }
 
     @Test
@@ -1808,14 +1828,18 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the promoter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1827,12 +1851,18 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the promoter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
@@ -1846,14 +1876,17 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the comparator is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1865,12 +1898,14 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the comparator is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception IS thrown.
     }
@@ -1884,14 +1919,17 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
-        // THEN an exception is NOT thrown.
+        // THEN an exception is NOT thrown directly, but a WTF IS logged.
+        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
     }
 
     @Test(expected = IllegalStateException.class)
@@ -1903,16 +1941,59 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
+        interceptWtfs();
+
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
         dispatchBuild();
-        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        try {
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        } finally {
+            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        }
 
         // THEN an exception IS thrown.
     }
 
+    private void interceptWtfs() {
+        assertNull(mOldWtfHandler);
+
+        mLastWtf = null;
+        mWtfCount = 0;
+
+        mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> {
+            Log.e("ShadeListBuilderTest", "Observed WTF: " + e);
+            mLastWtf = e;
+            mWtfCount++;
+        });
+    }
+
+    private void expectNoWtfs() {
+        assertNull(expectWtfs(0));
+    }
+
+    private Log.TerribleFailure expectWtf() {
+        return expectWtfs(1);
+    }
+
+    private Log.TerribleFailure expectWtfs(int expectedWtfCount) {
+        assertNotNull(mOldWtfHandler);
+
+        Log.setWtfHandler(mOldWtfHandler);
+        mOldWtfHandler = null;
+
+        Log.TerribleFailure wtf = mLastWtf;
+        int wtfCount = mWtfCount;
+
+        mLastWtf = null;
+        mWtfCount = 0;
+
+        assertEquals(expectedWtfCount, wtfCount);
+        return wtf;
+    }
+
     @Test
     public void testStableOrdering() {
         mStabilityManager.setAllowEntryReordering(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 2ee3126..2970807 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -337,6 +337,40 @@
     }
 
     @Test
+    fun testOnEntryUpdated_toAlert() {
+        // GIVEN that an entry is posted that should not heads up
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // WHEN it's updated to heads up
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onEntryUpdated(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification alerts
+        finishBind(mEntry)
+        verify(mHeadsUpManager).showNotification(mEntry)
+    }
+
+    @Test
+    fun testOnEntryUpdated_toNotAlert() {
+        // GIVEN that an entry is posted that should heads up
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // WHEN it's updated to not heads up
+        setShouldHeadsUp(mEntry, false)
+        mCollectionListener.onEntryUpdated(mEntry)
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN the notification is never bound or shown
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
+    @Test
     fun testOnEntryRemovedRemovesHeadsUpNotification() {
         // GIVEN the current HUN is mEntry
         addHUN(mEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
new file mode 100644
index 0000000..16e2441
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
@@ -0,0 +1,321 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryMonitorTest : SysuiTestCase() {
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_plainNotification() {
+        val notification = createBasicNotification().build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3316,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
+        val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+        val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = 0,
+            extras = 3316,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
+        val notification =
+            createBasicNotification()
+                .setCustomContentView(
+                    RemoteViews(context.packageName, android.R.layout.list_content)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3384,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = true,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
+        val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
+        val notification =
+            createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = 444444,
+            largeIcon = 0,
+            extras = 3212,
+            bigPicture = 0,
+            extender = 0,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_bigPictureStyle() {
+        val bigPicture =
+            Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
+        val bigPictureIcon =
+            Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val notification =
+            createBasicNotification()
+                .setStyle(
+                    Notification.BigPictureStyle()
+                        .bigPicture(bigPicture)
+                        .bigLargeIcon(bigPictureIcon)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 4092,
+            bigPicture = bigPicture.bitmap.allocationByteCount,
+            extender = 0,
+            style = "BigPictureStyle",
+            styleIcon = bigPictureIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_callingStyle() {
+        val personIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+        val fakeIntent =
+            PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+        val notification =
+            createBasicNotification()
+                .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 4084,
+            bigPicture = 0,
+            extender = 0,
+            style = "CallStyle",
+            styleIcon = personIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_messagingStyle() {
+        val personIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+        val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+        val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+        val historicPersonIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
+        val historicPerson =
+            Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
+        val historicMessage =
+            Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
+
+        val notification =
+            createBasicNotification()
+                .setStyle(
+                    Notification.MessagingStyle(person)
+                        .addMessage(message)
+                        .addHistoricMessage(historicMessage)
+                )
+                .build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 5024,
+            bigPicture = 0,
+            extender = 0,
+            style = "MessagingStyle",
+            styleIcon =
+                personIcon.bitmap.allocationByteCount +
+                    historicPersonIcon.bitmap.allocationByteCount,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_carExtender() {
+        val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
+        val extender = Notification.CarExtender().setLargeIcon(carIcon)
+        val notification = createBasicNotification().extend(extender).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3612,
+            bigPicture = 0,
+            extender = 556656,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    @Test
+    fun currentNotificationMemoryUse_tvWearExtender() {
+        val tvExtender = Notification.TvExtender().setChannel("channel2")
+        val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
+        val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
+        val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
+        val nmm = createNMMWithNotifications(listOf(notification))
+        val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+        assertNotificationObjectSizes(
+            memoryUse = memoryUse,
+            smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+            largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+            extras = 3820,
+            bigPicture = 0,
+            extender = 388 + wearBackground.allocationByteCount,
+            style = null,
+            styleIcon = 0,
+            hasCustomView = false,
+        )
+    }
+
+    private fun createBasicNotification(): Notification.Builder {
+        val smallIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
+        val largeIcon =
+            Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
+        return Notification.Builder(context)
+            .setSmallIcon(smallIcon)
+            .setLargeIcon(largeIcon)
+            .setContentTitle("This is a title")
+            .setContentText("This is content text.")
+    }
+
+    /** This will generate a nicer error message than comparing objects */
+    private fun assertNotificationObjectSizes(
+        memoryUse: NotificationMemoryUsage,
+        smallIcon: Int,
+        largeIcon: Int,
+        extras: Int,
+        bigPicture: Int,
+        extender: Int,
+        style: String?,
+        styleIcon: Int,
+        hasCustomView: Boolean
+    ) {
+        assertThat(memoryUse.packageName).isEqualTo("test_pkg")
+        assertThat(memoryUse.notificationId)
+            .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
+        assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
+        assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
+        assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
+        if (style == null) {
+            assertThat(memoryUse.objectUsage.style).isNull()
+        } else {
+            assertThat(memoryUse.objectUsage.style).isEqualTo(style)
+        }
+        assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
+        assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
+    }
+
+    private fun getUseObject(
+        singleItemUseList: List<NotificationMemoryUsage>
+    ): NotificationMemoryUsage {
+        assertThat(singleItemUseList).hasSize(1)
+        return singleItemUseList[0]
+    }
+
+    private fun createNMMWithNotifications(
+        notifications: List<Notification>
+    ): NotificationMemoryMonitor {
+        val notifPipeline: NotifPipeline = mock()
+        val notificationEntries =
+            notifications.map { n ->
+                NotificationEntryBuilder().setTag("test").setNotification(n).build()
+            }
+        whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
+        return NotificationMemoryMonitor(notifPipeline, mock())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index 7e97629..dae0aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -40,6 +40,10 @@
                 NotificationPanelLogger.toNotificationProto(visibleNotifications)));
     }
 
+    @Override
+    public void logNotificationDrag(NotificationEntry draggedNotification) {
+    }
+
     public static class CallRecord {
         public boolean isLockscreen;
         public Notifications.NotificationList list;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 922e93d..ed2afe7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -40,6 +40,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -63,6 +65,7 @@
     private NotificationMenuRowPlugin.MenuItem mMenuItem =
             mock(NotificationMenuRowPlugin.MenuItem.class);
     private ShadeController mShadeController = mock(ShadeController.class);
+    private NotificationPanelLogger mNotificationPanelLogger = mock(NotificationPanelLogger.class);
 
     @Before
     public void setUp() throws Exception {
@@ -82,7 +85,7 @@
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
 
         mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager,
-                mShadeController);
+                mShadeController, mNotificationPanelLogger);
     }
 
     @Test
@@ -96,6 +99,7 @@
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
         verify(mHeadsUpManager, times(1)).releaseAllImmediately();
+        verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
 
     @Test
@@ -107,6 +111,7 @@
         verify(controller).startDragAndDrop(mRow);
         verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
                 eq(false), anyFloat());
+        verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
 
     @Test
@@ -124,6 +129,7 @@
 
         // Verify that we never start the actual drag since there is no content
         verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt());
+        verify(mNotificationPanelLogger, never()).logNotificationDrag(any());
     }
 
     private ExpandableNotificationRowDragController createSpyController() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6ae021b..4353036 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -55,6 +55,7 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -179,6 +180,40 @@
     }
 
     @Test
+    public void testUpdateStackHeight_qsExpansionGreaterThanZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(1f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion,
+                BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction));
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
+    public void testUpdateStackHeight_qsExpansionZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(0f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion, expansionFraction);
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
     public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
         final float dozeAmount = 0.5f;
         mAmbientState.setDozeAmount(dozeAmount);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 9de9db1..996851e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -40,7 +40,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -73,7 +72,6 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
-    @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -101,7 +99,7 @@
         mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
                 mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
-                mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
+                () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
                 mAuthController, mNotificationIconAreaController);
@@ -132,19 +130,11 @@
         verify(mStatusBarStateController).setIsDozing(eq(false));
     }
 
-
     @Test
     public void testPulseWhileDozing_updatesScrimController() {
         mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
         mCentralSurfaces.showKeyguardImpl();
 
-        // Keep track of callback to be able to stop the pulse
-//        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-//        doAnswer(invocation -> {
-//            pulseCallback[0] = invocation.getArgument(0);
-//            return null;
-//        }).when(mDozeScrimController).pulse(any(), anyInt());
-
         // Starting a pulse should change the scrim controller to the pulsing state
         mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
             @Override
@@ -210,4 +200,17 @@
             }
         }
     }
+
+    @Test
+    public void testStopPulsing_setPendingPulseToFalse() {
+        // GIVEN a pending pulse
+        mDozeServiceHost.setPulsePending(true);
+
+        // WHEN pulsing is stopped
+        mDozeServiceHost.stopPulsing();
+
+        // THEN isPendingPulse=false, pulseOutNow is called
+        assertFalse(mDozeServiceHost.isPulsePending());
+        verify(mDozeScrimController).pulseOutNow();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index cfaa470..6ec5cf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.battery.BatteryMeterViewController;
@@ -123,6 +124,7 @@
     private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
     @Mock private SecureSettings mSecureSettings;
     @Mock private CommandQueue mCommandQueue;
+    @Mock private KeyguardLogger mLogger;
 
     private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
     private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -172,7 +174,8 @@
                 mStatusBarUserInfoTracker,
                 mSecureSettings,
                 mCommandQueue,
-                mFakeExecutor
+                mFakeExecutor,
+                mLogger
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 34399b8..9c56c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -80,6 +81,7 @@
                 StatusBarLocation.HOME,
                 mock(StatusBarPipelineFlags.class),
                 mock(WifiViewModel.class),
+                mock(MobileUiAdapter.class),
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -123,12 +125,14 @@
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiViewModel wifiViewModel,
+                MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
                     statusBarPipelineFlags,
                     wifiViewModel,
+                    mobileUiAdapter,
                     contextProvider,
                     darkIconDispatcher);
         }
@@ -169,6 +173,7 @@
                     StatusBarLocation.HOME,
                     mock(StatusBarPipelineFlags.class),
                     mock(WifiViewModel.class),
+                    mock(MobileUiAdapter.class),
                     contextProvider);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index dcce61b..f52bfca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -23,9 +23,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -525,4 +527,26 @@
         mBouncerExpansionCallback.onVisibilityChanged(false);
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
     }
+
+
+    @Test
+    public void testSetDozing_bouncerShowing_Dozing() {
+        clearInvocations(mBouncer);
+        when(mBouncer.isShowing()).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mBouncer.isShowing()).thenReturn(false);
+            return null;
+        }).when(mBouncer).hide(false);
+        mStatusBarKeyguardViewManager.onDozingChanged(true);
+        verify(mBouncer, times(1)).hide(false);
+    }
+
+    @Test
+    public void testSetDozing_bouncerShowing_notDozing() {
+        mStatusBarKeyguardViewManager.onDozingChanged(true);
+        when(mBouncer.isShowing()).thenReturn(true);
+        clearInvocations(mBouncer);
+        mStatusBarKeyguardViewManager.onDozingChanged(false);
+        verify(mBouncer, times(1)).hide(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 0000000..0d15268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+    private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+    override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+    private val _activeMobileDataSubscriptionId =
+        MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+    override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+    private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+    override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+        return subIdFlows[subId]
+            ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+    }
+
+    fun setSubscriptions(subs: List<SubscriptionInfo>) {
+        _subscriptionsFlow.value = subs
+    }
+
+    fun setActiveMobileDataSubscriptionId(subId: Int) {
+        _activeMobileDataSubscriptionId.value = subId
+    }
+
+    fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+        val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+        subscription.value = model
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..6c495c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+    private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+    fun setUserSetup(setup: Boolean) {
+        _isUserSetup.value = setup
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 0000000..316b795
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+    @Mock private lateinit var subscriptionManager: SubscriptionManager
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            MobileSubscriptionRepositoryImpl(
+                subscriptionManager,
+                telephonyManager,
+                IMMEDIATE,
+                scope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testSubscriptions_initiallyEmpty() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+        }
+
+    @Test
+    fun testSubscriptions_listUpdates() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_removingSub_updatesList() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+
+            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+            // WHEN 2 networks show up
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // WHEN one network is removed
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // THEN the subscriptions list represents the newest change
+            assertThat(latest).isEqualTo(listOf(SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+        runBlocking(IMMEDIATE) {
+            assertThat(underTest.activeMobileDataSubscriptionId.value)
+                .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+        }
+
+    @Test
+    fun testActiveDataSubscriptionId_updates() =
+        runBlocking(IMMEDIATE) {
+            var active: Int? = null
+
+            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+            getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(active).isEqualTo(SUB_2_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_default() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+
+            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_emergencyOnly_toggles() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<ServiceStateListener>()
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+            callback.onServiceStateChanged(serviceState)
+            serviceState.isEmergencyOnly = false
+            callback.onServiceStateChanged(serviceState)
+
+            assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_signalStrengths_levelsUpdate() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+            val strength = signalStrength(1, 2, true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest?.isGsm).isEqualTo(true)
+            assertThat(latest?.primaryLevel).isEqualTo(1)
+            assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataActivity() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DataActivityListener>()
+            callback.onDataActivity(3)
+
+            assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_carrierNetworkChange() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+            callback.onCarrierNetworkChange(true)
+
+            assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_displayInfo() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val ti = mock<TelephonyDisplayInfo>()
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest?.displayInfo).isEqualTo(ti)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_isCached() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            val state1 = underTest.getFlowForSubId(SUB_1_ID)
+            val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+            assertThat(state1).isEqualTo(state2)
+        }
+
+    @Test
+    fun testFlowForSubId_isRemovedAfterFinish() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+            var latest: MobileSubscriptionModel? = null
+
+            // Start collecting on some flow
+            val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+            // There should be once cached flow now
+            assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+            // When the job is canceled, the cache should be cleared
+            job.cancel()
+
+            assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+        }
+
+    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+        val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+        verify(subscriptionManager)
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+        getTelephonyCallbackForType()
+
+    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+        val callbackCaptor = argumentCaptor<TelephonyCallback>()
+        verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.allValues
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+        assertThat(cbs.size).isEqualTo(1)
+        return cbs[0]
+    }
+
+    /** Convenience constructor for SignalStrength */
+    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+        val signalStrength = mock<SignalStrength>()
+        whenever(signalStrength.isGsm).thenReturn(isGsm)
+        whenever(signalStrength.level).thenReturn(gsmLevel)
+        val cdmaStrength =
+            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+            .thenReturn(listOf(cdmaStrength))
+
+        return signalStrength
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..91c233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: UserSetupRepository
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            UserSetupRepositoryImpl(
+                deviceProvisionedController,
+                IMMEDIATE,
+                scope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun testUserSetup_defaultFalse() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+
+            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun testUserSetup_updatesOnChange() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+
+            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+            whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+            val callback = getDeviceProvisionedListener()
+            callback.onUserSetupChanged()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+        val captor = argumentCaptor<DeviceProvisionedListener>()
+        verify(deviceProvisionedController).addCallback(captor.capture())
+        return captor.value!!
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..8ec68f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+    private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+    override val iconGroup = _iconGroup
+
+    private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+    override val isEmergencyOnly = _isEmergencyOnly
+
+    private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+    override val level = _level
+
+    private val _numberOfLevels = MutableStateFlow<Int>(4)
+    override val numberOfLevels = _numberOfLevels
+
+    private val _cutOut = MutableStateFlow<Boolean>(false)
+    override val cutOut = _cutOut
+
+    fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+        _iconGroup.value = group
+    }
+
+    fun setIsEmergencyOnly(emergency: Boolean) {
+        _isEmergencyOnly.value = emergency
+    }
+
+    fun setLevel(level: Int) {
+        _level.value = level
+    }
+
+    fun setNumberOfLevels(num: Int) {
+        _numberOfLevels.value = num
+    }
+
+    fun setCutOut(cutOut: Boolean) {
+        _cutOut.value = cutOut
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
new file mode 100644
index 0000000..2f07d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconInteractor
+    private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+    private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+    @Before
+    fun setUp() {
+        underTest = MobileIconInteractorImpl(sub1Flow)
+    }
+
+    @Test
+    fun gsm_level_default_unknown() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(isGsm = true),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+            job.cancel()
+        }
+
+    @Test
+    fun gsm_usesGsmLevel() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(
+                    isGsm = true,
+                    primaryLevel = GSM_LEVEL,
+                    cdmaLevel = CDMA_LEVEL
+                ),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(GSM_LEVEL)
+
+            job.cancel()
+        }
+
+    @Test
+    fun cdma_level_default_unknown() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(isGsm = false),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+            job.cancel()
+        }
+
+    @Test
+    fun cdma_usesCdmaLevel() =
+        runBlocking(IMMEDIATE) {
+            mobileSubscriptionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(
+                    isGsm = false,
+                    primaryLevel = GSM_LEVEL,
+                    cdmaLevel = CDMA_LEVEL
+                ),
+                SUB_1_ID
+            )
+
+            var latest: Int? = null
+            val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+
+        private const val GSM_LEVEL = 1
+        private const val CDMA_LEVEL = 2
+
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 0000000..89ad9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconsInteractor
+    private val userSetupRepository = FakeUserSetupRepository()
+    private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+    @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            MobileIconsInteractor(
+                subscriptionsRepository,
+                carrierConfigTracker,
+                userSetupRepository,
+            )
+    }
+
+    @After fun tearDown() {}
+
+    @Test
+    fun filteredSubscriptions_default() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the active one when the config is false
+            assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the active one when the config is false
+            assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(true)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+            // true
+            assertThat(latest).isEqualTo(listOf(SUB_1))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+        runBlocking(IMMEDIATE) {
+            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(true)
+
+            var latest: List<SubscriptionInfo>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+            // true
+            assertThat(latest).isEqualTo(listOf(SUB_1))
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+
+        private const val SUB_1_ID = 1
+        private val SUB_1 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+        private const val SUB_3_ID = 3
+        private val SUB_3_OPP =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+                whenever(it.isOpportunistic).thenReturn(true)
+            }
+
+        private const val SUB_4_ID = 4
+        private val SUB_4_OPP =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+                whenever(it.isOpportunistic).thenReturn(true)
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 0000000..b374abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconViewModel
+    private val interactor = FakeMobileIconInteractor()
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        interactor.apply {
+            setLevel(1)
+            setCutOut(false)
+            setIconGroup(TelephonyIcons.THREE_G)
+            setIsEmergencyOnly(false)
+            setNumberOfLevels(4)
+        }
+        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+    }
+
+    @Test
+    fun iconId_correctLevel_notCutout() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private const val SUB_1_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
new file mode 100644
index 0000000..773a0d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.telephony.data.repository
+
+import android.telephony.TelephonyCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class TelephonyRepositoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var manager: TelephonyListenerManager
+
+    private lateinit var underTest: TelephonyRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            TelephonyRepositoryImpl(
+                manager = manager,
+            )
+    }
+
+    @Test
+    fun callState() =
+        runBlocking(IMMEDIATE) {
+            var callState: Int? = null
+            val job = underTest.callState.onEach { callState = it }.launchIn(this)
+            val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+            verify(manager).addCallStateListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+
+            listener.onCallStateChanged(0)
+            assertThat(callState).isEqualTo(0)
+
+            listener.onCallStateChanged(1)
+            assertThat(callState).isEqualTo(1)
+
+            listener.onCallStateChanged(2)
+            assertThat(callState).isEqualTo(2)
+
+            job.cancel()
+
+            verify(manager).removeCallStateListener(listener)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
new file mode 100644
index 0000000..4a8e055
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 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.user.data.repository
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
+
+    @Before
+    fun setUp() {
+        super.setUp(isRefactored = true)
+    }
+
+    @Test
+    fun userSwitcherSettings() = runSelfCancelingTest {
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = true,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        underTest = create(this)
+
+        var value: UserSwitcherSettingsModel? = null
+        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = true,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+
+        setUpGlobalSettings(
+            isSimpleUserSwitcher = false,
+            isAddUsersFromLockscreen = true,
+            isUserSwitcherEnabled = true,
+        )
+        assertUserSwitcherSettings(
+            model = value,
+            expectedSimpleUserSwitcher = false,
+            expectedAddUsersFromLockscreen = true,
+            expectedUserSwitcherEnabled = true,
+        )
+    }
+
+    @Test
+    fun refreshUsers() = runSelfCancelingTest {
+        underTest = create(this)
+        val initialExpectedValue =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(initialExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val secondExpectedValue =
+            setUpUsers(
+                count = 4,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(secondExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+        val selectedNonGuestUserId = selectedUserInfo?.id
+        val thirdExpectedValue =
+            setUpUsers(
+                count = 2,
+                hasGuest = true,
+                selectedIndex = 1,
+            )
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(thirdExpectedValue)
+        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+        assertThat(selectedUserInfo?.isGuest).isTrue()
+        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+    }
+
+    private fun setUpUsers(
+        count: Int,
+        hasGuest: Boolean = false,
+        selectedIndex: Int = 0,
+    ): List<UserInfo> {
+        val userInfos =
+            (0 until count).map { index ->
+                createUserInfo(
+                    index,
+                    isGuest = hasGuest && index == count - 1,
+                )
+            }
+        whenever(manager.aliveUsers).thenReturn(userInfos)
+        tracker.set(userInfos, selectedIndex)
+        return userInfos
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        isGuest: Boolean,
+    ): UserInfo {
+        val flags = 0
+        return UserInfo(
+            id,
+            "user_$id",
+            /* iconPath= */ "",
+            flags,
+            if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+        )
+    }
+
+    private fun setUpGlobalSettings(
+        isSimpleUserSwitcher: Boolean = false,
+        isAddUsersFromLockscreen: Boolean = false,
+        isUserSwitcherEnabled: Boolean = true,
+    ) {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+            true,
+        )
+        globalSettings.putIntForUser(
+            UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+            if (isSimpleUserSwitcher) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.ADD_USERS_WHEN_LOCKED,
+            if (isAddUsersFromLockscreen) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+        globalSettings.putIntForUser(
+            Settings.Global.USER_SWITCHER_ENABLED,
+            if (isUserSwitcherEnabled) 1 else 0,
+            UserHandle.USER_SYSTEM,
+        )
+    }
+
+    private fun assertUserSwitcherSettings(
+        model: UserSwitcherSettingsModel?,
+        expectedSimpleUserSwitcher: Boolean,
+        expectedAddUsersFromLockscreen: Boolean,
+        expectedUserSwitcherEnabled: Boolean,
+    ) {
+        checkNotNull(model)
+        assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+        assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+        assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(Dispatchers.Main.immediate) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 6fec343..dcea83a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,201 +17,54 @@
 
 package com.android.systemui.user.data.repository
 
-import android.content.pm.UserInfo
 import android.os.UserManager
-import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
+import kotlinx.coroutines.test.TestCoroutineScope
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplTest : SysuiTestCase() {
+abstract class UserRepositoryImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var manager: UserManager
-    @Mock private lateinit var controller: UserSwitcherController
-    @Captor
-    private lateinit var userSwitchCallbackCaptor:
-        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+    @Mock protected lateinit var manager: UserManager
+    @Mock protected lateinit var controller: UserSwitcherController
 
-    private lateinit var underTest: UserRepositoryImpl
+    protected lateinit var underTest: UserRepositoryImpl
 
-    @Before
-    fun setUp() {
+    protected lateinit var globalSettings: FakeSettings
+    protected lateinit var tracker: FakeUserTracker
+    protected lateinit var featureFlags: FakeFeatureFlags
+
+    protected fun setUp(isRefactored: Boolean) {
         MockitoAnnotations.initMocks(this)
-        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
-        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
-        whenever(controller.isGuestUserResetting).thenReturn(false)
 
-        underTest =
-            UserRepositoryImpl(
-                appContext = context,
-                manager = manager,
-                controller = controller,
-            )
+        globalSettings = FakeSettings()
+        tracker = FakeUserTracker()
+        featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
     }
 
-    @Test
-    fun `users - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `users - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.users.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `users - does not include actions`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                    )
-                )
-            var models: List<UserModel>? = null
-            val job = underTest.users.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)?.id).isEqualTo(0)
-            assertThat(models?.get(0)?.isSelected).isTrue()
-            assertThat(models?.get(1)?.id).isEqualTo(1)
-            assertThat(models?.get(1)?.isSelected).isFalse()
-            assertThat(models?.get(2)?.id).isEqualTo(2)
-            assertThat(models?.get(2)?.isSelected).isFalse()
-            job.cancel()
-        }
-
-    @Test
-    fun selectedUser() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                    )
-                )
-            var id: Int? = null
-            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
-            assertThat(id).isEqualTo(0)
-
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0),
-                        createUserRecord(1),
-                        createUserRecord(2, isSelected = true),
-                    )
-                )
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-            userSwitchCallbackCaptor.value.onUserSwitched()
-            assertThat(id).isEqualTo(2)
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - unregisters from updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
-            job.cancel()
-
-            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
-        }
-
-    @Test
-    fun `actions - registers for updates`() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.actions.onEach {}.launchIn(this)
-
-            verify(controller).addUserSwitchCallback(any())
-
-            job.cancel()
-        }
-
-    @Test
-    fun `actopms - does not include users`() =
-        runBlocking(IMMEDIATE) {
-            whenever(controller.users)
-                .thenReturn(
-                    arrayListOf(
-                        createUserRecord(0, isSelected = true),
-                        createActionRecord(UserActionModel.ADD_USER),
-                        createUserRecord(1),
-                        createUserRecord(2),
-                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
-                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
-                    )
-                )
-            var models: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { models = it }.launchIn(this)
-
-            assertThat(models).hasSize(3)
-            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
-            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
-            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
-            job.cancel()
-        }
-
-    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
-        return UserRecord(
-            info = UserInfo(id, "name$id", 0),
-            isCurrent = isSelected,
-        )
-    }
-
-    private fun createActionRecord(action: UserActionModel): UserRecord {
-        return UserRecord(
-            isAddUser = action == UserActionModel.ADD_USER,
-            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
-            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+    protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+        return UserRepositoryImpl(
+            appContext = context,
+            manager = manager,
+            controller = controller,
+            applicationScope = scope,
+            mainDispatcher = IMMEDIATE,
+            backgroundDispatcher = IMMEDIATE,
+            globalSettings = globalSettings,
+            tracker = tracker,
+            featureFlags = featureFlags,
         )
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
+        @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
new file mode 100644
index 0000000..d4b41c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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.user.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+
+    @Captor
+    private lateinit var userSwitchCallbackCaptor:
+        ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+
+    @Before
+    fun setUp() {
+        super.setUp(isRefactored = false)
+
+        whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
+        whenever(controller.isGuestUserAutoCreated).thenReturn(false)
+        whenever(controller.isGuestUserResetting).thenReturn(false)
+
+        underTest = create()
+    }
+
+    @Test
+    fun `users - registers for updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.users.onEach {}.launchIn(this)
+
+            verify(controller).addUserSwitchCallback(any())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - unregisters from updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.users.onEach {}.launchIn(this)
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+            job.cancel()
+
+            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+        }
+
+    @Test
+    fun `users - does not include actions`() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createActionRecord(UserActionModel.ADD_USER),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                    )
+                )
+            var models: List<UserModel>? = null
+            val job = underTest.users.onEach { models = it }.launchIn(this)
+
+            assertThat(models).hasSize(3)
+            assertThat(models?.get(0)?.id).isEqualTo(0)
+            assertThat(models?.get(0)?.isSelected).isTrue()
+            assertThat(models?.get(1)?.id).isEqualTo(1)
+            assertThat(models?.get(1)?.isSelected).isFalse()
+            assertThat(models?.get(2)?.id).isEqualTo(2)
+            assertThat(models?.get(2)?.isSelected).isFalse()
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                    )
+                )
+            var id: Int? = null
+            val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
+
+            assertThat(id).isEqualTo(0)
+
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0),
+                        createUserRecord(1),
+                        createUserRecord(2, isSelected = true),
+                    )
+                )
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+            userSwitchCallbackCaptor.value.onUserSwitched()
+            assertThat(id).isEqualTo(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - unregisters from updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.actions.onEach {}.launchIn(this)
+            verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+            job.cancel()
+
+            verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+        }
+
+    @Test
+    fun `actions - registers for updates`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.actions.onEach {}.launchIn(this)
+
+            verify(controller).addUserSwitchCallback(any())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - does not include users`() =
+        runBlocking(IMMEDIATE) {
+            whenever(controller.users)
+                .thenReturn(
+                    arrayListOf(
+                        createUserRecord(0, isSelected = true),
+                        createActionRecord(UserActionModel.ADD_USER),
+                        createUserRecord(1),
+                        createUserRecord(2),
+                        createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+                        createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+                    )
+                )
+            var models: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { models = it }.launchIn(this)
+
+            assertThat(models).hasSize(3)
+            assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
+            assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
+            assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+            job.cancel()
+        }
+
+    private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
+        return UserRecord(
+            info = UserInfo(id, "name$id", 0),
+            isCurrent = isSelected,
+        )
+    }
+
+    private fun createActionRecord(action: UserActionModel): UserRecord {
+        return UserRecord(
+            isAddUser = action == UserActionModel.ADD_USER,
+            isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
+            isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
new file mode 100644
index 0000000..120bf79
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class GuestUserInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var showDialog: (ShowDialogRequestModel) -> Unit
+    @Mock private lateinit var dismissDialog: () -> Unit
+    @Mock private lateinit var selectUser: (Int) -> Unit
+    @Mock private lateinit var switchUser: (Int) -> Unit
+
+    private lateinit var underTest: GuestUserInteractor
+
+    private lateinit var scope: TestCoroutineScope
+    private lateinit var repository: FakeUserRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(manager.createGuest(any())).thenReturn(GUEST_USER_INFO)
+
+        scope = TestCoroutineScope()
+        repository = FakeUserRepository()
+        repository.setUserInfos(ALL_USERS)
+
+        underTest =
+            GuestUserInteractor(
+                applicationContext = context,
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                backgroundDispatcher = IMMEDIATE,
+                manager = manager,
+                repository = repository,
+                deviceProvisionedController = deviceProvisionedController,
+                devicePolicyManager = devicePolicyManager,
+                refreshUsersScheduler =
+                    RefreshUsersScheduler(
+                        applicationScope = scope,
+                        mainDispatcher = IMMEDIATE,
+                        repository = repository,
+                    ),
+                uiEventLogger = uiEventLogger,
+            )
+    }
+
+    @Test
+    fun `onDeviceBootCompleted - allowed to add - create guest`() =
+        runBlocking(IMMEDIATE) {
+            setAllowedToAdd()
+
+            underTest.onDeviceBootCompleted()
+
+            verify(manager).createGuest(any())
+            verify(deviceProvisionedController, never()).addCallback(any())
+        }
+
+    @Test
+    fun `onDeviceBootCompleted - await provisioning - and create guest`() =
+        runBlocking(IMMEDIATE) {
+            setAllowedToAdd(isAllowed = false)
+            underTest.onDeviceBootCompleted()
+            val captor =
+                kotlinArgumentCaptor<DeviceProvisionedController.DeviceProvisionedListener>()
+            verify(deviceProvisionedController).addCallback(captor.capture())
+
+            setAllowedToAdd(isAllowed = true)
+            captor.value.onDeviceProvisionedChanged()
+
+            verify(manager).createGuest(any())
+            verify(deviceProvisionedController).removeCallback(captor.value)
+        }
+
+    @Test
+    fun createAndSwitchTo() =
+        runBlocking(IMMEDIATE) {
+            underTest.createAndSwitchTo(
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                selectUser = selectUser,
+            )
+
+            verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            verify(manager).createGuest(any())
+            verify(dismissDialog).invoke()
+            verify(selectUser).invoke(GUEST_USER_INFO.id)
+        }
+
+    @Test
+    fun `createAndSwitchTo - fails to create - does not switch to`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.createGuest(any())).thenReturn(null)
+
+            underTest.createAndSwitchTo(
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                selectUser = selectUser,
+            )
+
+            verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+            verify(manager).createGuest(any())
+            verify(dismissDialog).invoke()
+            verify(selectUser, never()).invoke(anyInt())
+        }
+
+    @Test
+    fun `exit - returns to target user`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            val targetUserId = NON_GUEST_USER_INFO.id
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - returns to last non-guest`() =
+        runBlocking(IMMEDIATE) {
+            val expectedUserId = NON_GUEST_USER_INFO.id
+            whenever(manager.getUserInfo(expectedUserId)).thenReturn(NON_GUEST_USER_INFO)
+            repository.lastSelectedNonGuestUserId = expectedUserId
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = UserHandle.USER_NULL,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(expectedUserId)
+        }
+
+    @Test
+    fun `exit - last non-guest was removed - returns to system`() =
+        runBlocking(IMMEDIATE) {
+            val removedUserId = 310
+            repository.lastSelectedNonGuestUserId = removedUserId
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = UserHandle.USER_NULL,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager, never()).markGuestForDeletion(anyInt())
+            verify(manager, never()).removeUser(anyInt())
+            verify(switchUser).invoke(UserHandle.USER_SYSTEM)
+        }
+
+    @Test
+    fun `exit - guest was ephemeral - it is removed`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
+            repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
+            val targetUserId = NON_GUEST_USER_INFO.id
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
+            verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - force remove guest - it is removed`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+            val targetUserId = NON_GUEST_USER_INFO.id
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                forceRemoveGuestOnExit = true,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `exit - selected different from guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = 123,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotExit()
+        }
+
+    @Test
+    fun `exit - selected is actually not a guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.exit(
+                guestUserId = NON_GUEST_USER_INFO.id,
+                targetUserId = 123,
+                forceRemoveGuestOnExit = false,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotExit()
+        }
+
+    @Test
+    fun `remove - returns to target user`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+            val targetUserId = NON_GUEST_USER_INFO.id
+            underTest.remove(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = targetUserId,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(switchUser).invoke(targetUserId)
+        }
+
+    @Test
+    fun `remove - selected different from guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.remove(
+                guestUserId = GUEST_USER_INFO.id,
+                targetUserId = 123,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotRemove()
+        }
+
+    @Test
+    fun `remove - selected is actually not a guest user - do nothing`() =
+        runBlocking(IMMEDIATE) {
+            whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+            repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+            underTest.remove(
+                guestUserId = NON_GUEST_USER_INFO.id,
+                targetUserId = 123,
+                showDialog = showDialog,
+                dismissDialog = dismissDialog,
+                switchUser = switchUser,
+            )
+
+            verifyDidNotRemove()
+        }
+
+    private fun setAllowedToAdd(isAllowed: Boolean = true) {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(isAllowed)
+        whenever(devicePolicyManager.isDeviceManaged).thenReturn(!isAllowed)
+    }
+
+    private fun verifyDidNotExit() {
+        verifyDidNotRemove()
+        verify(manager, never()).getUserInfo(anyInt())
+        verify(uiEventLogger, never()).log(any())
+    }
+
+    private fun verifyDidNotRemove() {
+        verify(manager, never()).markGuestForDeletion(anyInt())
+        verify(showDialog, never()).invoke(any())
+        verify(dismissDialog, never()).invoke()
+        verify(switchUser, never()).invoke(anyInt())
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private val NON_GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 818,
+                /* name= */ "non_guest",
+                /* flags= */ 0,
+            )
+        private val GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 669,
+                /* name= */ "guest",
+                /* iconPath= */ "",
+                /* flags= */ 0,
+                UserManager.USER_TYPE_FULL_GUEST,
+            )
+        private val EPHEMERAL_GUEST_USER_INFO =
+            UserInfo(
+                /* id= */ 669,
+                /* name= */ "guest",
+                /* iconPath= */ "",
+                /* flags= */ UserInfo.FLAG_EPHEMERAL,
+                UserManager.USER_TYPE_FULL_GUEST,
+            )
+        private val ALL_USERS =
+            listOf(
+                NON_GUEST_USER_INFO,
+                GUEST_USER_INFO,
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
new file mode 100644
index 0000000..593ce1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RefreshUsersSchedulerTest : SysuiTestCase() {
+
+    private lateinit var underTest: RefreshUsersScheduler
+
+    private lateinit var repository: FakeUserRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        repository = FakeUserRepository()
+    }
+
+    @Test
+    fun `pause - prevents the next refresh from happening`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.pause()
+
+            underTest.refreshIfNotPaused()
+            assertThat(repository.refreshUsersCallCount).isEqualTo(0)
+        }
+
+    @Test
+    fun `unpauseAndRefresh - forces the refresh even when paused`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.pause()
+
+            underTest.unpauseAndRefresh()
+
+            assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+        }
+
+    @Test
+    fun `refreshIfNotPaused - refreshes when not paused`() =
+        runBlocking(IMMEDIATE) {
+            underTest =
+                RefreshUsersScheduler(
+                    applicationScope = this,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                )
+            underTest.refreshIfNotPaused()
+
+            assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
new file mode 100644
index 0000000..3d5695a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorRefactoredTest : UserInteractorTest() {
+
+    override fun isRefactored(): Boolean {
+        return true
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+        overrideResource(R.dimen.max_avatar_size, 10)
+        overrideResource(
+            com.android.internal.R.string.config_supervisedUserCreationPackage,
+            SUPERVISED_USER_CREATION_APP_PACKAGE,
+        )
+        whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+        whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+    }
+
+    @Test
+    fun `users - switcher enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 3, includeGuest = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switches to second user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            userRepository.setSelectedUserInfo(userInfos[1])
+
+            assertUsers(models = value, count = 2, selectedIndex = 1)
+            job.cancel()
+        }
+
+    @Test
+    fun `users - switcher not enabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+            var value: List<UserModel>? = null
+            val job = underTest.users.onEach { value = it }.launchIn(this)
+            assertUsers(models = value, count = 1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun selectedUser() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var value: UserModel? = null
+            val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+            assertUser(value, id = 0, isSelected = true)
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            assertUser(value, id = 1, isSelected = true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user not primary - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device unlocked user is guest - empty list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            assertThat(userInfos[1].isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked add from lockscreen set - full list`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(
+                UserSwitcherSettingsModel(
+                    isUserSwitcherEnabled = true,
+                    isAddUsersFromLockscreen = true,
+                )
+            )
+            keyguardRepository.setKeyguardShowing(false)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - device locked - only guest action is shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(true)
+            var value: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+            assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add user - dialog shown`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.executeAction(UserActionModel.ADD_USER)
+            assertThat(dialogRequest)
+                .isEqualTo(
+                    ShowDialogRequestModel.ShowAddUserDialog(
+                        userHandle = userInfos[0].userHandle,
+                        isKeyguardShowing = false,
+                        showEphemeralMessage = false,
+                    )
+                )
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `executeAction - add supervised user - starts activity`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            assertThat(intentCaptor.value.action)
+                .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+            assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+        }
+
+    @Test
+    fun `executeAction - navigate to manage users`() =
+        runBlocking(IMMEDIATE) {
+            underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+            val intentCaptor = kotlinArgumentCaptor<Intent>()
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+        }
+
+    @Test
+    fun `executeAction - guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+            val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+            val showDialogsJob =
+                underTest.dialogShowRequests
+                    .onEach {
+                        dialogRequests.add(it)
+                        if (it != null) {
+                            underTest.onDialogShown()
+                        }
+                    }
+                    .launchIn(this)
+            val dismissDialogsJob =
+                underTest.dialogDismissRequests
+                    .onEach {
+                        if (it != null) {
+                            underTest.onDialogDismissed()
+                        }
+                    }
+                    .launchIn(this)
+
+            underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+            assertThat(dialogRequests)
+                .contains(
+                    ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+                )
+            verify(activityManager).switchUser(guestUserInfo.id)
+
+            showDialogsJob.cancel()
+            dismissDialogsJob.cancel()
+        }
+
+    @Test
+    fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = guestUserInfo.id)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = true)
+            val guestUserInfo = userInfos[1]
+            assertThat(guestUserInfo.isGuest).isTrue()
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(guestUserInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id)
+
+            assertThat(dialogRequest)
+                .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            job.cancel()
+        }
+
+    @Test
+    fun `selectUser - not currently guest - switches users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            var dialogRequest: ShowDialogRequestModel? = null
+            val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id)
+
+            assertThat(dialogRequest).isNull()
+            verify(activityManager).switchUser(userInfos[1].id)
+            job.cancel()
+        }
+
+    @Test
+    fun `Telephony call state changes - refreshes users`() =
+        runBlocking(IMMEDIATE) {
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            telephonyRepository.setCallState(1)
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User switched broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val callback1: UserInteractor.UserCallback = mock()
+            val callback2: UserInteractor.UserCallback = mock()
+            underTest.addCallback(callback1)
+            underTest.addCallback(callback2)
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            userRepository.setSelectedUserInfo(userInfos[1])
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_SWITCHED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+                )
+            }
+
+            verify(callback1).onUserStateChanged()
+            verify(callback2).onUserStateChanged()
+            assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `User info changed broadcast`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_INFO_CHANGED),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `System user unlocked broadcast - refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+        }
+
+    @Test
+    fun `Non-system user unlocked broadcast - do not refresh users`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach {
+                it.onReceive(
+                    context,
+                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+                )
+            }
+
+            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+        }
+
+    @Test
+    fun userRecords() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            testCoroutineScope.advanceUntilIdle()
+
+            assertRecords(
+                records = underTest.userRecords.value,
+                userIds = listOf(0, 1, 2),
+                selectedUserIndex = 0,
+                includeGuest = false,
+                expectedActions =
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                    ),
+            )
+        }
+
+    @Test
+    fun selectedUserRecord() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertRecordForUser(
+                record = underTest.selectedUserRecord.value,
+                id = 0,
+                hasPicture = true,
+                isCurrent = true,
+                isSwitchToEnabled = true,
+            )
+        }
+
+    private fun assertUsers(
+        models: List<UserModel>?,
+        count: Int,
+        selectedIndex: Int = 0,
+        includeGuest: Boolean = false,
+    ) {
+        checkNotNull(models)
+        assertThat(models.size).isEqualTo(count)
+        models.forEachIndexed { index, model ->
+            assertUser(
+                model = model,
+                id = index,
+                isSelected = index == selectedIndex,
+                isGuest = includeGuest && index == count - 1
+            )
+        }
+    }
+
+    private fun assertUser(
+        model: UserModel?,
+        id: Int,
+        isSelected: Boolean = false,
+        isGuest: Boolean = false,
+    ) {
+        checkNotNull(model)
+        assertThat(model.id).isEqualTo(id)
+        assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+        assertThat(model.isSelected).isEqualTo(isSelected)
+        assertThat(model.isSelectable).isTrue()
+        assertThat(model.isGuest).isEqualTo(isGuest)
+    }
+
+    private fun assertRecords(
+        records: List<UserRecord>,
+        userIds: List<Int>,
+        selectedUserIndex: Int = 0,
+        includeGuest: Boolean = false,
+        expectedActions: List<UserActionModel> = emptyList(),
+    ) {
+        assertThat(records.size >= userIds.size).isTrue()
+        userIds.indices.forEach { userIndex ->
+            val record = records[userIndex]
+            assertThat(record.info).isNotNull()
+            val isGuest = includeGuest && userIndex == userIds.size - 1
+            assertRecordForUser(
+                record = record,
+                id = userIds[userIndex],
+                hasPicture = !isGuest,
+                isCurrent = userIndex == selectedUserIndex,
+                isGuest = isGuest,
+                isSwitchToEnabled = true,
+            )
+        }
+
+        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+            val record = records[actionIndex]
+            assertThat(record.info).isNull()
+            assertRecordForAction(
+                record = record,
+                type = expectedActions[actionIndex - userIds.size],
+            )
+        }
+    }
+
+    private fun assertRecordForUser(
+        record: UserRecord?,
+        id: Int? = null,
+        hasPicture: Boolean = false,
+        isCurrent: Boolean = false,
+        isGuest: Boolean = false,
+        isSwitchToEnabled: Boolean = false,
+    ) {
+        checkNotNull(record)
+        assertThat(record.info?.id).isEqualTo(id)
+        assertThat(record.picture != null).isEqualTo(hasPicture)
+        assertThat(record.isCurrent).isEqualTo(isCurrent)
+        assertThat(record.isGuest).isEqualTo(isGuest)
+        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+    }
+
+    private fun assertRecordForAction(
+        record: UserRecord,
+        type: UserActionModel,
+    ) {
+        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+        assertThat(record.isAddSupervisedUser)
+            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+    }
+
+    private fun createUserInfos(
+        count: Int,
+        includeGuest: Boolean,
+    ): List<UserInfo> {
+        return (0 until count).map { index ->
+            val isGuest = includeGuest && index == count - 1
+            createUserInfo(
+                id = index,
+                name =
+                    if (isGuest) {
+                        "guest"
+                    } else {
+                        "user_$index"
+                    },
+                isPrimary = !isGuest && index == 0,
+                isGuest = isGuest,
+            )
+        }
+    }
+
+    private fun createUserInfo(
+        id: Int,
+        name: String,
+        isPrimary: Boolean = false,
+        isGuest: Boolean = false,
+    ): UserInfo {
+        return UserInfo(
+            id,
+            name,
+            /* iconPath= */ "",
+            /* flags= */ if (isPrimary) {
+                UserInfo.FLAG_PRIMARY
+            } else {
+                0
+            },
+            if (isGuest) {
+                UserManager.USER_TYPE_FULL_GUEST
+            } else {
+                UserManager.USER_TYPE_FULL_SYSTEM
+            },
+        )
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        private val GUEST_ICON: Drawable = mock()
+        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index e914e2e..8465f4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -17,51 +17,61 @@
 
 package com.android.systemui.user.domain.interactor
 
-import androidx.test.filters.SmallTest
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.os.UserManager
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import kotlinx.coroutines.test.TestCoroutineScope
 import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+abstract class UserInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var controller: UserSwitcherController
-    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock protected lateinit var controller: UserSwitcherController
+    @Mock protected lateinit var activityStarter: ActivityStarter
+    @Mock protected lateinit var manager: UserManager
+    @Mock protected lateinit var activityManager: ActivityManager
+    @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock protected lateinit var uiEventLogger: UiEventLogger
 
-    private lateinit var underTest: UserInteractor
+    protected lateinit var underTest: UserInteractor
 
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
+    protected lateinit var testCoroutineScope: TestCoroutineScope
+    protected lateinit var userRepository: FakeUserRepository
+    protected lateinit var keyguardRepository: FakeKeyguardRepository
+    protected lateinit var telephonyRepository: FakeTelephonyRepository
 
-    @Before
-    fun setUp() {
+    abstract fun isRefactored(): Boolean
+
+    open fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
+        telephonyRepository = FakeTelephonyRepository()
+        testCoroutineScope = TestCoroutineScope()
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = testCoroutineScope,
+                mainDispatcher = IMMEDIATE,
+                repository = userRepository,
+            )
         underTest =
             UserInteractor(
+                applicationContext = context,
                 repository = userRepository,
                 controller = controller,
                 activityStarter = activityStarter,
@@ -69,142 +79,34 @@
                     KeyguardInteractor(
                         repository = keyguardRepository,
                     ),
-            )
-    }
-
-    @Test
-    fun `actions - not actionable when locked and locked - no actions`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(UserActionModel.values().toList())
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions).isEmpty()
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - not actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
-            userRepository.setActionableWhenLocked(false)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
+                    },
+                manager = manager,
+                applicationScope = testCoroutineScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = telephonyRepository,
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                backgroundDispatcher = IMMEDIATE,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor =
+                    GuestUserInteractor(
+                        applicationContext = context,
+                        applicationScope = testCoroutineScope,
+                        mainDispatcher = IMMEDIATE,
+                        backgroundDispatcher = IMMEDIATE,
+                        manager = manager,
+                        repository = userRepository,
+                        deviceProvisionedController = deviceProvisionedController,
+                        devicePolicyManager = devicePolicyManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        uiEventLogger = uiEventLogger,
                     )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and not locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
             )
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(false)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun `actions - actionable when locked and locked`() =
-        runBlocking(IMMEDIATE) {
-            userRepository.setActions(
-                listOf(
-                    UserActionModel.ENTER_GUEST_MODE,
-                    UserActionModel.ADD_USER,
-                    UserActionModel.ADD_SUPERVISED_USER,
-                )
-            )
-            userRepository.setActionableWhenLocked(true)
-            keyguardRepository.setKeyguardShowing(true)
-
-            var actions: List<UserActionModel>? = null
-            val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
-            assertThat(actions)
-                .isEqualTo(
-                    listOf(
-                        UserActionModel.ENTER_GUEST_MODE,
-                        UserActionModel.ADD_USER,
-                        UserActionModel.ADD_SUPERVISED_USER,
-                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
-                    )
-                )
-            job.cancel()
-        }
-
-    @Test
-    fun selectUser() {
-        val userId = 3
-
-        underTest.selectUser(userId)
-
-        verify(controller).onUserSelected(eq(userId), nullable())
-    }
-
-    @Test
-    fun `executeAction - guest`() {
-        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
-        verify(controller).createAndSwitchToGuestUser(nullable())
-    }
-
-    @Test
-    fun `executeAction - add user`() {
-        underTest.executeAction(UserActionModel.ADD_USER)
-
-        verify(controller).showAddUserDialog(nullable())
-    }
-
-    @Test
-    fun `executeAction - add supervised user`() {
-        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
-        verify(controller).startSupervisedUserActivity()
-    }
-
-    @Test
-    fun `executeAction - manage users`() {
-        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
-        verify(activityStarter).startActivity(any(), anyBoolean())
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
new file mode 100644
index 0000000..c3a9705
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 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.user.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+open class UserInteractorUnrefactoredTest : UserInteractorTest() {
+
+    override fun isRefactored(): Boolean {
+        return false
+    }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+    }
+
+    @Test
+    fun `actions - not actionable when locked and locked - no actions`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(UserActionModel.values().toList())
+            userRepository.setActionableWhenLocked(false)
+            keyguardRepository.setKeyguardShowing(true)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions).isEmpty()
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - not actionable when locked and not locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(false)
+            keyguardRepository.setKeyguardShowing(false)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - actionable when locked and not locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(true)
+            keyguardRepository.setKeyguardShowing(false)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun `actions - actionable when locked and locked`() =
+        runBlocking(IMMEDIATE) {
+            userRepository.setActions(
+                listOf(
+                    UserActionModel.ENTER_GUEST_MODE,
+                    UserActionModel.ADD_USER,
+                    UserActionModel.ADD_SUPERVISED_USER,
+                )
+            )
+            userRepository.setActionableWhenLocked(true)
+            keyguardRepository.setKeyguardShowing(true)
+
+            var actions: List<UserActionModel>? = null
+            val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+            assertThat(actions)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
+    fun selectUser() {
+        val userId = 3
+
+        underTest.selectUser(userId)
+
+        verify(controller).onUserSelected(eq(userId), nullable())
+    }
+
+    @Test
+    fun `executeAction - guest`() {
+        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+        verify(controller).createAndSwitchToGuestUser(nullable())
+    }
+
+    @Test
+    fun `executeAction - add user`() {
+        underTest.executeAction(UserActionModel.ADD_USER)
+
+        verify(controller).showAddUserDialog(nullable())
+    }
+
+    @Test
+    fun `executeAction - add supervised user`() {
+        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+        verify(controller).startSupervisedUserActivity()
+    }
+
+    @Test
+    fun `executeAction - manage users`() {
+        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+        verify(activityStarter).startActivity(any(), anyBoolean())
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index ef4500d..0344e3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -17,17 +17,28 @@
 
 package com.android.systemui.user.ui.viewmodel
 
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
 import android.graphics.drawable.Drawable
+import android.os.UserManager
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import com.android.systemui.user.shared.model.UserActionModel
@@ -38,6 +49,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
@@ -52,6 +64,11 @@
 
     @Mock private lateinit var controller: UserSwitcherController
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
 
     private lateinit var underTest: UserSwitcherViewModel
 
@@ -66,22 +83,60 @@
         userRepository = FakeUserRepository()
         keyguardRepository = FakeKeyguardRepository()
         powerRepository = FakePowerRepository()
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
+        val scope = TestCoroutineScope()
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                repository = userRepository,
+            )
+        val guestUserInteractor =
+            GuestUserInteractor(
+                applicationContext = context,
+                applicationScope = scope,
+                mainDispatcher = IMMEDIATE,
+                backgroundDispatcher = IMMEDIATE,
+                manager = manager,
+                repository = userRepository,
+                deviceProvisionedController = deviceProvisionedController,
+                devicePolicyManager = devicePolicyManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                uiEventLogger = uiEventLogger,
+            )
+
         underTest =
             UserSwitcherViewModel.Factory(
                     userInteractor =
                         UserInteractor(
+                            applicationContext = context,
                             repository = userRepository,
                             controller = controller,
                             activityStarter = activityStarter,
                             keyguardInteractor =
                                 KeyguardInteractor(
                                     repository = keyguardRepository,
-                                )
+                                ),
+                            featureFlags = featureFlags,
+                            manager = manager,
+                            applicationScope = scope,
+                            telephonyInteractor =
+                                TelephonyInteractor(
+                                    repository = FakeTelephonyRepository(),
+                                ),
+                            broadcastDispatcher = fakeBroadcastDispatcher,
+                            backgroundDispatcher = IMMEDIATE,
+                            activityManager = activityManager,
+                            refreshUsersScheduler = refreshUsersScheduler,
+                            guestUserInteractor = guestUserInteractor,
                         ),
                     powerInteractor =
                         PowerInteractor(
                             repository = powerRepository,
                         ),
+                    featureFlags = featureFlags,
+                    guestUserInteractor = guestUserInteractor,
                 )
                 .create(UserSwitcherViewModel::class.java)
     }
@@ -97,6 +152,7 @@
                         image = USER_IMAGE,
                         isSelected = true,
                         isSelectable = true,
+                        isGuest = false,
                     ),
                     UserModel(
                         id = 1,
@@ -104,6 +160,7 @@
                         image = USER_IMAGE,
                         isSelected = false,
                         isSelectable = true,
+                        isGuest = false,
                     ),
                     UserModel(
                         id = 2,
@@ -111,6 +168,7 @@
                         image = USER_IMAGE,
                         isSelected = false,
                         isSelectable = false,
+                        isGuest = false,
                     ),
                 )
             )
@@ -260,7 +318,7 @@
             job.cancel()
         }
 
-    private fun setUsers(count: Int) {
+    private suspend fun setUsers(count: Int) {
         userRepository.setUsers(
             (0 until count).map { index ->
                 UserModel(
@@ -269,6 +327,7 @@
                     image = USER_IMAGE,
                     isSelected = index == 0,
                     isSelectable = true,
+                    isGuest = false,
                 )
             }
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index aaf2188..3769f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -46,6 +46,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +100,8 @@
     private KeyguardManager mKeyguardManager;
     @Mock
     private ActivityManager mActivityManager;
+    @Mock
+    private DumpManager mDumpManager;
 
 
     @Before
@@ -121,7 +124,7 @@
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                 mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
-                mActivityManager, mCallback);
+                mActivityManager, mDumpManager, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -202,11 +205,12 @@
                 CaptioningManager captioningManager,
                 KeyguardManager keyguardManager,
                 ActivityManager activityManager,
+                DumpManager dumpManager,
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
                     packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
-                    activityManager);
+                    activityManager, dumpManager);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 3434376..c254358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
 
 package com.android.systemui.wallpapers;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
 
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -31,17 +40,25 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.Surface;
 import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
 
 import org.junit.Before;
@@ -56,7 +73,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-@Ignore
 public class ImageWallpaperTest extends SysuiTestCase {
     private static final int LOW_BMP_WIDTH = 128;
     private static final int LOW_BMP_HEIGHT = 128;
@@ -66,44 +82,86 @@
     private static final int DISPLAY_HEIGHT = 1080;
 
     @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private WindowMetrics mWindowMetrics;
+    @Mock
+    private DisplayManager mDisplayManager;
+    @Mock
+    private WallpaperManager mWallpaperManager;
+    @Mock
     private SurfaceHolder mSurfaceHolder;
     @Mock
+    private Surface mSurface;
+    @Mock
     private Context mMockContext;
+
     @Mock
     private Bitmap mWallpaperBitmap;
+    private int mBitmapWidth = 1;
+    private int mBitmapHeight = 1;
+
     @Mock
     private Handler mHandler;
     @Mock
     private FeatureFlags mFeatureFlags;
 
+    FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+    FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+
     private CountDownLatch mEventCountdown;
 
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
-        mEventCountdown = new CountDownLatch(1);
+        //mEventCountdown = new CountDownLatch(1);
 
-        WallpaperManager wallpaperManager = mock(WallpaperManager.class);
+        // set up window manager
+        when(mWindowMetrics.getBounds()).thenReturn(
+                new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+
+        // set up display manager
+        doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+        when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+        // set up bitmap
+        when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+        when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+        when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+        when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+        // set up wallpaper manager
+        when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+                new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+        when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+        when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+        // set up surface
+        when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+        doNothing().when(mSurface).hwuiDestroy();
+
+        // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
         Resources resources = mock(Resources.class);
-
-        when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
-        when(mMockContext.getResources()).thenReturn(resources);
         when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-
+        when(mMockContext.getResources()).thenReturn(resources);
         DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.logicalWidth = DISPLAY_WIDTH;
         displayInfo.logicalHeight = DISPLAY_HEIGHT;
         when(mMockContext.getDisplay()).thenReturn(
                 new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+    }
 
-        when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
-        when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
-        when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+    private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+        mBitmapWidth = bitmapWidth;
+        mBitmapHeight = bitmapHeight;
     }
 
     private ImageWallpaper createImageWallpaper() {
-        return new ImageWallpaper(mFeatureFlags) {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
             @Override
             public Engine onCreateEngine() {
                 return new GLEngine(mHandler) {
@@ -130,6 +188,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_normal() {
         // Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
         // Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -140,6 +199,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_low_resolution() {
         // Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
         // Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -150,6 +210,7 @@
     }
 
     @Test
+    @Ignore
     public void testBitmapWallpaper_too_small() {
         // Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
         // Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -166,8 +227,7 @@
 
         ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
 
-        when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
-        when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+        setBitmapDimensions(bmpWidth, bmpHeight);
 
         ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
         doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -177,4 +237,116 @@
         assertWithMessage("setFixedSizeAllowed should have been called.").that(
                 mEventCountdown.getCount()).isEqualTo(0);
     }
+
+
+    private ImageWallpaper createImageWallpaperCanvas() {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+            @Override
+            public Engine onCreateEngine() {
+                return new CanvasEngine() {
+                    @Override
+                    public Context getDisplayContext() {
+                        return mMockContext;
+                    }
+
+                    @Override
+                    public SurfaceHolder getSurfaceHolder() {
+                        return mSurfaceHolder;
+                    }
+
+                    @Override
+                    public void setFixedSizeAllowed(boolean allowed) {
+                        super.setFixedSizeAllowed(allowed);
+                        assertWithMessage("mFixedSizeAllowed should be true").that(
+                                allowed).isTrue();
+                    }
+                };
+            }
+        };
+    }
+
+    private ImageWallpaper.CanvasEngine getSpyEngine() {
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+        doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+        doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+        doAnswer(invocation -> {
+            ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+            return null;
+        }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+        return spyEngine;
+    }
+
+    @Test
+    public void testMinSurface() {
+
+        // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+        testMinSurfaceHelper(8, 8);
+        testMinSurfaceHelper(100, 2000);
+        testMinSurfaceHelper(200, 1);
+        testMinSurfaceHelper(0, 1);
+        testMinSurfaceHelper(1, 0);
+        testMinSurfaceHelper(0, 0);
+    }
+
+    private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+        clearInvocations(mSurfaceHolder);
+        setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        engine.onCreate(mSurfaceHolder);
+
+        verify(mSurfaceHolder, times(1)).setFixedSize(
+                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+                intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+    }
+
+    @Test
+    public void testZeroBitmap() {
+        // test that a frame is never drawn with a 0 bitmap
+        testZeroBitmapHelper(0, 1);
+        testZeroBitmapHelper(1, 0);
+        testZeroBitmapHelper(0, 0);
+    }
+
+    private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+        clearInvocations(mSurfaceHolder);
+        setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+        ImageWallpaper.CanvasEngine engine =
+                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+        ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+        spyEngine.onCreate(mSurfaceHolder);
+        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+        verify(spyEngine, never()).drawFrameOnCanvas(any());
+    }
+
+    @Test
+    public void testLoadDrawAndUnloadBitmap() {
+        setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+        ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+        spyEngine.onCreate(mSurfaceHolder);
+        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+        assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+        int n = 0;
+        while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+            n++;
+            assertThat(n).isAtMost(10);
+            mFakeBackgroundExecutor.runNextReady();
+            mFakeMainExecutor.runNextReady();
+            mFakeSystemClock.advanceTime(1000);
+        }
+
+        verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+        assertThat(spyEngine.isBitmapLoaded()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
deleted file mode 100644
index 93f4f82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 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.wallpapers.canvas;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.intThat;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.DisplayInfo;
-import android.view.SurfaceHolder;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ImageCanvasWallpaperRendererTest extends SysuiTestCase {
-
-    private static final int MOBILE_DISPLAY_WIDTH = 720;
-    private static final int MOBILE_DISPLAY_HEIGHT = 1600;
-
-    @Mock
-    private SurfaceHolder mMockSurfaceHolder;
-
-    @Mock
-    private DisplayInfo mMockDisplayInfo;
-
-    @Mock
-    private Bitmap mMockBitmap;
-
-    @Before
-    public void setUp() throws Exception {
-        allowTestableLooperAsMainThread();
-        MockitoAnnotations.initMocks(this);
-    }
-
-    private void setDimensions(
-            int bitmapWidth, int bitmapHeight,
-            int displayWidth, int displayHeight) {
-        when(mMockBitmap.getWidth()).thenReturn(bitmapWidth);
-        when(mMockBitmap.getHeight()).thenReturn(bitmapHeight);
-        mMockDisplayInfo.logicalWidth = displayWidth;
-        mMockDisplayInfo.logicalHeight = displayHeight;
-    }
-
-    private void testMinDimensions(
-            int bitmapWidth, int bitmapHeight) {
-
-        clearInvocations(mMockSurfaceHolder);
-        setDimensions(bitmapWidth, bitmapHeight,
-                ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
-                ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
-        ImageCanvasWallpaperRenderer renderer =
-                new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
-        renderer.drawFrame(mMockBitmap, true);
-
-        verify(mMockSurfaceHolder, times(1)).setFixedSize(
-                intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)),
-                intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT)));
-    }
-
-    @Test
-    public void testMinSurface() {
-        // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
-        testMinDimensions(8, 8);
-
-        testMinDimensions(100, 2000);
-
-        testMinDimensions(200, 1);
-    }
-
-    private void testZeroDimensions(int bitmapWidth, int bitmapHeight) {
-
-        clearInvocations(mMockSurfaceHolder);
-        setDimensions(bitmapWidth, bitmapHeight,
-                ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
-                ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
-        ImageCanvasWallpaperRenderer renderer =
-                new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
-        ImageCanvasWallpaperRenderer spyRenderer = spy(renderer);
-        spyRenderer.drawFrame(mMockBitmap, true);
-
-        verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt());
-        verify(spyRenderer, never()).drawWallpaperWithCanvas(any());
-    }
-
-    @Test
-    public void testZeroBitmap() {
-        // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0
-        testZeroDimensions(
-                0, 1
-        );
-
-        testZeroDimensions(1, 0
-        );
-
-        testZeroDimensions(0, 0
-        );
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 0000000..76bff1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 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.wallpapers.canvas;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+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.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+    private static final int LOW_BMP_WIDTH = 128;
+    private static final int LOW_BMP_HEIGHT = 128;
+    private static final int HIGH_BMP_WIDTH = 3000;
+    private static final int HIGH_BMP_HEIGHT = 4000;
+    private static final int VERY_LOW_BMP_WIDTH = 1;
+    private static final int VERY_LOW_BMP_HEIGHT = 1;
+    private static final int DISPLAY_WIDTH = 1920;
+    private static final int DISPLAY_HEIGHT = 1080;
+
+    private static final int PAGES_LOW = 4;
+    private static final int PAGES_HIGH = 7;
+
+    private static final int MIN_AREAS = 4;
+    private static final int MAX_AREAS = 10;
+
+    private int mMiniBitmapWidth;
+    private int mMiniBitmapHeight;
+
+    @Mock
+    private Executor mBackgroundExecutor;
+
+    private int mColorsProcessed;
+    private int mMiniBitmapUpdatedCount;
+    private int mActivatedCount;
+    private int mDeactivatedCount;
+
+    @Before
+    public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation ->  {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mBackgroundExecutor).execute(any(Runnable.class));
+    }
+
+    private void resetCounters() {
+        mColorsProcessed = 0;
+        mMiniBitmapUpdatedCount = 0;
+        mActivatedCount = 0;
+        mDeactivatedCount = 0;
+    }
+
+    private Bitmap getMockBitmap(int width, int height) {
+        Bitmap bitmap = mock(Bitmap.class);
+        when(bitmap.getWidth()).thenReturn(width);
+        when(bitmap.getHeight()).thenReturn(height);
+        return bitmap;
+    }
+
+    private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+        WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+                mBackgroundExecutor,
+                new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+                    @Override
+                    public void onColorsProcessed(List<RectF> regions,
+                            List<WallpaperColors> colors) {
+                        assertThat(regions.size()).isEqualTo(colors.size());
+                        mColorsProcessed += regions.size();
+                    }
+
+                    @Override
+                    public void onMiniBitmapUpdated() {
+                        mMiniBitmapUpdatedCount++;
+                    }
+
+                    @Override
+                    public void onActivated() {
+                        mActivatedCount++;
+                    }
+
+                    @Override
+                    public void onDeactivated() {
+                        mDeactivatedCount++;
+                    }
+                });
+        WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+        doAnswer(invocation -> {
+            mMiniBitmapWidth = invocation.getArgument(1);
+            mMiniBitmapHeight = invocation.getArgument(2);
+            return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+        }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+        doAnswer(invocation -> getMockBitmap(
+                        invocation.getArgument(1),
+                        invocation.getArgument(2)))
+                .when(spyWallpaperColorExtractor)
+                .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+        doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+                .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+        return spyWallpaperColorExtractor;
+    }
+
+    private RectF randomArea() {
+        float width = (float) Math.random();
+        float startX = (float) (Math.random() * (1 - width));
+        float height = (float) Math.random();
+        float startY = (float) (Math.random() * (1 - height));
+        return new RectF(startX, startY, startX + width, startY + height);
+    }
+
+    private List<RectF> listOfRandomAreas(int min, int max) {
+        int nAreas = randomBetween(min, max);
+        List<RectF> result = new ArrayList<>();
+        for (int i = 0; i < nAreas; i++) {
+            result.add(randomArea());
+        }
+        return result;
+    }
+
+    private int randomBetween(int minIncluded, int maxIncluded) {
+        return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+    }
+
+    /**
+     * Test that for bitmaps of random dimensions, the mini bitmap is always created
+     * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+     */
+    @Test
+    public void testMiniBitmapCreation() {
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+            int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+            Bitmap bitmap = getMockBitmap(width, height);
+            spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+                    .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+        }
+    }
+
+    /**
+     * Test that for bitmaps with both width and height <= SMALL_SIDE,
+     * the mini bitmap is always created with both width and height <= SMALL_SIDE
+     */
+    @Test
+    public void testSmallMiniBitmapCreation() {
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+            int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+            Bitmap bitmap = getMockBitmap(width, height);
+            spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+                    .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+        }
+    }
+
+    /**
+     * Test that for a new color extractor with information
+     * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+     * the colors are processed and all the callbacks are properly executed.
+     */
+    @Test
+    public void testNewColorExtraction() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+            List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+            int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+            List<Runnable> tasks = Arrays.asList(
+                    () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+                    () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+                    () -> spyWallpaperColorExtractor.setDisplayDimensions(
+                            DISPLAY_WIDTH, DISPLAY_HEIGHT),
+                    () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+                            regions));
+            Collections.shuffle(tasks);
+            tasks.forEach(Runnable::run);
+
+            assertThat(mActivatedCount).isEqualTo(1);
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+            spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+            assertThat(mDeactivatedCount).isEqualTo(1);
+        }
+    }
+
+    /**
+     * Test that the method removeLocalColorAreas behaves properly and does not call
+     * the onDeactivated callback unless all color areas are removed.
+     */
+    @Test
+    public void testRemoveColors() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        int nSimulations = 10;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+            WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+            List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+            List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+            List<RectF> regions = new ArrayList<>();
+            regions.addAll(regions1);
+            regions.addAll(regions2);
+            int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+            List<Runnable> tasks = Arrays.asList(
+                    () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+                    () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+                    () -> spyWallpaperColorExtractor.setDisplayDimensions(
+                            DISPLAY_WIDTH, DISPLAY_HEIGHT),
+                    () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+            spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+            assertThat(mActivatedCount).isEqualTo(1);
+            Collections.shuffle(tasks);
+            tasks.forEach(Runnable::run);
+
+            assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            assertThat(mDeactivatedCount).isEqualTo(0);
+            spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+            assertThat(mDeactivatedCount).isEqualTo(1);
+        }
+    }
+
+    /**
+     * Test that if we change some information (wallpaper bitmap, number of pages),
+     * the colors are correctly recomputed.
+     * Test that if we remove some color areas in the middle of the process,
+     * only the remaining areas are recomputed.
+     */
+    @Test
+    public void testRecomputeColorExtraction() {
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+        List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+        List<RectF> regions = new ArrayList<>();
+        regions.addAll(regions1);
+        regions.addAll(regions2);
+        spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+        assertThat(mActivatedCount).isEqualTo(1);
+        int nPages = PAGES_LOW;
+        spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+        spyWallpaperColorExtractor.onPageChanged(nPages);
+        spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+        int nSimulations = 20;
+        for (int i = 0; i < nSimulations; i++) {
+            resetCounters();
+
+            // verify that if we remove some regions, they are not recomputed after other changes
+            if (i == nSimulations / 2) {
+                regions.removeAll(regions2);
+                spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+            }
+
+            if (Math.random() >= 0.5) {
+                int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+                if (nPagesNew == nPages) continue;
+                nPages = nPagesNew;
+                spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+            } else {
+                Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+                spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+                assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+            }
+            assertThat(mColorsProcessed).isEqualTo(regions.size());
+        }
+        spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+        assertThat(mDeactivatedCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testCleanUp() {
+        resetCounters();
+        Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+        doNothing().when(bitmap).recycle();
+        WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+        spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+        spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+        assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+        spyWallpaperColorExtractor.cleanUp();
+        spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+        assertThat(mColorsProcessed).isEqualTo(0);
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 53dcc8d..bb646f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -37,10 +37,18 @@
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger,
     userTracker: UserTracker
-) : BroadcastDispatcher(
-    context, looper, executor, dumpManager, logger, userTracker, PendingRemovalStore(logger)) {
+) :
+    BroadcastDispatcher(
+        context,
+        looper,
+        executor,
+        dumpManager,
+        logger,
+        userTracker,
+        PendingRemovalStore(logger)
+    ) {
 
-    private val registeredReceivers = ArraySet<BroadcastReceiver>()
+    val registeredReceivers = ArraySet<BroadcastReceiver>()
 
     override fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
@@ -78,4 +86,4 @@
         }
         registeredReceivers.clear()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 42b434a..725b1f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -44,6 +44,10 @@
     private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
+    override fun isKeyguardShowing(): Boolean {
+        return _isKeyguardShowing.value
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index b2b1764..9726bf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -26,20 +26,24 @@
 
 /** A fake [UserTracker] to be used in tests. */
 class FakeUserTracker(
-    userId: Int = 0,
-    userHandle: UserHandle = UserHandle.of(userId),
-    userInfo: UserInfo = mock(),
-    userProfiles: List<UserInfo> = emptyList(),
+    private var _userId: Int = 0,
+    private var _userHandle: UserHandle = UserHandle.of(_userId),
+    private var _userInfo: UserInfo = mock(),
+    private var _userProfiles: List<UserInfo> = emptyList(),
     userContentResolver: ContentResolver = MockContentResolver(),
     userContext: Context = mock(),
     private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
 ) : UserTracker {
     val callbacks = mutableListOf<UserTracker.Callback>()
 
-    override val userId: Int = userId
-    override val userHandle: UserHandle = userHandle
-    override val userInfo: UserInfo = userInfo
-    override val userProfiles: List<UserInfo> = userProfiles
+    override val userId: Int
+        get() = _userId
+    override val userHandle: UserHandle
+        get() = _userHandle
+    override val userInfo: UserInfo
+        get() = _userInfo
+    override val userProfiles: List<UserInfo>
+        get() = _userProfiles
 
     override val userContentResolver: ContentResolver = userContentResolver
     override val userContext: Context = userContext
@@ -55,4 +59,13 @@
     override fun createCurrentUserContext(context: Context): Context {
         return onCreateCurrentUserContext(context)
     }
+
+    fun set(userInfos: List<UserInfo>, selectedUserIndex: Int) {
+        _userProfiles = userInfos
+        _userInfo = userInfos[selectedUserIndex]
+        _userId = _userInfo.id
+        _userHandle = UserHandle.of(_userId)
+
+        callbacks.forEach { it.onUserChanged(_userId, userContext) }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
new file mode 100644
index 0000000..59f24ef
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.telephony.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTelephonyRepository : TelephonyRepository {
+
+    private val _callState = MutableStateFlow(0)
+    override val callState: Flow<Int> = _callState.asStateFlow()
+
+    fun setCallState(value: Int) {
+        _callState.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 20f1e36..4df8aa4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -17,12 +17,18 @@
 
 package com.android.systemui.user.data.repository
 
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
 
@@ -34,21 +40,71 @@
     private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
     override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
 
+    private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
+    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+        _userSwitcherSettings.asStateFlow()
+
+    private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
+    override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
+
+    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
+    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+
+    override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+
     private val _isActionableWhenLocked = MutableStateFlow(false)
     override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
 
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
         get() = _isGuestUserAutoCreated
-    private var _isGuestUserResetting: Boolean = false
-    override val isGuestUserResetting: Boolean
-        get() = _isGuestUserResetting
+
+    override var isGuestUserResetting: Boolean = false
+
+    override val isGuestUserCreationScheduled = AtomicBoolean()
+
+    override var secondaryUserId: Int = UserHandle.USER_NULL
+
+    override var isRefreshUsersPaused: Boolean = false
+
+    var refreshUsersCallCount: Int = 0
+        private set
+
+    override fun refreshUsers() {
+        refreshUsersCallCount++
+    }
+
+    override fun getSelectedUserInfo(): UserInfo {
+        return checkNotNull(_selectedUserInfo.value)
+    }
+
+    override fun isSimpleUserSwitcher(): Boolean {
+        return _userSwitcherSettings.value.isSimpleUserSwitcher
+    }
+
+    fun setUserInfos(infos: List<UserInfo>) {
+        _userInfos.value = infos
+    }
+
+    suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+        check(_userInfos.value.contains(userInfo)) {
+            "Cannot select the following user, it is not in the list of user infos: $userInfo!"
+        }
+
+        _selectedUserInfo.value = userInfo
+        yield()
+    }
+
+    suspend fun setSettings(settings: UserSwitcherSettingsModel) {
+        _userSwitcherSettings.value = settings
+        yield()
+    }
 
     fun setUsers(models: List<UserModel>) {
         _users.value = models
     }
 
-    fun setSelectedUser(userId: Int) {
+    suspend fun setSelectedUser(userId: Int) {
         check(_users.value.find { it.id == userId } != null) {
             "Cannot select a user with ID $userId - no user with that ID found!"
         }
@@ -62,6 +118,7 @@
                 }
             }
         )
+        yield()
     }
 
     fun setActions(models: List<UserActionModel>) {
@@ -75,8 +132,4 @@
     fun setGuestUserAutoCreated(value: Boolean) {
         _isGuestUserAutoCreated = value
     }
-
-    fun setGuestUserResetting(value: Boolean) {
-        _isGuestUserResetting = value
-    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67ed..23c7a61 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -70,6 +70,10 @@
     }
 
     @Override
+    public void setNewMobileIconSubIds(List<Integer> subIds) {
+    }
+
+    @Override
     public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
     }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index abc4937..a94e4b9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1152,6 +1152,9 @@
     }
 
     private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+        if (packageInfo == null) {
+            return;
+        }
         if (containsEither(packageInfo.requestedPermissions,
                 android.Manifest.permission.RUN_IN_BACKGROUND,
                 android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index f523773..451a700 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -54,12 +54,19 @@
     private static final String PROPERTY_PRIMARY_TAG =
             "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
 
-    static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
+    @Nullable
+    static PackageInfo getPackageInfo(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         final PackageManager pm = context.getPackageManager();
         final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
-        return Binder.withCleanCallingIdentity(() ->
-                pm.getPackageInfoAsUser(packageName, flags , userId));
+        return Binder.withCleanCallingIdentity(() -> {
+            try {
+                return pm.getPackageInfoAsUser(packageName, flags, userId);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "Package [" + packageName + "] is not found.");
+                return null;
+            }
+        });
     }
 
     static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index e9dd836..8d878af 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1831,7 +1831,7 @@
         if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
                 || mWindowManager.isKeyguardShowingAndNotOccluded()
                 || !mPowerManager.isInteractive())) {
-            Sandman.startDreamWhenDockedIfAppropriate(getContext());
+            mInjector.startDreamWhenDockedIfAppropriate(getContext());
         }
     }
 
@@ -2145,5 +2145,9 @@
         public int getCallingUid() {
             return Binder.getCallingUid();
         }
+
+        public void startDreamWhenDockedIfAppropriate(Context context) {
+            Sandman.startDreamWhenDockedIfAppropriate(context);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 6b731c3..c128b5e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -89,6 +89,7 @@
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -3100,7 +3101,7 @@
                              */
                             if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent)) {
+                                    result)) {
                                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                                         "invalid intent in bundle returned");
                                 return;
@@ -3519,7 +3520,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
@@ -4870,7 +4871,13 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-         protected boolean checkKeyIntent(int authUid, Intent intent) {
+        protected boolean checkKeyIntent(int authUid, Bundle bundle) {
+            if (!checkKeyIntentParceledCorrectly(bundle)) {
+            	EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
+                return false;
+            }
+
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
             // Explicitly set an empty ClipData to ensure that we don't offer to
             // promote any Uris contained inside for granting purposes
             if (intent.getClipData() == null) {
@@ -4905,6 +4912,25 @@
             }
         }
 
+        /**
+         * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't
+         * violate our security policy.
+         *
+         * In particular we want to make sure the Authenticator doesn't trick users
+         * into launching arbitrary intents on the device via exploiting any other Parcel read/write
+         * mismatch problems.
+         */
+        private boolean checkKeyIntentParceledCorrectly(Bundle bundle) {
+            Parcel p = Parcel.obtain();
+            p.writeBundle(bundle);
+            p.setDataPosition(0);
+            Bundle simulateBundle = p.readBundle();
+            p.recycle();
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+            return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+                Intent.class)));
+        }
+
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
             String className = activityInfo.name;
             return "android".equals(activityInfo.packageName) &&
@@ -5051,7 +5077,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9840e0f..9669c06 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4219,7 +4219,8 @@
         final String procName = r.processName;
         HostingRecord hostingRecord = new HostingRecord(
                 HostingRecord.HOSTING_TYPE_SERVICE, r.instanceName,
-                r.definingPackageName, r.definingUid, r.serviceInfo.processName);
+                r.definingPackageName, r.definingUid, r.serviceInfo.processName,
+                getHostingRecordTriggerType(r));
         ProcessRecord app;
 
         if (!isolated) {
@@ -4323,6 +4324,14 @@
         return null;
     }
 
+    private String getHostingRecordTriggerType(ServiceRecord r) {
+        if (Manifest.permission.BIND_JOB_SERVICE.equals(r.permission)
+                && r.mRecentCallingUid == SYSTEM_UID) {
+            return HostingRecord.TRIGGER_TYPE_JOB;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
+
     private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
             throws TransactionTooLargeException {
         for (int i=r.bindings.size()-1; i>=0; i--) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d4b760f..df5113b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5008,7 +5008,8 @@
                 hostingRecord.getType(),
                 hostingRecord.getName(),
                 shortAction,
-                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()));
+                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+                HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 606a09c..207c10c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -36,6 +36,7 @@
 import android.net.INetworkManagementEventObserver;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.os.BatteryConsumer;
 import android.os.BatteryManagerInternal;
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
@@ -2282,6 +2283,10 @@
         pw.println("  --settings: dump the settings key/values related to batterystats");
         pw.println("  --cpu: dump cpu stats for debugging purpose");
         pw.println("  --power-profile: dump the power profile constants");
+        pw.println("  --usage: write battery usage stats. Optional arguments:");
+        pw.println("     --proto: output as a binary protobuffer");
+        pw.println("     --model power-profile: use the power profile model"
+                + " even if measured energy is available");
         pw.println("  <package.name>: optional name of package to filter output by.");
         pw.println("  -h: print this help text.");
         pw.println("Battery stats (batterystats) commands:");
@@ -2325,6 +2330,31 @@
         }
     }
 
+    private void dumpUsageStatsToProto(FileDescriptor fd, PrintWriter pw, int model,
+            boolean proto) {
+        awaitCompletion();
+        syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
+
+        BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
+                .setMaxStatsAgeMs(0)
+                .includeProcessStateData()
+                .includePowerModels();
+        if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
+            builder.powerProfileModeledOnly();
+        }
+        BatteryUsageStatsQuery query = builder.build();
+        synchronized (mStats) {
+            mStats.prepareForDumpLocked();
+            BatteryUsageStats batteryUsageStats =
+                    mBatteryUsageStatsProvider.getBatteryUsageStats(query);
+            if (proto) {
+                batteryUsageStats.dumpToProto(fd);
+            } else {
+                batteryUsageStats.dump(pw, "");
+            }
+        }
+    }
+
     private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
         i++;
         if (i >= args.length) {
@@ -2478,6 +2508,35 @@
                 } else if ("--power-profile".equals(arg)) {
                     dumpPowerProfile(pw);
                     return;
+                } else if ("--usage".equals(arg)) {
+                    int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
+                    boolean proto = false;
+                    for (int j = i + 1; j < args.length; j++) {
+                        switch (args[j]) {
+                            case "--proto":
+                                proto = true;
+                                break;
+                            case "--model": {
+                                if (j + 1 < args.length) {
+                                    j++;
+                                    if ("power-profile".equals(args[j])) {
+                                        model = BatteryConsumer.POWER_MODEL_POWER_PROFILE;
+                                    } else {
+                                        pw.println("Unknown power model: " + args[j]);
+                                        dumpHelp(pw);
+                                        return;
+                                    }
+                                } else {
+                                    pw.println("--model without a value");
+                                    dumpHelp(pw);
+                                    return;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                    dumpUsageStatsToProto(fd, pw, model, proto);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 5856949..f366cec 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1921,7 +1921,7 @@
                 info.activityInfo.applicationInfo, true,
                 r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                 new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction()),
+                        r.intent.getAction(), getHostingRecordTriggerType(r)),
                 isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
                 (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
         if (r.curApp == null) {
@@ -1944,6 +1944,16 @@
         mPendingBroadcastRecvIndex = recIdx;
     }
 
+    private String getHostingRecordTriggerType(BroadcastRecord r) {
+        if (r.alarm) {
+            return HostingRecord.TRIGGER_TYPE_ALARM;
+        } else if (r.pushMessage) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+        } else if (r.pushMessageOverQuota) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
 
     @Nullable
     private String getTargetPackage(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528b..baaae1d 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -71,6 +71,8 @@
     final boolean ordered;  // serialize the send to receivers?
     final boolean sticky;   // originated from existing sticky data?
     final boolean alarm;    // originated from an alarm triggering?
+    final boolean pushMessage; // originated from a push message?
+    final boolean pushMessageOverQuota; // originated from a push message which was over quota?
     final boolean initialSticky; // initial broadcast from register to sticky?
     final int userId;       // user id this broadcast was for
     final String resolvedType; // the resolved data type
@@ -309,6 +311,8 @@
         mBackgroundActivityStartsToken = backgroundActivityStartsToken;
         this.timeoutExempt = timeoutExempt;
         alarm = options != null && options.isAlarmBroadcast();
+        pushMessage = options != null && options.isPushMessagingBroadcast();
+        pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
     }
 
     /**
@@ -362,6 +366,8 @@
         mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
         timeoutExempt = from.timeoutExempt;
         alarm = from.alarm;
+        pushMessage = from.pushMessage;
+        pushMessageOverQuota = from.pushMessageOverQuota;
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index f88a8ce..30811a1 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -16,10 +16,30 @@
 
 package com.android.server.am;
 
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
-import android.os.ProcessStartTime;
 
 /**
  * This class describes various information required to start a process.
@@ -32,6 +52,9 @@
  *
  * The {@code mHostingZygote} field describes from which Zygote the new process should be spawned.
  *
+ * The {@code mTriggerType} field describes the trigger that started this processs. This could be
+ * an alarm or a push-message for a broadcast, for example. This is purely for logging and stats.
+ *
  * {@code mDefiningPackageName} contains the packageName of the package that defines the
  * component we want to start; this can be different from the packageName and uid in the
  * ApplicationInfo that we're creating the process with, in case the service is a
@@ -71,7 +94,13 @@
     public static final String HOSTING_TYPE_TOP_ACTIVITY = "top-activity";
     public static final String HOSTING_TYPE_EMPTY = "";
 
-    private @NonNull final String mHostingType;
+    public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
+    public static final String TRIGGER_TYPE_ALARM = "alarm";
+    public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+    public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
+    public static final String TRIGGER_TYPE_JOB = "job";
+
+    @NonNull private final String mHostingType;
     private final String mHostingName;
     private final int mHostingZygote;
     private final String mDefiningPackageName;
@@ -79,11 +108,12 @@
     private final boolean mIsTopApp;
     private final String mDefiningProcessName;
     @Nullable private final String mAction;
+    @NonNull private final String mTriggerType;
 
     public HostingRecord(@NonNull String hostingType) {
         this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
                 -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
-                null /* action */);
+                null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName) {
@@ -91,22 +121,24 @@
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
-            @Nullable String action) {
+            @Nullable String action, @Nullable String triggerType) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
                 null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */,
-                null /* definingProcessName */, action);
+                null /* definingProcessName */, action, triggerType);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
-            String definingPackageName, int definingUid, String definingProcessName) {
+            String definingPackageName, int definingUid, String definingProcessName,
+            String triggerType) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
-                definingUid, false /* isTopApp */, definingProcessName, null /* action */);
+                definingUid, false /* isTopApp */, definingProcessName, null /* action */,
+                triggerType);
     }
 
     public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
         this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
                 null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
-                null /* definingProcessName */, null /* action */);
+                null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     public HostingRecord(@NonNull String hostingType, String hostingName) {
@@ -121,12 +153,12 @@
     private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote) {
         this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
                 -1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
-                null /* action */);
+                null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote,
             String definingPackageName, int definingUid, boolean isTopApp,
-            String definingProcessName, @Nullable String action) {
+            String definingProcessName, @Nullable String action, String triggerType) {
         mHostingType = hostingType;
         mHostingName = hostingName;
         mHostingZygote = hostingZygote;
@@ -135,6 +167,7 @@
         mIsTopApp = isTopApp;
         mDefiningProcessName = definingProcessName;
         mAction = action;
+        mTriggerType = triggerType;
     }
 
     public @NonNull String getType() {
@@ -188,6 +221,11 @@
         return mAction;
     }
 
+    /** Returns the type of trigger that led to this process start. */
+    public @NonNull String getTriggerType() {
+        return mTriggerType;
+    }
+
     /**
      * Creates a HostingRecord for a process that must spawn from the webview zygote
      * @param hostingName name of the component to be hosted in this process
@@ -197,7 +235,7 @@
             String definingPackageName, int definingUid, String definingProcessName) {
         return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
                 WEBVIEW_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
-                definingProcessName, null /* action */);
+                definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     /**
@@ -211,7 +249,7 @@
             int definingUid, String definingProcessName) {
         return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
                 APP_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
-                definingProcessName, null /* action */);
+                definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
     }
 
     /**
@@ -236,35 +274,55 @@
     public static int getHostingTypeIdStatsd(@NonNull String hostingType) {
         switch(hostingType) {
             case HOSTING_TYPE_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
             case HOSTING_TYPE_ADDED_APPLICATION:
-                return ProcessStartTime.HOSTING_TYPE_ADDED_APPLICATION;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
             case HOSTING_TYPE_BACKUP:
-                return ProcessStartTime.HOSTING_TYPE_BACKUP;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
             case HOSTING_TYPE_BROADCAST:
-                return ProcessStartTime.HOSTING_TYPE_BROADCAST;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
             case HOSTING_TYPE_CONTENT_PROVIDER:
-                return ProcessStartTime.HOSTING_TYPE_CONTENT_PROVIDER;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
             case HOSTING_TYPE_LINK_FAIL:
-                return ProcessStartTime.HOSTING_TYPE_LINK_FAIL;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
             case HOSTING_TYPE_ON_HOLD:
-                return ProcessStartTime.HOSTING_TYPE_ON_HOLD;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
             case HOSTING_TYPE_NEXT_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_NEXT_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
             case HOSTING_TYPE_NEXT_TOP_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
             case HOSTING_TYPE_RESTART:
-                return ProcessStartTime.HOSTING_TYPE_RESTART;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
             case HOSTING_TYPE_SERVICE:
-                return ProcessStartTime.HOSTING_TYPE_SERVICE;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
             case HOSTING_TYPE_SYSTEM:
-                return ProcessStartTime.HOSTING_TYPE_SYSTEM;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
             case HOSTING_TYPE_TOP_ACTIVITY:
-                return ProcessStartTime.HOSTING_TYPE_TOP_ACTIVITY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
             case HOSTING_TYPE_EMPTY:
-                return ProcessStartTime.HOSTING_TYPE_EMPTY;
+                return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
             default:
-                return ProcessStartTime.HOSTING_TYPE_UNKNOWN;
+                return PROCESS_START_TIME__TYPE__UNKNOWN;
+        }
+    }
+
+    /**
+     * Map the string triggerType to enum TriggerType defined in ProcessStartTime proto.
+     * @param triggerType
+     * @return enum TriggerType defined in ProcessStartTime proto
+     */
+    public static int getTriggerTypeForStatsd(@NonNull String triggerType) {
+        switch(triggerType) {
+            case TRIGGER_TYPE_ALARM:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+            case TRIGGER_TYPE_PUSH_MESSAGE:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
+            case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+            case TRIGGER_TYPE_JOB:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
+            default:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
         }
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0d08db9..736914a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1365,7 +1365,8 @@
                     break;
                 case MSG_II_SET_HEARING_AID_VOLUME:
                     synchronized (mDeviceStateLock) {
-                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2,
+                                mDeviceInventory.isHearingAidConnected());
                     }
                     break;
                 case MSG_II_SET_LE_AUDIO_OUT_VOLUME: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ee0d79f..35da73e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -377,7 +377,8 @@
                         makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
                         makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+                                streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
+                                btInfo.mAudioSystemDevice,
                                 "onSetBtActiveDevice");
                     }
                     break;
@@ -1161,6 +1162,22 @@
                 .record();
     }
 
+    /**
+     * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
+     * Visibility by APM plays no role
+     * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
+     */
+    boolean isHearingAidConnected() {
+        synchronized (mDevicesLock) {
+            for (DeviceInfo di : mConnectedDevices.values()) {
+                if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
             int volumeIndex, int device, String eventSource) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0b6b890..1827781 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4011,7 +4011,7 @@
         }
     }
 
-    private void setLeAudioVolumeOnModeUpdate(int mode) {
+    private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
         switch (mode) {
             case AudioSystem.MODE_IN_COMMUNICATION:
             case AudioSystem.MODE_IN_CALL:
@@ -4025,8 +4025,6 @@
                 return;
         }
 
-        int streamType = getBluetoothContextualVolumeStream(mode);
-
         // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
         // (See AudioDeviceBroker#createBtDeviceInfo())
         int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
@@ -4037,6 +4035,7 @@
                     + index + " maxIndex=" + maxIndex + " streamType=" + streamType);
         }
         mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+        mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "setLeAudioVolumeOnModeUpdate");
     }
 
     private void setStreamVolume(int streamType, int index, int flags,
@@ -5417,7 +5416,7 @@
 
                 // Forcefully set LE audio volume as a workaround, since the value of 'device'
                 // is not DEVICE_OUT_BLE_* even when BLE is connected.
-                setLeAudioVolumeOnModeUpdate(mode);
+                setLeAudioVolumeOnModeUpdate(mode, streamType, device);
 
                 // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
                 // connections not started by the application changing the mode when pid changes
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index d0f5470..6cd42f8 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -424,7 +424,8 @@
         mLeAudio.setVolume(volume);
     }
 
-    /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
+    /*package*/ synchronized void setHearingAidVolume(int index, int streamType,
+            boolean isHeadAidConnected) {
         if (mHearingAid == null) {
             if (AudioService.DEBUG_VOL) {
                 Log.i(TAG, "setHearingAidVolume: null mHearingAid");
@@ -441,8 +442,11 @@
             Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
                     + index + " gain=" + gainDB);
         }
-        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
-                AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+        // do not log when hearing aid is not connected to avoid confusion when reading dumpsys
+        if (isHeadAidConnected) {
+            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                    AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+        }
         mHearingAid.setVolume(gainDB);
     }
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a7d3729..40e28da 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -592,10 +592,14 @@
         }
 
         pw.println();
+        pw.println("  mAmbientBrightnessThresholds=");
         mAmbientBrightnessThresholds.dump(pw);
+        pw.println("  mScreenBrightnessThresholds=");
         mScreenBrightnessThresholds.dump(pw);
+        pw.println("  mScreenBrightnessThresholdsIdle=");
         mScreenBrightnessThresholdsIdle.dump(pw);
-        mScreenBrightnessThresholdsIdle.dump(pw);
+        pw.println("  mAmbientBrightnessThresholdsIdle=");
+        mAmbientBrightnessThresholdsIdle.dump(pw);
     }
 
     private String configStateToString(int state) {
@@ -860,6 +864,7 @@
                 Slog.d(TAG, "updateAmbientLux: "
                         + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
                         + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+                        + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
                         + "mAmbientLux=" + mAmbientLux);
             }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4165186..1ae37bb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -27,6 +27,7 @@
 import android.os.PowerManager;
 import android.text.TextUtils;
 import android.util.MathUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.Spline;
 import android.view.DisplayAddress;
@@ -51,7 +52,7 @@
 import com.android.server.display.config.SensorDetails;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.Thresholds;
+import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.XmlParser;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -188,42 +189,153 @@
  *      <ambientLightHorizonLong>10001</ambientLightHorizonLong>
  *      <ambientLightHorizonShort>2001</ambientLightHorizonShort>
  *
- *      <displayBrightnessChangeThresholds> // Thresholds for screen changes
- *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
- *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.002</minimum>  // Minimum change needed in screen brightness to darken.
- *        </darkeningThresholds>
- *      </displayBrightnessChangeThresholds>
- *
- *      <ambientBrightnessChangeThresholds> // Thresholds for lux changes
- *        <brighteningThresholds>     // Thresholds for active mode brightness changes.
- *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.004</minimum>  // Minimum change needed in ambient brightness to darken.
- *        </darkeningThresholds>
- *      </ambientBrightnessChangeThresholds>
- *
- *      <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
- *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
- *          <minimum>0.001</minimum>  // Minimum change needed in screen brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.002</minimum>  // Minimum change needed in screen brightness to darken.
- *        </darkeningThresholds>
- *      </displayBrightnessChangeThresholdsIdle>
- *
- *      <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
- *        <brighteningThresholds>     // Thresholds for idle mode brightness changes.
- *          <minimum>0.003</minimum>  // Minimum change needed in ambient brightness to brighten.
- *        </brighteningThresholds>
- *        <darkeningThresholds>
- *          <minimum>0.004</minimum>  // Minimum change needed in ambient brightness to darken.
- *        </darkeningThresholds>
- *      </ambientBrightnessChangeThresholdsIdle>
- *
+ *     <ambientBrightnessChangeThresholds>  // Thresholds for lux changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in ambient brightness to brighten screen.
+ *             <minimum>10</minimum>
+ *             // Percentage increase of lux needed to increase the screen brightness at a lux range
+ *             // above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>13</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>100</threshold><percentage>14</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>200</threshold><percentage>15</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+*             // Minimum change needed in ambient brightness to darken screen.
+ *             <minimum>30</minimum>
+ *             // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ *             // above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>15</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>300</threshold><percentage>16</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>400</threshold><percentage>17</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </ambientBrightnessChangeThresholds>
+ *     <displayBrightnessChangeThresholds>   // Thresholds for screen brightness changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in screen brightness to brighten screen.
+ *             <minimum>0.1</minimum>
+ *             // Percentage increase of screen brightness needed to increase the screen brightness
+ *             // at a lux range above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold>
+ *                     <percentage>9</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.10</threshold>
+ *                     <percentage>10</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.20</threshold>
+ *                     <percentage>11</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in screen brightness to darken screen.
+ *             <minimum>0.3</minimum>
+ *             // Percentage increase of screen brightness needed to decrease the screen brightness
+ *             // at a lux range above the specified threshold.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>11</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.11</threshold><percentage>12</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.21</threshold><percentage>13</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </displayBrightnessChangeThresholds>
+ *     <ambientBrightnessChangeThresholdsIdle>   // Thresholds for lux changes in idle mode
+ *         <brighteningThresholds>
+ *             // Minimum change needed in ambient brightness to brighten screen in idle mode
+ *             <minimum>20</minimum>
+ *             // Percentage increase of lux needed to increase the screen brightness at a lux range
+ *             // above the specified threshold whilst in idle mode.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>21</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>500</threshold><percentage>22</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>600</threshold><percentage>23</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in ambient brightness to darken screen in idle mode
+ *             <minimum>40</minimum>
+ *             // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ *             // above the specified threshold whilst in idle mode.
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>23</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>700</threshold><percentage>24</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>800</threshold><percentage>25</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </ambientBrightnessChangeThresholdsIdle>
+ *     <displayBrightnessChangeThresholdsIdle>    // Thresholds for idle screen brightness changes
+ *         <brighteningThresholds>
+ *             // Minimum change needed in screen brightness to brighten screen in idle mode
+ *             <minimum>0.2</minimum>
+ *             // Percentage increase of screen brightness needed to increase the screen brightness
+ *             // at a lux range above the specified threshold whilst in idle mode
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>17</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.12</threshold><percentage>18</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.22</threshold><percentage>19</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </brighteningThresholds>
+ *         <darkeningThresholds>
+ *             // Minimum change needed in screen brightness to darken screen in idle mode
+ *             <minimum>0.4</minimum>
+ *             // Percentage increase of screen brightness needed to decrease the screen brightness
+ *             // at a lux range above the specified threshold whilst in idle mode
+ *             <brightnessThresholdPoints>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0</threshold><percentage>19</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.13</threshold><percentage>20</percentage>
+ *                 </brightnessThresholdPoint>
+ *                 <brightnessThresholdPoint>
+ *                     <threshold>0.23</threshold><percentage>21</percentage>
+ *                 </brightnessThresholdPoint>
+ *             </brightnessThresholdPoints>
+ *         </darkeningThresholds>
+ *     </displayBrightnessChangeThresholdsIdle>
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -247,6 +359,13 @@
     private static final String NO_SUFFIX_FORMAT = "%d";
     private static final long STABLE_FLAG = 1L << 62;
 
+    private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+    private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
     private static final int INTERPOLATION_DEFAULT = 0;
     private static final int INTERPOLATION_LINEAR = 1;
 
@@ -344,6 +463,31 @@
     private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
     private float mAmbientLuxDarkeningMinThreshold = 0.0f;
     private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
+
+    // Screen brightness thresholds levels & percentages
+    private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+    private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+    // Screen brightness thresholds levels & percentages for idle mode
+    private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+    private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+    private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+    // Ambient brightness thresholds levels & percentages
+    private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+    private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+    // Ambient brightness thresholds levels & percentages for idle mode
+    private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+    private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+    private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
     private Spline mBrightnessToBacklightSpline;
     private Spline mBacklightToBrightnessSpline;
     private Spline mBacklightToNitsSpline;
@@ -684,7 +828,7 @@
     /**
      * The minimum value for the ambient lux increase for a screen brightness change to actually
      * occur.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxBrighteningMinThreshold() {
         return mAmbientLuxBrighteningMinThreshold;
@@ -693,7 +837,7 @@
     /**
      * The minimum value for the ambient lux decrease for a screen brightness change to actually
      * occur.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxDarkeningMinThreshold() {
         return mAmbientLuxDarkeningMinThreshold;
@@ -702,7 +846,7 @@
     /**
      * The minimum value for the ambient lux increase for a screen brightness change to actually
      * occur while in idle screen brightness mode.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxBrighteningMinThresholdIdle() {
         return mAmbientLuxBrighteningMinThresholdIdle;
@@ -711,12 +855,262 @@
     /**
      * The minimum value for the ambient lux decrease for a screen brightness change to actually
      * occur while in idle screen brightness mode.
-     * @return float value in brightness scale of 0 - 1.
+     * @return float value in lux.
      */
     public float getAmbientLuxDarkeningMinThresholdIdle() {
         return mAmbientLuxDarkeningMinThresholdIdle;
     }
 
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenBrighteningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentages[n]
+     * level[MAX] <= value             = mScreenBrighteningPercentages[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenBrighteningPercentages applies
+     */
+    public float[] getScreenBrighteningLevels() {
+        return mScreenBrighteningLevels;
+    }
+
+    /**
+     * The array that describes the screen brightening threshold percentage change at each screen
+     * brightness level described in mScreenBrighteningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change
+     */
+    public float[] getScreenBrighteningPercentages() {
+        return mScreenBrighteningPercentages;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenDarkeningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentages[n]
+     * level[MAX] <= value             = mScreenDarkeningPercentages[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenDarkeningPercentages applies
+     */
+    public float[] getScreenDarkeningLevels() {
+        return mScreenDarkeningLevels;
+    }
+
+    /**
+     * The array that describes the screen darkening threshold percentage change at each screen
+     * brightness level described in mScreenDarkeningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change
+     */
+    public float[] getScreenDarkeningPercentages() {
+        return mScreenDarkeningPercentages;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold
+     * percentage applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientBrighteningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentages[n]
+     * level[MAX] <= value             = mAmbientBrighteningPercentages[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientBrighteningPercentages applies
+     */
+    public float[] getAmbientBrighteningLevels() {
+        return mAmbientBrighteningLevels;
+    }
+
+    /**
+     * The array that describes the ambient brightening threshold percentage change at each ambient
+     * brightness level described in mAmbientBrighteningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change
+     */
+    public float[] getAmbientBrighteningPercentages() {
+        return mAmbientBrighteningPercentages;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientDarkeningLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentages[n]
+     * level[MAX] <= value             = mAmbientDarkeningPercentages[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientDarkeningPercentages applies
+     */
+    public float[] getAmbientDarkeningLevels() {
+        return mAmbientDarkeningLevels;
+    }
+
+    /**
+     * The array that describes the ambient darkening threshold percentage change at each ambient
+     * brightness level described in mAmbientDarkeningLevels.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change
+     */
+    public float[] getAmbientDarkeningPercentages() {
+        return mAmbientDarkeningPercentages;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenBrighteningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentagesIdle[n]
+     * level[MAX] <= value             = mScreenBrighteningPercentagesIdle[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenBrighteningPercentagesIdle applies
+     */
+    public float[] getScreenBrighteningLevelsIdle() {
+        return mScreenBrighteningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the screen brightening threshold percentage change at each screen
+     * brightness level described in mScreenBrighteningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of brightness increase required in order for the
+     * screen brightness to change while in idle mode.
+     */
+    public float[] getScreenBrighteningPercentagesIdle() {
+        return mScreenBrighteningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of screen brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current screen brightness value
+     * level = mScreenDarkeningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentagesIdle[n]
+     * level[MAX] <= value             = mScreenDarkeningPercentagesIdle[MAX]
+     *
+     * @return the screen brightness levels between 0.0 and 1.0 for which each
+     * mScreenDarkeningPercentagesIdle applies
+     */
+    public float[] getScreenDarkeningLevelsIdle() {
+        return mScreenDarkeningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the screen darkening threshold percentage change at each screen
+     * brightness level described in mScreenDarkeningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of brightness decrease required in order for the
+     * screen brightness to change while in idle mode.
+     */
+    public float[] getScreenDarkeningPercentagesIdle() {
+        return mScreenDarkeningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientBrighteningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentagesIdle[n]
+     * level[MAX] <= value             = mAmbientBrighteningPercentagesIdle[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientBrighteningPercentagesIdle applies
+     */
+    public float[] getAmbientBrighteningLevelsIdle() {
+        return mAmbientBrighteningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the ambient brightness threshold percentage change whilst in
+     * idle screen brightness mode at each ambient brightness level described in
+     * mAmbientBrighteningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of ambient brightness increase required in order
+     * for the screen brightness to change
+     */
+    public float[] getAmbientBrighteningPercentagesIdle() {
+        return mAmbientBrighteningPercentagesIdle;
+    }
+
+    /**
+     * The array that describes the range of ambient brightness that each threshold percentage
+     * applies within whilst in idle screen brightness mode.
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current ambient brightness value
+     * level = mAmbientDarkeningLevelsIdle
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentagesIdle[n]
+     * level[MAX] <= value             = mAmbientDarkeningPercentagesIdle[MAX]
+     *
+     * @return the ambient brightness levels from 0 lux upwards for which each
+     * mAmbientDarkeningPercentagesIdle applies
+     */
+    public float[] getAmbientDarkeningLevelsIdle() {
+        return mAmbientDarkeningLevelsIdle;
+    }
+
+    /**
+     * The array that describes the ambient brightness threshold percentage change whilst in
+     * idle screen brightness mode at each ambient brightness level described in
+     * mAmbientDarkeningLevelsIdle.
+     *
+     * @return the percentages between 0 and 100 of ambient brightness decrease required in order
+     * for the screen brightness to change
+     */
+    public float[] getAmbientDarkeningPercentagesIdle() {
+        return mAmbientDarkeningPercentagesIdle;
+    }
+
     SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
@@ -812,14 +1206,17 @@
                 + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
                 + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
                 + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+                + "\n"
                 + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
                 + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
                 + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
                 + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
                 + ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
                 + ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
+                + "\n"
                 + ", mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
+                + "\n"
                 + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
                 + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
                 + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
@@ -829,6 +1226,41 @@
                 + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
                 + ", mAmbientLuxBrighteningMinThresholdIdle="
                 + mAmbientLuxBrighteningMinThresholdIdle
+                + "\n"
+                + ", mScreenBrighteningLevels=" + Arrays.toString(
+                mScreenBrighteningLevels)
+                + ", mScreenBrighteningPercentages=" + Arrays.toString(
+                mScreenBrighteningPercentages)
+                + ", mScreenDarkeningLevels=" + Arrays.toString(
+                mScreenDarkeningLevels)
+                + ", mScreenDarkeningPercentages=" + Arrays.toString(
+                mScreenDarkeningPercentages)
+                + ", mAmbientBrighteningLevels=" + Arrays.toString(
+                mAmbientBrighteningLevels)
+                + ", mAmbientBrighteningPercentages=" + Arrays.toString(
+                mAmbientBrighteningPercentages)
+                + ", mAmbientDarkeningLevels=" + Arrays.toString(
+                mAmbientDarkeningLevels)
+                + ", mAmbientDarkeningPercentages=" + Arrays.toString(
+                mAmbientDarkeningPercentages)
+                + "\n"
+                + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+                mAmbientBrighteningLevelsIdle)
+                + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
+                mAmbientBrighteningPercentagesIdle)
+                + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
+                mAmbientDarkeningLevelsIdle)
+                + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
+                mAmbientDarkeningPercentagesIdle)
+                + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
+                mScreenBrighteningLevelsIdle)
+                + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
+                mScreenBrighteningPercentagesIdle)
+                + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
+                mScreenDarkeningLevelsIdle)
+                + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
+                mScreenDarkeningPercentagesIdle)
+                + "\n"
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -914,6 +1346,7 @@
         loadBrightnessMapFromConfigXml();
         loadBrightnessRampsFromConfigXml();
         loadAmbientLightSensorFromConfigXml();
+        loadBrightnessChangeThresholdsFromXml();
         setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
         mLoadedFrom = "<config.xml>";
@@ -1454,91 +1887,287 @@
         }
     }
 
+    private void loadBrightnessChangeThresholdsFromXml() {
+        loadBrightnessChangeThresholds(/* config= */ null);
+    }
+
     private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
-        Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
-        Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
-        Thresholds displayBrightnessThresholdsIdle =
-                config.getDisplayBrightnessChangeThresholdsIdle();
-        Thresholds ambientBrightnessThresholdsIdle =
-                config.getAmbientBrightnessChangeThresholdsIdle();
-
-        loadDisplayBrightnessThresholds(displayBrightnessThresholds);
-        loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
-        loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
-        loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+        loadDisplayBrightnessThresholds(config);
+        loadAmbientBrightnessThresholds(config);
+        loadDisplayBrightnessThresholdsIdle(config);
+        loadAmbientBrightnessThresholdsIdle(config);
     }
 
-    private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
-        if (displayBrightnessThresholds != null) {
-            BrightnessThresholds brighteningScreen =
-                    displayBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningScreen =
-                    displayBrightnessThresholds.getDarkeningThresholds();
+    private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
+        BrightnessThresholds brighteningScreen = null;
+        BrightnessThresholds darkeningScreen = null;
+        if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
+            brighteningScreen =
+                    config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
+            darkeningScreen =
+                    config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
 
-            if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
-                mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
-            }
-            if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
-                mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
-            }
+        }
+
+        // Screen bright/darkening threshold levels for active mode
+        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningScreen,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+
+        mScreenBrighteningLevels = screenBrighteningPair.first;
+        mScreenBrighteningPercentages = screenBrighteningPair.second;
+
+        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningScreen,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenDarkeningLevels = screenDarkeningPair.first;
+        mScreenDarkeningPercentages = screenDarkeningPair.second;
+
+        // Screen bright/darkening threshold minimums for active mode
+        if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+            mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
+        }
+        if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+            mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
         }
     }
 
-    private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
-        if (ambientBrightnessThresholds != null) {
-            BrightnessThresholds brighteningAmbientLux =
-                    ambientBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningAmbientLux =
-                    ambientBrightnessThresholds.getDarkeningThresholds();
+    private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
+        // Ambient Brightness Threshold Levels
+        BrightnessThresholds brighteningAmbientLux = null;
+        BrightnessThresholds darkeningAmbientLux = null;
+        if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
+            brighteningAmbientLux =
+                    config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
+            darkeningAmbientLux =
+                    config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
+        }
 
-            final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
-            final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+        // Ambient bright/darkening threshold levels for active mode
+        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningAmbientLux,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+        mAmbientBrighteningLevels = ambientBrighteningPair.first;
+        mAmbientBrighteningPercentages = ambientBrighteningPair.second;
 
-            if (ambientBrighteningThreshold != null) {
-                mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
-            }
-            if (ambientDarkeningThreshold != null) {
-                mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
-            }
+        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningAmbientLux,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+        mAmbientDarkeningLevels = ambientDarkeningPair.first;
+        mAmbientDarkeningPercentages = ambientDarkeningPair.second;
+
+        // Ambient bright/darkening threshold minimums for active/idle mode
+        if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
+            mAmbientLuxBrighteningMinThreshold =
+                    brighteningAmbientLux.getMinimum().floatValue();
+        }
+
+        if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
+            mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
         }
     }
 
-    private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
-        if (idleDisplayBrightnessThresholds != null) {
-            BrightnessThresholds brighteningScreenIdle =
-                    idleDisplayBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningScreenIdle =
-                    idleDisplayBrightnessThresholds.getDarkeningThresholds();
+    private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
+        BrightnessThresholds brighteningScreenIdle = null;
+        BrightnessThresholds darkeningScreenIdle = null;
+        if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
+            brighteningScreenIdle =
+                    config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+            darkeningScreenIdle =
+                    config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+        }
 
-            if (brighteningScreenIdle != null
-                    && brighteningScreenIdle.getMinimum() != null) {
-                mScreenBrighteningMinThresholdIdle =
-                        brighteningScreenIdle.getMinimum().floatValue();
-            }
-            if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
-                mScreenDarkeningMinThresholdIdle =
-                        darkeningScreenIdle.getMinimum().floatValue();
-            }
+        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningScreenIdle,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
+        mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
+
+        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningScreenIdle,
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                /* potentialOldBrightnessScale= */ true);
+        mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
+        mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
+
+        if (brighteningScreenIdle != null
+                && brighteningScreenIdle.getMinimum() != null) {
+            mScreenBrighteningMinThresholdIdle =
+                    brighteningScreenIdle.getMinimum().floatValue();
+        }
+        if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+            mScreenDarkeningMinThresholdIdle =
+                    darkeningScreenIdle.getMinimum().floatValue();
         }
     }
 
-    private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
-        if (idleAmbientBrightnessThresholds != null) {
-            BrightnessThresholds brighteningAmbientLuxIdle =
-                    idleAmbientBrightnessThresholds.getBrighteningThresholds();
-            BrightnessThresholds darkeningAmbientLuxIdle =
-                    idleAmbientBrightnessThresholds.getDarkeningThresholds();
+    private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
+        BrightnessThresholds brighteningAmbientLuxIdle = null;
+        BrightnessThresholds darkeningAmbientLuxIdle = null;
+        if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
+            brighteningAmbientLuxIdle =
+                    config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+            darkeningAmbientLuxIdle =
+                    config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+        }
 
-            if (brighteningAmbientLuxIdle != null
-                    && brighteningAmbientLuxIdle.getMinimum() != null) {
-                mAmbientLuxBrighteningMinThresholdIdle =
-                        brighteningAmbientLuxIdle.getMinimum().floatValue();
+        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+                brighteningAmbientLuxIdle,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+        mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
+        mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
+
+        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+                darkeningAmbientLuxIdle,
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+        mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
+        mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
+
+        if (brighteningAmbientLuxIdle != null
+                && brighteningAmbientLuxIdle.getMinimum() != null) {
+            mAmbientLuxBrighteningMinThresholdIdle =
+                    brighteningAmbientLuxIdle.getMinimum().floatValue();
+        }
+
+        if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+            mAmbientLuxDarkeningMinThresholdIdle =
+                    darkeningAmbientLuxIdle.getMinimum().floatValue();
+        }
+    }
+
+    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+            int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
+            float[] defaultPercentage) {
+        return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
+                configFallbackPercentage, defaultLevels, defaultPercentage, false);
+    }
+
+    // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+    // percentages for brightness levels at or above the lux value.
+    // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+    // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+    // rest of the framework. Values were also defined in different units (permille vs percent).
+    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+            int configFallbackThreshold, int configFallbackPermille,
+            float[] defaultLevels, float[] defaultPercentage,
+            boolean potentialOldBrightnessScale) {
+        if (thresholds != null
+                && thresholds.getBrightnessThresholdPoints() != null
+                && thresholds.getBrightnessThresholdPoints()
+                .getBrightnessThresholdPoint().size() != 0) {
+
+            // The level and percentages arrays are equal length in the ddc (new system)
+            List<ThresholdPoint> points =
+                    thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+            final int size = points.size();
+
+            float[] thresholdLevels = new float[size];
+            float[] thresholdPercentages = new float[size];
+
+            int i = 0;
+            for (ThresholdPoint point : points) {
+                thresholdLevels[i] = point.getThreshold().floatValue();
+                thresholdPercentages[i] = point.getPercentage().floatValue();
+                i++;
             }
-            if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
-                mAmbientLuxDarkeningMinThresholdIdle =
-                        darkeningAmbientLuxIdle.getMinimum().floatValue();
+            return new Pair<>(thresholdLevels, thresholdPercentages);
+        } else {
+            // The level and percentages arrays are unequal length in config.xml (old system)
+            // We prefix the array with a 0 value to ensure they can be handled consistently
+            // with the new system.
+
+            // Load levels array
+            int[] configThresholdArray = mContext.getResources().getIntArray(
+                    configFallbackThreshold);
+            int configThresholdsSize;
+            if (configThresholdArray == null || configThresholdArray.length == 0) {
+                configThresholdsSize = 1;
+            } else {
+                configThresholdsSize = configThresholdArray.length + 1;
+            }
+
+
+            // Load percentage array
+            int[] configPermille = mContext.getResources().getIntArray(
+                    configFallbackPermille);
+
+            // Ensure lengths match up
+            boolean emptyArray = configPermille == null || configPermille.length == 0;
+            if (emptyArray && configThresholdsSize == 1) {
+                return new Pair<>(defaultLevels, defaultPercentage);
+            }
+            if (emptyArray || configPermille.length != configThresholdsSize) {
+                throw new IllegalArgumentException(
+                        "Brightness threshold arrays do not align in length");
+            }
+
+            // Calculate levels array
+            float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+            // Start at 1, so that 0 index value is 0.0f (default)
+            for (int i = 1; i < configThresholdsSize; i++) {
+                configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+            }
+            if (potentialOldBrightnessScale) {
+                configThresholdWithZeroPrefixed =
+                        constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+            }
+
+            // Calculate percentages array
+            float[] configPercentage = new float[configThresholdsSize];
+            for (int i = 0; i < configPermille.length; i++) {
+                configPercentage[i] = configPermille[i] / 10.0f;
+            }
+            return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+        }
+    }
+
+    /**
+     * This check is due to historical reasons, where screen thresholdLevels used to be
+     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+     * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+     * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+     */
+    private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+        if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+                /* maxValueInclusive= */ 1.0f)) {
+            return thresholdLevels;
+        }
+
+        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+        for (int index = 0; thresholdLevels.length > index; ++index) {
+            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+        }
+        return thresholdLevelsScaled;
+    }
+
+    private boolean isAllInRange(float[] configArray, float minValueInclusive,
+            float maxValueInclusive) {
+        for (float v : configArray) {
+            if (v < minValueInclusive || v > maxValueInclusive) {
+                return false;
             }
         }
+        return true;
     }
 
     private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index efd2e34..458cf1a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -956,53 +956,77 @@
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
-            int[] ambientBrighteningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientBrighteningThresholds);
-            int[] ambientDarkeningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientDarkeningThresholds);
-            int[] ambientThresholdLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_ambientThresholdLevels);
+            // Ambient Lux - Active Mode Brightness Thresholds
+            float[] ambientBrighteningThresholds =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+            float[] ambientDarkeningThresholds =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+            float[] ambientBrighteningLevels =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevels();
+            float[] ambientDarkeningLevels =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevels();
             float ambientDarkeningMinThreshold =
                     mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
             float ambientBrighteningMinThreshold =
                     mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
             HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
                     ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
                     ambientBrighteningMinThreshold);
 
-            int[] screenBrighteningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_screenBrighteningThresholds);
-            int[] screenDarkeningThresholds = resources.getIntArray(
-                    com.android.internal.R.array.config_screenDarkeningThresholds);
-            float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
-                    .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
+            // Display - Active Mode Brightness Thresholds
+            float[] screenBrighteningThresholds =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentages();
+            float[] screenDarkeningThresholds =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentages();
+            float[] screenBrighteningLevels =
+                    mDisplayDeviceConfig.getScreenBrighteningLevels();
+            float[] screenDarkeningLevels =
+                    mDisplayDeviceConfig.getScreenDarkeningLevels();
             float screenDarkeningMinThreshold =
                     mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
             float screenBrighteningMinThreshold =
                     mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
             HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
-                    screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+                    screenBrighteningThresholds, screenDarkeningThresholds,
+                    screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+                    screenBrighteningMinThreshold);
 
-            // Idle screen thresholds
-            float screenDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
-            float screenBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
-            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
-                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
-            // Idle ambient thresholds
+            // Ambient Lux - Idle Screen Brightness Thresholds
             float ambientDarkeningMinThresholdIdle =
                     mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
             float ambientBrighteningMinThresholdIdle =
                     mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+            float[] ambientBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+            float[] ambientDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+            float[] ambientBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+            float[] ambientDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
             HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
-                    ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
-                    ambientBrighteningMinThresholdIdle);
+                    ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+                    ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+                    ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+            // Display - Idle Screen Brightness Thresholds
+            float screenDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+            float screenBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+            float[] screenBrighteningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+            float[] screenDarkeningThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+            float[] screenBrighteningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+            float[] screenDarkeningLevelsIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+                    screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+                    screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
 
             long brighteningLightDebounce = mDisplayDeviceConfig
                     .getAutoBrightnessBrighteningLightDebounce();
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index abf8fe3..faa4c3d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -29,52 +29,38 @@
 
     private static final boolean DEBUG = false;
 
-    private final float[] mBrighteningThresholds;
-    private final float[] mDarkeningThresholds;
-    private final float[] mThresholdLevels;
+    private final float[] mBrighteningThresholdsPercentages;
+    private final float[] mDarkeningThresholdsPercentages;
+    private final float[] mBrighteningThresholdLevels;
+    private final float[] mDarkeningThresholdLevels;
     private final float mMinDarkening;
     private final float mMinBrightening;
 
     /**
-     * Creates a {@code HysteresisLevels} object for ambient brightness.
-     * @param brighteningThresholds an array of brightening hysteresis constraint constants.
-     * @param darkeningThresholds an array of darkening hysteresis constraint constants.
-     * @param thresholdLevels a monotonically increasing array of threshold levels.
-     * @param minBrighteningThreshold the minimum value for which the brightening value needs to
-     *                                return.
-     * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
-    */
-    HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
-            int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
-        if (brighteningThresholds.length != darkeningThresholds.length
-                || darkeningThresholds.length != thresholdLevels.length + 1) {
-            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
-        }
-        mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
-        mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
-        mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
-        mMinDarkening = minDarkeningThreshold;
-        mMinBrightening = minBrighteningThreshold;
-    }
-
-    /**
-     * Creates a {@code HysteresisLevels} object for screen brightness.
-     * @param brighteningThresholds an array of brightening hysteresis constraint constants.
-     * @param darkeningThresholds an array of darkening hysteresis constraint constants.
-     * @param thresholdLevels a monotonically increasing array of threshold levels.
+     * Creates a {@code HysteresisLevels} object with the given equal-length
+     * float arrays.
+     * @param brighteningThresholdsPercentages 0-100 of thresholds
+     * @param darkeningThresholdsPercentages 0-100 of thresholds
+     * @param brighteningThresholdLevels float array of brightness values in the relevant units
+     * @param darkeningThresholdLevels float array of brightness values in the relevant units
      * @param minBrighteningThreshold the minimum value for which the brightening value needs to
      *                                return.
      * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
      */
-    HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
-            float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
-        if (brighteningThresholds.length != darkeningThresholds.length
-                || darkeningThresholds.length != thresholdLevels.length + 1) {
+    HysteresisLevels(float[] brighteningThresholdsPercentages,
+            float[] darkeningThresholdsPercentages,
+            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+            float minDarkeningThreshold, float minBrighteningThreshold) {
+        if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+                || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
             throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
         }
-        mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
-        mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
-        mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+        mBrighteningThresholdsPercentages =
+                setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+        mDarkeningThresholdsPercentages =
+                setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+        mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+        mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
         mMinDarkening = minDarkeningThreshold;
         mMinBrightening = minBrighteningThreshold;
     }
@@ -83,7 +69,9 @@
      * Return the brightening hysteresis threshold for the given value level.
      */
     public float getBrighteningThreshold(float value) {
-        final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+        final float brightConstant = getReferenceLevel(value,
+                mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
         float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
             Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
@@ -98,7 +86,8 @@
      * Return the darkening hysteresis threshold for the given value level.
      */
     public float getDarkeningThreshold(float value) {
-        final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+        final float darkConstant = getReferenceLevel(value,
+                mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
         float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
@@ -111,60 +100,39 @@
     /**
      * Return the hysteresis constant for the closest threshold value from the given array.
      */
-    private float getReferenceLevel(float value, float[] referenceLevels) {
-        int index = 0;
-        while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
-            ++index;
+    private float getReferenceLevel(float value, float[] thresholdLevels,
+            float[] thresholdPercentages) {
+        if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+            return 0.0f;
         }
-        return referenceLevels[index];
+        int index = 0;
+        while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+            index++;
+        }
+        return thresholdPercentages[index];
     }
 
     /**
      * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
      */
-    private float[] setArrayFormat(int[] configArray, float divideFactor) {
+    private float[] setArrayFormat(float[] configArray, float divideFactor) {
         float[] levelArray = new float[configArray.length];
         for (int index = 0; levelArray.length > index; ++index) {
-            levelArray[index] = (float) configArray[index] / divideFactor;
+            levelArray[index] = configArray[index] / divideFactor;
         }
         return levelArray;
     }
 
-    /**
-     * This check is due to historical reasons, where screen thresholdLevels used to be
-     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
-     * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
-     * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
-     */
-    private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
-        if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
-                1.0f)) {
-            return thresholdLevels;
-        }
-
-        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
-        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
-        for (int index = 0; thresholdLevels.length > index; ++index) {
-            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
-        }
-        return thresholdLevelsScaled;
-    }
-
-    private boolean isAllInRange(float[] configArray, float minValueInclusive,
-            float maxValueInclusive) {
-        int configArraySize = configArray.length;
-        for (int index = 0; configArraySize > index; ++index) {
-            if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
-                return false;
-            }
-        }
-        return true;
-    }
 
     void dump(PrintWriter pw) {
         pw.println("HysteresisLevels");
-        pw.println("  mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
-        pw.println("  mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
-        pw.println("  mThresholdLevels=" + Arrays.toString(mThresholdLevels));
+        pw.println("  mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
+        pw.println("  mBrighteningThresholdsPercentages="
+                + Arrays.toString(mBrighteningThresholdsPercentages));
+        pw.println("  mMinBrightening=" + mMinBrightening);
+        pw.println("  mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
+        pw.println("  mDarkeningThresholdsPercentages="
+                + Arrays.toString(mDarkeningThresholdsPercentages));
+        pw.println("  mMinDarkening=" + mMinDarkening);
     }
 }
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
new file mode 100644
index 0000000..c57d7e7
--- /dev/null
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 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.input;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.InputManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the battery state
+ * of input devices.
+ */
+final class BatteryController {
+    private static final String TAG = BatteryController.class.getSimpleName();
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final NativeInputManagerService mNative;
+
+    // Maps a pid to the registered listener record for that process. There can only be one battery
+    // listener per process.
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
+
+    BatteryController(Context context, NativeInputManagerService nativeService) {
+        mContext = context;
+        mNative = nativeService;
+    }
+
+    /**
+     * Register the battery listener for the given input device and start monitoring its battery
+     * state.
+     */
+    @BinderThread
+    void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+            int pid) {
+        synchronized (mLock) {
+            ListenerRecord listenerRecord = mListenerRecords.get(pid);
+
+            if (listenerRecord == null) {
+                listenerRecord = new ListenerRecord(pid, listener);
+                try {
+                    listener.asBinder().linkToDeath(listenerRecord.mDeathRecipient, 0);
+                } catch (RemoteException e) {
+                    Slog.i(TAG, "Client died before battery listener could be registered.");
+                    return;
+                }
+                mListenerRecords.put(pid, listenerRecord);
+                if (DEBUG) Slog.d(TAG, "Battery listener added for pid " + pid);
+            }
+
+            if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+                throw new SecurityException(
+                        "Cannot register a new battery listener when there is already another "
+                                + "registered listener for pid "
+                                + pid);
+            }
+            if (!listenerRecord.mMonitoredDevices.add(deviceId)) {
+                throw new IllegalArgumentException(
+                        "The battery listener for pid " + pid
+                                + " is already monitoring deviceId " + deviceId);
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "Battery listener for pid " + pid
+                        + " is monitoring deviceId " + deviceId);
+            }
+
+            notifyBatteryListener(deviceId, listenerRecord);
+        }
+    }
+
+    private void notifyBatteryListener(int deviceId, ListenerRecord record) {
+        final long eventTime = SystemClock.uptimeMillis();
+        try {
+            record.mListener.onBatteryStateChanged(
+                    deviceId,
+                    hasBattery(deviceId),
+                    mNative.getBatteryStatus(deviceId),
+                    mNative.getBatteryCapacity(deviceId) / 100.f,
+                    eventTime);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to notify listener", e);
+        }
+    }
+
+    private boolean hasBattery(int deviceId) {
+        final InputDevice device =
+                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+                        .getInputDevice(deviceId);
+        return device != null && device.hasBattery();
+    }
+
+    /**
+     * Unregister the battery listener for the given input device and stop monitoring its battery
+     * state. If there are no other input devices that this listener is monitoring, the listener is
+     * removed.
+     */
+    @BinderThread
+    void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+            int pid) {
+        synchronized (mLock) {
+            final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+            if (listenerRecord == null) {
+                throw new IllegalArgumentException(
+                        "Cannot unregister battery callback: No listener registered for pid "
+                                + pid);
+            }
+
+            if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalArgumentException(
+                        "Cannot unregister battery callback: The listener is not the one that "
+                                + "is registered for pid "
+                                + pid);
+            }
+
+            if (!listenerRecord.mMonitoredDevices.contains(deviceId)) {
+                throw new IllegalArgumentException(
+                        "Cannot unregister battery callback: The device is not being "
+                                + "monitored for deviceId " + deviceId);
+            }
+
+            unregisterRecordLocked(listenerRecord, deviceId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId) {
+        final int pid = listenerRecord.mPid;
+
+        if (!listenerRecord.mMonitoredDevices.remove(deviceId)) {
+            throw new IllegalStateException("Cannot unregister battery callback: The deviceId "
+                    + deviceId
+                    + " is not being monitored by pid "
+                    + pid);
+        }
+
+        if (listenerRecord.mMonitoredDevices.isEmpty()) {
+            // There are no more devices being monitored by this listener.
+            listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0);
+            mListenerRecords.remove(pid);
+            if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
+        }
+    }
+
+    private void handleListeningProcessDied(int pid) {
+        synchronized (mLock) {
+            final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+            if (listenerRecord == null) {
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "Removing battery listener for pid " + pid + " because the process died");
+            }
+            for (final int deviceId : listenerRecord.mMonitoredDevices) {
+                unregisterRecordLocked(listenerRecord, deviceId);
+            }
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + TAG + ": " + mListenerRecords.size()
+                    + " battery listeners");
+            for (int i = 0; i < mListenerRecords.size(); i++) {
+                pw.println(prefix + "  " + i + ": " + mListenerRecords.valueAt(i));
+            }
+        }
+    }
+
+    @SuppressWarnings("all")
+    void monitor() {
+        synchronized (mLock) {
+            return;
+        }
+    }
+
+    // A record of a registered battery listener from one process.
+    private class ListenerRecord {
+        final int mPid;
+        final IInputDeviceBatteryListener mListener;
+        final IBinder.DeathRecipient mDeathRecipient;
+        // The set of deviceIds that are currently being monitored by this listener.
+        final Set<Integer> mMonitoredDevices;
+
+        ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
+            mPid = pid;
+            mListener = listener;
+            mMonitoredDevices = new ArraySet<>();
+            mDeathRecipient = () -> handleListeningProcessDied(pid);
+        }
+
+        @Override
+        public String toString() {
+            return "pid=" + mPid
+                    + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 72612a0..91d5698 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
 import android.hardware.SensorPrivacyManagerInternal;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayViewport;
+import android.hardware.input.IInputDeviceBatteryListener;
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
@@ -318,6 +319,9 @@
     @GuardedBy("mInputMonitors")
     final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
 
+    // Manages battery state for input devices.
+    private final BatteryController mBatteryController;
+
     // Maximum number of milliseconds to wait for input event injection.
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -425,6 +429,7 @@
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
+        mBatteryController = new BatteryController(mContext, mNative);
 
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -2674,6 +2679,18 @@
     }
 
     @Override
+    public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+        Objects.requireNonNull(listener);
+        mBatteryController.registerBatteryListener(deviceId, listener, Binder.getCallingPid());
+    }
+
+    @Override
+    public void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+        Objects.requireNonNull(listener);
+        mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
@@ -2686,7 +2703,8 @@
         pw.println("Input Manager Service (Java) State:");
         dumpAssociations(pw, "  " /*prefix*/);
         dumpSpyWindowGestureMonitors(pw, "  " /*prefix*/);
-        dumpDisplayInputPropertiesValues(pw, "  " /* prefix */);
+        dumpDisplayInputPropertiesValues(pw, "  " /*prefix*/);
+        mBatteryController.dump(pw, "  " /*prefix*/);
     }
 
     private void dumpAssociations(PrintWriter pw, String prefix) {
@@ -2797,6 +2815,7 @@
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
         synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
         synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
+        mBatteryController.monitor();
         mNative.monitor();
     }
 
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 8e944b7..39d1188 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -217,6 +217,7 @@
     synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
         final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
         Connection c = null;
+        int iteratorId = -1;
         try {
             c = connect();
             final IIdmap2 service = c.getIdmap2();
@@ -225,9 +226,9 @@
                 return Collections.emptyList();
             }
 
-            service.acquireFabricatedOverlayIterator();
+            iteratorId = service.acquireFabricatedOverlayIterator();
             List<FabricatedOverlayInfo> infos;
-            while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) {
+            while (!(infos = service.nextFabricatedOverlayInfos(iteratorId)).isEmpty()) {
                 allInfos.addAll(infos);
             }
             return allInfos;
@@ -235,8 +236,8 @@
             Slog.wtf(TAG, "failed to get all fabricated overlays", e);
         } finally {
             try {
-                if (c.getIdmap2() != null) {
-                    c.getIdmap2().releaseFabricatedOverlayIterator();
+                if (c.getIdmap2() != null && iteratorId != -1) {
+                    c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
                 }
             } catch (RemoteException e) {
                 // ignore
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0c601bf..890c891 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1962,10 +1962,15 @@
 
                             continue;
                         case TAG_SHORTCUT:
-                            final ShortcutInfo si = parseShortcut(parser, packageName,
-                                    shortcutUser.getUserId(), fromBackup);
-                            // Don't use addShortcut(), we don't need to save the icon.
-                            ret.mShortcuts.put(si.getId(), si);
+                            try {
+                                final ShortcutInfo si = parseShortcut(parser, packageName,
+                                        shortcutUser.getUserId(), fromBackup);
+                                // Don't use addShortcut(), we don't need to save the icon.
+                                ret.mShortcuts.put(si.getId(), si);
+                            } catch (Exception e) {
+                                // b/246540168 malformed shortcuts should be ignored
+                                Slog.e(TAG, "Failed parsing shortcut.", e);
+                            }
                             continue;
                         case TAG_SHARE_TARGET:
                             ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f888ff6..7170773 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -687,7 +687,7 @@
      */
     public void lockUser(int userId) {
         mLockPatternUtils.requireStrongAuth(
-                StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
+                StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
         try {
             WindowManagerGlobal.getWindowManagerService().lockNow(null);
         } catch (RemoteException e) {
@@ -2084,7 +2084,7 @@
             if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
                 if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
                 mLockPatternUtils.requireStrongAuth(
-                        mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
+                        mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
             }
             maybeLockScreen(mUserId);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d2a00af..eca2e74 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -27,6 +27,8 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
@@ -332,8 +334,8 @@
     }
 
     @Override
-    public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
-            Intent resultData) {
+    public boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType,
+            int resultCode, Intent resultData) {
         final ActivityRecord r;
         synchronized (mGlobalLock) {
             r = ActivityRecord.isInRootTaskLocked(token);
@@ -348,7 +350,7 @@
 
         synchronized (mGlobalLock) {
             return r.getRootTask().navigateUpTo(
-                    r, destIntent, destGrants, resultCode, resultData, resultGrants);
+                    r, destIntent, resolvedType, destGrants, resultCode, resultData, resultGrants);
         }
     }
 
@@ -707,7 +709,26 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                return r != null && r.setOccludesParent(true);
+                // Create a transition if the activity is playing in case the below activity didn't
+                // commit invisible. That's because if any activity below this one has changed its
+                // visibility while playing transition, there won't able to commit visibility until
+                // the running transition finish.
+                final Transition transition = r != null
+                        && r.mTransitionController.inPlayingTransition(r)
+                        ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
+                if (transition != null) {
+                    r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+                final boolean changed = r != null && r.setOccludesParent(true);
+                if (transition != null) {
+                    if (changed) {
+                        r.mTransitionController.setReady(r.getDisplayContent());
+                    } else {
+                        transition.abort();
+                    }
+                }
+                return changed;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -728,7 +749,25 @@
                 if (under != null) {
                     under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
                 }
-                return r.setOccludesParent(false);
+                // Create a transition if the activity is playing in case the current activity
+                // didn't commit invisible. That's because if this activity has changed its
+                // visibility while playing transition, there won't able to commit visibility until
+                // the running transition finish.
+                final Transition transition = r.mTransitionController.inPlayingTransition(r)
+                        ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+                if (transition != null) {
+                    r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+                final boolean changed = r.setOccludesParent(false);
+                if (transition != null) {
+                    if (changed) {
+                        r.mTransitionController.setReady(r.getDisplayContent());
+                    } else {
+                        transition.abort();
+                    }
+                }
+                return changed;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8486e7..fab1683 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -120,6 +120,8 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -658,7 +660,7 @@
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
-    boolean mUseTransferredAnimation;
+    int mTransitionChangeFlags;
 
     /** Whether we need to setup the animation to animate only within the letterbox. */
     private boolean mNeedsLetterboxedAnimation;
@@ -4395,10 +4397,10 @@
                     // When transferring an animation, we no longer need to apply an animation to
                     // the token we transfer the animation over. Thus, set this flag to indicate
                     // we've transferred the animation.
-                    mUseTransferredAnimation = true;
+                    mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 } else if (mTransitionController.getTransitionPlayer() != null) {
                     // In the new transit system, just set this every time we transfer the window
-                    mUseTransferredAnimation = true;
+                    mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 }
                 // Post cleanup after the visibility and animation are transferred.
                 fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -5247,6 +5249,10 @@
 
         // If in a transition, defer commits for activities that are going invisible
         if (!visible && inTransition()) {
+            if (mTransitionController.inPlayingTransition(this)
+                    && mTransitionController.isCollecting(this)) {
+                mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+            }
             return;
         }
         // If we are preparing an app transition, then delay changing
@@ -5297,7 +5303,7 @@
     @Override
     boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
             boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
-        if (mUseTransferredAnimation) {
+        if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
             return false;
         }
         // If it was set to true, reset the last request to force the transition.
@@ -5370,7 +5376,7 @@
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
         displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
-        mUseTransferredAnimation = false;
+        mTransitionChangeFlags = 0;
 
         postApplyAnimation(visible, fromTransition);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 0707b81..30454d4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@
 import android.util.SparseArray;
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowManager;
+import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -563,14 +564,39 @@
             return false;
         }
         mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
-        final ActivityMetricsLogger.LaunchingState launchingState =
-                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
-        final Task task = r.getTask();
-        mService.deferWindowLayout();
-        try {
+        final RemoteTransition remote = options.getRemoteTransition();
+        if (remote != null && rootTask.mTransitionController.isCollecting()) {
+            final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+                    0 /* flags */, rootTask.mTransitionController,
+                    mService.mWindowManager.mSyncEngine);
+            // Special case: we are entering recents while an existing transition is running. In
+            // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+            // that it can get its own transition and thus update launcher correctly.
+            mService.mWindowManager.mSyncEngine.queueSyncSet(
+                    () -> rootTask.mTransitionController.moveToCollecting(transition),
+                    () -> {
+                        final Task task = r.getTask();
+                        task.mTransitionController.requestStartTransition(transition,
+                                task, remote, null /* displayChange */);
+                        task.mTransitionController.collect(task);
+                        startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+                    });
+        } else {
+            final Task task = r.getTask();
             task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
                     0 /* flags */, task, task /* readyGroupRef */,
                     options.getRemoteTransition(), null /* displayChange */);
+            startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+        }
+        return true;
+    }
+
+    void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+            ActivityRecord r, Task task, Task rootTask) {
+        final ActivityMetricsLogger.LaunchingState launchingState =
+                mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+        mService.deferWindowLayout();
+        try {
             r.mTransitionController.setTransientLaunch(r,
                     TaskDisplayArea.getRootTaskAbove(rootTask));
             task.moveToFront("startExistingRecents");
@@ -582,7 +608,6 @@
             task.mInResumeTopActivity = false;
             mService.continueWindowLayout();
         }
-        return true;
     }
 
     void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index e80c260..0bfc48b 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
                 return mService.getRecentTasks().createRecentTaskInfo(task,
-                        false /* stripExtras */);
+                        false /* stripExtras */, true /* getTasksAllowed */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 8c5f053..7d9ae87 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,8 +202,7 @@
         // target windows. But the windows still need to use sync transaction to keep the appearance
         // in previous rotation, so request a no-op sync to keep the state.
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
-            if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
-                    && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) {
+            if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
                 // Expect a screenshot layer will cover the non seamless windows.
                 continue;
             }
@@ -489,7 +488,7 @@
             return false;
         }
         final Operation op = mTargetWindowTokens.get(w.mToken);
-        if (op == null) return false;
+        if (op == null || op.canDrawBeforeStartTransaction()) return false;
         if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
         if (op.mDrawTransaction == null) {
             if (w.isClientLocal()) {
@@ -554,5 +553,14 @@
         Operation(@Action int action) {
             mAction = action;
         }
+
+        /**
+         * Returns {@code true} if the corresponding window can draw its latest content before the
+         * start transaction of rotation transition is applied.
+         */
+        boolean canDrawBeforeStartTransaction() {
+            return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
+                    && mAction != ACTION_SEAMLESS;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 317c93e..ea82417 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -481,7 +481,7 @@
     }
 
     private void updateRoundedCorners(WindowState mainWindow) {
-        final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+        final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
         if (windowSurface != null && windowSurface.isValid()) {
             final Transaction transaction = mActivityRecord.getSyncTransaction();
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
                 continue;
             }
 
-            res.add(createRecentTaskInfo(task, true /* stripExtras */));
+            res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
         }
         return res;
     }
@@ -1895,7 +1895,8 @@
     /**
      * Creates a new RecentTaskInfo from a Task.
      */
-    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+            boolean getTasksAllowed) {
         final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         // If the recent Task is detached, we consider it will be re-attached to the default
         // TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
         rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+        if (!getTasksAllowed) {
+            Task.trimIneffectiveInfo(tr, rti);
+        }
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0..0e60274 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -142,6 +142,10 @@
         task.fillTaskInfo(rti, !mKeepIntentExtra);
         // Fill in some deprecated values
         rti.id = rti.taskId;
+
+        if (!mAllowed) {
+            Task.trimIneffectiveInfo(task, rti);
+        }
         return rti;
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aede32c..b67d2ef 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3446,6 +3446,27 @@
         info.isSleeping = shouldSleepActivities();
     }
 
+    /**
+     * Removes the activity info if the activity belongs to a different uid, which is
+     * different from the app that hosts the task.
+     */
+    static void trimIneffectiveInfo(Task task, TaskInfo info) {
+        final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+                false /* traverseTopToBottom */);
+        final int baseActivityUid =
+                baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+        if (info.topActivityInfo != null
+                && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+            info.topActivity = null;
+            info.topActivityInfo = null;
+        }
+
+        if (task.effectiveUid != baseActivityUid) {
+            info.baseActivity = null;
+        }
+    }
+
     @Nullable PictureInPictureParams getPictureInPictureParams() {
         final Task topTask = getTopMostTask();
         if (topTask == null) return null;
@@ -5308,8 +5329,9 @@
         return false;
     }
 
-    boolean navigateUpTo(ActivityRecord srec, Intent destIntent, NeededUriGrants destGrants,
-            int resultCode, Intent resultData, NeededUriGrants resultGrants) {
+    boolean navigateUpTo(ActivityRecord srec, Intent destIntent, String resolvedType,
+            NeededUriGrants destGrants, int resultCode, Intent resultData,
+            NeededUriGrants resultGrants) {
         if (!srec.attachedToProcess()) {
             // Nothing to do if the caller is not attached, because this method should be called
             // from an alive activity.
@@ -5402,28 +5424,22 @@
                             srec.packageName);
                 }
             } else {
-                try {
-                    ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
-                            destIntent.getComponent(), ActivityManagerService.STOCK_PM_FLAGS,
-                            srec.mUserId);
-                    // TODO(b/64750076): Check if calling pid should really be -1.
-                    final int res = mAtmService.getActivityStartController()
-                            .obtainStarter(destIntent, "navigateUpTo")
-                            .setCaller(srec.app.getThread())
-                            .setActivityInfo(aInfo)
-                            .setResultTo(parent.token)
-                            .setCallingPid(-1)
-                            .setCallingUid(callingUid)
-                            .setCallingPackage(srec.packageName)
-                            .setCallingFeatureId(parent.launchedFromFeatureId)
-                            .setRealCallingPid(-1)
-                            .setRealCallingUid(callingUid)
-                            .setComponentSpecified(true)
-                            .execute();
-                    foundParentInTask = res == ActivityManager.START_SUCCESS;
-                } catch (RemoteException e) {
-                    foundParentInTask = false;
-                }
+                // TODO(b/64750076): Check if calling pid should really be -1.
+                final int res = mAtmService.getActivityStartController()
+                        .obtainStarter(destIntent, "navigateUpTo")
+                        .setResolvedType(resolvedType)
+                        .setUserId(srec.mUserId)
+                        .setCaller(srec.app.getThread())
+                        .setResultTo(parent.token)
+                        .setCallingPid(-1)
+                        .setCallingUid(callingUid)
+                        .setCallingPackage(srec.packageName)
+                        .setCallingFeatureId(parent.launchedFromFeatureId)
+                        .setRealCallingPid(-1)
+                        .setRealCallingUid(callingUid)
+                        .setComponentSpecified(true)
+                        .execute();
+                foundParentInTask = res == ActivityManager.START_SUCCESS;
                 parent.finishIfPossible(resultCode, resultData, resultGrants,
                         "navigate-top", true /* oomAdj */);
             }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 147c9cb..526a366 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,7 +55,6 @@
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
 
@@ -1331,7 +1330,7 @@
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "        sibling is a participant with mode %s",
                     TransitionInfo.modeToString(siblingMode));
-            if (mode != siblingMode) {
+            if (reduceMode(mode) != reduceMode(siblingMode)) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "          SKIP: common mode mismatch. was %s",
                         TransitionInfo.modeToString(mode));
@@ -1341,6 +1340,16 @@
         return true;
     }
 
+    /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
+    @TransitionInfo.TransitionMode
+    private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
+        switch (mode) {
+            case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
+            case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
+            default: return mode;
+        }
+    }
+
     /**
      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
      *
@@ -1842,7 +1851,7 @@
         @TransitionInfo.TransitionMode
         int getTransitMode(@NonNull WindowContainer wc) {
             if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
-                return TRANSIT_CLOSE;
+                return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
             }
             final boolean nowVisible = wc.isVisibleRequested();
             if (nowVisible == mVisible) {
@@ -1879,12 +1888,10 @@
             final ActivityRecord record = wc.asActivityRecord();
             if (record != null) {
                 parentTask = record.getTask();
-                if (record.mUseTransferredAnimation) {
-                    flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-                }
                 if (record.mVoiceInteraction) {
                     flags |= FLAG_IS_VOICE_INTERACTION;
                 }
+                flags |= record.mTransitionChangeFlags;
             }
             final TaskFragment taskFragment = wc.asTaskFragment();
             if (taskFragment != null && task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4d37e08..ac720be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6007,7 +6007,11 @@
         if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
                 && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
             ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
-            w.setOrientationChanging(true);
+            // WindowsState#reportResized won't tell invisible requested window to redraw,
+            // so do not set it as changing orientation to avoid affecting draw state.
+            if (w.isVisibleRequested()) {
+                w.setOrientationChanging(true);
+            }
             if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
                 mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
                 // XXX should probably keep timeout from
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e1477d..949fa96 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -242,8 +243,18 @@
     }
 
     @Override
-    public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+    public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+        return startTransition(type, null /* transitionToken */, t);
+    }
+
+    @Override
+    public void startTransition(@NonNull IBinder transitionToken,
             @Nullable WindowContainerTransaction t) {
+        startTransition(-1 /* unused type */, transitionToken, t);
+    }
+
+    private IBinder startTransition(@WindowManager.TransitionType int type,
+            @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
         enforceTaskPermission("startTransition()");
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
@@ -1557,10 +1568,6 @@
         return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
     }
 
-    private void enforceTaskPermission(String func) {
-        mService.enforceTaskPermission(func);
-    }
-
     private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
         if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
                 .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 42d2861..f61bccf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3869,7 +3869,7 @@
         // configuration update when the window has requested to be hidden. Doing so can lead to
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
-        if (useLatestConfig || (relayoutVisible && (shouldCheckTokenVisibleRequested()
+        if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
                 || mToken.isVisibleRequested()))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 267cff6..4a51b83 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -351,6 +351,35 @@
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
+        <xs:sequence>
+            <!--  Thresholds as tenths of percent of current brightness level, at each level of
+                brightness -->
+            <xs:element name="brightnessThresholdPoints" type="thresholdPoints" maxOccurs="1" minOccurs="0">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="thresholdPoints">
+        <xs:sequence>
+            <xs:element type="thresholdPoint" name="brightnessThresholdPoint" maxOccurs="unbounded" minOccurs="1">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="thresholdPoint">
+        <xs:sequence>
+            <xs:element type="nonNegativeDecimal" name="threshold">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="nonNegativeDecimal" name="percentage">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
     </xs:complexType>
 
     <xs:complexType name="autoBrightness">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f8bff75..748ef4b 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,7 +13,9 @@
 
   public class BrightnessThresholds {
     ctor public BrightnessThresholds();
+    method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
     method @NonNull public final java.math.BigDecimal getMinimum();
+    method public final void setBrightnessThresholdPoints(com.android.server.display.config.ThresholdPoints);
     method public final void setMinimum(@NonNull java.math.BigDecimal);
   }
 
@@ -204,6 +206,19 @@
     method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
   }
 
+  public class ThresholdPoint {
+    ctor public ThresholdPoint();
+    method @NonNull public final java.math.BigDecimal getPercentage();
+    method @NonNull public final java.math.BigDecimal getThreshold();
+    method public final void setPercentage(@NonNull java.math.BigDecimal);
+    method public final void setThreshold(@NonNull java.math.BigDecimal);
+  }
+
+  public class ThresholdPoints {
+    ctor public ThresholdPoints();
+    method @NonNull public final java.util.List<com.android.server.display.config.ThresholdPoint> getBrightnessThresholdPoint();
+  }
+
   public class Thresholds {
     ctor public Thresholds();
     method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6aca14f0..538d6bc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7783,6 +7783,7 @@
         Preconditions.checkCallAuthorization(
                 isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
             if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
@@ -7823,6 +7824,7 @@
         Preconditions.checkCallAuthorization(
                 isProfileOwner(caller) || isDefaultDeviceOwner(caller)
                         || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
 
         synchronized (getLockObject()) {
             if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..4f2b613 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -422,13 +422,13 @@
 
     @Test
     public void testHysteresisLevels() {
-        int[] ambientBrighteningThresholds = {100, 200};
-        int[] ambientDarkeningThresholds = {400, 500};
-        int[] ambientThresholdLevels = {500};
+        float[] ambientBrighteningThresholds = {50, 100};
+        float[] ambientDarkeningThresholds = {10, 20};
+        float[] ambientThresholdLevels = {0, 500};
         float ambientDarkeningMinChangeThreshold = 3.0f;
         float ambientBrighteningMinChangeThreshold = 1.5f;
         HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
-                ambientDarkeningThresholds, ambientThresholdLevels,
+                ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
                 ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
 
         // test low, activate minimum change thresholds.
@@ -437,16 +437,17 @@
         assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
 
         // test max
-        assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
-        assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+        // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+        assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+        assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
 
         // test just below threshold
-        assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
-        assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+        assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+        assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
 
         // test at (considered above) threshold
-        assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
-        assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+        assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+        assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..a719f52 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -32,6 +32,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,6 +50,9 @@
 @RunWith(AndroidJUnit4.class)
 public final class DisplayDeviceConfigTest {
     private DisplayDeviceConfig mDisplayDeviceConfig;
+    private static final float ZERO_DELTA = 0.0f;
+    private static final float SMALL_DELTA = 0.0001f;
+
     @Mock
     private Context mContext;
 
@@ -69,26 +74,74 @@
         assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
-        assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
-        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
-                0.0f);
-        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+                ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
+                ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
-                0.0f);
-        assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
-        assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+                ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
         assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.32f, 75.43f}, 0.0f);
+                float[]{45.32f, 75.43f}, ZERO_DELTA);
+
+        // Test thresholds
+        assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
+                ZERO_DELTA);
+        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{9, 10, 11},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{11, 12, 13},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 100, 200},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{13, 14, 15},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 300, 400},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{15, 16, 17},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{17, 18, 19},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{19, 20, 21},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 500, 600},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{21, 22, 23},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 700, 800},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{23, 24, 25},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -97,9 +150,60 @@
     public void testConfigValuesFromConfigResource() {
         setupDisplayDeviceConfigFromConfigResourceFile();
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+                float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+                float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+
+        // Test thresholds
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        // screen levels will be considered "old screen brightness scale"
+        // and therefore will divide by 255
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
         // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -154,11 +258,126 @@
                 +   "<ambientBrightnessChangeThresholds>\n"
                 +       "<brighteningThresholds>\n"
                 +           "<minimum>10</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>100</threshold><percentage>14</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>200</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
                 +       "</brighteningThresholds>\n"
                 +       "<darkeningThresholds>\n"
-                +           "<minimum>2</minimum>\n"
+                +           "<minimum>30</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>300</threshold><percentage>16</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>400</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
                 +       "</darkeningThresholds>\n"
                 +   "</ambientBrightnessChangeThresholds>\n"
+                +   "<displayBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.1</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold>\n"
+                +                   "<percentage>9</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.10</threshold>\n"
+                +                   "<percentage>10</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.20</threshold>\n"
+                +                   "<percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.3</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.11</threshold><percentage>12</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.21</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholds>\n"
+                +   "<ambientBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>20</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>500</threshold><percentage>22</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>600</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>40</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>700</threshold><percentage>24</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>800</threshold><percentage>25</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholdsIdle>\n"
+                +   "<displayBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.2</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.12</threshold><percentage>18</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.22</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.4</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.13</threshold><percentage>20</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.23</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholdsIdle>\n"
                 +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
                 +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
                 +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
@@ -171,18 +390,6 @@
                 +   "</screenBrightnessRampDecreaseMaxMillis>"
                 +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
                 +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
-                +   "<displayBrightnessChangeThresholds>"
-                +       "<brighteningThresholds>"
-                +           "<minimum>"
-                +               "0.001"
-                +           "</minimum>"
-                +       "</brighteningThresholds>"
-                +       "<darkeningThresholds>"
-                +           "<minimum>"
-                +               "0.002"
-                +           "</minimum>"
-                +       "</darkeningThresholds>"
-                +   "</displayBrightnessChangeThresholds>"
                 +   "<screenBrightnessRampIncreaseMaxMillis>"
                 +       "2000"
                 +    "</screenBrightnessRampIncreaseMaxMillis>\n"
@@ -241,8 +448,24 @@
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(screenBrightnessLevelLux);
 
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+        // Thresholds
+        // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+        // of length N+1
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+                .thenReturn(new int[]{30, 31});
+        when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+                .thenReturn(new int[]{42, 43});
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_ambientBrighteningThresholds))
+                .thenReturn(new int[]{270, 280, 290});
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+                .thenReturn(new int[]{290, 300, 310});
+        when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+                .thenReturn(new int[]{350, 360, 370});
+        when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+                .thenReturn(new int[]{370, 380, 390});
 
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
new file mode 100644
index 0000000..246aa90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.BatteryState.STATUS_CHARGING
+import android.hardware.BatteryState.STATUS_FULL
+import android.hardware.input.IInputDeviceBatteryListener
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.Binder
+import android.os.IBinder
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.notNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link InputDeviceBatteryController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputDeviceBatteryControllerTests
+ */
+@Presubmit
+class BatteryControllerTests {
+    companion object {
+        const val PID = 42
+        const val DEVICE_ID = 13
+        const val SECOND_DEVICE_ID = 11
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    @Mock
+    private lateinit var iInputManager: IInputManager
+
+    private lateinit var batteryController: BatteryController
+    private lateinit var context: Context
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+        val inputManager = InputManager.resetInstance(iInputManager)
+        `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
+        `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
+        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID))
+        `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
+            .thenReturn(createInputDevice(SECOND_DEVICE_ID))
+
+        batteryController = BatteryController(context, native)
+    }
+
+    private fun createInputDevice(deviceId: Int): InputDevice =
+        InputDevice(deviceId, 0 /*generation*/, 0 /*controllerNumber*/,
+            "Device $deviceId" /*name*/, 0 /*vendorId*/, 0 /*productId*/, "descriptor$deviceId",
+            true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/, null /*keyCharacterMap*/,
+            false /*hasVibrator*/, false /*hasMicrophone*/, false /*hasButtonUnderPad*/,
+            false /*hasSensor*/, true /*hasBattery*/)
+
+    @After
+    fun tearDown() {
+        InputManager.clearInstance()
+    }
+
+    private fun createMockListener(): IInputDeviceBatteryListener {
+        val listener = mock(IInputDeviceBatteryListener::class.java)
+        val binder = mock(Binder::class.java)
+        `when`(listener.asBinder()).thenReturn(binder)
+        return listener
+    }
+
+    @Test
+    fun testRegisterAndUnregisterBinderLifecycle() {
+        val listener = createMockListener()
+        // Ensure the binder lifecycle is tracked when registering a listener.
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
+        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
+
+        // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
+        batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
+        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+    }
+
+    @Test
+    fun testOneListenerPerProcess() {
+        val listener1 = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
+        verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
+
+        // If a second listener is added for the same process, a security exception is thrown.
+        val listener2 = createMockListener()
+        try {
+            batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
+            fail("Expected security exception when registering more than one listener per process")
+        } catch (ignored: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testProcessDeathRemovesListener() {
+        val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
+
+        // When the binder dies, the callback is unregistered.
+        deathRecipient.value!!.binderDied(listener.asBinder())
+        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+
+        // It is now possible to register the same listener again.
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
+    }
+
+    @Test
+    fun testRegisteringListenerNotifiesStateImmediately() {
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+            eq(STATUS_FULL), eq(1f), anyLong())
+
+        `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
+        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
+            eq(STATUS_CHARGING), eq(0.78f), anyLong())
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 617a34f..91c2fe0 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -54,6 +54,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -83,7 +84,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -101,6 +101,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Spy;
 
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -137,8 +138,8 @@
     private PackageManager mPackageManager;
     @Mock
     private IBinder mBinder;
-    @Mock
-    private DreamManagerInternal mDreamManager;
+    @Spy
+    private TestInjector mInjector;
     @Captor
     private ArgumentCaptor<Intent> mOrderedBroadcastIntent;
     @Captor
@@ -207,10 +208,10 @@
         addLocalService(WindowManagerInternal.class, mWindowManager);
         addLocalService(PowerManagerInternal.class, mLocalPowerManager);
         addLocalService(TwilightManager.class, mTwilightManager);
-        addLocalService(DreamManagerInternal.class, mDreamManager);
-        
+
+        mInjector = spy(new TestInjector());
         mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
-                mTwilightManager, new TestInjector());
+                mTwilightManager, mInjector);
         try {
             mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         } catch (SecurityException e) {/* ignore for permission denial */}
@@ -1321,84 +1322,53 @@
 
     @Test
     public void dreamWhenDocked() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
-
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
-    }
-
-    @Test
-    public void noDreamWhenDocked_dreamsDisabled() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(false);
-
-        triggerDockIntent();
-        verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
-    }
-
-    @Test
-    public void noDreamWhenDocked_dreamsWhenDockedDisabled() {
-        setScreensaverActivateOnDock(false);
-        setScreensaverEnabled(true);
-
-        triggerDockIntent();
-        verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void noDreamWhenDocked_keyguardNotShowing_interactive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
         when(mPowerManager.isInteractive()).thenReturn(true);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager, never()).requestDream();
+        verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardShowing_interactive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardNotShowing_notInteractive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     @Test
     public void dreamWhenDocked_keyguardShowing_notInteractive() {
-        setScreensaverActivateOnDock(true);
-        setScreensaverEnabled(true);
         mUiManagerService.setStartDreamImmediatelyOnDock(false);
         when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         triggerDockIntent();
         verifyAndSendResultBroadcast();
-        verify(mDreamManager).requestDream();
+        verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
     private void triggerDockIntent() {
@@ -1435,22 +1405,6 @@
                 mOrderedBroadcastIntent.getValue());
     }
 
-    private void setScreensaverEnabled(boolean enable) {
-        Settings.Secure.putIntForUser(
-                mContentResolver,
-                Settings.Secure.SCREENSAVER_ENABLED,
-                enable ? 1 : 0,
-                UserHandle.USER_CURRENT);
-    }
-
-    private void setScreensaverActivateOnDock(boolean enable) {
-        Settings.Secure.putIntForUser(
-                mContentResolver,
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
-                enable ? 1 : 0,
-                UserHandle.USER_CURRENT);
-    }
-
     private void requestAllPossibleProjectionTypes() throws RemoteException {
         for (int i = 0; i < Integer.SIZE; ++i) {
             mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
@@ -1467,11 +1421,17 @@
         }
 
         public TestInjector(int callingUid) {
-          this.callingUid = callingUid;
+            this.callingUid = callingUid;
         }
 
+        @Override
         public int getCallingUid() {
             return callingUid;
         }
+
+        @Override
+        public void startDreamWhenDockedIfAppropriate(Context context) {
+            // do nothing
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..0462e1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,34 @@
 
     @Test
     public void testCreateRecentTaskInfo_detachedTask() {
-        final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+        final Task task = createTaskBuilder(".Task").build();
+        new ActivityBuilder(mSupervisor.mService)
+                .setTask(task)
+                .setUid(NOBODY_UID)
+                .setComponent(getUniqueComponentName())
+                .build();
         final TaskDisplayArea tda = task.getDisplayArea();
 
         assertTrue(task.isAttached());
         assertTrue(task.supportsMultiWindow());
 
-        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                false /* getTasksAllowed */);
+
+        assertTrue(info.topActivity == null);
+        assertTrue(info.topActivityInfo == null);
+        assertTrue(info.baseActivity == null);
+
         // The task can be put in split screen even if it is not attached now.
         task.removeImmediately();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
@@ -1242,7 +1257,8 @@
         doReturn(false).when(tda).supportsNonResizableMultiWindow();
         doReturn(false).when(task).isResizeable();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertFalse(info.supportsMultiWindow);
 
@@ -1250,7 +1266,8 @@
         // the device supports it.
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 6128428..b46e90d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1312,13 +1312,15 @@
         secondActivity.app.setThread(null);
         // This should do nothing from a non-attached caller.
         assertFalse(task.navigateUpTo(secondActivity /* source record */,
-                firstActivity.intent /* destIntent */, null /* destGrants */,
-                0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+                firstActivity.intent /* destIntent */, null /* resolvedType */,
+                null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+                null /* resultGrants */));
 
         secondActivity.app.setThread(thread);
         assertTrue(task.navigateUpTo(secondActivity /* source record */,
-                firstActivity.intent /* destIntent */, null /* destGrants */,
-                0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+                firstActivity.intent /* destIntent */, null /* resolvedType */,
+                null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+                null /* resultGrants */));
         // The firstActivity uses default launch mode, so the activities between it and itself will
         // be finished.
         assertTrue(secondActivity.finishing);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 45d8e22..e976fd4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -393,6 +393,70 @@
     }
 
     @Test
+    public void testCreateInfo_PromoteSimilarClose() {
+        final Transition transition = createTestTransition(TRANSIT_CLOSE);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task topTask = createTask(mDisplayContent);
+        final Task belowTask = createTask(mDisplayContent);
+        final ActivityRecord showing = createActivityRecord(belowTask);
+        final ActivityRecord hiding = createActivityRecord(topTask);
+        final ActivityRecord closing = createActivityRecord(topTask);
+        // Start states.
+        changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+        fillChangeMap(changes, topTask);
+        // End states.
+        showing.mVisibleRequested = true;
+        closing.mVisibleRequested = false;
+        hiding.mVisibleRequested = false;
+
+        participants.add(belowTask);
+        participants.add(hiding);
+        participants.add(closing);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        assertEquals(2, targets.size());
+        assertTrue(targets.contains(belowTask));
+        assertTrue(targets.contains(topTask));
+    }
+
+    @Test
+    public void testCreateInfo_PromoteSimilarOpen() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task topTask = createTask(mDisplayContent);
+        final Task belowTask = createTask(mDisplayContent);
+        final ActivityRecord showing = createActivityRecord(topTask);
+        final ActivityRecord opening = createActivityRecord(topTask);
+        final ActivityRecord closing = createActivityRecord(belowTask);
+        // Start states.
+        changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+        changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        fillChangeMap(changes, topTask);
+        // End states.
+        showing.mVisibleRequested = true;
+        opening.mVisibleRequested = true;
+        closing.mVisibleRequested = false;
+
+        participants.add(belowTask);
+        participants.add(showing);
+        participants.add(opening);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        assertEquals(2, targets.size());
+        assertTrue(targets.contains(belowTask));
+        assertTrue(targets.contains(topTask));
+    }
+
+    @Test
     public void testTargets_noIntermediatesToWallpaper() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
 
@@ -732,6 +796,11 @@
         assertTrue(asyncRotationController.isTargetToken(decorToken));
         assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
 
+        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+            // Only seamless window syncs its draw transaction with transition.
+            assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
+            assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
+        }
         screenDecor.setOrientationChanging(false);
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
         // executed until the transaction is committed, so it is still in target tokens.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index b8da8cc..5c1c193 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -43,6 +43,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -195,14 +196,25 @@
         mWm.mWindowMap.put(win.mClient.asBinder(), win);
         final int w = 100;
         final int h = 200;
+        final ClientWindowFrames outFrames = new ClientWindowFrames();
+        final MergedConfiguration outConfig = new MergedConfiguration();
+        final SurfaceControl outSurfaceControl = new SurfaceControl();
+        final InsetsState outInsetsState = new InsetsState();
+        final InsetsSourceControl[] outControls = new InsetsSourceControl[0];
+        final Bundle outBundle = new Bundle();
         mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
-                new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
-                new InsetsState(), new InsetsSourceControl[0], new Bundle());
+                outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
         // Because the window is already invisible, it doesn't need to apply exiting animation
         // and WMS#tryStartExitingAnimation() will destroy the surface directly.
         assertFalse(win.mAnimatingExit);
         assertFalse(win.mHasSurface);
         assertNull(win.mWinAnimator.mSurfaceController);
+
+        doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
+        // Invisible requested activity should not get the last config even if its view is visible.
+        mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
+                outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
+        assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
     }
 
     @Test
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 cfc0da7..9bcc136 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -731,6 +731,15 @@
         assertTrue(mWm.mResizingWindows.contains(startingApp));
         assertTrue(startingApp.isDrawn());
         assertFalse(startingApp.getOrientationChanging());
+
+        // Even if the display is frozen, invisible requested window should not be affected.
+        startingApp.mActivityRecord.mVisibleRequested = false;
+        mWm.startFreezingDisplay(0, 0, mDisplayContent);
+        doReturn(true).when(mWm.mPolicy).isScreenOn();
+        startingApp.getWindowFrames().setInsetsChanged(true);
+        startingApp.updateResizingWindowIfNeeded();
+        assertTrue(startingApp.isDrawn());
+        assertFalse(startingApp.getOrientationChanging());
     }
 
     @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ef532f5..139e440 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1727,7 +1727,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit, null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index d11ca49..fa83f22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -64,7 +64,7 @@
         wmHelper: WindowManagerStateHelper,
         direction: Direction
     ): Boolean {
-        val ratioForScreenBottom = 0.97
+        val ratioForScreenBottom = 0.99
         val fullView = wmHelper.getWindowRegion(component)
         require(!fullView.isEmpty) { "Target $component view not found." }