Merge "Allow SearchBox title to scale to system font size" into udc-qpr-dev
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index cd45f4d..b4f4a7e 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -212,6 +212,11 @@
     }
 
     /** @hide */
+    public AttributionSource withDefaultToken() {
+        return withToken(sDefaultToken);
+    }
+
+    /** @hide */
     public AttributionSource withPid(int pid) {
         return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
                 getToken(), mAttributionSourceState.renouncedPermissions, getNext());
@@ -520,16 +525,28 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         AttributionSource that = (AttributionSource) o;
-        return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+        return equalsExceptToken(that) && Objects.equals(
+                mAttributionSourceState.token, that.mAttributionSourceState.token);
+    }
+
+    /**
+     * We store trusted attribution sources without their token (the token is the key to the map)
+     * to avoid having a strong reference to the token. This means, when checking the equality of a
+     * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to
+     * compare everything except the token.
+     *
+     * @hide
+     */
+    public boolean equalsExceptToken(@Nullable AttributionSource o) {
+        if (o == null) return false;
+        return mAttributionSourceState.uid == o.mAttributionSourceState.uid
                 && Objects.equals(mAttributionSourceState.packageName,
-                        that.mAttributionSourceState.packageName)
+                o.mAttributionSourceState.packageName)
                 && Objects.equals(mAttributionSourceState.attributionTag,
-                        that.mAttributionSourceState.attributionTag)
-                && Objects.equals(mAttributionSourceState.token,
-                        that.mAttributionSourceState.token)
+                o.mAttributionSourceState.attributionTag)
                 && Arrays.equals(mAttributionSourceState.renouncedPermissions,
-                        that.mAttributionSourceState.renouncedPermissions)
-                && Objects.equals(getNext(), that.getNext());
+                o.mAttributionSourceState.renouncedPermissions)
+                && Objects.equals(getNext(), o.getNext());
     }
 
     @Override
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 7a153ef..c5f5614 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -116,8 +116,9 @@
      * Builder for DeviceBatteryConsumer.
      */
     public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
-        public Builder(BatteryConsumer.BatteryConsumerData data, int scope) {
-            super(data, CONSUMER_TYPE_AGGREGATE);
+        public Builder(BatteryConsumer.BatteryConsumerData data, int scope,
+                double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_AGGREGATE, minConsumedPowerThreshold);
             data.putInt(COLUMN_INDEX_SCOPE, scope);
         }
 
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 0ba8d51..ca84b35 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -795,11 +795,12 @@
         protected final BatteryConsumer.BatteryConsumerData mData;
         protected final PowerComponents.Builder mPowerComponentsBuilder;
 
-        public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType) {
+        public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType,
+                double minConsumedPowerThreshold) {
             mData = data;
             data.putLong(COLUMN_INDEX_BATTERY_CONSUMER_TYPE, consumerType);
 
-            mPowerComponentsBuilder = new PowerComponents.Builder(data);
+            mPowerComponentsBuilder = new PowerComponents.Builder(data, minConsumedPowerThreshold);
         }
 
         @Nullable
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index e2c52ce..cc37b54 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -707,7 +707,7 @@
                         XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
 
                 builder = new Builder(customComponentNames.toArray(new String[0]), true,
-                        includesProcStateData);
+                        includesProcStateData, 0);
 
                 builder.setStatsStartTimestamp(
                         parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -782,6 +782,7 @@
         private final String[] mCustomPowerComponentNames;
         private final boolean mIncludePowerModels;
         private final boolean mIncludesProcessStateData;
+        private final double mMinConsumedPowerThreshold;
         private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
         private long mStatsStartTimestampMs;
         private long mStatsEndTimestampMs;
@@ -802,11 +803,11 @@
         private BatteryStatsHistory mBatteryStatsHistory;
 
         public Builder(@NonNull String[] customPowerComponentNames) {
-            this(customPowerComponentNames, false, false);
+            this(customPowerComponentNames, false, false, 0);
         }
 
         public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
-                boolean includeProcessStateData) {
+                boolean includeProcessStateData, double minConsumedPowerThreshold) {
             mBatteryConsumersCursorWindow =
                     new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
             mBatteryConsumerDataLayout =
@@ -817,12 +818,14 @@
             mCustomPowerComponentNames = customPowerComponentNames;
             mIncludePowerModels = includePowerModels;
             mIncludesProcessStateData = includeProcessStateData;
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
                 mAggregateBatteryConsumersBuilders[scope] =
-                        new AggregateBatteryConsumer.Builder(data, scope);
+                        new AggregateBatteryConsumer.Builder(
+                                data, scope, mMinConsumedPowerThreshold);
             }
         }
 
@@ -961,7 +964,8 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UidBatteryConsumer.Builder(data, batteryStatsUid);
+                builder = new UidBatteryConsumer.Builder(data, batteryStatsUid,
+                        mMinConsumedPowerThreshold);
                 mUidBatteryConsumerBuilders.put(uid, builder);
             }
             return builder;
@@ -979,7 +983,7 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UidBatteryConsumer.Builder(data, uid);
+                builder = new UidBatteryConsumer.Builder(data, uid, mMinConsumedPowerThreshold);
                 mUidBatteryConsumerBuilders.put(uid, builder);
             }
             return builder;
@@ -996,7 +1000,7 @@
                 final BatteryConsumer.BatteryConsumerData data =
                         BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
                                 mBatteryConsumerDataLayout);
-                builder = new UserBatteryConsumer.Builder(data, userId);
+                builder = new UserBatteryConsumer.Builder(data, userId, mMinConsumedPowerThreshold);
                 mUserBatteryConsumerBuilders.put(userId, builder);
             }
             return builder;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index b3f4d98..49d7e8b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -80,6 +80,7 @@
     private final long mMaxStatsAgeMs;
     private final long mFromTimestamp;
     private final long mToTimestamp;
+    private final double mMinConsumedPowerThreshold;
     private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
     private BatteryUsageStatsQuery(@NonNull Builder builder) {
@@ -87,6 +88,7 @@
         mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
                 : new int[]{UserHandle.USER_ALL};
         mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
+        mMinConsumedPowerThreshold = builder.mMinConsumedPowerThreshold;
         mFromTimestamp = builder.mFromTimestamp;
         mToTimestamp = builder.mToTimestamp;
         mPowerComponents = builder.mPowerComponents;
@@ -137,6 +139,14 @@
     }
 
     /**
+     * Returns the minimal power component consumed power threshold. The small power consuming
+     * components will be reported as zero.
+     */
+    public double getMinConsumedPowerThreshold() {
+        return mMinConsumedPowerThreshold;
+    }
+
+    /**
      * Returns the exclusive lower bound of the stored snapshot timestamps that should be included
      * in the aggregation.  Ignored if {@link #getToTimestamp()} is zero.
      */
@@ -158,6 +168,7 @@
         mUserIds = new int[in.readInt()];
         in.readIntArray(mUserIds);
         mMaxStatsAgeMs = in.readLong();
+        mMinConsumedPowerThreshold = in.readDouble();
         mFromTimestamp = in.readLong();
         mToTimestamp = in.readLong();
         mPowerComponents = in.createIntArray();
@@ -169,6 +180,7 @@
         dest.writeInt(mUserIds.length);
         dest.writeIntArray(mUserIds);
         dest.writeLong(mMaxStatsAgeMs);
+        dest.writeDouble(mMinConsumedPowerThreshold);
         dest.writeLong(mFromTimestamp);
         dest.writeLong(mToTimestamp);
         dest.writeIntArray(mPowerComponents);
@@ -202,6 +214,7 @@
         private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
         private long mFromTimestamp;
         private long mToTimestamp;
+        private double mMinConsumedPowerThreshold = 0;
         private @BatteryConsumer.PowerComponent int[] mPowerComponents;
 
         /**
@@ -301,5 +314,14 @@
             mMaxStatsAgeMs = maxStatsAgeMs;
             return this;
         }
+
+        /**
+         * Set the minimal power component consumed power threshold. The small power consuming
+         * components will be reported as zero.
+         */
+        public Builder setMinConsumedPowerThreshold(double minConsumedPowerThreshold) {
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
+            return this;
+        }
     }
 }
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 5dffa0a..9e5f539 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -461,9 +461,11 @@
         private static final byte POWER_MODEL_UNINITIALIZED = -1;
 
         private final BatteryConsumer.BatteryConsumerData mData;
+        private final double mMinConsumedPowerThreshold;
 
