Merge "Use UserManagerInternal#hasUserRestriction() in IMMS"
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 812e422..0a99c36 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,6 +784,8 @@
         mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
+        // NOTE: Ensure that only properties that are safe to be modified by the service itself
+        // are included here (regardless of hidden setters, etc.).
     }
 
     private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 422b6a3..32d0d75 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -87,11 +87,13 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -155,6 +157,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ToolbarActionBar;
 import com.android.internal.app.WindowDecorActionBar;
@@ -1602,6 +1605,25 @@
         return callbacks;
     }
 
+    private void notifyVoiceInteractionManagerServiceActivityEvent(
+            @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+
+        final IVoiceInteractionManagerService service =
+                IVoiceInteractionManagerService.Stub.asInterface(
+                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        if (service == null) {
+            Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+                    + "VoiceInteractionManagerService");
+            return;
+        }
+
+        try {
+            service.notifyActivityEventChanged(mToken, type);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
     /**
      * Called when the activity is starting.  This is where most initialization
      * should go: calling {@link #setContentView(int)} to inflate the
@@ -1877,6 +1899,9 @@
         mCalled = true;
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
     }
 
     /**
@@ -2020,6 +2045,12 @@
         final Window win = getWindow();
         if (win != null) win.makeActive();
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+        // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+        // allow any binder call in onResume, we call this method in onPostResume.
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
         mCalled = true;
     }
 
@@ -2395,6 +2426,10 @@
         getAutofillClientController().onActivityPaused();
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
         mCalled = true;
     }
 
@@ -2624,6 +2659,9 @@
 
         getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
         mEnterAnimationComplete = false;
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 8da9631..7d19ed4 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -747,10 +747,9 @@
      */
     public interface VoiceInteractionManagerProvider {
         /**
-         * Notifies the service when a high-level activity event has been changed, for example,
-         * an activity was resumed or stopped.
+         * Notifies the service when an activity is destroyed.
          */
-        void notifyActivityEventChanged();
+        void notifyActivityDestroyed(IBinder activityToken);
     }
 
     /**
diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java
index 15411e5..01c869d 100644
--- a/core/java/android/app/time/TimeState.java
+++ b/core/java/android/app/time/TimeState.java
@@ -63,6 +63,12 @@
         return new TimeState(unixEpochTime, userShouldConfirmId);
     }
 
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mUnixEpochTime, 0);
+        dest.writeBoolean(mUserShouldConfirmTime);
+    }
+
     /** @hide */
     @Nullable
     public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) {
@@ -119,12 +125,6 @@
         return 0;
     }
 
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeParcelable(mUnixEpochTime, 0);
-        dest.writeBoolean(mUserShouldConfirmTime);
-    }
-
     @NonNull
     public UnixEpochTime getUnixEpochTime() {
         return mUnixEpochTime;
diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java
index efdff81..8e87111 100644
--- a/core/java/android/app/time/TimeZoneState.java
+++ b/core/java/android/app/time/TimeZoneState.java
@@ -58,11 +58,17 @@
     }
 
     private static TimeZoneState createFromParcel(Parcel in) {
-        String zoneId = in.readString();
+        String zoneId = in.readString8();
         boolean userShouldConfirmId = in.readBoolean();
         return new TimeZoneState(zoneId, userShouldConfirmId);
     }
 
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mId);
+        dest.writeBoolean(mUserShouldConfirmId);
+    }
+
     /** @hide */
     @Nullable
     public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) {
@@ -107,12 +113,6 @@
         return 0;
     }
 
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(mId);
-        dest.writeBoolean(mUserShouldConfirmId);
-    }
-
     @NonNull
     public String getId() {
         return mId;
diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java
index f1a176e..576bf64 100644
--- a/core/java/android/app/time/UnixEpochTime.java
+++ b/core/java/android/app/time/UnixEpochTime.java
@@ -94,7 +94,6 @@
     }
 
     /** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */
-    @Nullable
     public long getUnixEpochTimeMillis() {
         return mUnixEpochTimeMillis;
     }
@@ -109,7 +108,7 @@
         }
         UnixEpochTime that = (UnixEpochTime) o;
         return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
-                && Objects.equals(mUnixEpochTimeMillis, that.mUnixEpochTimeMillis);
+                && mUnixEpochTimeMillis == that.mUnixEpochTimeMillis;
     }
 
     @Override
@@ -125,32 +124,19 @@
                 + '}';
     }
 
-    public static final @NonNull Creator<UnixEpochTime> CREATOR =
-            new ClassLoaderCreator<UnixEpochTime>() {
+    public static final @NonNull Creator<UnixEpochTime> CREATOR = new Creator<>() {
+        @Override
+        public UnixEpochTime createFromParcel(@NonNull Parcel source) {
+            long elapsedRealtimeMillis = source.readLong();
+            long unixEpochTimeMillis = source.readLong();
+            return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+        }
 
-                @Override
-                public UnixEpochTime createFromParcel(@NonNull Parcel source) {
-                    return createFromParcel(source, null);
-                }
-
-                @Override
-                public UnixEpochTime createFromParcel(
-                        @NonNull Parcel source, @Nullable ClassLoader classLoader) {
-                    long elapsedRealtimeMillis = source.readLong();
-                    long unixEpochTimeMillis = source.readLong();
-                    return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
-                }
-
-                @Override
-                public UnixEpochTime[] newArray(int size) {
-                    return new UnixEpochTime[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
+        @Override
+        public UnixEpochTime[] newArray(int size) {
+            return new UnixEpochTime[size];
+        }
+    };
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -158,6 +144,11 @@
         dest.writeLong(mUnixEpochTimeMillis);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
     /**
      * Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
      * Unix epoch time by the difference between the elapsed realtime value supplied and the one
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a13eada..36ac1a0 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -279,7 +279,6 @@
         mPrimaryId = Objects.requireNonNull(primaryId);
         mSecondaryIds = secondaryIds;
         mVendorIds = vendorIds;
-        Arrays.sort(mSecondaryIds);
     }
 
     /**
@@ -525,7 +524,9 @@
         // vendorIds are ignored for equality
         // programType can be inferred from primaryId, thus not checked
         return mPrimaryId.equals(other.getPrimaryId())
-                && Arrays.equals(mSecondaryIds, other.mSecondaryIds);
+                && mSecondaryIds.length == other.mSecondaryIds.length
+                && Arrays.asList(mSecondaryIds).containsAll(
+                        Arrays.asList(other.mSecondaryIds));
     }
 
     private ProgramSelector(Parcel in) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d71b023..adeb722 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -296,8 +296,10 @@
      * New in version 35:
      *   - Fixed bug that was not reporting high cellular tx power correctly
      *   - Added out of service and emergency service modes to data connection types
+     * New in version 36:
+     *   - Added PowerStats and CPU time-in-state data
      */
-    static final int CHECKIN_VERSION = 35;
+    static final int CHECKIN_VERSION = 36;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -1810,6 +1812,36 @@
     }
 
     /**
+     * CPU usage for a given UID.
+     */
+    public static final class CpuUsageDetails {
+        /**
+         * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+         */
+        public String[] cpuBracketDescriptions;
+        public int uid;
+        /**
+         *  The delta, in milliseconds, per CPU power bracket, from the previous record for the
+         *  same UID.
+         */
+        public long[] cpuUsageMs;
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            UserHandle.formatUid(sb, uid);
+            sb.append(": ");
+            for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
+                if (bracket != 0) {
+                    sb.append(", ");
+                }
+                sb.append(cpuUsageMs[bracket]);
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
      * Battery history record.
      */
     public static final class HistoryItem {
@@ -1952,6 +1984,9 @@
         // Non-null when there is measured energy information
         public MeasuredEnergyDetails measuredEnergyDetails;
 
+        // Non-null when there is CPU usage information
+        public CpuUsageDetails cpuUsageDetails;
+
         public static final int EVENT_FLAG_START = 0x8000;
         public static final int EVENT_FLAG_FINISH = 0x4000;
 
@@ -2161,6 +2196,7 @@
             eventTag = null;
             tagsFirstOccurrence = false;
             measuredEnergyDetails = null;
+            cpuUsageDetails = null;
         }
 
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2211,6 +2247,7 @@
             tagsFirstOccurrence = o.tagsFirstOccurrence;
             currentTime = o.currentTime;
             measuredEnergyDetails = o.measuredEnergyDetails;
+            cpuUsageDetails = o.cpuUsageDetails;
         }
 
         public boolean sameNonEvent(HistoryItem o) {
@@ -6808,6 +6845,25 @@
         private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
                 boolean verbose) {
             StringBuilder item = new StringBuilder();
+
+            if (rec.cpuUsageDetails != null
+                    && rec.cpuUsageDetails.cpuBracketDescriptions != null
+                    && checkin) {
+                String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+                for (int bracket = 0; bracket < descriptions.length; bracket++) {
+                    item.append(BATTERY_STATS_CHECKIN_VERSION);
+                    item.append(',');
+                    item.append(HISTORY_DATA);
+                    item.append(",0,XB,");
+                    item.append(descriptions.length);
+                    item.append(',');
+                    item.append(bracket);
+                    item.append(',');
+                    item.append(descriptions[bracket]);
+                    item.append("\n");
+                }
+            }
+
             if (!checkin) {
                 item.append("  ");
                 TimeUtils.formatDuration(
@@ -7000,14 +7056,6 @@
                         item.append("\"");
                     }
                 }
-                if ((rec.states2 & HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
-                    if (!checkin) {
-                        item.append(" ext=");
-                        if (rec.measuredEnergyDetails != null) {
-                            item.append("E");
-                        }
-                    }
-                }
                 if (rec.eventCode != HistoryItem.EVENT_NONE) {
                     item.append(checkin ? "," : " ");
                     if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
@@ -7036,6 +7084,58 @@
                         item.append("\"");
                     }
                 }
+                boolean firstExtension = true;
+                if (rec.measuredEnergyDetails != null) {
+                    firstExtension = false;
+                    if (!checkin) {
+                        item.append(" ext=energy:");
+                        item.append(rec.measuredEnergyDetails);
+                    } else {
+                        item.append(",XE");
+                        for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
+                            if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+                                item.append(',');
+                                item.append(rec.measuredEnergyDetails.consumers[i].name);
+                                item.append('=');
+                                item.append(rec.measuredEnergyDetails.chargeUC[i]);
+                            }
+                        }
+                    }
+                }
+                if (rec.cpuUsageDetails != null) {
+                    if (!checkin) {
+                        if (!firstExtension) {
+                            item.append("\n                ");
+                        }
+                        String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+                        if (descriptions != null) {
+                            for (int bracket = 0; bracket < descriptions.length; bracket++) {
+                                item.append(" ext=cpu-bracket:");
+                                item.append(bracket);
+                                item.append(":");
+                                item.append(descriptions[bracket]);
+                                item.append("\n                ");
+                            }
+                        }
+                        item.append(" ext=cpu:");
+                        item.append(rec.cpuUsageDetails);
+                    } else {
+                        if (!firstExtension) {
+                            item.append('\n');
+                            item.append(BATTERY_STATS_CHECKIN_VERSION);
+                            item.append(',');
+                            item.append(HISTORY_DATA);
+                            item.append(",0");
+                        }
+                        item.append(",XC,");
+                        item.append(rec.cpuUsageDetails.uid);
+                        for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
+                            item.append(',');
+                            item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
+                        }
+                    }
+                    firstExtension = false;
+                }
                 item.append("\n");
                 if (rec.stepDetails != null) {
                     if (!checkin) {
@@ -7132,25 +7232,6 @@
                         item.append("\n");
                     }
                 }