-        Builder(BatteryConsumer.BatteryConsumerData data) {
+        Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
             mData = data;
+            mMinConsumedPowerThreshold = minConsumedPowerThreshold;
             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
                 for (BatteryConsumer.Key key : keys) {
                     if (key.mPowerModelColumnIndex != -1) {
@@ -476,6 +478,9 @@
         @NonNull
         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
                 int powerModel) {
+            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+                componentPower = 0;
+            }
             mData.putDouble(key.mPowerColumnIndex, componentPower);
             if (key.mPowerModelColumnIndex != -1) {
                 mData.putInt(key.mPowerModelColumnIndex, powerModel);
@@ -491,6 +496,9 @@
          */
         @NonNull
         public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
+            if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+                componentPower = 0;
+            }
             final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
             if (index < 0 || index >= mData.layout.customPowerComponentCount) {
                 throw new IllegalArgumentException(
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 103452d..03a1b6f 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -207,17 +207,18 @@
         private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED;
         private boolean mExcludeFromBatteryUsageStats;
 
-        public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) {
-            this(data, batteryStatsUid, batteryStatsUid.getUid());
+        public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid,
+                double minConsumedPowerThreshold) {
+            this(data, batteryStatsUid, batteryStatsUid.getUid(), minConsumedPowerThreshold);
         }
 
-        public Builder(BatteryConsumerData data, int uid) {
-            this(data, null, uid);
+        public Builder(BatteryConsumerData data, int uid, double minConsumedPowerThreshold) {
+            this(data, null, uid, minConsumedPowerThreshold);
         }
 
         private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid,
-                int uid) {
-            super(data, CONSUMER_TYPE_UID);
+                int uid, double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_UID, minConsumedPowerThreshold);
             mBatteryStatsUid = batteryStatsUid;
             mUid = uid;
             mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID;
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index 6b4a5cf..a2ff078 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -107,8 +107,8 @@
     public static final class Builder extends BaseBuilder<Builder> {
         private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
 
-        Builder(BatteryConsumerData data, int userId) {
-            super(data, CONSUMER_TYPE_USER);
+        Builder(BatteryConsumerData data, int userId, double minConsumedPowerThreshold) {
+            super(data, CONSUMER_TYPE_USER, minConsumedPowerThreshold);
             data.putLong(COLUMN_INDEX_USER_ID, userId);
         }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1cf41cf..d66ffce 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5164,7 +5164,6 @@
         public static final Uri DEFAULT_RINGTONE_URI = getUriFor(RINGTONE);
 
         /** {@hide} */
-        @Readable
         public static final String RINGTONE_CACHE = "ringtone_cache";
         /** {@hide} */
         public static final Uri RINGTONE_CACHE_URI = getUriFor(RINGTONE_CACHE);
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5127f05..dfa0000 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -1155,9 +1155,15 @@
         }
 
         private void allocateFrameTimelines(int length) {
-            mFrameTimelines = new FrameTimeline[length];
-            for (int i = 0; i < mFrameTimelines.length; i++) {
-                mFrameTimelines[i] = new FrameTimeline();
+            // Maintain one default frame timeline for API (such as getFrameTimelines and
+            // getPreferredFrameTimeline) consistency. It should have default data when accessed.
+            length = Math.max(1, length);
+
+            if (mFrameTimelines == null || mFrameTimelines.length != length) {
+                mFrameTimelines = new FrameTimeline[length];
+                for (int i = 0; i < mFrameTimelines.length; i++) {
+                    mFrameTimelines[i] = new FrameTimeline();
+                }
             }
         }
 
@@ -1167,12 +1173,7 @@
          */
         FrameTimeline update(
                 long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
-            // Even if the frame timelines length is 0, continue with allocation for API
-            // FrameData.getFrameTimelines consistency. The 0 length frame timelines code path
-            // should only occur when USE_VSYNC property is false.
-            if (mFrameTimelines.length != vsyncEventData.frameTimelinesLength) {
-                allocateFrameTimelines(vsyncEventData.frameTimelinesLength);
-            }
+            allocateFrameTimelines(vsyncEventData.frameTimelinesLength);
             mFrameTimeNanos = frameTimeNanos;
             mPreferredFrameTimelineIndex = vsyncEventData.preferredFrameTimelineIndex;
             for (int i = 0; i < mFrameTimelines.length; i++) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 963e806..d680d04 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -50,6 +50,8 @@
 import static android.view.ViewRootImplProto.WIDTH;
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER;
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -96,6 +98,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.UiContext;
+import android.annotation.UiThread;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ICompatCameraControlCallback;
@@ -240,6 +243,7 @@
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 /**
@@ -423,6 +427,74 @@
     }
 
     /**
+     * Used to notify if the user is typing or not.
+     * @hide
+     */
+    public interface TypingHintNotifier {
+        /**
+         * Called when the typing hint is changed. This would be invoked by the
+         * {@link android.view.inputmethod.RemoteInputConnectionImpl}
+         * to hint if the user is typing when the it is {@link #isActive() active}.
+         *
+         * This can be only happened on the UI thread. The behavior won't be guaranteed if
+         * invoking this on a non-UI thread.
+         *
+         * @param isTyping {@code true} if the user is typing.
+         */
+        @UiThread
+        void onTypingHintChanged(boolean isTyping);
+
+        /**
+         * Indicates whether the notifier is currently in active state or not.
+         *
+         * @see #deactivate()
+         */
+        boolean isActive();
+
+        /**
+         * Deactivate the notifier when no longer in use. Mostly invoked when finishing the typing.
+         */
+        void deactivate();
+    }
+
+    /**
+     * The {@link TypingHintNotifier} implementation used to handle
+     * the refresh rate preference when the typing state is changed.
+     */
+    private static class TypingHintNotifierImpl implements TypingHintNotifier {
+
+        private final AtomicReference<TypingHintNotifier> mActiveNotifier;
+
+        @NonNull
+        private final ViewRootRefreshRateController mController;
+
+        TypingHintNotifierImpl(@NonNull AtomicReference<TypingHintNotifier> notifier,
+                @NonNull ViewRootRefreshRateController controller) {
+            mController = controller;
+            mActiveNotifier = notifier;
+        }
+
+        @Override
+        public void onTypingHintChanged(boolean isTyping) {
+            if (!isActive()) {
+                // No-op when the listener was deactivated.
+                return;
+            }
+            mController.updateRefreshRatePreference(isTyping ? LOWER : RESTORE);
+        }
+
+        @Override
+        public boolean isActive() {
+            return mActiveNotifier.get() == this;
+        }
+
+        @Override
+        public void deactivate() {
+            mActiveNotifier.compareAndSet(this, null);
+        }
+    }
+
+    /**
      * Callback used to notify corresponding activity about camera compat control changes, override
      * configuration change and make sure that all resources are set correctly before updating the
      * ViewRootImpl's internal state.
@@ -430,6 +502,32 @@
     private ActivityConfigCallback mActivityConfigCallback;
 
     /**
+     * The current active {@link TypingHintNotifier} to handle
+     * typing hint change operations.
+     */
+    private final AtomicReference<TypingHintNotifier> mActiveTypingHintNotifier =
+            new AtomicReference<>(null);
+
+    /**
+     * Create a {@link TypingHintNotifier} if the client support variable
+     * refresh rate for typing. The {@link TypingHintNotifier} is created
+     * and mapped to a new active input connection each time.
+     *
+     * @hide
+     */
+    @Nullable
+    public TypingHintNotifier createTypingHintNotifierIfSupported() {
+        if (mRefreshRateController == null) {
+            return null;
+        }
+        final TypingHintNotifier newNotifier = new TypingHintNotifierImpl(mActiveTypingHintNotifier,
+                mRefreshRateController);
+        mActiveTypingHintNotifier.set(newNotifier);
+
+        return newNotifier;
+    }
+
+    /**
      * Used when configuration change first updates the config of corresponding activity.
      * In that case we receive a call back from {@link ActivityThread} and this flag is used to
      * preserve the initial value.
@@ -858,6 +956,8 @@
     private final InsetsController mInsetsController;
     private final ImeFocusController mImeFocusController;
 
+    private ViewRootRefreshRateController mRefreshRateController;
+
     private boolean mIsSurfaceOpaque;
 
     private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
@@ -1048,6 +1148,13 @@
                 mViewConfiguration,
                 mContext.getSystemService(InputMethodManager.class));
 
+        // Whether the variable refresh rate for typing is supported.
+        boolean useVariableRefreshRateWhenTyping = context.getResources().getBoolean(
+                R.bool.config_variableRefreshRateTypingSupported);
+        if (useVariableRefreshRateWhenTyping) {
+            mRefreshRateController = new ViewRootRefreshRateController(this);
+        }
+
         mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
         mIsStylusPointerIconEnabled =
                 InputSettings.isStylusPointerIconEnabled(mContext);
@@ -2089,6 +2196,10 @@
         if (!mIsInTraversal) {
             scheduleTraversals();
         }
+
+        if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+            notifyLeaveTypingEvent();
+        }
     }
 
     @Override
@@ -6850,6 +6961,17 @@
     }
 
     /**
+     * Restores the refresh rate after leaving typing, the leaving typing cases like
+     * the IME insets is invisible or the user interacts the screen outside keyboard.
+     */
+    @UiThread
+    private void notifyLeaveTypingEvent() {
+        if (mRefreshRateController != null && mActiveTypingHintNotifier.get() != null) {
+            mRefreshRateController.updateRefreshRatePreference(RESTORE);
+        }
+    }
+
+    /**
      * Delivers post-ime input events to the view hierarchy.
      */
     final class ViewPostImeInputStage extends InputStage {
@@ -7066,6 +7188,10 @@
                 mLastClickToolType = event.getToolType(event.getActionIndex());
             }
 
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                notifyLeaveTypingEvent();
+            }
+
             mAttachInfo.mUnbufferedDispatchRequested = false;
             mAttachInfo.mHandlingPointerEvent = true;
             // If the event was fully handled by the handwriting initiator, then don't dispatch it
diff --git a/core/java/android/view/ViewRootRefreshRateController.java b/core/java/android/view/ViewRootRefreshRateController.java
new file mode 100644
index 0000000..cb9a81c
--- /dev/null
+++ b/core/java/android/view/ViewRootRefreshRateController.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 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.view;
+
+import static android.os.Trace.TRACE_TAG_VIEW;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Trace;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Controller to request refresh rate preference operations to the {@link ViewRootImpl}.
+ *
+ * @hide
+ */
+public class ViewRootRefreshRateController {
+
+    private static final String TAG = "VRRefreshRateController";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f;
+
+    @NonNull
+    private final ViewRootImpl mViewRootImpl;
+
+    private final RefreshRateParams mRateParams;
+
+    private final boolean mHasPreferredRefreshRate;
+
+    private int mRefreshRatePref = RefreshRatePref.NONE;
+
+    private boolean mMaxRefreshRateOverride = false;
+
+    @IntDef(value = {
+            RefreshRatePref.NONE,
+            RefreshRatePref.LOWER,
+            RefreshRatePref.RESTORE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RefreshRatePref {
+        /**
+         * Indicates that no refresh rate preference.
+         */
+        int NONE = 0;
+
+        /**
+         * Indicates that apply the lower refresh rate.
+         */
+        int LOWER = 1;
+
+        /**
+         * Indicates that restore to previous refresh rate.
+         */
+        int RESTORE = 2;
+    }
+
+    public ViewRootRefreshRateController(@NonNull ViewRootImpl viewRoot) {
+        mViewRootImpl = viewRoot;
+        mRateParams = new RefreshRateParams(getLowerSupportedRefreshRate());
+        mHasPreferredRefreshRate = hasPreferredRefreshRate();
+        if (mHasPreferredRefreshRate && DEBUG) {
+            Log.d(TAG, "App has preferred refresh rate. name:" + viewRoot);
+        }
+    }
+
+    /**
+     * Updates the preference to {@link ViewRootRefreshRateController#mRefreshRatePref},
+     * and check if it's needed to update the preferred refresh rate on demand. Like if the
+     * user is typing, try to apply the {@link RefreshRateParams#mTargetRefreshRate}.
+     *
+     * @param refreshRatePref to indicate the refresh rate preference
+     */
+    public void updateRefreshRatePreference(@RefreshRatePref int refreshRatePref) {
+        mRefreshRatePref = refreshRatePref;
+        doRefreshRateCheck();
+    }
+
+    private void doRefreshRateCheck() {
+        if (mRefreshRatePref == RefreshRatePref.NONE) {
+            return;
+        }
+        if (mHasPreferredRefreshRate) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "mMaxRefreshRateOverride:" + mMaxRefreshRateOverride
+                    + ", mRefreshRatePref:" + refreshRatePrefToString(mRefreshRatePref));
+        }
+
+        switch (mRefreshRatePref) {
+            case RefreshRatePref.LOWER :
+                if (!mMaxRefreshRateOverride) {
+                    // Save previous preferred rate before update
+                    mRateParams.savePreviousRefreshRateParams(mViewRootImpl.mWindowAttributes);
+                    updateMaxRefreshRate();
+                } else if (mViewRootImpl.mDisplay.getRefreshRate()
+                        > mRateParams.mTargetRefreshRate) {
+                    // Boosted, try to update again.
+                    updateMaxRefreshRate();
+                }
+                break;
+            case RefreshRatePref.RESTORE :
+                resetRefreshRate();
+                break;
+            default :
+                throw new RuntimeException("Unexpected value: " + mRefreshRatePref);
+        }
+    }
+
+    private void updateMaxRefreshRate() {
+        Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.updateMaxRefreshRate");
+        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+        params.preferredMaxDisplayRefreshRate = mRateParams.mTargetRefreshRate;
+        mViewRootImpl.setLayoutParams(params, false);
+        mMaxRefreshRateOverride = true;
+        Trace.instant(TRACE_TAG_VIEW, "VRRC update preferredMax="
+                + mRateParams.mTargetRefreshRate);
+        Trace.traceEnd(TRACE_TAG_VIEW);
+        if (DEBUG) {
+            Log.d(TAG, "update max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
+        }
+    }
+
+    private void resetRefreshRate() {
+        if (!mMaxRefreshRateOverride) {
+            return;
+        }
+        Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.resetRefreshRate");
+        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+        params.preferredMaxDisplayRefreshRate = mRateParams.mPreviousPreferredMaxRefreshRate;
+        mViewRootImpl.setLayoutParams(params, false);
+        mMaxRefreshRateOverride = false;
+        Trace.instant(TRACE_TAG_VIEW, "VRRC restore previous="
+                + mRateParams.mPreviousPreferredMaxRefreshRate);
+        Trace.traceEnd(TRACE_TAG_VIEW);
+        if (DEBUG) {
+            Log.d(TAG, "reset max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
+        }
+    }
+
+    private boolean hasPreferredRefreshRate() {
+        WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+        return params.preferredRefreshRate > 0
+                || params.preferredMaxDisplayRefreshRate > 0
+                || params.preferredMinDisplayRefreshRate > 0
+                || params.preferredDisplayModeId > 0;
+    }
+
+    private float getLowerSupportedRefreshRate() {
+        final Display display = mViewRootImpl.mDisplay;
+        final Display.Mode defaultMode = display.getDefaultMode();
+        float targetRefreshRate = defaultMode.getRefreshRate();
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getRefreshRate() < targetRefreshRate) {
+                targetRefreshRate = mode.getRefreshRate();
+            }
+        }
+        if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) {
+            targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND;
+        }
+        return targetRefreshRate;
+    }
+
+    private static String refreshRatePrefToString(@RefreshRatePref int pref) {
+        switch (pref) {
+            case RefreshRatePref.NONE:
+                return "NONE";
+            case RefreshRatePref.LOWER:
+                return "LOWER";
+            case RefreshRatePref.RESTORE:
+                return "RESTORE";
+            default:
+                return "Unknown pref=" + pref;
+        }
+    }
+
+    /**
+     * A class for recording refresh rate parameters of the target view, including the target
+     * refresh rate we want to apply when entering particular states, and the original preferred
+     * refresh rate for restoring when leaving the state.
+     */
+    private static class RefreshRateParams {
+        float mTargetRefreshRate;
+
+        float mPreviousPreferredMaxRefreshRate = 0;
+
+        RefreshRateParams(float targetRefreshRate) {
+            mTargetRefreshRate = targetRefreshRate;
+            if (DEBUG) {
+                Log.d(TAG, "The target rate: " + targetRefreshRate);
+            }
+        }
+        void savePreviousRefreshRateParams(WindowManager.LayoutParams param) {
+            mPreviousPreferredMaxRefreshRate = param.preferredMaxDisplayRefreshRate;
+            if (DEBUG) {
+                Log.d(TAG, "Save previous params, preferred: " + param.preferredRefreshRate
+                        + ", Max: " + param.preferredMaxDisplayRefreshRate);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index e9d7b9b..364adc7 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,6 +28,7 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiThread;
 import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -182,6 +183,8 @@
 
     private CancellationSignalBeamer.Receiver mBeamer;
 
+    private ViewRootImpl.TypingHintNotifier mTypingHintNotifier;
+
     RemoteInputConnectionImpl(@NonNull Looper looper,
             @NonNull InputConnection inputConnection,
             @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
@@ -190,6 +193,12 @@
         mH = new Handler(mLooper);
         mParentInputMethodManager = inputMethodManager;
         mServedView = new WeakReference<>(servedView);
+        if (servedView != null) {
+            final ViewRootImpl viewRoot = servedView.getViewRootImpl();
+            if (viewRoot != null) {
+                mTypingHintNotifier = viewRoot.createTypingHintNotifierIfSupported();
+            }
+        }
     }
 
     /**
@@ -364,6 +373,12 @@
             return;
         }
         dispatch(() -> {
+            notifyTypingHint(false /* isTyping */);
+            // Deactivate the notifier when finishing typing.
+            if (mTypingHintNotifier != null) {
+                mTypingHintNotifier.deactivate();
+            }
+
             // Note that we do not need to worry about race condition here, because 1) mFinished is
             // updated only inside this block, and 2) the code here is running on a Handler hence we
             // assume multiple closeConnection() tasks will not be handled at the same time.
@@ -628,6 +643,7 @@
                 return;
             }
             ic.commitText(text, newCursorPosition);
+            notifyTypingHint(true /* isTyping */);
         });
     }
 
@@ -783,6 +799,7 @@
                 return;
             }
             ic.setComposingText(text, newCursorPosition);
+            notifyTypingHint(true /* isTyping */);
         });
     }
 
@@ -910,6 +927,7 @@
                 return;
             }
             ic.deleteSurroundingText(beforeLength, afterLength);
+            notifyTypingHint(true /* isTyping */);
         });
     }
 
@@ -1473,4 +1491,16 @@
     private static boolean useImeTracing() {
         return ImeTracing.getInstance().isEnabled();
     }
+
+    /**
+     * Dispatch the typing hint to {@link ViewRootImpl.TypingHintNotifier}.
+     * The input connection indicates that the user is typing when {@link #commitText} or
+     * {@link #setComposingText)} and the user finish typing when {@link #deactivate()}.
+     */
+    @UiThread
+    private void notifyTypingHint(boolean isTyping) {
+        if (mTypingHintNotifier != null) {
+            mTypingHintNotifier.onTypingHintChanged(isTyping);
+        }
+    }
 }
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index d1a4935..37d2fa0 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -85,14 +85,18 @@
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
         android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
 
         <Button
             android:id="@+id/autofill_dialog_no"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:layout_marginTop="6dp"
-            android:layout_marginBottom="6dp"
+            android:layout_height="40dp"
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:paddingTop="0dp"
+            android:paddingBottom="0dp"
+            android:minWidth="0dp"
             style="?android:attr/borderlessButtonStyle"
             android:text="@string/autofill_save_no">
         </Button>
@@ -107,9 +111,8 @@
         <Button
             android:id="@+id/autofill_dialog_yes"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:layout_marginTop="6dp"
-            android:layout_marginBottom="6dp"
+            android:layout_height="40dp"
+            android:minWidth="0dp"
             style="@style/AutofillHalfSheetTonalButton"
             android:text="@string/autofill_save_yes"
             android:visibility="gone" >
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 85529d6..bed19a8 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -72,14 +72,18 @@
             android:layout_marginTop="32dp"
             android:layout_marginBottom="18dp"
             android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
-            android:orientation="horizontal">
+            android:orientation="horizontal"
+            android:gravity="center_vertical">
 
             <Button
                 android:id="@+id/autofill_save_no"
                 android:layout_width="wrap_content"
-                android:layout_height="36dp"
-                android:layout_marginTop="6dp"
-                android:layout_marginBottom="6dp"
+                android:layout_height="40dp"
+                android:paddingStart="12dp"
+                android:paddingEnd="12dp"
+                android:paddingTop="0dp"
+                android:paddingBottom="0dp"
+                android:minWidth="0dp"
                 style="?android:attr/borderlessButtonStyle"
                 android:text="@string/autofill_save_no">
             </Button>
@@ -94,9 +98,8 @@
             <Button
                 android:id="@+id/autofill_save_yes"
                 android:layout_width="wrap_content"
-                android:layout_height="36dp"
-                android:layout_marginTop="6dp"
-                android:layout_marginBottom="6dp"
+                android:layout_height="40dp"
+                android:minWidth="0dp"
                 style="@style/AutofillHalfSheetTonalButton"
                 android:text="@string/autofill_save_yes">
             </Button>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4184e79..478b01c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5377,6 +5377,13 @@
          of known compatibility issues. -->
     <string-array name="config_highRefreshRateBlacklist"></string-array>
 
+    <!-- The list of packages to automatically opt in to refresh rate suppressing by small area
+    detection. Format of this array should be packageName:threshold and threshold value should
+     be between 0 to 1-->
+    <string-array name="config_smallAreaDetectionAllowlist" translatable="false">
+        <!-- Add packages:threshold here -->
+    </string-array>
+
     <!-- The list of packages to force slowJpegMode for Apps using Camera API1 -->
     <string-array name="config_forceSlowJpegModeList" translatable="false">
         <!-- Add packages here -->
@@ -6089,6 +6096,8 @@
 
     <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED -->
     <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool>
+    <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED for search overlay -->
+    <bool name="config_searchLongPressHomeEnabledDefault">true</bool>
 
     <!-- The maximum byte size of the information contained in the bundle of
     HotwordDetectedResult. -->
@@ -6562,6 +6571,9 @@
          device. -->
     <bool name="config_enableAppCloningBuildingBlocks">true</bool>
 
+    <!-- Whether the variable refresh rate when typing feature is enabled for the device. -->
+    <bool name="config_variableRefreshRateTypingSupported">false</bool>
+
     <!-- Enables or disables support for repair mode. The feature creates a secure
          environment to protect the user's privacy when the device is being repaired.
          Off by default, since OEMs may have had a similar feature on their devices. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 79964b3..d238a130 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1500,12 +1500,8 @@
         <item name="fontFamily">google-sans-text-medium</item>
         <item name="textStyle">normal</item>
         <item name="textAllCaps">false</item>
-        <item name="layout_marginTop">6dp</item>
-        <item name="layout_marginBottom">6dp</item>
-        <item name="paddingStart">16dp</item>
-        <item name="paddingEnd">16dp</item>
-        <item name="paddingTop">8dp</item>
-        <item name="paddingBottom">8dp</item>
+        <item name="paddingStart">24dp</item>
+        <item name="paddingEnd">24dp</item>
     </style>
     <!-- @hide Tonal button for Autofill half screen dialog -->
     <style name="AutofillHalfSheetTonalButton" parent="AutofillHalfSheetButton">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 81fbc38..644597c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4239,6 +4239,8 @@
   <java-symbol type="array" name="config_highRefreshRateBlacklist" />
   <java-symbol type="array" name="config_forceSlowJpegModeList" />
 
+  <java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
+
   <java-symbol type="layout" name="chooser_dialog" />
   <java-symbol type="layout" name="chooser_dialog_item" />
   <java-symbol type="drawable" name="chooser_dialog_background" />
@@ -4866,6 +4868,7 @@
   <java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
 
   <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" />
+  <java-symbol type="bool" name="config_searchLongPressHomeEnabledDefault" />
 
   <java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" />
 
@@ -4942,6 +4945,8 @@
 
   <java-symbol type="bool" name="config_repairModeSupported" />
 
+  <java-symbol type="bool" name="config_variableRefreshRateTypingSupported" />
+
   <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index 0676f89..aaaa3c7 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -241,7 +241,8 @@
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
                         /* includePowerModels */ true,
-                        /* includeProcessStats */true)
+                        /* includeProcessStats */ true,
+                        /* minConsumedPowerThreshold */ 0)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
                         .setDischargeDurationMs(1234)
@@ -325,7 +326,7 @@
     @Test
     public void testLargeAtomTruncated() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[0], true, false);
+                new BatteryUsageStats.Builder(new String[0], true, false, 0);
         // If not truncated, this BatteryUsageStats object would generate a proto buffer
         // significantly larger than 50 Kb
         for (int i = 0; i < 3000; i++) {
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a358c4f..57f0920 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1389,6 +1389,15 @@
                        android:resource="@xml/accessibility_shortcut_test_activity"/>
         </activity>
 
+        <activity android:name="android.view.ViewRefreshRateTestActivity"
+                  android:label="ViewRefreshRateTestActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <!-- Activity-level metadata -->
         <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" />
         <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
diff --git a/core/tests/coretests/res/layout/activity_refresh_rate_test.xml b/core/tests/coretests/res/layout/activity_refresh_rate_test.xml
new file mode 100644
index 0000000..ad57fc1
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_refresh_rate_test.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/layout"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java b/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java
new file mode 100644
index 0000000..2b11851
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewRefreshRateTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class ViewRefreshRateTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_refresh_rate_test);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java b/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java
new file mode 100644
index 0000000..d278bc3
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewRootRefreshRateControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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.view;
+
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER;
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.platform.test.annotations.Presubmit;
+import android.widget.EditText;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewRootRefreshRateControllerTest {
+
+    private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f;
+
+    private boolean mUseVariableRefreshRateWhenTyping;
+
+    private ViewRootRefreshRateController mRefreshRateController;
+
+    @Rule
+    public ActivityTestRule<ViewRefreshRateTestActivity> mActivityRule =
+            new ActivityTestRule<>(ViewRefreshRateTestActivity.class);
+
+    private ViewRefreshRateTestActivity mActivity;
+
+    private float mLowestSupportRefreshRate;
+
+    private Instrumentation mInstrumentation;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.getActivity();
+        mLowestSupportRefreshRate = getLowerSupportedRefreshRate();
+        mUseVariableRefreshRateWhenTyping = mInstrumentation.getContext().getResources()
+                .getBoolean(com.android.internal.R.bool.config_variableRefreshRateTypingSupported);
+    }
+
+    @Test
+    public void testUpdateRefreshRatePreference_shouldLowerThenRestore() throws Throwable {
+        // Ignored if the feature is not enabled.
+        assumeTrue(mUseVariableRefreshRateWhenTyping);
+
+        final ViewGroup viewGroup = mActivity.findViewById(R.id.layout);
+        final EditText editText = new EditText(mActivity);
+
+        mActivityRule.runOnUiThread(() -> viewGroup.addView(editText));
+        mInstrumentation.waitForIdleSync();
+
+        final ViewRootImpl viewRootImpl = editText.getViewRootImpl();
+        mRefreshRateController = new ViewRootRefreshRateController(viewRootImpl);
+        final float originalPreferredMaxDisplayRefreshRate =
+                viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate;
+
+        mRefreshRateController.updateRefreshRatePreference(LOWER);
+
+        // Update to lower rate.
+        assertEquals(viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate,
+                mLowestSupportRefreshRate);
+
+        mRefreshRateController.updateRefreshRatePreference(RESTORE);
+
+        // Restore to previous preferred rate.
+        assertEquals(viewRootImpl.mWindowAttributes.preferredMaxDisplayRefreshRate,
+                originalPreferredMaxDisplayRefreshRate);
+    }
+
+    private float getLowerSupportedRefreshRate() {
+        final Display display = mActivity.getDisplay();
+        final Display.Mode defaultMode = display.getDefaultMode();
+        float targetRefreshRate = defaultMode.getRefreshRate();
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getRefreshRate() < targetRefreshRate) {
+                targetRefreshRate = mode.getRefreshRate();
+            }
+        }
+        if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) {
+            targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND;
+        }
+        return targetRefreshRate;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 896fe61..d894487 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -651,7 +651,8 @@
         if (minDimensionsPair == null) {
             return splitAttributes;
         }
-        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+                taskProperties, splitAttributes);
         final Configuration taskConfiguration = taskProperties.getConfiguration();
         final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
                 foldingFeature);
@@ -726,7 +727,8 @@
     Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
             @NonNull SplitAttributes splitAttributes) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
-        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+                taskProperties, splitAttributes);
         if (!shouldShowSplit(splitAttributes)) {
             return new Rect();
         }
@@ -933,6 +935,17 @@
     }
 
     @Nullable
+    private FoldingFeature getFoldingFeatureForHingeType(
+            @NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes) {
+        SplitType splitType = splitAttributes.getSplitType();
+        if (!(splitType instanceof HingeSplitType)) {
+            return null;
+        }
+        return getFoldingFeature(taskProperties);
+    }
+
+    @Nullable
     @VisibleForTesting
     FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
         final int displayId = taskProperties.getDisplayId();
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 43acdd5..ff369c8 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -40,7 +40,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -934,18 +933,6 @@
 
         Settings.System.putStringForUser(resolver, setting,
                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
-
-        // Stream selected ringtone into cache so it's available for playback
-        // when CE storage is still locked
-        if (ringtoneUri != null) {
-            final Uri cacheUri = getCacheForType(type, context.getUserId());
-            try (InputStream in = openRingtone(context, ringtoneUri);
-                    OutputStream out = resolver.openOutputStream(cacheUri, "wt")) {
-                FileUtils.copy(in, out);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to cache ringtone: " + e);
-            }
-        }
     }
 
     private static boolean isInternalRingtoneUri(Uri uri) {
@@ -1041,28 +1028,6 @@
         }
     }
 