-                if (rec.measuredEnergyDetails != null) {
-                    if (!checkin) {
-                        item.append("                 Energy: ");
-                        item.append(rec.measuredEnergyDetails);
-                        item.append("\n");
-                    } else {
-                        item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
-                        item.append(HISTORY_DATA); item.append(",0,XE");
-                        for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
-                            if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
-                                item.append(',');
-                                item.append(rec.measuredEnergyDetails.consumers[i].name);
-                                item.append('=');
-                                item.append(rec.measuredEnergyDetails.chargeUC[i]);
-                            }
-                        }
-                        item.append("\n");
-                    }
-                }
                 oldState = rec.states;
                 oldState2 = rec.states2;
                 // Clear High Tx Power Flag for volta positioning
@@ -7158,7 +7239,6 @@
                     rec.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
                 }
             }
-
             return item.toString();
         }
 
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 82e1d5f0..fc47778 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -20,6 +20,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -73,6 +74,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -146,6 +149,25 @@
      */
     public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
 
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = {
+            VOICE_INTERACTION_ACTIVITY_EVENT_START,
+            VOICE_INTERACTION_ACTIVITY_EVENT_RESUME,
+            VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE,
+            VOICE_INTERACTION_ACTIVITY_EVENT_STOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VoiceInteractionActivityEventType{}
+
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
diff --git a/core/java/android/app/usage/TimeSparseArray.java b/core/java/android/util/TimeSparseArray.java
similarity index 65%
rename from core/java/android/app/usage/TimeSparseArray.java
rename to core/java/android/util/TimeSparseArray.java
index 2bd6b24..6efc683 100644
--- a/core/java/android/app/usage/TimeSparseArray.java
+++ b/core/java/android/util/TimeSparseArray.java
@@ -14,13 +14,11 @@
  * under the License.
  */
 
-package android.app.usage;
-
-import android.util.LongSparseArray;
-import android.util.Slog;
+package android.util;
 
 /**
  * An array that indexes by a long timestamp, representing milliseconds since the epoch.
+ * @param <E> The type of values this container maps to a timestamp.
  *
  * {@hide}
  */
@@ -29,53 +27,41 @@
 
     private boolean mWtfReported;
 
-    public TimeSparseArray() {
-        super();
-    }
-
     /**
      * Finds the index of the first element whose timestamp is greater or equal to
      * the given time.
      *
      * @param time The timestamp for which to search the array.
-     * @return The index of the matched element, or -1 if no such match exists.
+     * @return The smallest {@code index} for which {@code (keyAt(index) >= timeStamp)} is
+     * {@code true}, or {@link #size() size} if no such {@code index} exists.
      */
     public int closestIndexOnOrAfter(long time) {
-        // This is essentially a binary search, except that if no match is found
-        // the closest index is returned.
         final int size = size();
+        int result = size;
         int lo = 0;
         int hi = size - 1;
-        int mid = -1;
-        long key = -1;
         while (lo <= hi) {
-            mid = lo + ((hi - lo) / 2);
-            key = keyAt(mid);
+            final int mid = lo + ((hi - lo) / 2);
+            final long key = keyAt(mid);
 
             if (time > key) {
                 lo = mid + 1;
             } else if (time < key) {
                 hi = mid - 1;
+                result = mid;
             } else {
                 return mid;
             }
         }
-
-        if (time < key) {
-            return mid;
-        } else if (time > key && lo < size) {
-            return lo;
-        } else {
-            return -1;
-        }
+        return result;
     }
 
     /**
      * {@inheritDoc}
      *
-     * <p> As this container is being used only to keep {@link android.util.AtomicFile files},
-     * there should not be any collisions. Reporting a {@link Slog#wtf(String, String)} in case that
-     * happens, as that will lead to one whole file being dropped.
+     * <p> This container can store only one value for each timestamp. And so ideally, the caller
+     * should ensure that there are no collisions. Reporting a {@link Slog#wtf(String, String)}
+     * if that happens, as that will lead to the previous value being overwritten.
      */
     @Override
     public void put(long key, E value) {
@@ -93,16 +79,13 @@
      * the given time.
      *
      * @param time The timestamp for which to search the array.
-     * @return The index of the matched element, or -1 if no such match exists.
+     * @return The largest {@code index} for which {@code (keyAt(index) <= timeStamp)} is
+     * {@code true}, or -1 if no such {@code index} exists.
      */
     public int closestIndexOnOrBefore(long time) {
         final int index = closestIndexOnOrAfter(time);
-        if (index < 0) {
-            // Everything is larger, so we use the last element, or -1 if the list is empty.
-            return size() - 1;
-        }
 
-        if (keyAt(index) == time) {
+        if (index < size() && keyAt(index) == time) {
             return index;
         }
         return index - 1;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 5ec7962..b5dff5e 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1273,8 +1273,8 @@
     public static final int AXIS_GENERIC_16 = 47;
 
     // NOTE: If you add a new axis here you must also add it to:
-    //  native/include/android/input.h
-    //  frameworks/base/include/ui/KeycodeLabels.h
+    //  frameworks/native/include/android/input.h
+    //  frameworks/native/libs/input/InputEventLabels.cpp
 
     // Symbolic names of all axes.
     private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
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/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 8d51c9c..bbcf982 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
 import android.os.Bundle;
@@ -25,18 +27,17 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VisibleActivityInfo;
 
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.internal.app.IVoiceInteractionSessionShowCallback;
-import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.service.voice.IVoiceInteractionService;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import com.android.internal.app.IVoiceInteractor;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -303,4 +304,14 @@
      * Notifies when the session window is shown or hidden.
      */
     void setSessionWindowVisible(in IBinder token, boolean visible);
+
+    /**
+     * Notifies when the Activity lifecycle event changed.
+     *
+     * @param activityToken The token of activity.
+     * @param type The type of lifecycle event of the activity lifecycle.
+     */
+    oneway void notifyActivityEventChanged(
+            in IBinder activityToken,
+            int type);
 }
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/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 5523344..0614cd2 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.BitDescription;
+import android.os.BatteryStats.CpuUsageDetails;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.HistoryStepDetails;
 import android.os.BatteryStats.HistoryTag;
@@ -122,6 +123,8 @@
 
     static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
     static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
+    static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
+    static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
 
     private final Parcel mHistoryBuffer;
     private final File mSystemDir;
@@ -194,6 +197,7 @@
     private long mTrackRunningHistoryUptimeMs = 0;
     private long mHistoryBaseTimeMs;
     private boolean mMeasuredEnergyHeaderWritten = false;
+    private boolean mCpuUsageHeaderWritten = false;
 
     private byte mLastHistoryStepLevel = 0;
 
@@ -351,6 +355,7 @@
         mTrackRunningHistoryElapsedRealtimeMs = 0;
         mTrackRunningHistoryUptimeMs = 0;
         mMeasuredEnergyHeaderWritten = false;
+        mCpuUsageHeaderWritten = false;
 
         mHistoryBuffer.setDataSize(0);
         mHistoryBuffer.setDataPosition(0);
@@ -1180,7 +1185,7 @@
 
         final int idx = code & HistoryItem.EVENT_TYPE_MASK;
         final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
-                  (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
+                (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
 
         final String[] names = BatteryStats.HISTORY_EVENT_NAMES;
         if (idx < 0 || idx >= names.length) return;
@@ -1191,6 +1196,17 @@
     }
 
     /**
+     * Records CPU usage by a specific UID.  The recorded data is the delta from
+     * the previous record for the same UID.
+     */
+    public void recordCpuUsage(long elapsedRealtimeMs, long uptimeMs,
+            CpuUsageDetails cpuUsageDetails) {
+        mHistoryCur.cpuUsageDetails = cpuUsageDetails;
+        mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+    }
+
+    /**
      * Writes changes to a HistoryItem state bitmap to Atrace.
      */
     private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
@@ -1338,6 +1354,7 @@
                 entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
             }
             mMeasuredEnergyHeaderWritten = false;
+            mCpuUsageHeaderWritten = false;
 
             // Make a copy of mHistoryCur.
             HistoryItem copy = new HistoryItem();
@@ -1377,6 +1394,7 @@
         cur.eventTag = null;
         cur.tagsFirstOccurrence = false;
         cur.measuredEnergyDetails = null;
+        cur.cpuUsageDetails = null;
         if (DEBUG) {
             Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
                     + " now " + mHistoryBuffer.dataPosition()
@@ -1502,12 +1520,18 @@
                 extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
             }
         }
+        if (cur.cpuUsageDetails != null) {
+            extensionFlags |= EXTENSION_CPU_USAGE_FLAG;
+            if (!mCpuUsageHeaderWritten) {
+                extensionFlags |= BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG;
+            }
+        }
         if (extensionFlags != 0) {
             cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
         } else {
             cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG;
         }
-        final boolean state2IntChanged = cur.states2 != last.states2;
+        final boolean state2IntChanged = cur.states2 != last.states2 || extensionFlags != 0;
         if (state2IntChanged) {
             firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
         }
@@ -1645,6 +1669,20 @@
                     dest.writeLong(chargeUC);
                 }
             }
+
+            if (cur.cpuUsageDetails != null) {
+                if (DEBUG) {
+                    Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
+                }
+                if (!mCpuUsageHeaderWritten) {
+                    dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+                    mCpuUsageHeaderWritten = true;
+                }
+                dest.writeInt(cur.cpuUsageDetails.uid);
+                for (long cpuUsageMs: cur.cpuUsageDetails.cpuUsageMs) {
+                    dest.writeLong(cpuUsageMs);
+                }
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index ee3d15b..2429a88 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -34,6 +34,7 @@
             new BatteryStats.HistoryStepDetails();
     private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
     private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails;
+    private BatteryStats.CpuUsageDetails mCpuUsageDetails;
 
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
@@ -229,9 +230,35 @@
                     mMeasuredEnergyDetails.chargeUC[i] = src.readLong();
                 }
                 cur.measuredEnergyDetails = mMeasuredEnergyDetails;