-    /**
-     * Try opening the given ringtone locally first, but failover to
-     * {@link IRingtonePlayer} if we can't access it directly. Typically happens
-     * when process doesn't hold
-     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
-     */
-    private static InputStream openRingtone(Context context, Uri uri) throws IOException {
-        final ContentResolver resolver = context.getContentResolver();
-        try {
-            return resolver.openInputStream(uri);
-        } catch (SecurityException | IOException e) {
-            Log.w(TAG, "Failed to open directly; attempting failover: " + e);
-            final IRingtonePlayer player = context.getSystemService(AudioManager.class)
-                    .getRingtonePlayer();
-            try {
-                return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri));
-            } catch (Exception e2) {
-                throw new IOException(e2);
-            }
-        }
-    }
-
     private static String getSettingForType(int type) {
         if ((type & TYPE_RINGTONE) != 0) {
             return Settings.System.RINGTONE;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 15446b6..3790490 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -34,7 +34,6 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -53,11 +52,10 @@
 
     private static final String DOWNLOADS_AUTHORITY = "downloads";
 
-    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = 1;
-    private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = 2;
     private PackageManager mPackageManager;
     private UserManager mUserManager;
     private boolean mAbortInstall = false;
+    private boolean mShouldFinish = true;
 
     private final boolean mLocalLOGV = false;
 
@@ -130,7 +128,7 @@
             mAbortInstall = true;
         }
 
-        checkDevicePolicyRestriction();
+        checkDevicePolicyRestrictions();
 
         final String installerPackageNameFromIntent = getIntent().getStringExtra(
                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
@@ -149,7 +147,9 @@
 
         if (mAbortInstall) {
             setResult(RESULT_CANCELED);
-            finish();
+            if (mShouldFinish) {
+                finish();
+            }
             return;
         }
 
@@ -291,58 +291,52 @@
         return originatingUid == installerUid;
     }
 
-    private void checkDevicePolicyRestriction() {
-        // Check for install apps user restriction first.
-        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
-                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
-        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
-            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
-            mAbortInstall = true;
-            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
-            return;
-        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
-            if (mLocalLOGV) {
-                Log.i(TAG, "install not allowed by admin; showing "
-                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
-            }
-            mAbortInstall = true;
-            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
-            return;
-        }
+    private void checkDevicePolicyRestrictions() {
+        final String[] restrictions = new String[] {
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+        };
 
-        final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
-        final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
-        final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
-                & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
-        if (systemRestriction != 0) {
-            if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
+        final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+        for (String restriction : restrictions) {
+            if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+                continue;
+            }
+
             mAbortInstall = true;
-            showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
-        } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
-            mAbortInstall = true;
-            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-        } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
-            mAbortInstall = true;
-            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+
+            // If the given restriction is set by an admin, display information about the
+            // admin enforcing the restriction for the affected user. If not enforced by the admin,
+            // show the system dialog.
+            final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
+            if (showAdminSupportDetailsIntent != null) {
+                if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
+                startActivity(showAdminSupportDetailsIntent);
+            } else {
+                if (mLocalLOGV) Log.i(TAG, "Restriction set by system: " + restriction);
+                mShouldFinish = false;
+                showDialogInner(restriction);
+            }
+            break;
         }
     }
 
     /**
-     * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
+     * Replace any dialog shown by the dialog with the one for the given
+     * {@link #createDialog(String)}.
      *
-     * @param id The dialog type to add
+     * @param restriction The restriction to create the dialog for
      */
-    private void showDialogInner(int id) {
-        if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")");
+    private void showDialogInner(String restriction) {
+        if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + restriction + ")");
         DialogFragment currentDialog =
                 (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
         if (currentDialog != null) {
             currentDialog.dismissAllowingStateLoss();
         }
 
-        DialogFragment newDialog = createDialog(id);
+        DialogFragment newDialog = createDialog(restriction);
         if (newDialog != null) {
             getFragmentManager().beginTransaction()
                     .add(newDialog, "dialog").commitAllowingStateLoss();
@@ -352,35 +346,20 @@
     /**
      * Create a new dialog.
      *
-     * @param id The id of the dialog (determines dialog type)
-     *
+     * @param restriction The restriction to create the dialog for
      * @return The dialog
      */
-    private DialogFragment createDialog(int id) {
-        if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")");
-        switch (id) {
-            case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
+    private DialogFragment createDialog(String restriction) {
+        if (mLocalLOGV) Log.i(TAG, "createDialog(" + restriction + ")");
+        switch (restriction) {
+            case UserManager.DISALLOW_INSTALL_APPS:
                 return PackageUtil.SimpleErrorDialog.newInstance(
                         R.string.install_apps_user_restriction_dlg_text);
-            case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
+            case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+            case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY:
                 return PackageUtil.SimpleErrorDialog.newInstance(
                         R.string.unknown_apps_user_restriction_dlg_text);
         }
         return null;
     }
-
-    private void startAdminSupportDetailsActivity(String restriction) {
-        if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction);
-
-        // If the given restriction is set by an admin, display information about the
-        // admin enforcing the restriction for the affected user.
-        final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
-        final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
-        if (showAdminSupportDetailsIntent != null) {
-            if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
-            startActivity(showAdminSupportDetailsIntent);
-        } else {
-            if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction);
-        }
-    }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 67f4418..0a33b9b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -18,6 +18,7 @@
 
 import androidx.activity.compose.BackHandler
 import androidx.appcompat.R
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
@@ -96,7 +97,8 @@
             Modifier
                 .padding(paddingValues.horizontalValues())
                 .padding(top = paddingValues.calculateTopPadding())
-                .fillMaxSize(),
+                .focusable()
+                .fillMaxSize()
         ) {
             content(
                 bottomPadding = paddingValues.calculateBottomPadding(),
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 765edd7..e45913c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -58,6 +58,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -76,6 +77,7 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.hardware.camera2.utils.ArrayUtils;
 import android.media.AudioManager;
+import android.media.IRingtonePlayer;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -110,6 +112,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -129,7 +132,10 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.security.InvalidKeyException;
@@ -841,29 +847,68 @@
         uri = ContentProvider.getUriWithoutUserId(uri);
 
         final String cacheRingtoneSetting;
-        final String cacheName;
         if (Settings.System.RINGTONE_CACHE_URI.equals(uri)) {
             cacheRingtoneSetting = Settings.System.RINGTONE;
-            cacheName = Settings.System.RINGTONE_CACHE;
         } else if (Settings.System.NOTIFICATION_SOUND_CACHE_URI.equals(uri)) {
             cacheRingtoneSetting = Settings.System.NOTIFICATION_SOUND;
-            cacheName = Settings.System.NOTIFICATION_SOUND_CACHE;
         } else if (Settings.System.ALARM_ALERT_CACHE_URI.equals(uri)) {
             cacheRingtoneSetting = Settings.System.ALARM_ALERT;
-            cacheName = Settings.System.ALARM_ALERT_CACHE;
         } else {
             throw new FileNotFoundException("Direct file access no longer supported; "
                     + "ringtone playback is available through android.media.Ringtone");
         }
 
+        final File cacheFile = getCacheFile(cacheRingtoneSetting, userId);
+        return ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(mode));
+    }
+
+    @Nullable
+    private String getCacheName(String setting) {
+        if (Settings.System.RINGTONE.equals(setting)) {
+            return Settings.System.RINGTONE_CACHE;
+        } else if (Settings.System.NOTIFICATION_SOUND.equals(setting)) {
+            return Settings.System.NOTIFICATION_SOUND_CACHE;
+        } else if (Settings.System.ALARM_ALERT.equals(setting)) {
+            return Settings.System.ALARM_ALERT_CACHE;
+        }
+        return null;
+    }
+
+    @Nullable
+    private File getCacheFile(String setting, int userId) {
         int actualCacheOwner;
         // Redirect cache to parent if ringtone setting is owned by profile parent
         synchronized (mLock) {
-            actualCacheOwner = resolveOwningUserIdForSystemSettingLocked(userId,
-                    cacheRingtoneSetting);
+            actualCacheOwner = resolveOwningUserIdForSystemSettingLocked(userId, setting);
+        }
+        final String cacheName = getCacheName(setting);
+        if (cacheName == null) {
+            return null;
         }
         final File cacheFile = new File(getRingtoneCacheDir(actualCacheOwner), cacheName);
-        return ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(mode));
+        return cacheFile;
+    }
+
+
+    /**
+     * Try opening the given ringtone locally first, but failover to
+     * {@link IRingtonePlayer} if we can't access it directly. Typically, happens
+     * when process doesn't hold {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
+     */
+    private static InputStream openRingtone(Context context, Uri uri) throws IOException {
+        final ContentResolver resolver = context.getContentResolver();
+        try {
+            return resolver.openInputStream(uri);
+        } catch (SecurityException | IOException e) {
+            Log.w(LOG_TAG, "Failed to open directly; attempting failover: " + e);
+            final IRingtonePlayer player = context.getSystemService(AudioManager.class)
+                    .getRingtonePlayer();
+            try {
+                return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri));
+            } catch (Exception e2) {
+                throw new IOException(e2);
+            }
+        }
     }
 
     private File getRingtoneCacheDir(int userId) {
@@ -1938,49 +1983,65 @@
             return false;
         }
 
-        // Invalidate any relevant cache files
-        String cacheName = null;
-        if (Settings.System.RINGTONE.equals(name)) {
-            cacheName = Settings.System.RINGTONE_CACHE;
-        } else if (Settings.System.NOTIFICATION_SOUND.equals(name)) {
-            cacheName = Settings.System.NOTIFICATION_SOUND_CACHE;
-        } else if (Settings.System.ALARM_ALERT.equals(name)) {
-            cacheName = Settings.System.ALARM_ALERT_CACHE;
-        }
-        if (cacheName != null) {
+        File cacheFile = getCacheFile(name, callingUserId);
+        if (cacheFile != null) {
             if (!isValidAudioUri(name, value)) {
                 return false;
             }
-            final File cacheFile = new File(
-                    getRingtoneCacheDir(owningUserId), cacheName);
+            // Invalidate any relevant cache files
             cacheFile.delete();
         }
 
+        final boolean success;
         // Mutate the value.
         synchronized (mLock) {
             switch (operation) {
                 case MUTATION_OPERATION_INSERT: {
                     validateSystemSettingValue(name, value);
-                    return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
+                    success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null, overrideableByRestore);
+                    break;
                 }
 
                 case MUTATION_OPERATION_DELETE: {
-                    return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
+                    success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, false, null);
+                    break;
                 }
 
                 case MUTATION_OPERATION_UPDATE: {
                     validateSystemSettingValue(name, value);
-                    return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
+                    success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null);
+                    break;
+                }
+
+                default: {
+                    success = false;
+                    Slog.e(LOG_TAG, "Unknown operation code: " + operation);
                 }
             }
-            Slog.e(LOG_TAG, "Unknown operation code: " + operation);
+        }
+
+        if (!success) {
             return false;
         }
+
+        if ((operation == MUTATION_OPERATION_INSERT || operation == MUTATION_OPERATION_UPDATE)
+                && cacheFile != null && value != null) {
+            final Uri ringtoneUri = Uri.parse(value);
+            // Stream selected ringtone into cache, so it's available for playback
+            // when CE storage is still locked
+            try (InputStream in = openRingtone(getContext(), ringtoneUri);
+                 OutputStream out = new FileOutputStream(cacheFile)) {
+                FileUtils.copy(in, out);
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Failed to cache ringtone: " + e);
+            }
+        }
+        return true;
     }
 
     private boolean isValidAudioUri(String name, String uri) {
@@ -3267,20 +3328,21 @@
             return Global.SECURE_FRP_MODE.equals(setting.getName());
         }
 
-        public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+        public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
-            resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
+            return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
                     null);
         }
 
-        public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+        public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag, @Nullable String prefix) {
             final int key = makeKey(type, userId);
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState == null) {
-                return;
+                return false;
             }
 
+            boolean success = false;
             banConfigurationIfNecessary(type, prefix, settingsState);
             switch (mode) {
                 case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
@@ -3300,6 +3362,7 @@
                         }
                         if (someSettingChanged) {
                             settingsState.persistSyncLocked();
+                            success = true;
                         }
                     }
                 } break;
@@ -3321,6 +3384,7 @@
                         }
                         if (someSettingChanged) {
                             settingsState.persistSyncLocked();
+                            success = true;
                         }
                     }
                 } break;
@@ -3348,6 +3412,7 @@
                         }
                         if (someSettingChanged) {
                             settingsState.persistSyncLocked();
+                            success = true;
                         }
                     }
                 } break;
@@ -3372,10 +3437,12 @@
                         }
                         if (someSettingChanged) {
                             settingsState.persistSyncLocked();
+                            success = true;
                         }
                     }
                 } break;
             }
+            return success;
         }
 
         public void removeSettingsForPackageLocked(String packageName, int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index a9d2b30..4be572f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -55,7 +55,6 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
 import com.android.systemui.screenrecord.ScreenShareOption;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -73,7 +72,6 @@
 
     private final FeatureFlags mFeatureFlags;
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
-    private final ActivityStarter mActivityStarter;
 
     private String mPackageName;
     private int mUid;
@@ -89,10 +87,8 @@
 
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
-            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
-            ActivityStarter activityStarter) {
+            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
         mFeatureFlags = featureFlags;
-        mActivityStarter = activityStarter;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
     }
 
@@ -313,16 +309,8 @@
                 // Start activity from the current foreground user to avoid creating a separate
                 // SystemUI process without access to recent tasks because it won't have
                 // WM Shell running inside.
-                // It is also important to make sure the shade is dismissed, otherwise users won't
-                // see the app selector.
                 mUserSelectingTask = true;
-                mActivityStarter.startActivity(
-                        intent,
-                        /* dismissShade= */ true,
-                        /* animationController= */ null,
-                        /* showOverLockscreenWhenLocked= */ false,
-                        UserHandle.of(ActivityManager.getCurrentUser())
-                );
+                startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 37f032b..2c15e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -125,6 +125,7 @@
 
     // FrameCallback used to delay starting the light reveal animation until the next frame
     private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") {
+        lightRevealAnimationPlaying = true
         lightRevealAnimator.start()
     }
 
@@ -268,7 +269,6 @@
             decidedToAnimateGoingToSleep = true
 
             shouldAnimateInKeyguard = true
-            lightRevealAnimationPlaying = true
 
             // Start the animation on the next frame. startAnimation() is called after
             // PhoneWindowManager makes a binder call to System UI on
@@ -283,7 +283,8 @@
                 // dispatched, a race condition could make it possible for this callback to be run
                 // as the device is waking up. That results in the AOD UI being shown while we wake
                 // up, with unpredictable consequences.
-                if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY)) {
+                if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
+                        shouldAnimateInKeyguard) {
                     aodUiAnimationPlaying = true
 
                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e76f26d..e6f8c48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -134,6 +134,22 @@
         verify(shadeViewController, times(1)).showAodUi()
     }
 
+    @Test
+    fun testAodUiShowNotInvokedIfWakingUp() {
+        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
+        `when`(powerManager.isInteractive).thenReturn(false)
+
+        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        controller.startAnimation()
+        controller.onStartedWakingUp()
+
+        verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+
+        callbackCaptor.value.run()
+
+        verify(shadeViewController, never()).showAodUi()
+    }
+
     /**
      * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
      * animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index dc6f858..2c745ae 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.BATTERY_STATS;
-import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.Manifest.permission.DEVICE_POWER;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.POWER_SAVER;
@@ -31,6 +30,7 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
+import android.annotation.SuppressLint;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -82,6 +82,7 @@
 import android.os.health.HealthStatsWriter;
 import android.os.health.UidHealthStats;
 import android.power.PowerStatsInternal;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -169,6 +170,7 @@
                     .replaceWith("?");
     private static final int MAX_LOW_POWER_STATS_SIZE = 32768;
     private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+    private static final String MIN_CONSUMED_POWER_THRESHOLD_KEY = "min_consumed_power_threshold";
     private static final String EMPTY = "Empty";
 
     private final HandlerThread mHandlerThread;
@@ -855,12 +857,17 @@
             final BatteryUsageStats bus;
             switch (atomTag) {
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+                    @SuppressLint("MissingPermission")
+                    final double minConsumedPowerThreshold =
+                            DeviceConfig.getFloat(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                                    MIN_CONSUMED_POWER_THRESHOLD_KEY, 0);
                     final BatteryUsageStatsQuery querySinceReset =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
                                     .includePowerModels()
+                                    .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
                                     .build();
                     bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
                     break;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 898a3c4..6546f6e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -468,6 +468,8 @@
     private SensorManager mSensorManager;
     private BrightnessTracker mBrightnessTracker;
 
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
 
     // Whether minimal post processing is allowed by the user.
     @GuardedBy("mSyncRoot")
@@ -731,6 +733,8 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
 
         mContext.registerReceiver(mIdleModeReceiver, filter);
+
+        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
     }
 
     @VisibleForTesting
@@ -3046,6 +3050,9 @@
         pw.println();
         mDisplayModeDirector.dump(pw);
         mBrightnessSynchronizer.dump(pw);
+        if (mSmallAreaDetectionController != null) {
+            mSmallAreaDetectionController.dump(pw);
+        }
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
new file mode 100644
index 0000000..adaa539
--- /dev/null
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Map;
+
+final class SmallAreaDetectionController {
+    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
+    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+
+    // TODO(b/281720315): Move this to DeviceConfig once server side ready.
+    private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
+            "small_area_detection_allowlist";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final PackageManagerInternal mPackageManager;
+    private final UserManagerInternal mUserManager;
+    @GuardedBy("mLock")
+    private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
+    // TODO(b/298722189): Update allowlist when user changes
+    @GuardedBy("mLock")
+    private int[] mUserIds;
+
+    static SmallAreaDetectionController create(@NonNull Context context) {
+        final SmallAreaDetectionController controller =
+                new SmallAreaDetectionController(context, DeviceConfigInterface.REAL);
+        final String property = DeviceConfigInterface.REAL.getProperty(
+                DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST);
+        controller.updateAllowlist(property);
+        return controller;
+    }
+
+    @VisibleForTesting
+    SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
+        mContext = context;
+        mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+        mUserManager = LocalServices.getService(UserManagerInternal.class);
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                BackgroundThread.getExecutor(),
+                new SmallAreaDetectionController.OnPropertiesChangedListener());
+        mPackageManager.getPackageList(new PackageReceiver());
+    }
+
+    @VisibleForTesting
+    void updateAllowlist(@Nullable String property) {
+        synchronized (mLock) {
+            mAllowPkgMap.clear();
+            if (property != null) {
+                final String[] mapStrings = property.split(",");
+                for (String mapString : mapStrings) putToAllowlist(mapString);
+            } else {
+                final String[] defaultMapStrings = mContext.getResources()
+                        .getStringArray(R.array.config_smallAreaDetectionAllowlist);
+                for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
+            }
+            updateSmallAreaDetection();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void putToAllowlist(String rowData) {
+        // Data format: package:threshold - e.g. "com.abc.music:0.05"
+        final String[] items = rowData.split(":");
+        if (items.length == 2) {
+            try {
+                final String pkg = items[0];
+                final float threshold = Float.valueOf(items[1]);
+                mAllowPkgMap.put(pkg, threshold);
+            } catch (Exception e) {
+                // Just skip if items[1] - the threshold is not parsable number
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
+        for (int i = 0; i < mUserIds.length; i++) {
+            final int userId = mUserIds[i];
+            final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
+            if (uid > 0) list.put(uid, threshold);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateSmallAreaDetection() {
+        if (mAllowPkgMap.isEmpty()) return;
+
+        mUserIds = mUserManager.getUserIds();
+
+        final SparseArray<Float> uidThresholdList = new SparseArray<>();
+        for (String pkg : mAllowPkgMap.keySet()) {
+            final float threshold = mAllowPkgMap.get(pkg);
+            updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+        }
+
+        final int[] uids = new int[uidThresholdList.size()];
+        final float[] thresholds = new float[uidThresholdList.size()];
+        for (int i = 0; i < uidThresholdList.size();  i++) {
+            uids[i] = uidThresholdList.keyAt(i);
+            thresholds[i] = uidThresholdList.valueAt(i);
+        }
+        updateSmallAreaDetection(uids, thresholds);
+    }
+
+    @VisibleForTesting
+    void updateSmallAreaDetection(int[] uids, float[] thresholds) {
+        nativeUpdateSmallAreaDetection(uids, thresholds);
+    }
+
+    void setSmallAreaDetectionThreshold(int uid, float threshold) {
+        nativeSetSmallAreaDetectionThreshold(uid, threshold);
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("Small area detection allowlist");
+        pw.println("  Packages:");
+        synchronized (mLock) {
+            for (String pkg : mAllowPkgMap.keySet()) {
+                pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
+            }
+            pw.println("  mUserIds=" + Arrays.toString(mUserIds));
+        }
+    }
+
+    private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
+        public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+            if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) {
+                updateAllowlist(
+                        properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/));
+            }
+        }
+    }
+
+    private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
+        @Override
+        public void onPackageAdded(@NonNull String packageName, int uid) {
+            synchronized (mLock) {
+                if (mAllowPkgMap.containsKey(packageName)) {
+                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d662aae..204e358 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1365,11 +1365,13 @@
         assertCallerIsOwnerRootOrVerifier();
         synchronized (mLock) {
             assertPreparedAndNotDestroyedLocked("getNames");
+            String[] names;
             if (!isCommitted()) {
-                return getNamesLocked();
+                names = getNamesLocked();
             } else {
-                return getStageDirContentsLocked();
+                names = getStageDirContentsLocked();
             }
+            return ArrayUtils.removeString(names, APP_METADATA_FILE_NAME);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 297ad73..c24d523 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1001,7 +1001,9 @@
             }
 
             synchronized (mLock) {
-                mAttributions.put(source.getToken(), source);
+                // Change the token for the AttributionSource we're storing, so that we don't store
+                // a strong reference to the original token inside the map itself.
+                mAttributions.put(source.getToken(), source.withDefaultToken());
             }
         }
 
@@ -1009,7 +1011,7 @@
             synchronized (mLock) {
                 final AttributionSource cachedSource = mAttributions.get(source.getToken());
                 if (cachedSource != null) {
-                    return cachedSource.equals(source);
+                    return cachedSource.equalsExceptToken(source);
                 }
                 return false;
             }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index ebd4aec..5e5f6f2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -166,10 +166,11 @@
                 && mStats.isProcessStateDataAvailable();
         final boolean includeVirtualUids =  ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
                 mStats.getCustomEnergyConsumerNames(), includePowerModels,
-                includeProcessStateData);
+                includeProcessStateData, minConsumedPowerThreshold);
         // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
         // of stats sessions to wall-clock adjustments
         batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime());
@@ -303,10 +304,12 @@
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
                 && mStats.isProcessStateDataAvailable();
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customEnergyConsumerNames, includePowerModels, includeProcessStateData);
+                customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+                minConsumedPowerThreshold);
         if (mBatteryUsageStatsStore == null) {
             Log.e(TAG, "BatteryUsageStatsStore is unavailable");
             return builder.build();
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
index 4f221b5..9718194 100644
--- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
@@ -244,6 +244,8 @@
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed
                 && mSystemClockUpdateThresholdMillis == that.mSystemClockUpdateThresholdMillis
+                && mSystemClockConfidenceThresholdMillis
+                == that.mSystemClockConfidenceThresholdMillis
                 && mAutoSuggestionLowerBound.equals(that.mAutoSuggestionLowerBound)
                 && mManualSuggestionLowerBound.equals(that.mManualSuggestionLowerBound)
                 && mSuggestionUpperBound.equals(that.mSuggestionUpperBound)
@@ -253,7 +255,8 @@
     @Override
     public int hashCode() {
         int result = Objects.hash(mAutoDetectionSupported, mAutoDetectionEnabledSetting, mUserId,
-                mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mAutoSuggestionLowerBound,
+                mUserConfigAllowed, mSystemClockUpdateThresholdMillis,
+                mSystemClockConfidenceThresholdMillis, mAutoSuggestionLowerBound,
                 mManualSuggestionLowerBound, mSuggestionUpperBound);
         result = 31 * result + Arrays.hashCode(mOriginPriorities);
         return result;
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index fc960d8..c52f8f8 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -22,15 +22,16 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.StateChangeListener;
 
-import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Objects;
 
 /**
@@ -41,14 +42,11 @@
     private static final String LOG_TAG = TimeDetectorService.TAG;
 
     @NonNull private final Handler mHandler;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final PowerManager.WakeLock mWakeLock;
     @NonNull private final AlarmManagerInternal mAlarmManagerInternal;
 
-    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler) {
         mHandler = Objects.requireNonNull(handler);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
 
         PowerManager powerManager = context.getSystemService(PowerManager.class);
         mWakeLock = Objects.requireNonNull(
@@ -59,19 +57,6 @@
     }
 
     @Override
-    public void setConfigurationInternalChangeListener(
-            @NonNull StateChangeListener listener) {
-        StateChangeListener stateChangeListener =
-                () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
-    }
-
-    @Override
-    public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
-    }
-
-    @Override
     public void acquireWakeLock() {
         if (mWakeLock.isHeld()) {
             Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " already held");
@@ -126,8 +111,19 @@
     }
 
     @Override
-    public void dumpDebugLog(@NonNull PrintWriter printWriter) {
-        SystemClockTime.dump(printWriter);
+    public void dumpDebugLog(@NonNull IndentingPrintWriter pw) {
+        long elapsedRealtimeMillis = elapsedRealtimeMillis();
+        pw.printf("elapsedRealtimeMillis()=%s (%s)\n",
+                Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis);
+        long systemClockMillis = systemClockMillis();
+        pw.printf("systemClockMillis()=%s (%s)\n",
+                Instant.ofEpochMilli(systemClockMillis), systemClockMillis);
+        pw.println("systemClockConfidence()=" + systemClockConfidence());
+
+        pw.println("SystemClockTime debug log:");
+        pw.increaseIndent();
+        SystemClockTime.dump(pw);
+        pw.decreaseIndent();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 22f096b..d88f426 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -96,8 +96,8 @@
 
             CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeDetectorService service = new TimeDetectorService(
-                    context, handler, callerIdentityInjector, serviceConfigAccessor,
-                    timeDetectorStrategy, NtpTrustedTime.getInstance(context));
+                    context, handler, callerIdentityInjector, timeDetectorStrategy,
+                    NtpTrustedTime.getInstance(context));
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
@@ -108,7 +108,6 @@
     @NonNull private final Handler mHandler;
     @NonNull private final Context mContext;
     @NonNull private final CallerIdentityInjector mCallerIdentityInjector;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
     @NonNull private final NtpTrustedTime mNtpTrustedTime;
 
@@ -123,20 +122,18 @@
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeDetectorStrategy timeDetectorStrategy,
             @NonNull NtpTrustedTime ntpTrustedTime) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
         mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime);
 
-        // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
-        // the configuration changes for any reason.
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(
-                () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+        // Wire up a change listener so that ITimeDetectorListeners can be notified when the
+        // detector state changes for any reason.
+        mTimeDetectorStrategy.addChangeListener(
+                () -> mHandler.post(this::handleChangeOnHandlerThread));
     }
 
     @Override
@@ -151,10 +148,8 @@
 
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            ConfigurationInternal configurationInternal =
-                    mServiceConfigAccessor.getConfigurationInternal(userId);
-            final boolean bypassUserPolicyCheck = false;
-            return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyCheck);
+            final boolean bypassUserPolicyChecks = false;
+            return mTimeDetectorStrategy.getCapabilitiesAndConfig(userId, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -180,9 +175,9 @@
 
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            final boolean bypassUserPolicyCheck = false;
-            return mServiceConfigAccessor.updateConfiguration(
-                    resolvedUserId, configuration, bypassUserPolicyCheck);
+            final boolean bypassUserPolicyChecks = false;
+            return mTimeDetectorStrategy.updateConfiguration(
+                    resolvedUserId, configuration, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -262,7 +257,7 @@
         }
     }
 
-    private void handleConfigurationInternalChangedOnHandlerThread() {
+    private void handleChangeOnHandlerThread() {
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 11cec66..15c0a80 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,6 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -87,6 +89,48 @@
      */
     boolean confirmTime(@NonNull UnixEpochTime confirmationTime);
 