+            } else {
+                cur.measuredEnergyDetails = null;
+            }
+
+            if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
+                mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
+                mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+                mCpuUsageDetails.cpuUsageMs =
+                        new long[mCpuUsageDetails.cpuBracketDescriptions.length];
+            } else if (mCpuUsageDetails != null) {
+                mCpuUsageDetails.cpuBracketDescriptions = null;
+            }
+
+            if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_FLAG) != 0) {
+                if (mCpuUsageDetails == null) {
+                    throw new IllegalStateException("CpuUsageDetails without a header");
+                }
+
+                mCpuUsageDetails.uid = src.readInt();
+                for (int i = 0; i < mCpuUsageDetails.cpuUsageMs.length; i++) {
+                    mCpuUsageDetails.cpuUsageMs[i] = src.readLong();
+                }
+                cur.cpuUsageDetails = mCpuUsageDetails;
+            } else {
+                cur.cpuUsageDetails = null;
             }
         } else {
             cur.measuredEnergyDetails = null;
+            cur.cpuUsageDetails = null;
         }
     }
 
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 07a8998..de3edeb 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -33,7 +33,6 @@
 import java.nio.file.Paths;
 import java.util.Arrays;
 
-@VisibleForTesting(visibility = PACKAGE)
 public class KernelSingleUidTimeReader {
     private static final String TAG = KernelSingleUidTimeReader.class.getName();
     private static final boolean DBG = false;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 55a26fe..953b36b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1508,8 +1508,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 {}
 
@@ -1562,12 +1561,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/res/res/values/config.xml b/core/res/res/values/config.xml
index 061eb48..47faf2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3667,10 +3667,6 @@
          is interactive. -->
     <bool name="config_volumeHushGestureEnabled">true</bool>
 
-    <!-- Name of the component to handle network policy notifications. If present,
-         disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
-    <string translatable="false" name="config_networkPolicyNotificationComponent"></string>
-
     <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
          automatically try to pair with it when the device exits tablet mode. -->
     <string translatable="false" name="config_packagedKeyboardName"></string>
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index a032290..bce0909 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,12 +16,12 @@
 
 package android.app.time;
 
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.os.Parcel;
 import android.os.ShellCommand;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -60,19 +60,8 @@
     @Test
     public void testParceling() {
         UnixEpochTime time = new UnixEpochTime(1, 2);
-        TimeState value = new TimeState(time, true);
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeParcelable(value, 0);
-
-            parcel.setDataPosition(0);
-
-            TimeState stringValueCopy =
-                    parcel.readParcelable(null /* classLoader */, TimeState.class);
-            assertEquals(value, stringValueCopy);
-        } finally {
-            parcel.recycle();
-        }
+        assertRoundTripParcelable(new TimeState(time, true));
+        assertRoundTripParcelable(new TimeState(time, false));
     }
 
     @Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 9786bb0..35a9dbc 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,12 +16,12 @@
 
 package android.app.time;
 
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.os.Parcel;
 import android.os.ShellCommand;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -59,19 +59,8 @@
 
     @Test
     public void testParceling() {
-        TimeZoneState value = new TimeZoneState("Europe/London", true);
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeParcelable(value, 0);
-
-            parcel.setDataPosition(0);
-
-            TimeZoneState stringValueCopy =
-                    parcel.readParcelable(null /* classLoader */, TimeZoneState.class);
-            assertEquals(value, stringValueCopy);
-        } finally {
-            parcel.recycle();
-        }
+        assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
+        assertRoundTripParcelable(new TimeZoneState("Europe/London", false));
     }
 
     @Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
index cd75348..3ab01f3 100644
--- a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -16,12 +16,12 @@
 
 package android.app.time;
 
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.os.Parcel;
 import android.os.ShellCommand;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -57,19 +57,7 @@
 
     @Test
     public void testParceling() {
-        UnixEpochTime value = new UnixEpochTime(1000, 1);
-        Parcel parcel = Parcel.obtain();
-        try {
-            parcel.writeParcelable(value, 0);
-
-            parcel.setDataPosition(0);
-
-            UnixEpochTime stringValueCopy =
-                    parcel.readParcelable(null /* classLoader */, UnixEpochTime.class);
-            assertEquals(value, stringValueCopy);
-        } finally {
-            parcel.recycle();
-        }
+        assertRoundTripParcelable(new UnixEpochTime(1000, 1));
     }
 
     @Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
new file mode 100644
index 0000000..c8e2364
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link TimeSparseArray}.
+ * This class only tests subclass specific functionality. Tests for the super class
+ * {@link LongSparseArray} should be covered under {@link LongSparseArrayTest}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TimeSparseArrayTest {
+
+    @Test
+    public void closestIndexOnOrAfter() {
+        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();
+
+        // Values don't matter for this test.
+        timeSparseArray.put(51, new Object());
+        timeSparseArray.put(10, new Object());
+        timeSparseArray.put(59, new Object());
+
+        assertThat(timeSparseArray.size()).isEqualTo(3);
+
+        // Testing any number arbitrarily smaller than 10.
+        assertThat(timeSparseArray.closestIndexOnOrAfter(-141213)).isEqualTo(0);
+        for (long time = -43; time <= 10; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(0);
+        }
+
+        for (long time = 11; time <= 51; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(1);
+        }
+
+        for (long time = 52; time <= 59; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(2);
+        }
+
+        for (long time = 60; time <= 102; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(3);
+        }
+        // Testing any number arbitrarily larger than 59.
+        assertThat(timeSparseArray.closestIndexOnOrAfter(15332)).isEqualTo(3);
+    }
+
+    @Test
+    public void closestIndexOnOrBefore() {
+        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();
+
+        // Values don't matter for this test.
+        timeSparseArray.put(21, new Object());
+        timeSparseArray.put(4, new Object());
+        timeSparseArray.put(91, new Object());
+        timeSparseArray.put(39, new Object());
+
+        assertThat(timeSparseArray.size()).isEqualTo(4);
+
+        // Testing any number arbitrarily smaller than 4.
+        assertThat(timeSparseArray.closestIndexOnOrBefore(-1478133)).isEqualTo(-1);
+        for (long time = -42; time < 4; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(-1);
+        }
+
+        for (long time = 4; time < 21; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(0);
+        }
+
+        for (long time = 21; time < 39; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(1);
+        }
+
+        for (long time = 39; time < 91; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(2);
+        }
+
+        for (long time = 91; time < 109; time++) {
+            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(3);
+        }
+        // Testing any number arbitrarily larger than 91.
+        assertThat(timeSparseArray.closestIndexOnOrBefore(1980732)).isEqualTo(3);
+    }
+}
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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2214a98..7c3125b 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
@@ -293,17 +293,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 {
@@ -317,7 +317,7 @@
     //
 
     @BindsOptionalOf
-    abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+    abstract WindowDecorViewModel optionalWindowDecorViewModel();
 
     //
     // Unfold transition
@@ -769,7 +769,7 @@
             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 47b6659..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,6 @@
 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;
@@ -183,7 +182,7 @@
 
     @WMSingleton
     @Provides
-    static WindowDecorViewModel<?> provideWindowDecorViewModel(
+    static WindowDecorViewModel provideWindowDecorViewModel(
             Context context,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
@@ -209,7 +208,7 @@
     @Provides
     @DynamicOverride
     static FreeformComponents provideFreeformComponents(
-            FreeformTaskListener<?> taskListener,
+            FreeformTaskListener taskListener,
             FreeformTaskTransitionHandler transitionHandler,
             FreeformTaskTransitionObserver transitionObserver) {
         return new FreeformComponents(
@@ -218,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);
     }
 
@@ -238,7 +237,7 @@
     static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel<?> windowDecorViewModel) {
+            WindowDecorViewModel windowDecorViewModel) {
         return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
     }
 
@@ -248,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);
     }
 
     //
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/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 fb5a504..63e01a7 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);
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/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 275f6c8..47167b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -143,4 +143,10 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 251217773)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 1fc0375..7d498dc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -92,7 +92,7 @@
     }
 
     /** Checks that [pipApp] window is animated towards default position in right bottom corner */
-    @Presubmit
+    @FlakyTest(bugId = 251135384)
     @Test
     fun pipLayerMovesTowardsRightBottomCorner() {
         // in gestural nav the swipe makes PiP first go upwards
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 104b409..b4594de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -141,6 +141,12 @@
     @Test
     fun entireScreenCoveredAtStartAndEnd() = testSpec.entireScreenCovered(allStates = false)
 
+    @FlakyTest(bugId = 251219769)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
     /**
      * Checks [pipApp] window remains visible and on top throughout the transition
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 5e3194c..6bf7e8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
@@ -81,7 +82,7 @@
     override fun entireScreenCovered() = super.entireScreenCovered()
 
     /** {@inheritDoc}  */
-    @Presubmit
+    @FlakyTest(bugId = 197726610)
     @Test
     override fun pipLayerExpands() = super.pipLayerExpands()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index cb49e18..a2eefec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -106,7 +106,7 @@
     fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251268711)
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 504238f..201594b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,7 +51,6 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
@@ -117,7 +117,7 @@
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251269324)
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 2ecf819..553840c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
@@ -104,7 +105,7 @@
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 384489d..e2f7f7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
@@ -103,7 +104,7 @@
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest
     @Test
     override fun entireScreenCovered() =
         super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 04ebbf5..d7b3ec2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.WindowManagerPolicyConstants
@@ -150,7 +151,7 @@
     override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
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/packages/SettingsLib/res/values-w320dp-port/dimens.xml b/packages/SettingsLib/res/values-w320dp-port/dimens.xml
new file mode 100644
index 0000000..bddf391
--- /dev/null
+++ b/packages/SettingsLib/res/values-w320dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+    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>
+    <integer name="avatar_picker_columns">2</integer>
+    <dimen name="avatar_size_in_picker">96dp</dimen>
+    <dimen name="avatar_picker_padding">6dp</dimen>
+    <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
index 3c75abf..c1ee7ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -32,7 +32,6 @@
 public abstract class MobileNetworkDatabase extends RoomDatabase {
 
     public static final String TAG = "MobileNetworkDatabase";
-    public static final String DATABASE_NAME = "mobilenetworksettings_db";
 
     public abstract SubscriptionInfoDao mSubscriptionInfoDao();
 
@@ -47,8 +46,7 @@
      * @return The MobileNetworkDatabase.
      */
     public static MobileNetworkDatabase createDatabase(Context context) {
-        return Room.databaseBuilder(context,
-                        MobileNetworkDatabase.class, DATABASE_NAME)
+        return Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class)
                 .fallbackToDestructiveMigration()
                 .enableMultiInstanceInvalidation()
                 .build();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f3614d3..5e0d935 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -300,5 +300,6 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
+        "wmshell.protolog.json.gz",
     ],
 }
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/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 716c4fe..1ce106e 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -61,7 +61,7 @@
         </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-            android:id="@id/settings_button_container"
+            android:id="@+id/settings_button_container"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
             android:background="@drawable/qs_footer_action_circle"
@@ -85,7 +85,7 @@
         </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 
         <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:id="@id/pm_lite"
+            android:id="@+id/pm_lite"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
             android:background="@drawable/qs_footer_action_circle_color"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ba5f67f..f22e797 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -188,10 +188,5 @@
     <item type="id" name="face_scanning_anim"/>
 
     <item type="id" name="qqs_tile_layout"/>
-
-    <!-- The buttons in the Quick Settings footer actions.-->
-    <item type="id" name="settings_button_container"/>
-    <item type="id" name="pm_lite"/>
-
 </resources>
 
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/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/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 80b9c4e..50012a5 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -1,3 +1,19 @@
+/*
+ * 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
@@ -14,6 +30,11 @@
 
 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)
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 00236f1..2cca086 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,6 +881,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/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/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 3b629f7..2e46b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -172,7 +172,7 @@
     public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
             new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
 
-    public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
+    public static final UnreleasedFlag NEW_FOOTER_ACTIONS = new UnreleasedFlag(507, true);
 
     /***************************************/
     // 600- status bar
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index edd6188f..5816f6b 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 4c4b588..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
@@ -157,7 +157,11 @@
                         }
                     }
                 dozeHost.addCallback(callback)
-                trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+                trySendWithFailureLogging(
+                    statusBarStateController.isDozing,
+                    TAG,
+                    "initial isDozing",
+                )
 
                 awaitClose { dozeHost.removeCallback(callback) }
             }
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/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index dd1ffcc..28ddead 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -222,7 +222,6 @@
 
     private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
         val buttonView = button.view
-        buttonView.id = model?.id ?: View.NO_ID
         buttonView.isVisible = model != null
         if (model == null) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 5a8f684..2ad0513 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -25,7 +25,6 @@
  * power buttons.
  */
 data class FooterActionsButtonViewModel(
-    val id: Int?,
     val icon: Icon,
     val iconTint: Int?,
     @DrawableRes val background: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 8b3f4b4..a935338 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -138,7 +138,6 @@
     /** The model for the settings button. */
     val settings: FooterActionsButtonViewModel =
         FooterActionsButtonViewModel(
-            id = R.id.settings_button_container,
             Icon.Resource(
                 R.drawable.ic_settings,
                 ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
@@ -152,7 +151,6 @@
     val power: FooterActionsButtonViewModel? =
         if (showPowerButton) {
             FooterActionsButtonViewModel(
-                id = R.id.pm_lite,
                 Icon.Resource(
                     android.R.drawable.ic_lock_power_off,
                     ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
@@ -258,7 +256,6 @@
             }
 
         return FooterActionsButtonViewModel(
-            id = null,
             Icon.Loaded(
                 icon,
                 ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
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/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/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index eea2e95..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
@@ -153,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/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
index f4d61c8..1531f88 100644
--- 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
@@ -290,9 +290,7 @@
             .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
         assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
         assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
-        assertThat(memoryUse.objectUsage.extras).isEqualTo(extras)
         assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
-        assertThat(memoryUse.objectUsage.extender).isEqualTo(extender)
         if (style == null) {
             assertThat(memoryUse.objectUsage.style).isNull()
         } else {
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/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5e7d814..16fe121 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -735,6 +735,9 @@
     // initialized in the constructor.
     public int CUR_MAX_EMPTY_PROCESSES;
 
+    /** @see mEnforceReceiverExportedFlagRequirement */
+    private static final String KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT =
+            "enforce_exported_flag_requirement";
 
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED =
@@ -744,6 +747,9 @@
     private static final String KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS =
             "no_kill_cached_processes_post_boot_completed_duration_millis";
 
+    /** @see mEnforceReceiverExportedFlagRequirement */
+    private static final boolean DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT = false;
+
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final boolean DEFAULT_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED = true;
 
@@ -752,6 +758,15 @@
             DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS = 600_000;
 
     /**
+     * If true, enforce the requirement that dynamically registered receivers specify one of
+     * {@link android.content.Context#RECEIVER_EXPORTED} or
+     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} if registering for any non-system
+     * broadcasts.
+     */
+    volatile boolean mEnforceReceiverExportedFlagRequirement =
+            DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT;
+
+    /**
      * If true, do not kill excessive cached processes proactively, until user-0 is unlocked.
      * @see #mNoKillCachedProcessesPostBootCompletedDurationMillis
      */
@@ -1010,6 +1025,9 @@
                             case KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED:
                                 updateNoKillCachedProcessesUntilBootCompleted();
                                 break;
+                            case KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT:
+                                updateEnforceReceiverExportedFlagRequirement();
+                                break;
                             case KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS:
                                 updateNoKillCachedProcessesPostBootCompletedDurationMillis();
                                 break;
@@ -1482,6 +1500,13 @@
                 DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST);
     }
 
+    private void updateEnforceReceiverExportedFlagRequirement() {
+        mEnforceReceiverExportedFlagRequirement = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT,
+                DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
+    }
+
     private void updateNoKillCachedProcessesUntilBootCompleted() {
         mNoKillCachedProcessesUntilBootCompleted = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1819,6 +1844,8 @@
         pw.print("="); pw.println(mPrioritizeAlarmBroadcasts);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED);
         pw.print("="); pw.println(mNoKillCachedProcessesUntilBootCompleted);
+        pw.print("  "); pw.print(KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
+        pw.print("="); pw.println(mEnforceReceiverExportedFlagRequirement);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS);
         pw.print("="); pw.println(mNoKillCachedProcessesPostBootCompletedDurationMillis);
         pw.print("  "); pw.print(KEY_MAX_EMPTY_TIME_MILLIS);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 34e3eec..5c1031a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -215,8 +215,8 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -588,7 +588,7 @@
      * unprotected broadcast in code.
      */
     @ChangeId
-    @Disabled
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
@@ -2935,10 +2935,13 @@
                 || event == Event.ACTIVITY_DESTROYED)) {
             contentCaptureService.notifyActivityEvent(userId, activity, event);
         }
-        // TODO(b/201234353): Move the logic to client side.
-        if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
-                || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
-            mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+        // Currently we have move most of logic to the client side. When the activity lifecycle
+        // event changed, the client side will notify the VoiceInteractionManagerService. But
+        // when the application process died, the VoiceInteractionManagerService will miss the
+        // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to
+        // know if the activity has been destroyed.
+        if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) {
+            mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken);
         }
     }
 
@@ -13440,7 +13443,8 @@
             // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
             // not be used generally, so we will be marking them as exported by default
             final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
+                    && mConstants.mEnforceReceiverExportedFlagRequirement;
             if (!onlyProtectedBroadcasts) {
                 if (receiver == null && !explicitExportStateDefined) {
                     // sticky broadcast, no flag specified (flag isn't required)
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 542f381..067c35f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -68,7 +68,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.AppGlobals;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -81,7 +80,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -177,6 +175,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInputMethodManager;
@@ -195,8 +194,6 @@
 import com.android.server.utils.PriorityDump;
 import com.android.server.wm.WindowManagerInternal;
 
-import com.google.android.collect.Sets;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -211,7 +208,6 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.OptionalInt;
-import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
@@ -283,7 +279,7 @@
      * {@link #mPreventImeStartupUnlessTextEditor}.
      */
     @NonNull
-    private final Set<String> mNonPreemptibleInputMethods;
+    private final String[] mNonPreemptibleInputMethods;
 
     @UserIdInt
     private int mLastSwitchUserId;
@@ -297,12 +293,12 @@
     private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
             new SparseBooleanArray(0);
     final WindowManagerInternal mWindowManagerInternal;
+    private final ActivityManagerInternal mActivityManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
     final ImePlatformCompatUtils mImePlatformCompatUtils;
     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
     private final DisplayManagerInternal mDisplayManagerInternal;
-    final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
     private final UserManager mUserManager;
@@ -800,7 +796,6 @@
     private LocaleList mLastSystemLocales;
     private boolean mAccessibilityRequestingNoSoftKeyboard;
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
-    private final IPackageManager mIPackageManager;
     private final String mSlotIme;
 
     /**
@@ -1546,11 +1541,13 @@
                     int change = isPackageDisappearing(curIm.getPackageName());
                     if (change == PACKAGE_TEMPORARY_CHANGE
                             || change == PACKAGE_PERMANENT_CHANGE) {
+                        final PackageManager userAwarePackageManager =
+                                getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
                         ServiceInfo si = null;
                         try {
-                            si = mIPackageManager.getServiceInfo(
-                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
-                        } catch (RemoteException ex) {
+                            si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
+                                    PackageManager.ComponentInfoFlags.of(0));
+                        } catch (PackageManager.NameNotFoundException ignored) {
                         }
                         if (si == null) {
                             // Uh oh, current input method is no longer around!
@@ -1708,7 +1705,6 @@
     }
 
     public InputMethodManagerService(Context context) {
-        mIPackageManager = AppGlobals.getPackageManager();
         mContext = context;
         mRes = context.getResources();
         // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
@@ -1722,6 +1718,7 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mImePlatformCompatUtils = new ImePlatformCompatUtils();
@@ -1730,8 +1727,6 @@
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-        mHasFeature = context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INPUT_METHODS);
 
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
 
@@ -1756,12 +1751,7 @@
         mShowOngoingImeSwitcherForPhones = false;
 
         mNotificationShown = false;
-        int userId = 0;
-        try {
-            userId = ActivityManager.getService().getCurrentUser().id;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
-        }
+        final int userId = mActivityManagerInternal.getCurrentUserId();
 
         mLastSwitchUserId = userId;
 
@@ -1777,8 +1767,8 @@
         mAutofillController = new AutofillSuggestionsController(this);
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
-        mNonPreemptibleInputMethods = Sets.newHashSet(mRes.getStringArray(
-                com.android.internal.R.array.config_nonPreemptibleInputMethods));
+        mNonPreemptibleInputMethods = mRes.getStringArray(
+                com.android.internal.R.array.config_nonPreemptibleInputMethods);
         mHwController = new HandwritingModeController(thread.getLooper(),
                 new InkWindowInitializer());
         registerDeviceListenerAndCheckStylusSupport();
@@ -2605,23 +2595,20 @@
         if (!mPreventImeStartupUnlessTextEditor) {
             return false;
         }
-
-        final boolean imeVisibleAllowed =
-                isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags);
-
-        return !(imeVisibleAllowed
-                || mShowRequested
-                || isNonPreemptibleImeLocked(selectedMethodId));
-    }
-
-    /** Return {@code true} if the given IME is non-preemptible like the tv remote service. */
-    @GuardedBy("ImfLock.class")
-    private boolean isNonPreemptibleImeLocked(@NonNull  String selectedMethodId) {
-        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
-        if (imi != null) {
-            return mNonPreemptibleInputMethods.contains(imi.getPackageName());
+        if (mShowRequested) {
+            return false;
         }
-        return false;
+        if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
+            return false;
+        }
+        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        if (imi == null) {
+            return false;
+        }
+        if (ArrayUtils.contains(mNonPreemptibleInputMethods, imi.getPackageName())) {
+            return false;
+        }
+        return true;
     }
 
     @GuardedBy("ImfLock.class")
@@ -3211,27 +3198,30 @@
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
         if (enabledMayChange) {
+            final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
+                    mSettings.getCurrentUserId());
+
             List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
                 InputMethodInfo imm = enabled.get(i);
+                ApplicationInfo ai = null;
                 try {
-                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
-                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
-                            mSettings.getCurrentUserId());
-                    if (ai != null && ai.enabledSetting
-                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Update state(" + imm.getId()
-                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
-                        }
-                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
-                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
-                                mContext.getBasePackageName());
+                    ai = userAwarePackageManager.getApplicationInfo(imm.getPackageName(),
+                            PackageManager.ApplicationInfoFlags.of(
+                                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS));
+                } catch (PackageManager.NameNotFoundException ignored) {
+                }
+                if (ai != null && ai.enabledSetting
+                        == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Update state(" + imm.getId()
+                                + "): DISABLED_UNTIL_USED -> DEFAULT");
                     }
-                } catch (RemoteException e) {
+                    userAwarePackageManager.setApplicationEnabledSetting(imm.getPackageName(),
+                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                            PackageManager.DONT_KILL_APP);
                 }
             }
         }