+    /**
+     * Adds a listener that will be triggered when something changes that could affect the result
+     * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+     * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+     * can monitor for changes to data derived from {@link TimeCapabilitiesAndConfig} and update
+     * the UI accordingly.
+     */
+    void addChangeListener(@NonNull StateChangeListener listener);
+
+    /**
+     * Returns a {@link TimeCapabilitiesAndConfig} object for the specified user.
+     *
+     * <p>The strategy is dependent on device state like current user, settings and device config.
+     * These updates are usually handled asynchronously, so callers should expect some delay between
+     * a change being made directly to services like settings and the strategy becoming aware of
+     * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+     *
+     * @param userId the user ID to retrieve the information for
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    TimeCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+    /**
+     * Updates the configuration properties that control a device's time behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed, {@code false}
+     * otherwise.
+     *
+     * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+     * subsequent calls.
+     *
+     * @param userId the current user ID, supplied to make sure that the asynchronous process
+     *   that happens when users switch is completed when the call is made
+     * @param configuration the configuration changes
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    boolean updateConfiguration(@UserIdInt int userId,
+            @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks);
+
     /** Processes the suggested time from telephony sources. */
     void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion);
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index b293bac..fd35df6 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -30,6 +30,7 @@
 import android.app.time.ExternalTimeSuggestion;
 import android.app.time.TimeCapabilities;
 import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -48,10 +49,10 @@
 import com.android.server.timezonedetector.ReferenceWithHistory;
 import com.android.server.timezonedetector.StateChangeListener;
 
-import java.io.PrintWriter;
-import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -94,6 +95,12 @@
     @NonNull
     private final Environment mEnvironment;
 
+    @NonNull
+    private final ServiceConfigAccessor mServiceConfigAccessor;
+
+    @GuardedBy("this")
+    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
     @GuardedBy("this")
     @NonNull
     private ConfigurationInternal mCurrentConfigurationInternal;
@@ -139,16 +146,6 @@
      */
     public interface Environment {
 
-        /**
-         * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
-         * could affect the content of {@link ConfigurationInternal}.
-         * This is invoked during system server setup.
-         */
-        void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
-
-        /** Returns the {@link ConfigurationInternal} for the current user. */
-        @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
 
@@ -174,16 +171,15 @@
         /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
         void releaseWakeLock();
 
-
         /**
          * Adds a standalone entry to the time debug log.
          */
         void addDebugLogEntry(@NonNull String logMsg);
 
         /**
-         * Dumps the time debug log to the supplied {@link PrintWriter}.
+         * Dumps the time debug log to the supplied {@link IndentingPrintWriter}.
          */
-        void dumpDebugLog(PrintWriter printWriter);
+        void dumpDebugLog(IndentingPrintWriter ipw);
 
         /**
          * Requests that the supplied runnable is invoked asynchronously.
@@ -195,19 +191,23 @@
             @NonNull Context context, @NonNull Handler handler,
             @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
-        TimeDetectorStrategyImpl.Environment environment =
-                new EnvironmentImpl(context, handler, serviceConfigAccessor);
-        return new TimeDetectorStrategyImpl(environment);
+        TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler);
+        return new TimeDetectorStrategyImpl(environment, serviceConfigAccessor);
     }
 
     @VisibleForTesting
-    TimeDetectorStrategyImpl(@NonNull Environment environment) {
+    TimeDetectorStrategyImpl(@NonNull Environment environment,
+            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
         mEnvironment = Objects.requireNonNull(environment);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
 
         synchronized (this) {
-            mEnvironment.setConfigurationInternalChangeListener(
-                    this::handleConfigurationInternalChanged);
-            mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+            // Listen for config and user changes and get an initial snapshot of configuration.
+            StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+            mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+
+            // Initialize mCurrentConfigurationInternal with a starting value.
+            updateCurrentConfigurationInternalIfRequired("TimeDetectorStrategyImpl:");
         }
     }
 
@@ -421,6 +421,57 @@
         }
     }
 
+    @GuardedBy("this")
+    private void notifyStateChangeListenersAsynchronously() {
+        for (StateChangeListener listener : mStateChangeListeners) {
+            // This is queuing asynchronous notification, so no need to surrender the "this" lock.
+            mEnvironment.runAsync(listener::onChange);
+        }
+    }
+
+    @Override
+    public synchronized void addChangeListener(@NonNull StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
+    public synchronized TimeCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal;
+        if (mCurrentConfigurationInternal.getUserId() == userId) {
+            // Use the cached snapshot we have.
+            configurationInternal = mCurrentConfigurationInternal;
+        } else {
+            // This is not a common case: It would be unusual to want the configuration for a user
+            // other than the "current" user, but it is supported because it is trivial to do so.
+            // Unlike the current user config, there's no cached copy to worry about so read it
+            // directly from mServiceConfigAccessor.
+            configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+        }
+        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+    }
+
+    @Override
+    public synchronized boolean updateConfiguration(@UserIdInt int userId,
+            @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks) {
+        // Write-through
+        boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+
+        // The update above will trigger config update listeners asynchronously if they are needed,
+        // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+        // wouldn't see the update. So, handle the cache update and notifications here. When the
+        // async update listener triggers it will find everything already up to date and do nothing.
+        if (updateSuccessful) {
+            String logMsg = "updateConfiguration:"
+                    + " userId=" + userId
+                    + ", configuration=" + configuration
+                    + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+            updateCurrentConfigurationInternalIfRequired(logMsg);
+        }
+        return updateSuccessful;
+    }
+
     @Override
     public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion) {
         // Empty time suggestion means that telephony network connectivity has been lost.
@@ -448,26 +499,49 @@
         doAutoTimeDetection(reason);
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
-        ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        addDebugLogEntry(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
+        String logMsg = "handleConfigurationInternalMaybeChanged:";
+        updateCurrentConfigurationInternalIfRequired(logMsg);
+    }
 
-        boolean autoDetectionEnabled =
-                mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior();
-        // When automatic time detection is enabled we update the system clock instantly if we can.
-        // Conversely, when automatic time detection is disabled we leave the clock as it is.
-        if (autoDetectionEnabled) {
-            String reason = "Auto time zone detection config changed.";
-            doAutoTimeDetection(reason);
-        } else {
-            // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
-            // it should be in future.
-            mLastAutoSystemClockTimeSet = null;
+    @GuardedBy("this")
+    private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+        ConfigurationInternal newCurrentConfigurationInternal =
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        // mCurrentConfigurationInternal is null the first time this method is called.
+        ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+        // If the configuration actually changed, update the cached copy synchronously and do
+        // other necessary house-keeping / (async) listener notifications.
+        if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+            mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+            logMsg = new StringBuilder(logMsg)
+                    .append(" [oldConfiguration=").append(oldCurrentConfigurationInternal)
+                    .append(", newConfiguration=").append(newCurrentConfigurationInternal)
+                    .append("]")
+                    .toString();
+            addDebugLogEntry(logMsg);
+
+            // The configuration and maybe the status changed so notify listeners.
+            notifyStateChangeListenersAsynchronously();
+
+            boolean autoDetectionEnabled =
+                    mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior();
+            // When automatic time detection is enabled we update the system clock instantly if we
+            // can. Conversely, when automatic time detection is disabled we leave the clock as it
+            // is.
+            if (autoDetectionEnabled) {
+                String reason = "Auto time zone detection config changed.";
+                doAutoTimeDetection(reason);
+            } else {
+                // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict
+                // what it should be in future.
+                mLastAutoSystemClockTimeSet = null;
+            }
         }
     }
 
@@ -489,13 +563,11 @@
         ipw.println("[Capabilities="
                 + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
                 + "]");
-        long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
-        ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n",
-                Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis);
-        long systemClockMillis = mEnvironment.systemClockMillis();
-        ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n",
-                Instant.ofEpochMilli(systemClockMillis), systemClockMillis);
-        ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence());
+
+        ipw.println("mEnvironment:");
+        ipw.increaseIndent();
+        mEnvironment.dumpDebugLog(ipw);
+        ipw.decreaseIndent();
 
         ipw.println("Time change log:");
         ipw.increaseIndent(); // level 2
@@ -525,6 +597,11 @@
         ipw.decreaseIndent(); // level 1
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
     @GuardedBy("this")
     private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) {
         UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9e3a611..97b3e32 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -880,6 +880,58 @@
                         false /* forTabletopMode */);
     }
 
+    /**
+     * Overrides persistent horizontal position of the letterboxed app window when horizontal
+     * reachability is enabled.
+     */
+    void setPersistentLetterboxPositionForHorizontalReachability(boolean forBookMode,
+            @LetterboxHorizontalReachabilityPosition int position) {
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                forBookMode, position);
+    }
+
+    /**
+     * Overrides persistent vertical position of the letterboxed app window when vertical
+     * reachability is enabled.
+     */
+    void setPersistentLetterboxPositionForVerticalReachability(boolean forTabletopMode,
+            @LetterboxVerticalReachabilityPosition int position) {
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                forTabletopMode, position);
+    }
+
+    /**
+     * Resets persistent horizontal position of the letterboxed app window when horizontal
+     * reachability
+     * is enabled to default position.
+     */
+    void resetPersistentLetterboxPositionForHorizontalReachability() {
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+                        false /* forBookMode */));
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+                true /* forBookMode */,
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+                        true /* forBookMode */));
+    }
+
+    /**
+     * Resets persistent vertical position of the letterboxed app window when vertical reachability
+     * is
+     * enabled to default position.
+     */
+    void resetPersistentLetterboxPositionForVerticalReachability() {
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+                        false /* forTabletopMode */));
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+                true /* forTabletopMode */,
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+                        true /* forTabletopMode */));
+    }
+
     @LetterboxHorizontalReachabilityPosition
     private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context,
             boolean forBookMode) {
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index ad46770..bd39130 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -559,7 +559,7 @@
             return false;
         }
 