@@ -3313,7 +3303,7 @@
             // setSelectedInputMethodAndSubtypeLocked().
             setSelectedMethodIdLocked(id);
 
-            if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
+            if (mActivityManagerInternal.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
                 intent.putExtra("input_method_id", id);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 2a6ae44..cb6e43c 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -17,7 +17,6 @@
 package com.android.server.locksettings;
 
 import android.security.AndroidKeyStoreMaintenance;
-import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
@@ -223,13 +222,6 @@
                 keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
                 Slog.i(TAG, "Using rollback-resistant key");
             } catch (KeyStoreException e) {
-                if (!(e.getCause() instanceof android.security.KeyStoreException)) {
-                    throw e;
-                }
-                int errorCode = ((android.security.KeyStoreException) e.getCause()).getErrorCode();
-                if (errorCode != KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE) {
-                    throw e;
-                }
                 Slog.w(TAG, "Rollback-resistant keys unavailable.  Falling back to "
                         + "non-rollback-resistant key");
                 keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4166364..a0748b4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5518,6 +5518,7 @@
         switch (effectId) {
             case HapticFeedbackConstants.CONTEXT_CLICK:
             case HapticFeedbackConstants.GESTURE_END:
+            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
                 if (!mHapticTextHandleEnabled) {
@@ -5536,6 +5537,8 @@
             case HapticFeedbackConstants.EDGE_RELEASE:
             case HapticFeedbackConstants.CONFIRM:
             case HapticFeedbackConstants.GESTURE_START:
+            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8a84860..6e93171 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -270,6 +270,8 @@
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+    private int[] mCpuPowerBracketMap;
+    private final CpuUsageDetails mCpuUsageDetails = new CpuUsageDetails();
 
     public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
         return mKernelMemoryStats;
@@ -478,7 +480,7 @@
     @GuardedBy("this")
     @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
     @VisibleForTesting
-    public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+    public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (!initKernelSingleUidTimeReaderLocked()) {
             return;
         }
@@ -488,12 +490,20 @@
         mNumSingleUidCpuTimeReads++;
 
         LongArrayMultiStateCounter onBatteryCounter =
-                u.getProcStateTimeCounter(timestampMs).getCounter();
+                u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
         LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+                u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
 
-        mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+        if (isUsageHistoryEnabled()) {
+            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                    getCpuTimeInFreqContainer();
+            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs,
+                    deltaContainer);
+            recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+        } else {
+            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
+        }
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
 
         if (u.mChildUids != null) {
             LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
@@ -504,14 +514,27 @@
                         u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
                 if (cpuTimeInFreqCounter != null) {
                     mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
-                            cpuTimeInFreqCounter, timestampMs, deltaContainer);
+                            cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
                     onBatteryCounter.addCounts(deltaContainer);
+                    if (isUsageHistoryEnabled()) {
+                        recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                    }
                     onBatteryScreenOffCounter.addCounts(deltaContainer);
                 }
             }
         }
     }
 
+    private void recordCpuUsage(int uid, LongArrayMultiStateCounter.LongArrayContainer cpuUsage,
+            long elapsedRealtimeMs, long uptimeMs) {
+        if (!cpuUsage.combineValues(mCpuUsageDetails.cpuUsageMs, mCpuPowerBracketMap)) {
+            return;
+        }
+
+        mCpuUsageDetails.uid = uid;
+        mHistory.recordCpuUsage(elapsedRealtimeMs, uptimeMs, mCpuUsageDetails);
+    }
+
     /**
      * Removes kernel CPU stats for removed UIDs, in the order they were added to the
      * mPendingRemovedUids queue.
@@ -557,16 +580,26 @@
                     continue;
                 }
 
-                final long timestampMs = mClock.elapsedRealtime();
+                final long elapsedRealtimeMs = mClock.elapsedRealtime();
+                final long uptimeMs = mClock.uptimeMillis();
                 final LongArrayMultiStateCounter onBatteryCounter =
-                        u.getProcStateTimeCounter(timestampMs).getCounter();
+                        u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
                 final LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                        u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+                        u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
 
                 if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
-                    mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter, timestampMs);
+                    if (isUsageHistoryEnabled()) {
+                        LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                                getCpuTimeInFreqContainer();
+                        mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+                                elapsedRealtimeMs, deltaContainer);
+                        recordCpuUsage(parentUid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                    } else {
+                        mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+                                elapsedRealtimeMs);
+                    }
                     mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
-                            timestampMs);
+                            elapsedRealtimeMs);
                 } else {
                     Uid.ChildUid childUid = u.getChildUid(uid);
                     if (childUid != null) {
@@ -574,9 +607,12 @@
                         if (counter != null) {
                             final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
                                     getCpuTimeInFreqContainer();
-                            mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+                            mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
                                     deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
+                            if (isUsageHistoryEnabled()) {
+                                recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+                            }
                             onBatteryScreenOffCounter.addCounts(deltaContainer);
                         }
                     }
@@ -585,17 +621,6 @@
         }
     }
 
-    @VisibleForTesting
-    public static long[] addCpuTimes(long[] timesA, long[] timesB) {
-        if (timesA != null && timesB != null) {
-            for (int i = timesA.length - 1; i >= 0; --i) {
-                timesA[i] += timesB[i];
-            }
-            return timesA;
-        }
-        return timesA == null ? (timesB == null ? null : timesB) : timesA;
-    }
-
     @GuardedBy("this")
     private boolean initKernelSingleUidTimeReaderLocked() {
         if (mKernelSingleUidTimeReader == null) {
@@ -7422,8 +7447,10 @@
     @GuardedBy("this")
     public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs,
             long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) {
-        mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
-                measuredEnergyDetails);
+        if (isUsageHistoryEnabled()) {
+            mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
+                    measuredEnergyDetails);
+        }
     }
 
     @GuardedBy("this")
@@ -10285,7 +10312,7 @@
                 }
 
                 if (mBsi.trackPerProcStateCpuTimes()) {
-                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs);
 
                     LongArrayMultiStateCounter onBatteryCounter =
                             getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
@@ -10784,6 +10811,10 @@
         mBatteryLevel = 0;
     }
 
+    /**
+     * Injects a power profile.
+     */
+    @GuardedBy("this")
     public void setPowerProfileLocked(PowerProfile profile) {
         mPowerProfile = profile;
 
@@ -10800,6 +10831,27 @@
             firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
         }
 
+        // Initialize CPU power bracket map, which combines CPU states (cluster/freq pairs)
+        // into a small number of brackets
+        mCpuPowerBracketMap = new int[getCpuFreqCount()];
+        int index = 0;
+        int numCpuClusters = mPowerProfile.getNumCpuClusters();
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            int steps = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int step = 0; step < steps; step++) {
+                mCpuPowerBracketMap[index++] =
+                        mPowerProfile.getPowerBracketForCpuCore(cluster, step);
+            }
+        }
+
+        int cpuPowerBracketCount = mPowerProfile.getCpuPowerBracketCount();
+        mCpuUsageDetails.cpuBracketDescriptions = new String[cpuPowerBracketCount];
+        mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
+        for (int i = 0; i < cpuPowerBracketCount; i++) {
+            mCpuUsageDetails.cpuBracketDescriptions[i] =
+                    mPowerProfile.getCpuPowerBracketDescription(i);
+        }
+
         if (mEstimatedBatteryCapacityMah == -1) {
             // Initialize the estimated battery capacity to a known preset one.
             mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
@@ -14630,11 +14682,16 @@
     }
 
     @GuardedBy("this")
-    public boolean trackPerProcStateCpuTimes() {
+    private boolean trackPerProcStateCpuTimes() {
         return mCpuUidFreqTimeReader.isFastCpuTimesReader();
     }
 
     @GuardedBy("this")
+    private boolean isUsageHistoryEnabled() {
+        return false;
+    }
+
+    @GuardedBy("this")
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
         registerUsbStateReceiver(context);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 4186330..39672b8 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -88,8 +88,10 @@
                     new TimeDetectorInternalImpl(context, handler, timeDetectorStrategy);
             publishLocalService(TimeDetectorInternal.class, internal);
 
+            CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeDetectorService service = new TimeDetectorService(
-                    context, handler, serviceConfigAccessor, timeDetectorStrategy);
+                    context, handler, callerIdentityInjector, serviceConfigAccessor,
+                    timeDetectorStrategy, NtpTrustedTime.getInstance(context));
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
@@ -114,23 +116,15 @@
 
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
-            @NonNull TimeDetectorStrategy timeDetectorStrategy) {
-        this(context, handler, serviceConfigAccessor, timeDetectorStrategy,
-                CallerIdentityInjector.REAL, NtpTrustedTime.getInstance(context));
-    }
-
-    @VisibleForTesting
-    public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
+            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeDetectorStrategy timeDetectorStrategy,
-            @NonNull CallerIdentityInjector callerIdentityInjector,
             @NonNull NtpTrustedTime ntpTrustedTime) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
+        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
-        mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
         mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime);
 
         // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 510896d..c3f05cc 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -300,6 +300,7 @@
     }
 
     @Override