-        final int sourceWindowingMode = source.getWindowingMode();
+        final int sourceWindowingMode = source.getTask().getWindowingMode();
         if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN
                 && sourceWindowingMode != WINDOWING_MODE_FREEFORM) {
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index ceebb27..bfe0553 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -911,6 +911,70 @@
         return 0;
     }
 
+    private int runSetPersistentLetterboxPositionForHorizontalReachability(PrintWriter pw)
+            throws RemoteException {
+        @LetterboxHorizontalReachabilityPosition final int position;
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "left":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+                    break;
+                case "center":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+                    break;
+                case "right":
+                    position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'left', 'center' or 'right' are expected as an argument");
+                    return -1;
+            }
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println(
+                    "Error: 'left', 'center' or 'right' are expected as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+                    false /* IsInBookMode */, position);
+        }
+        return 0;
+    }
+
+    private int runSetPersistentLetterboxPositionForVerticalReachability(PrintWriter pw)
+            throws RemoteException {
+        @LetterboxVerticalReachabilityPosition final int position;
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "top":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+                    break;
+                case "center":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+                    break;
+                case "bottom":
+                    position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: 'top', 'center' or 'bottom' are expected as an argument");
+                    return -1;
+            }
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println(
+                    "Error: 'top', 'center' or 'bottom' are expected as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+                    false /* forTabletopMode */, position);
+        }
+        return 0;
+    }
+
     private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
             throws RemoteException {
         String arg = getNextArg();
@@ -994,6 +1058,12 @@
                 case "--defaultPositionForVerticalReachability":
                     runSetLetterboxDefaultPositionForVerticalReachability(pw);
                     break;
+                case "--persistentPositionForHorizontalReachability":
+                    runSetPersistentLetterboxPositionForHorizontalReachability(pw);
+                    break;
+                case "--persistentPositionForVerticalReachability":
+                    runSetPersistentLetterboxPositionForVerticalReachability(pw);
+                    break;
                 case "--isEducationEnabled":
                     runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
                     break;
@@ -1080,6 +1150,14 @@
                     case "defaultPositionForVerticalReachability":
                         mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
                         break;
+                    case "persistentPositionForHorizontalReachability":
+                        mLetterboxConfiguration
+                                .resetPersistentLetterboxPositionForHorizontalReachability();
+                        break;
+                    case "persistentPositionForVerticalReachability":
+                        mLetterboxConfiguration
+                                .resetPersistentLetterboxPositionForVerticalReachability();
+                        break;
                     case "isEducationEnabled":
                         mLetterboxConfiguration.resetIsEducationEnabled();
                         break;
@@ -1206,6 +1284,8 @@
             mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
             mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
+            mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+            mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
@@ -1233,6 +1313,12 @@
             pw.println("Vertical position multiplier (tabletop mode): "
                     + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
                             true /* isInTabletopMode */));
+            pw.println("Horizontal position multiplier for reachability: "
+                    + mLetterboxConfiguration.getHorizontalMultiplierForReachability(
+                            false /* isInBookMode */));
+            pw.println("Vertical position multiplier for reachability: "
+                    + mLetterboxConfiguration.getVerticalMultiplierForReachability(
+                            false /* isInTabletopMode */));
             pw.println("Aspect ratio: "
                     + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
             pw.println("Default min aspect ratio for unresizable apps: "
@@ -1472,6 +1558,12 @@
         pw.println("      --defaultPositionForVerticalReachability [top|center|bottom]");
         pw.println("        Default position of app window when vertical reachability is.");
         pw.println("        enabled.");
+        pw.println("      --persistentPositionForHorizontalReachability [left|center|right]");
+        pw.println("        Persistent position of app window when horizontal reachability is.");
+        pw.println("        enabled.");
+        pw.println("      --persistentPositionForVerticalReachability [top|center|bottom]");
+        pw.println("        Persistent position of app window when vertical reachability is.");
+        pw.println("        enabled.");
         pw.println("      --isEducationEnabled [true|1|false|0]");
         pw.println("        Whether education is allowed for letterboxed fullscreen apps.");
         pw.println("      --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
@@ -1493,8 +1585,10 @@
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
-        pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+        pw.println("      |isEducationEnabled|defaultPositionMultiplierForHorizontalReachability");
         pw.println("      |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
+        pw.println("      |persistentPositionMultiplierForHorizontalReachability");
+        pw.println("      |persistentPositionMultiplierForVerticalReachability");
         pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d5217c8..77db960 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -41,6 +41,7 @@
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_display_DisplayControl.cpp",
+        "com_android_server_display_SmallAreaDetectionController.cpp",
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
new file mode 100644
index 0000000..b256f16
--- /dev/null
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "SmallAreaDetectionController"
+
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+                                           jfloatArray jthresholds) {
+    if (juids == nullptr || jthresholds == nullptr) return;
+
+    ScopedIntArrayRO uids(env, juids);
+    ScopedFloatArrayRO thresholds(env, jthresholds);
+
+    if (uids.size() != thresholds.size()) {
+        ALOGE("uids size exceeds thresholds size!");
+        return;
+    }
+
+    std::vector<int32_t> uidVector;
+    std::vector<float> thresholdVector;
+    size_t size = uids.size();
+    uidVector.reserve(size);
+    thresholdVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        uidVector.push_back(static_cast<int32_t>(uids[i]));
+        thresholdVector.push_back(static_cast<float>(thresholds[i]));
+    }
+    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+}
+
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+                                                 jfloat threshold) {
+    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+}
+
+static const JNINativeMethod gMethods[] = {
+        {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection},
+        {"nativeSetSmallAreaDetectionThreshold", "(IF)V",
+         (void*)nativeSetSmallAreaDetectionThreshold},
+};
+
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController",
+                                    gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 290ad8d..63c3b4d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,6 +66,7 @@
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env);
 };
 
 using namespace android;
@@ -124,5 +125,6 @@
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
+    register_android_server_display_smallAreaDetectionController(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f3c2de6..b104185 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -679,7 +679,7 @@
     // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
     // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
     // step.
-    static final int DPMS_VERSION = 5;
+    static final int DPMS_VERSION = 6;
 
     static {
         SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -875,8 +875,7 @@
     private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
 
     // TODO(b/265683382) remove the flag after rollout.
-    private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
-    public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
+    public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
 
     // TODO(b/261999445) remove the flag after rollout.
     private static final String HEADLESS_FLAG = "headless";
@@ -23839,10 +23838,7 @@
     }
 
     private static boolean isKeepProfilesRunningFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                KEEP_PROFILES_RUNNING_FLAG,
-                DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+        return DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
     }
 
     private boolean isUnicornFlagEnabled() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 06f11be..913860c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -116,6 +116,19 @@
             currentVersion = 5;
         }
 
+        if (currentVersion == 5) {
+            Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+            // No-op upgrade here:
+            // DevicePolicyData.mEffectiveKeepProfilesRunning is only stored in XML file when it is
+            // different from its default value, otherwise the tag is not written. When loading, if
+            // the tag is missing, the field retains the value previously assigned in the
+            // constructor, which is the default value.
+            // In version 5 the default value was 'true', in version 6 it is 'false', so when
+            // loading XML version 5 we need to initialize the field to 'true' for it to be restored
+            // correctly in case the tag is missing. This is done in loadDataForUser().
+            currentVersion = 6;
+        }
+
         writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
     }
 
@@ -281,6 +294,10 @@
     private DevicePolicyData loadDataForUser(
             int userId, int loadVersion, ComponentName ownerComponent) {
         DevicePolicyData policy = new DevicePolicyData(userId);
+        // See version 5 -> 6 step in upgradePolicy()
+        if (loadVersion == 5 && userId == UserHandle.USER_SYSTEM) {
+            policy.mEffectiveKeepProfilesRunning = true;
+        }
         DevicePolicyData.load(policy,
                 mProvider.makeDevicePoliciesJournaledFile(userId),
                 mProvider.getAdminInfoSupplier(userId),
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index a421db0..8eb1b0b 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -27,6 +27,7 @@
         "mockingservicestests-utils-mockito",
         "platform-compat-test-rules",
         "platform-test-annotations",
+        "service-permission.stubs.system_server",
         "services.core",
         "servicestests-utils",
         "testables",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 50cf169..14586ae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -58,6 +58,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -106,6 +107,7 @@
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -254,10 +256,11 @@
     @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
     @Mock IBinder mMockDisplayToken;
     @Mock SensorManagerInternal mMockSensorManagerInternal;
-
     @Mock SensorManager mSensorManager;
-
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock PackageManagerInternal mMockPackageManagerInternal;
+    @Mock UserManagerInternal mMockUserManagerInternal;
+
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
 
@@ -276,6 +279,10 @@
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
         LocalServices.addService(
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
new file mode 100644
index 0000000..1ce79a5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.os.Process.INVALID_UID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfigInterface;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SmallAreaDetectionControllerTest {
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+    @Mock
+    private UserManagerInternal mMockUserManagerInternal;
+
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
+    private static final String PKG_A = "com.a.b.c";
+    private static final String PKG_B = "com.d.e.f";
+    private static final String PKG_NOT_INSTALLED = "com.not.installed";
+    private static final float THRESHOLD_A = 0.05f;
+    private static final float THRESHOLD_B = 0.07f;
+    private static final int USER_1 = 110;
+    private static final int USER_2 = 111;
+    private static final int UID_A_1 = 11011111;
+    private static final int UID_A_2 = 11111111;
+    private static final int UID_B_1 = 11022222;
+    private static final int UID_B_2 = 11122222;
+
+    @Before
+    public void setup() {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+
+        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
+                INVALID_UID);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
+                INVALID_UID);
+
+        mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
+                new ContextWrapper(ApplicationProvider.getApplicationContext()),
+                DeviceConfigInterface.REAL));
+        doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any());
+    }
+
+    @Test
+    public void testUpdateAllowlist_validProperty() {
+        final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeInvalidRow() {
+        final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_B_1, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeNotInstalledPkg() {
+        final String property =
+                PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_A_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_invalidProperty() {
+        final String property = PKG_A;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any());
+    }
+}
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
new file mode 100644
index 0000000..4785a88
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <keep-profiles-running value="false" />
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991" />
+        <strong-auth-unlock-timeout value="0" />
+        <organization-color value="-16738680" />
+        <active-password value="0" />
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
new file mode 100644
index 0000000..07ec229
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+    <keep-profiles-running value="true" />
+    <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+        <policies flags="991" />
+        <strong-auth-unlock-timeout value="0" />
+        <organization-color value="-16738680" />
+        <active-password value="0" />
+    </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index eb2ee35..d2b921d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -76,7 +76,7 @@
 public class PolicyVersionUpgraderTest extends DpmTestBase {
     // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
     // to the new version.
-    private static final int LATEST_TESTED_VERSION = 5;
+    private static final int LATEST_TESTED_VERSION = 6;
     public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
     public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
     private ComponentName mFakeAdmin;
@@ -313,7 +313,7 @@
     }
 
     @Test
-    public void testEffectiveKeepProfilesRunningSet() throws Exception {
+    public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception {
         writeVersionToXml(4);
 
         final int userId = UserHandle.USER_SYSTEM;
@@ -327,8 +327,109 @@
         Document policies = readPolicies(userId);
         Element keepProfilesRunning = (Element) policies.getDocumentElement()
                 .getElementsByTagName("keep-profiles-running").item(0);
-        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false");
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
     }
+    @Test
+    public void testEffectiveKeepProfilesRunningIsToFalse4To6() throws Exception {
+        writeVersionToXml(4);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
+    /**
+     * Verify correct behaviour when upgrading from Android 13
+     */
+    @Test
+    public void testEffectiveKeepProfilesRunningIsToFalse3To6() throws Exception {
+        writeVersionToXml(3);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningMissingInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningTrueInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies_keep_profiles_running_true.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+        assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+    }
+
+    @Test
+    public void testEffectiveKeepProfilesRunningFalseInV5() throws Exception {
+        writeVersionToXml(5);
+
+        final int userId = UserHandle.USER_SYSTEM;
+        mProvider.mUsers = new int[]{userId};
+        preparePoliciesFile(userId, "device_policies_keep_profiles_running_false.xml");
+
+        mUpgrader.upgradePolicy(6);
+
+        assertThat(readVersionFromXml()).isAtLeast(6);
+
+        Document policies = readPolicies(userId);
+        Element keepProfilesRunning = (Element) policies.getDocumentElement()
+                .getElementsByTagName("keep-profiles-running").item(0);
+
+        // Default value (false) is not serialized.
+        assertThat(keepProfilesRunning).isNull();
+    }
+
 
     @Test
     public void isLatestVersionTested() {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 3135215..a1e1da6 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -222,8 +222,10 @@
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+        final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customPowerComponentNames, includePowerModels, includeProcessStateData);
+                customPowerComponentNames, includePowerModels, includeProcessStateData,
+                minConsumedPowerThreshold);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 266a226..07c486c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -181,7 +181,7 @@
         final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
 
         final BatteryUsageStats sum =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
                         .add(stats1)
                         .add(stats2)
                         .build();
@@ -222,7 +222,7 @@
     @Test
     public void testAdd_customComponentMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -231,7 +231,7 @@
     @Test
     public void testAdd_processStateDataMismatch() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -260,7 +260,7 @@
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
@@ -305,7 +305,7 @@
 
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(customPowerComponentNames, true,
-                        includeProcessStateData);
+                        includeProcessStateData, 0);
         builder.setDischargePercentage(30)
                 .setDischargedPowerRange(1234, 2345)
                 .setStatsStartTimestamp(2000)
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index 93464cd..d9bc74d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -35,7 +35,7 @@
 
     private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
-    private ConfigurationInternal mConfigurationInternal;
+    private ConfigurationInternal mCurrentUserConfigurationInternal;
 
     @Override
     public void addConfigurationInternalChangeListener(StateChangeListener listener) {
@@ -49,21 +49,23 @@
 
     @Override
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mConfigurationInternal;
+        return mCurrentUserConfigurationInternal;
     }
 
     @Override
     public boolean updateConfiguration(
-            @UserIdInt int userID, @NonNull TimeConfiguration requestedChanges,
+            @UserIdInt int userId, @NonNull TimeConfiguration requestedChanges,
             boolean bypassUserPolicyChecks) {
-        assertNotNull(mConfigurationInternal);
+        assertNotNull(mCurrentUserConfigurationInternal);
         assertNotNull(requestedChanges);
 
+        ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
         // old configuration merged with the new if the user has the capability to up the settings.
         // Then, if the configuration changed, the change listener is invoked.
         TimeCapabilitiesAndConfig capabilitiesAndConfig =
-                mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+                toUpdate.createCapabilitiesAndConfig(bypassUserPolicyChecks);
         TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
         TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
         TimeConfiguration newConfiguration =
@@ -73,28 +75,36 @@
         }
 
         if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
-            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
+            mCurrentUserConfigurationInternal = toUpdate.merge(newConfiguration);
 
             // Note: Unlike the real strategy, the listeners are invoked synchronously.
-            simulateConfigurationChangeForTests();
+            notifyConfigurationChange();
         }
         return true;
     }
 