+    @NonNull
     public synchronized TimeState getTimeState() {
         boolean userShouldConfirmTime = mEnvironment.systemClockConfidence() < TIME_CONFIDENCE_HIGH;
         UnixEpochTime unixEpochTime = new UnixEpochTime(
@@ -330,37 +331,32 @@
     public synchronized boolean confirmTime(@NonNull UnixEpochTime confirmationTime) {
         Objects.requireNonNull(confirmationTime);
 
-        @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
-        @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
-        boolean timeNeedsConfirmation = currentTimeConfidence < newTimeConfidence;
-
         // All system clock calculation take place under a wake lock.
         mEnvironment.acquireWakeLock();
         try {
             // Check if the specified time matches the current system clock time (closely
             // enough) to raise the confidence.
-            long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
-            long actualSystemClockMillis = mEnvironment.systemClockMillis();
-            long adjustedAutoDetectedUnixEpochMillis =
-                    confirmationTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis();
-            long absTimeDifferenceMillis =
-                    Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
-            int confidenceUpgradeThresholdMillis =
-                    mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
-            boolean timeConfirmed = absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
-            boolean updateConfidenceRequired = timeNeedsConfirmation && timeConfirmed;
-            if (updateConfidenceRequired) {
-                String logMsg = "Confirm system clock time."
-                        + " confirmationTime=" + confirmationTime
-                        + " newTimeConfidence=" + newTimeConfidence
-                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                        + " (old) actualSystemClockMillis=" + actualSystemClockMillis
-                        + " currentTimeConfidence=" + currentTimeConfidence;
-                if (DBG) {
-                    Slog.d(LOG_TAG, logMsg);
-                }
+            long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+            long currentSystemClockMillis = mEnvironment.systemClockMillis();
+            boolean timeConfirmed = isTimeWithinConfidenceThreshold(
+                    confirmationTime, currentElapsedRealtimeMillis, currentSystemClockMillis);
+            if (timeConfirmed) {
+                @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
+                @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
+                boolean confidenceUpgradeRequired = currentTimeConfidence < newTimeConfidence;
+                if (confidenceUpgradeRequired) {
+                    String logMsg = "Confirm system clock time."
+                            + " confirmationTime=" + confirmationTime
+                            + " newTimeConfidence=" + newTimeConfidence
+                            + " currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis
+                            + " currentSystemClockMillis=" + currentSystemClockMillis
+                            + " (old) currentTimeConfidence=" + currentTimeConfidence;
+                    if (DBG) {
+                        Slog.d(LOG_TAG, logMsg);
+                    }
 
-                mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
+                    mEnvironment.setSystemClockConfidence(newTimeConfidence, logMsg);
+                }
             }
             return timeConfirmed;
         } finally {
@@ -470,8 +466,7 @@
     }
 
     @GuardedBy("this")
-    private boolean storeTelephonySuggestion(
-            @NonNull TelephonyTimeSuggestion suggestion) {
+    private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) {
         UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
 
         int slotIndex = suggestion.getSlotIndex();
@@ -712,7 +707,8 @@
     }
 
     private static int scoreTelephonySuggestion(
-            long elapsedRealtimeMillis, @NonNull TelephonyTimeSuggestion timeSuggestion) {
+            @ElapsedRealtimeLong long elapsedRealtimeMillis,
+            @NonNull TelephonyTimeSuggestion timeSuggestion) {
 
         // Validate first.
         UnixEpochTime unixEpochTime = timeSuggestion.getUnixEpochTime();
@@ -845,10 +841,12 @@
     @GuardedBy("this")
     private void upgradeSystemClockConfidenceIfRequired(
             @NonNull UnixEpochTime autoDetectedUnixEpochTime, @NonNull String cause) {
+
+        // Fast path: No need to upgrade confidence if confidence is already high.
         @TimeConfidence int newTimeConfidence = TIME_CONFIDENCE_HIGH;
         @TimeConfidence int currentTimeConfidence = mEnvironment.systemClockConfidence();
-        boolean timeNeedsConfirmation = currentTimeConfidence < newTimeConfidence;
-        if (!timeNeedsConfirmation) {
+        boolean confidenceUpgradeRequired = currentTimeConfidence < newTimeConfidence;
+        if (!confidenceUpgradeRequired) {
             return;
         }
 
@@ -857,23 +855,18 @@
         try {
             // Check if the specified time matches the current system clock time (closely
             // enough) to raise the confidence.
-            long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
-            long actualSystemClockMillis = mEnvironment.systemClockMillis();
-            long adjustedAutoDetectedUnixEpochMillis =
-                    autoDetectedUnixEpochTime.at(elapsedRealtimeMillis).getUnixEpochTimeMillis();
-            long absTimeDifferenceMillis =
-                    Math.abs(adjustedAutoDetectedUnixEpochMillis - actualSystemClockMillis);
-            int confidenceUpgradeThresholdMillis =
-                    mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
-            boolean updateConfidenceRequired =
-                    absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
+            long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
+            long currentSystemClockMillis = mEnvironment.systemClockMillis();
+            boolean updateConfidenceRequired = isTimeWithinConfidenceThreshold(
+                    autoDetectedUnixEpochTime, currentElapsedRealtimeMillis,
+                    currentSystemClockMillis);
             if (updateConfidenceRequired) {
                 String logMsg = "Upgrade system clock confidence."
                         + " autoDetectedUnixEpochTime=" + autoDetectedUnixEpochTime
                         + " newTimeConfidence=" + newTimeConfidence
                         + " cause=" + cause
-                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                        + " (old) actualSystemClockMillis=" + actualSystemClockMillis
+                        + " currentElapsedRealtimeMillis=" + currentElapsedRealtimeMillis
+                        + " currentSystemClockMillis=" + currentSystemClockMillis
                         + " currentTimeConfidence=" + currentTimeConfidence;
                 if (DBG) {
                     Slog.d(LOG_TAG, logMsg);
@@ -891,6 +884,19 @@
     }
 
     @GuardedBy("this")
+    private boolean isTimeWithinConfidenceThreshold(@NonNull UnixEpochTime timeToCheck,
+            @ElapsedRealtimeLong long currentElapsedRealtimeMillis,
+            @CurrentTimeMillisLong long currentSystemClockMillis) {
+        long adjustedAutoDetectedUnixEpochMillis =
+                timeToCheck.at(currentElapsedRealtimeMillis).getUnixEpochTimeMillis();
+        long absTimeDifferenceMillis =
+                Math.abs(adjustedAutoDetectedUnixEpochMillis - currentSystemClockMillis);
+        int confidenceUpgradeThresholdMillis =
+                mCurrentConfigurationInternal.getSystemClockConfidenceThresholdMillis();
+        return absTimeDifferenceMillis <= confidenceUpgradeThresholdMillis;
+    }
+
+    @GuardedBy("this")
     private boolean setSystemClockAndConfidenceUnderWakeLock(
             @Origin int origin, @NonNull UnixEpochTime newTime,
             @TimeConfidence int newTimeConfidence, @NonNull String cause) {
@@ -1066,7 +1072,8 @@
     }
 
     private static boolean validateSuggestionUnixEpochTime(
-            long currentElapsedRealtimeMillis, UnixEpochTime unixEpochTime) {
+            @ElapsedRealtimeLong long currentElapsedRealtimeMillis,
+            @NonNull UnixEpochTime unixEpochTime) {
         long suggestionElapsedRealtimeMillis = unixEpochTime.getElapsedRealtimeMillis();
         if (suggestionElapsedRealtimeMillis > currentElapsedRealtimeMillis) {
             // Future elapsed realtimes are ignored. They imply the elapsed realtime was wrong, or
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 0e58d91..822cd41 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -102,8 +102,10 @@
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
-            TimeZoneDetectorService service = TimeZoneDetectorService.create(
-                    context, handler, serviceConfigAccessor, timeZoneDetectorStrategy);
+            CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
+            TimeZoneDetectorService service = new TimeZoneDetectorService(
+                    context, handler, callerIdentityInjector, serviceConfigAccessor,
+                    timeZoneDetectorStrategy);
 
             // Dump the device activity monitor when the service is dumped.
             service.addDumpable(deviceActivityMonitor);
@@ -142,16 +144,6 @@
     @GuardedBy("mDumpables")
     private final List<Dumpable> mDumpables = new ArrayList<>();
 
-    private static TimeZoneDetectorService create(
-            @NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
-            @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
-
-        CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
-        return new TimeZoneDetectorService(context, handler, callerIdentityInjector,
-                serviceConfigAccessor, timeZoneDetectorStrategy);
-    }
-
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 886e8e6..99950a0 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) {
@@ -2083,7 +2083,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 7bc62ec..59f37c2 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;
@@ -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 9ed77fc..c317be7 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;
@@ -657,7 +659,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;
@@ -4384,10 +4386,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);
@@ -5236,6 +5238,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
@@ -5286,7 +5292,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.
@@ -5359,7 +5365,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/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 93d9b0e..cb29e3f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -49,7 +49,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;
 
@@ -1297,7 +1296,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));
@@ -1307,6 +1306,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.
      *
@@ -1808,7 +1817,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) {
@@ -1845,12 +1854,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/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 19df5a2..57f5777 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -306,6 +306,18 @@
     }
 
     @Test
+    public void setServiceInfo_ChangeAccessibilityTool_updateFails() {
+        assertFalse(mSpyServiceInfo.isAccessibilityTool());
+
+        final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
+        serviceInfo.setAccessibilityTool(true);
+        mServiceConnection.setServiceInfo(serviceInfo);
+
+        // isAccessibilityTool should not be dynamically updatable
+        assertFalse(mSpyServiceInfo.isAccessibilityTool());
+    }
+
+    @Test
     public void canReceiveEvents_hasEventType_returnTrue() {
         final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
         updateServiceInfo(serviceInfo,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 4100ff1..39b0770 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BatteryStats.CpuUsageDetails;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.MeasuredEnergyDetails;
 import android.os.Parcel;
@@ -315,8 +316,7 @@
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
-        assertThat(dump).contains("ext=E");
-        assertThat(dump).contains("Energy: A=0 B/0=100 B/1=200");
+        assertThat(dump).contains("ext=energy:A=0 B/0=100 B/1=200");
         assertThat(dump).doesNotContain("C=");
 
         String checkin = toString(item, /* checkin */ true);
@@ -325,6 +325,55 @@
         assertThat(checkin).doesNotContain("C=");
     }
 
+    @Test
+    public void cpuUsageDetails() {
+        mHistory.forceRecordAllHistory();
+        mHistory.startRecordingHistory(0, 0, /* reset */ true);
+        mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
+                1234);
+
+        CpuUsageDetails details = new CpuUsageDetails();
+        details.cpuBracketDescriptions = new String[] {"low", "Med", "HIGH"};
+        details.uid = 10123;
+        details.cpuUsageMs = new long[] { 100, 200, 300};
+        mHistory.recordCpuUsage(200, 200, details);
+
+        details.uid = 10321;
+        details.cpuUsageMs = new long[] { 400, 500, 600};
+        mHistory.recordCpuUsage(300, 300, details);
+
+        BatteryStatsHistoryIterator iterator = mHistory.iterate();
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+
+        assertThat(iterator.next(item)).isTrue();
+
+        String dump = toString(item, /* checkin */ false);
+        assertThat(dump).contains("+200ms");
+        assertThat(dump).contains("ext=cpu:u0a123: 100, 200, 300");
+        assertThat(dump).contains("ext=cpu-bracket:0:low");
+        assertThat(dump).contains("ext=cpu-bracket:1:Med");
+        assertThat(dump).contains("ext=cpu-bracket:2:HIGH");
+
+        String checkin = toString(item, /* checkin */ true);
+        assertThat(checkin).contains("XB,3,0,low");
+        assertThat(checkin).contains("XB,3,1,Med");
+        assertThat(checkin).contains("XB,3,2,HIGH");
+        assertThat(checkin).contains("XC,10123,100,200,300");
+
+        assertThat(iterator.next(item)).isTrue();
+
+        dump = toString(item, /* checkin */ false);
+        assertThat(dump).contains("+300ms");
+        assertThat(dump).contains("ext=cpu:u0a321: 400, 500, 600");
+        // Power bracket descriptions are written only once
+        assertThat(dump).doesNotContain("ext=cpu-bracket");
+
+        checkin = toString(item, /* checkin */ true);
+        assertThat(checkin).doesNotContain("XB");
+        assertThat(checkin).contains("XC,10321,400,500,600");
+    }
+
     private String toString(BatteryStats.HistoryItem item, boolean checkin) {
         BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter();
         StringWriter writer = new StringWriter();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 5403269..c6a7fbc 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -28,7 +28,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -117,6 +116,7 @@
 
         // Initialize time-in-freq counters
         mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], new long[5]);
             mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
@@ -142,11 +142,12 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -171,6 +172,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -179,7 +181,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -200,7 +202,7 @@
         }
 
         // Validate the on-battery-screen-off counter
-        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, mMockClock.uptime * 1000,
                 mMockClock.realtime * 1000);
 
         final long[][] delta2 = {
@@ -211,6 +213,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -219,7 +222,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -253,6 +256,7 @@
         };
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         final int parentUid = testUids[1];
         final int childUid = 99099;
@@ -267,7 +271,7 @@
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.realtime);
+                    mMockClock.realtime, mMockClock.uptime);
         }
 
         for (int i = 0; i < testUids.length; ++i) {
@@ -302,6 +306,7 @@
         mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         mMockClock.realtime = 1000;
+        mMockClock.uptime = 1000;
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] testProcStates = {
@@ -316,7 +321,7 @@
             uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
             mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
             mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
-                    mMockClock.elapsedRealtime());
+                    mMockClock.elapsedRealtime(), mMockClock.uptime);
         }
 
         final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
@@ -341,6 +346,7 @@
         }
 
         mMockClock.realtime += 1000;
+        mMockClock.uptime += 1000;
 
         mBatteryStatsImpl.updateCpuTimesForAllUids();
 
@@ -394,27 +400,6 @@
     }
 
     @Test
-    public void testAddCpuTimes() {
-        long[] timesA = null;
-        long[] timesB = null;
-        assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        timesA = new long[] {34, 23, 45, 24};
-        assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        timesB = timesA;
-        timesA = null;
-        assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
-        final long[] expected = {434, 6784, 34987, 9984};
-        timesA = new long[timesB.length];
-        for (int i = 0; i < timesA.length; ++i) {
-            timesA[i] = expected[i] - timesB[i];
-        }
-        assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-    }
-
-    @Test
     public void testMulticastWakelockAcqRel() {
         final int testUid = 10032;
         final int acquireTimeMs = 1000;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index ba5f66c..f776d3d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -104,8 +103,8 @@
         mMockNtpTrustedTime = mock(NtpTrustedTime.class);
 
         mTimeDetectorService = new TimeDetectorService(
-                mMockContext, mTestHandler, mFakeServiceConfigAccessor,
-                mFakeTimeDetectorStrategy, mTestCallerIdentityInjector, mMockNtpTrustedTime);
+                mMockContext, mTestHandler, mTestCallerIdentityInjector, mFakeServiceConfigAccessor,
+                mFakeTimeDetectorStrategy, mMockNtpTrustedTime);
     }
 
     @After
@@ -114,19 +113,14 @@
         mHandlerThread.join();
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetCapabilitiesAndConfig_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeDetectorService.getCapabilitiesAndConfig();
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeDetectorService::getCapabilitiesAndConfig);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -141,40 +135,30 @@
                 mTimeDetectorService.getCapabilitiesAndConfig());
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testAddListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
-        try {
-            mTimeDetectorService.addListener(mockListener);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, () -> mTimeDetectorService.addListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testRemoveListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
-        try {
-            mTimeDetectorService.removeListener(mockListener);
-            fail("Expected a SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.removeListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -193,8 +177,7 @@
             mTimeDetectorService.addListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).linkToDeath(any(), anyInt());
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -213,8 +196,7 @@
             mTestHandler.waitForMessagesToBeProcessed();
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
@@ -230,8 +212,7 @@
             mTimeDetectorService.removeListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -246,28 +227,23 @@
             mTimeDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestTelephonyTime(timeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestTelephonyTime(timeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -279,27 +255,22 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestManualTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestManualTime(manualTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -313,24 +284,20 @@
                 mTestCallerIdentityInjector.getCallingUserId(), manualTimeSuggestion);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
 
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestNetworkTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         NetworkTimeSuggestion networkTimeSuggestion = createNetworkTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
@@ -348,19 +315,16 @@
         mFakeTimeDetectorStrategy.verifySuggestNetworkTimeCalled(networkTimeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestGnssTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
@@ -378,19 +342,16 @@
         mFakeTimeDetectorStrategy.verifySuggestGnssTimeCalled(gnssTimeSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestExternalTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
 
-        try {
-            mTimeDetectorService.suggestExternalTime(externalTimeSuggestion);
-            fail();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.suggestExternalTime(externalTimeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
     }
 
     @Test
@@ -434,23 +395,18 @@
         TimeState actualState = mTimeDetectorService.getTimeState();
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         assertEquals(actualState, fakeState);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetTimeState_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeDetectorService.getTimeState();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeDetectorService::getTimeState);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -461,24 +417,19 @@
         mTimeDetectorService.setTimeState(state);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         assertEquals(mFakeTimeDetectorStrategy.getTimeState(), state);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSetTimeState_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
-        try {
-            mTimeDetectorService.setTimeState(state);
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, () -> mTimeDetectorService.setTimeState(state));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -490,23 +441,19 @@
         assertFalse(mTimeDetectorService.confirmTime(confirmationTime));
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         mFakeTimeDetectorStrategy.verifyConfirmTimeCalled(confirmationTime);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testConfirmTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L));
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L)));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -524,24 +471,19 @@
                 mTestCallerIdentityInjector.getCallingUserId(), timeSuggestion);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSetManualTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
 
-        try {
-            mTimeDetectorService.setManualTime(timeSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeDetectorService.setManualTime(timeSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -562,7 +504,7 @@
                 .build();
     }
 
-    private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
+    static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
         return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index b8c3ea7..fc64597 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -97,19 +97,14 @@
         mHandlerThread.join();
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetCapabilitiesAndConfig_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeZoneDetectorService.getCapabilitiesAndConfig();
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeZoneDetectorService::getCapabilitiesAndConfig);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -124,40 +119,31 @@
                 mTimeZoneDetectorService.getCapabilitiesAndConfig());
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testAddListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
-        try {
-            mTimeZoneDetectorService.addListener(mockListener);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.addListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testRemoveListener_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
-        try {
-            mTimeZoneDetectorService.removeListener(mockListener);
-            fail("Expected a SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.removeListener(mockListener));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -176,8 +162,7 @@
             mTimeZoneDetectorService.addListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).linkToDeath(any(), anyInt());
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -196,8 +181,7 @@
             mTestHandler.waitForMessagesToBeProcessed();
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
@@ -213,8 +197,7 @@
             mTimeZoneDetectorService.removeListener(mockListener);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener).asBinder();
             verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -229,28 +212,23 @@
             mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
 
             verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
+                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
             verify(mockListener, never()).onChange();
             verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
             reset(mockListenerBinder, mockListener, mMockContext);
         }
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestGeolocationTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SET_TIME_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
     }
 
     @Test
@@ -263,27 +241,22 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SET_TIME_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeZoneDetectorStrategy.verifySuggestGeolocationTimeZoneCalled(timeZoneSuggestion);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestManualTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
         ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingOrSelfPermission(
-                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -300,40 +273,31 @@
                 mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
 
         verify(mMockContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTime_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSuggestTelephonyTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
     }
 
     @Test
@@ -345,8 +309,7 @@
         mTestHandler.assertTotalMessagesEnqueued(1);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE),
-                anyString());
+                eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
 
         mTestHandler.waitForMessagesToBeProcessed();
         mFakeTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion);
@@ -361,23 +324,18 @@
         TimeZoneState actualState = mTimeZoneDetectorService.getTimeZoneState();
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         assertEquals(actualState, fakeState);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testGetTimeZoneState_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeZoneDetectorService.getTimeZoneState();
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class, mTimeZoneDetectorService::getTimeZoneState);
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -388,24 +346,20 @@
         mTimeZoneDetectorService.setTimeZoneState(state);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         assertEquals(mFakeTimeZoneDetectorStrategy.getTimeZoneState(), state);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSetTimeZoneState_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         TimeZoneState state = new TimeZoneState("Europe/Narnia", true);
-        try {
-            mTimeZoneDetectorService.setTimeZoneState(state);
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.setTimeZoneState(state));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -416,23 +370,19 @@
         assertFalse(mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         mFakeTimeZoneDetectorStrategy.verifyConfirmTimeZoneCalled("Europe/Narnia");
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testConfirmTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        try {
-            mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.confirmTimeZone("Europe/Narnia"));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
@@ -450,24 +400,19 @@
                 mTestCallerIdentityInjector.getCallingUserId(), timeZoneSuggestion);
 
         verify(mMockContext).enforceCallingPermission(
-                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                anyString());
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testSetManualTimeZone_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
         ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
 
-        try {
-            mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion);
-            fail("Expected SecurityException");
-        } finally {
-            verify(mMockContext).enforceCallingPermission(
-                    eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
-                    anyString());
-        }
+        assertThrows(SecurityException.class,
+                () -> mTimeZoneDetectorService.setManualTimeZone(timeZoneSuggestion));
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index bc2c57e..3adee0f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -24,7 +24,6 @@
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
@@ -32,6 +31,7 @@
 import android.content.res.Configuration;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