-    void initializeConfiguration(ConfigurationInternal configurationInternal) {
-        mConfigurationInternal = configurationInternal;
+
+    void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
     }
 
-    void simulateConfigurationChangeForTests() {
-        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
-            listener.onChange();
-        }
+    void simulateCurrentUserConfigurationInternalChange(
+            ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
     }
 
     @Override
     public ConfigurationInternal getConfigurationInternal(int userId) {
         assertEquals("Multi-user testing not supported currently",
-                userId, mConfigurationInternal.getUserId());
-        return mConfigurationInternal;
+                userId, mCurrentUserConfigurationInternal.getUserId());
+        return mCurrentUserConfigurationInternal;
+    }
+
+    private void notifyConfigurationChange() {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+            listener.onChange();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
index 87aa272..a7a9c0c 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -18,6 +18,8 @@
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -31,10 +33,20 @@
  * in tests.
  */
 public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor;
+
     // State
     private TimeState mTimeState;
     private NetworkTimeSuggestion mLatestNetworkTimeSuggestion;
 
+    FakeTimeDetectorStrategy() {
+        mFakeServiceConfigAccessor = new FakeServiceConfigAccessor();
+    }
+
+    void initializeConfiguration(ConfigurationInternal configuration) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+    }
+
     @Override
     public TimeState getTimeState() {
         return mTimeState;
@@ -51,6 +63,26 @@
     }
 
     @Override
+    public void addChangeListener(StateChangeListener listener) {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(listener);
+    }
+
+    @Override
+    public TimeCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getConfigurationInternal(userId);
+        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+    }
+
+    @Override
+    public boolean updateConfiguration(int userId, TimeConfiguration configuration,
+            boolean bypassUserPolicyChecks) {
+        return mFakeServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+    }
+
+    @Override
     public void suggestTelephonyTime(TelephonyTimeSuggestion suggestion) {
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
index a0845a6..de5a37b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
@@ -89,7 +89,7 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(testConfig);
 
         TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeDetectorInternal.getCapabilitiesAndConfigForDpm();
@@ -108,7 +108,8 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+                initialConfigurationInternal);
 
         TimeConfiguration timeConfiguration = new TimeConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
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 daa6823..6b2d4b0 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -83,7 +83,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeDetectorStrategy mFakeTimeDetectorStrategySpy;
 
     private NtpTrustedTime mMockNtpTrustedTime;
@@ -101,13 +100,12 @@
         mTestCallerIdentityInjector = new TestCallerIdentityInjector();
         mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
 
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeDetectorStrategySpy = spy(new FakeTimeDetectorStrategy());
         mMockNtpTrustedTime = mock(NtpTrustedTime.class);
 
         mTimeDetectorService = new TimeDetectorService(
                 mMockContext, mTestHandler, mTestCallerIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime);
+                mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime);
     }
 
     @After
@@ -132,14 +130,14 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeDetectorStrategySpy.initializeConfiguration(configuration);
 
         TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeDetectorService.getCapabilitiesAndConfig();
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+        verify(mFakeTimeDetectorStrategySpy).getCapabilitiesAndConfig(expectedUserId, false);
 
         boolean bypassUserPolicyChecks = false;
         TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig =
@@ -174,7 +172,7 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+        mFakeTimeDetectorStrategySpy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 4df21e0..dd58135 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -29,14 +29,22 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.os.TimestampedValue;
+import android.util.IndentingPrintWriter;
 
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
@@ -47,14 +55,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.PrintWriter;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
@@ -120,13 +126,108 @@
                     .build();
 
     private FakeEnvironment mFakeEnvironment;
+    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
+    private TimeDetectorStrategyImpl mTimeDetectorStrategy;
 
     @Before
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
-        mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED);
         mFakeEnvironment.initializeFakeClocks(
                 ARBITRARY_CLOCK_INITIALIZATION_INFO, TIME_CONFIDENCE_LOW);
+
+        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(CONFIG_AUTO_DISABLED);
+
+        mTimeDetectorStrategy = new TimeDetectorStrategyImpl(
+                mFakeEnvironment, mFakeServiceConfigAccessorSpy);
+    }
+
+    @Test
+    public void testChangeListenerBehavior() throws Exception {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        // Report a config change, but not one that actually changes anything.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_DISABLED);
+            assertStateChangeNotificationsSent(stateChangeListener, 0);
+            assertEquals(CONFIG_AUTO_DISABLED,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+            assertEquals(CONFIG_AUTO_ENABLED,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Perform a (current user) update via the strategy.
+        {
+            TimeConfiguration requestedChanges =
+                    new TimeConfiguration.Builder().setAutoDetectionEnabled(false).build();
+            mTimeDetectorStrategy.updateConfiguration(
+                    ARBITRARY_USER_ID, requestedChanges, bypassUserPolicyChecks);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+        }
+    }
+
+    // Current user behavior: the strategy caches and returns the latest configuration.
+    @Test
+    public void testReadAndWriteConfiguration() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        ConfigurationInternal cachedConfigurationInternal =
+                mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+        assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeDetectorStrategy.getCapabilitiesAndConfig(
+                            currentUserConfig.getUserId(), bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+                    currentUserConfig.getUserId());
+
+            TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig =
+                    currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+            assertEquals(expectedCapabilitiesAndConfig.getCapabilities(),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(expectedCapabilitiesAndConfig.getConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+        // the cached copy.
+        {
+            boolean newAutoDetectionEnabled =
+                    !cachedConfigurationInternal.getAutoDetectionEnabledBehavior();
+            TimeConfiguration requestedChanges = new TimeConfiguration.Builder()
+                    .setAutoDetectionEnabled(newAutoDetectionEnabled)
+                    .build();
+            ConfigurationInternal expectedConfigAfterChange =
+                    new ConfigurationInternal.Builder(cachedConfigurationInternal)
+                            .setAutoDetectionEnabledSetting(newAutoDetectionEnabled)
+                            .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(expectedConfigAfterChange,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
     }
 
     @Test
@@ -1939,20 +2040,14 @@
 
         private final List<Runnable> mAsyncRunnables = new ArrayList<>();
 
-        private ConfigurationInternal mConfigurationInternal;
         private boolean mWakeLockAcquired;
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
-        private StateChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
 
-        void initializeConfig(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-        }
-
         public void initializeFakeClocks(
                 TimestampedValue<Instant> timeInfo, @TimeConfidence int timeConfidence) {
             pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
@@ -1960,16 +2055,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(StateChangeListener listener) {
-            mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public void acquireWakeLock() {
             if (mWakeLockAcquired) {
                 fail("Wake lock already acquired");
@@ -2019,7 +2104,7 @@
         }
 
         @Override
-        public void dumpDebugLog(PrintWriter printWriter) {
+        public void dumpDebugLog(IndentingPrintWriter pw) {
             // No-op for tests
         }
 
@@ -2037,11 +2122,6 @@
             mAsyncRunnables.clear();
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
@@ -2095,13 +2175,6 @@
      */
     private class Script {
 
-        private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
-
-        Script() {
-            mFakeEnvironment = new FakeEnvironment();
-            mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
-        }
-
         Script pokeFakeClocks(TimestampedValue<Instant> initialClockTime,
                 @TimeConfidence int timeConfidence) {
             mFakeEnvironment.pokeElapsedRealtimeMillis(initialClockTime.getReferenceTimeMillis());
@@ -2122,7 +2195,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -2167,14 +2241,15 @@
 
         Script simulateAutoTimeDetectionToggle() {
             ConfigurationInternal configurationInternal =
-                    mFakeEnvironment.getCurrentUserConfigurationInternal();
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal();
             boolean autoDetectionEnabledSetting =
                     !configurationInternal.getAutoDetectionEnabledSetting();
             ConfigurationInternal newConfigurationInternal =
                     new ConfigurationInternal.Builder(configurationInternal)
                             .setAutoDetectionEnabledSetting(autoDetectionEnabledSetting)
                             .build();
-            mFakeEnvironment.simulateConfigurationInternalChange(newConfigurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    newConfigurationInternal);
             return this;
         }
 
@@ -2389,4 +2464,12 @@
         return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second)
                 .toInstant(ZoneOffset.UTC);
     }
+
+    private void assertStateChangeNotificationsSent(
+            TestStateChangeListener stateChangeListener, int expectedCount) {
+        // The fake environment needs to be told to run posted work.
+        mFakeEnvironment.runAsyncRunnables();
+
+        stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1ba8f7d..9c754b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1679,9 +1679,10 @@
 
     @Test
     public void testResultCanceledWhenNotAllowedStartingActivity() {
+        final Task task = new TaskBuilder(mSupervisor).build();
         final ActivityStarter starter = prepareStarter(0, false);
         final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
-        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
         targetRecord.resultTo = sourceRecord;
 
         // Abort the activity start and ensure the sourceRecord gets the result (RESULT_CANCELED).
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 06033c7..3fcec96 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -184,7 +184,7 @@
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
         firstPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
-        waitForCompletion(mPersisterQueue);
+        waitForCompletion(firstPersisterQueue);
         final int newPositionForHorizontalReachability =
                 firstPersister.getLetterboxPositionForHorizontalReachability(false);
         final int newPositionForVerticalReachability =
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index e1fc0cf..80e169d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -250,4 +250,42 @@
                 times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose,
                 expected);
     }
+
+    @Test
+    public void test_letterboxPositionWhenReachabilityEnabledIsReset() {
+        // Check that horizontal reachability is set with correct arguments
+        mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                true /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+        // Check that vertical reachability is set with correct arguments
+        mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                true /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+    }
+
+    @Test
+    public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+        // Check that horizontal reachability is set with correct arguments
+        mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                false /* forBookMode */,
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+        // Check that vertical reachability is set with correct arguments
+        mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                false /* forTabletopMode */,
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 739737e..07cfbf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -678,6 +678,39 @@
                 WINDOWING_MODE_FULLSCREEN);
     }
 
+    @Test
+    public void testInheritsSourceTaskWindowingModeWhenActivityIsInDifferentWindowingMode() {
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_PINNED);
+        source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).calculate());
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void testDoesNotInheritsSourceTaskWindowingModeWhenActivityIsInFreeformWindowingMode() {
+        // The activity could end up in different windowing mode state after calling finish()
+        // while the task would still hold the WINDOWING_MODE_PINNED state, or in other words
+        // be still in the Picture in Picture mode.
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        source.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setSource(source).calculate());
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+    }
+
 
     @Test
     public void testKeepsPictureInPictureLaunchModeInOptions() {
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index fe2fe0b..08430f2 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
 
     private static BatteryUsageStats buildBatteryUsageStats() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)