+import android.util.TimeSparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
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 51894f3..52280d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -396,6 +396,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);
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 26a1e9d..361b1c0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
@@ -29,6 +28,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeSparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 34c6c16..9203208 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -34,7 +34,6 @@
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventList;
 import android.app.usage.EventStats;
-import android.app.usage.TimeSparseArray;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
@@ -50,6 +49,7 @@
 import android.util.Slog;
 import android.util.SparseArrayMap;
 import android.util.SparseIntArray;
+import android.util.TimeSparseArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index be7c112..79546b8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -172,11 +172,11 @@
         mAmInternal.setVoiceInteractionManagerProvider(
                 new ActivityManagerInternal.VoiceInteractionManagerProvider() {
                     @Override
-                    public void notifyActivityEventChanged() {
+                    public void notifyActivityDestroyed(IBinder activityToken) {
                         if (DEBUG) {
-                            Slog.d(TAG, "call notifyActivityEventChanged");
+                            Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken);
                         }
-                        mServiceStub.notifyActivityEventChanged();
+                        mServiceStub.notifyActivityDestroyed(activityToken);
                     }
                 });
     }
@@ -449,11 +449,12 @@
             return mImpl.supportsLocalVoiceInteraction();
         }
 
-        void notifyActivityEventChanged() {
+        void notifyActivityDestroyed(@NonNull IBinder activityToken) {
             synchronized (this) {
-                if (mImpl == null) return;
+                if (mImpl == null || activityToken == null) return;
 
-                Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityDestroyedLocked(activityToken));
             }
         }
 
@@ -1226,6 +1227,16 @@
             }
         }
 
+        @Override
+        public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) {
+            synchronized (this) {
+                if (mImpl == null || activityToken == null) {
+                    return;
+                }
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityEventChangedLocked(activityToken, type));
+            }
+        }
         //----------------- Hotword Detection/Validation APIs --------------------------------//
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 9f66059..dcf7b78 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -522,9 +522,23 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    public void notifyActivityEventChangedLocked() {
+    public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (mActiveSession == null || !mActiveSession.mShown) {
+            if (DEBUG) {
+                Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
+                        + " hidden session");
+            }
+            return;
+        }
+        mActiveSession.notifyActivityDestroyedLocked(activityToken);
+    }
+
+    public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
         }
         if (mActiveSession == null || !mActiveSession.mShown) {
             if (DEBUG) {
@@ -533,7 +547,7 @@
             }
             return;
         }
-        mActiveSession.notifyActivityEventChangedLocked();
+        mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
     public void updateStateLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 980b3b5..f8bc499 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -61,6 +61,7 @@
 import android.service.voice.VisibleActivityInfo;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IWindowManager;
 
@@ -130,7 +131,11 @@
     private boolean mListeningVisibleActivity;
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
-    private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+    // Records the visible activity information the system has already called onVisible, without
+    // confirming the result of callback. When activity visible state is changed, we use this to
+    // determine to call onVisible or onInvisible to assistant application.
+    private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken =
+            new ArrayMap<>();
     private final PowerManagerInternal mPowerManagerInternal;
     private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
     private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
@@ -535,7 +540,7 @@
 
     public void cancelLocked(boolean finishTask) {
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
         hideLocked();
         mCanceled = true;
         if (mBound) {
@@ -613,17 +618,24 @@
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
         }
-        mListeningVisibleActivity = true;
-        mVisibleActivityInfos.clear();
 
-        mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening");
-            }
-            synchronized (mLock) {
-                handleVisibleActivitiesLocked();
-            }
-        });
+        if (!mShown || mCanceled || mSession == null) {
+            return;
+        }
+
+        mListeningVisibleActivity = true;
+        mVisibleActivityInfoForToken.clear();
+
+        // It should only need to report which activities are visible
+        final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+
+        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+            return;
+        }
+        notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+                VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos);
     }
 
     void stopListeningVisibleActivityChangedLocked() {
@@ -631,12 +643,13 @@
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
         }
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
     }
 
-    void notifyActivityEventChangedLocked() {
+    void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
         if (!mListeningVisibleActivity) {
             if (DEBUG) {
@@ -645,99 +658,139 @@
             return;
         }
         mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event");
-            }
             synchronized (mLock) {
-                handleVisibleActivitiesLocked();
+                handleVisibleActivitiesLocked(activityToken, type);
             }
         });
     }
 
-    private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+    private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "getVisibleActivityInfosLocked");
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked");
         }
         List<ActivityAssistInfo> allVisibleActivities =
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .getTopVisibleActivities();
         if (DEBUG) {
-            Slog.d(TAG,
-                    "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities="
+                    + allVisibleActivities);
         }
-        if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+        if (allVisibleActivities.isEmpty()) {
             Slog.w(TAG, "no visible activity");
             return null;
         }
         final int count = allVisibleActivities.size();
-        final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap =
+                new ArrayMap<>(count);
         for (int i = 0; i < count; i++) {
             ActivityAssistInfo info = allVisibleActivities.get(i);
             if (DEBUG) {
-                Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+                Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken()
                         + ", assistToken=" + info.getAssistToken()
                         + ", taskId=" + info.getTaskId());
             }
-            visibleActivityInfos.add(
+            visibleActivityInfoArrayMap.put(info.getActivityToken(),
                     new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
         }
-        return visibleActivityInfos;
+        return visibleActivityInfoArrayMap;
     }
 
-    private void handleVisibleActivitiesLocked() {
+    // TODO(b/242359988): Split this method up
+    private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "handleVisibleActivitiesLocked");
+            Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
-        if (mSession == null) {
-            return;
-        }
-        if (!mShown || !mListeningVisibleActivity || mCanceled) {
-            return;
-        }
-        final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
 
-        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-            mVisibleActivityInfos.clear();
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
             return;
         }
-        if (mVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
-            mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+        if (!mShown || mCanceled || mSession == null) {
             return;
         }
 
-        final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
-        final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
+        // We use this local variable to determine to call onVisible or onInvisible.
+        boolean notifyOnVisible = false;
+        VisibleActivityInfo notifyVisibleActivityInfo = null;
 
-        removedActivities.addAll(mVisibleActivityInfos);
-        for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
-            final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
-            if (!removedActivities.isEmpty() && removedActivities.contains(
-                    candidateVisibleActivityInfo)) {
-                removedActivities.remove(candidateVisibleActivityInfo);
-            } else {
-                addedActivities.add(candidateVisibleActivityInfo);
+        if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START
+                || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) {
+            // It seems that the onStart is unnecessary. But if we have it, the assistant
+            // application can request the directActions early. Even if we have the onStart,
+            // we still need the onResume because it is possible that the activity goes to
+            // onResume from onPause with invisible before the activity goes to onStop from
+            // onPause.
+
+            // Check if we have reported this activity as visible. If we have reported it as
+            // visible, do nothing.
+            if (mVisibleActivityInfoForToken.containsKey(activityToken)) {
+                return;
+            }
+
+            // Before reporting this activity as visible, we need to make sure the activity
+            // is really visible.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+            notifyOnVisible = true;
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) {
+            // For the onPause stage, the Activity is not necessarily invisible now, so we need
+            // to check its state.
+            // Note: After syncing with Activity owner, before the onPause is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo != null) {
+                return;
+            }
+
+            // Also make sure we previously reported this Activity as visible.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) {
+            // For the onStop stage, the activity is in invisible state. We only need to consider if
+            // we have reported this activity as visible. If we have reported it as visible, we
+            // need to report it as invisible.
+            // Why we still need onStop? Because it is possible that the activity is in a visible
+            // state during onPause stage, when the activity enters onStop from onPause, we may
+            // need to notify onInvisible.
+            // Note: After syncing with Activity owner, before the onStop is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else {
+            Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type);
+            return;
+        }
+
+        try {
+            mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo,
+                    notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED
+                            : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+        } catch (RemoteException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e);
             }
         }
 
-        if (!addedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(addedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        if (notifyOnVisible) {
+            mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo);
+        } else {
+            mVisibleActivityInfoForToken.remove(activityToken);
         }
-        if (!removedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(removedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-        }
-
-        mVisibleActivityInfos.clear();
-        mVisibleActivityInfos.addAll(newVisibleActivityInfos);
     }
 
     private void notifyVisibleActivitiesChangedLocked(
-            List<VisibleActivityInfo> visibleActivityInfos, int type) {
+            ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) {
         if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
             return;
         }
@@ -746,7 +799,7 @@
         }
         try {
             for (int i = 0; i < visibleActivityInfos.size(); i++) {
-                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type);
+                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type);
             }
         } catch (RemoteException e) {
             if (DEBUG) {
@@ -759,6 +812,51 @@
         }
     }
 
+    private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity(
+            @NonNull IBinder activityToken) {
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+        if (visibleActivityInfos == null) {
+            return null;
+        }
+        return visibleActivityInfos.get(activityToken);
+    }
+
+    void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
+            return;
+        }
+        mScheduledExecutorService.execute(() -> {
+            synchronized (mLock) {
+                if (!mListeningVisibleActivity) {
+                    return;
+                }
+                if (!mShown || mCanceled || mSession == null) {
+                    return;
+                }
+
+                VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove(
+                        activityToken);
+                if (visibleActivityInfo != null) {
+                    try {
+                        mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo,
+                                VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+                    } catch (RemoteException e) {
+                        if (DEBUG) {
+                            Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
     private void removeFromLowPowerStandbyAllowlist() {
         synchronized (mLock) {
             if (mLowPowerStandbyAllowlisted) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 4c47684..a27b2da 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.FlakyTest
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -61,7 +61,7 @@
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         // depends on how much of the animation transactions are sent to SF at once
@@ -79,7 +79,7 @@
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
@@ -95,7 +95,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun launcherWindowBecomesInvisible() {
         testSpec.assertWm {
@@ -105,9 +105,11 @@
         }
     }
 
-    @Presubmit @Test fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+    @FlakyTest(bugId = 251214932)
+    @Test
+    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun imeAppWindowIsAlwaysVisible() {
         // the app starts visible in live tile, and stays visible for the duration of entering
@@ -117,13 +119,13 @@
         testSpec.assertWm { this.isAppWindowVisible(testApp) }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun imeLayerBecomesVisible() {
         testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 251214932)
     @Test
     fun appLayerReplacesLauncher() {
         testSpec.assertLayers {
@@ -135,6 +137,60 @@
         }
     }
 
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 251214932)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 5597287..89e3b5f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -197,6 +197,12 @@
     @Test
     override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
 
+    @FlakyTest(bugId = 251217585)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
     companion object {
         /**
          * Creates the test configurations.