Merge "Rename InputMethodManagerServiceTestBase#mCallingUserId to mUserId" into main
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 000a537..623196b 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -209,7 +209,8 @@
                 POWER_COMPONENT_VIDEO,
                 POWER_COMPONENT_FLASHLIGHT,
                 POWER_COMPONENT_CAMERA,
-                POWER_COMPONENT_GNSS};
+                POWER_COMPONENT_GNSS,
+                POWER_COMPONENT_SENSORS};
         Arrays.sort(supportedPowerComponents);
         SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = IntArray.wrap(supportedPowerComponents);
     };
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cdffea4..c7751e3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3077,7 +3077,7 @@
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
             "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
             "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive",
-            "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity", "state"
+            "tmpwhitelist", "screenwake", "wakeupap", "longwake", "state"
     };
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index d454716..74545a8 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1346,7 +1346,11 @@
                     Slog.w(mTag, "WakeUp was called before the dream was attached.");
                 } else {
                     try {
-                        mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
+                        if (startAndStopDozingInBackground()) {
+                            mDreamManager.finishSelfOneway(mDreamToken, false /*immediate*/);
+                        } else {
+                            mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
+                        }
                     } catch (RemoteException ex) {
                         // system server died
                     }
@@ -1497,7 +1501,11 @@
         if (mFinished || mWaking) {
             Slog.w(mTag, "attach() called after dream already finished");
             try {
-                mDreamManager.finishSelf(dreamToken, true /*immediate*/);
+                if (startAndStopDozingInBackground()) {
+                    mDreamManager.finishSelfOneway(dreamToken, true /*immediate*/);
+                } else {
+                    mDreamManager.finishSelf(dreamToken, true /*immediate*/);
+                }
             } catch (RemoteException ex) {
                 // system server died
             }
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 0861454..8860452 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -90,6 +90,16 @@
 }
 
 flag {
+    name: "update_corner_radius_on_display_changed"
+    namespace: "accessibility"
+    description: "Updates the corner radius to the magnification fullscreen border when the display changes."
+    bug: "335113174"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "hearing_devices_dialog_related_tools"
     namespace: "accessibility"
     description: "Shows the related tools for hearing devices dialog."
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 3c0ac9a..394f8dd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -30,6 +30,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.drawable.GradientDrawable;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.util.Log;
 import android.view.AttachedSurfaceControl;
@@ -49,6 +51,8 @@
 import androidx.annotation.UiThread;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
 import com.android.systemui.util.leak.RotationUtils;
@@ -70,6 +74,7 @@
     private SurfaceControl.Transaction mTransaction;
     private View mFullscreenBorder = null;
     private int mBorderOffset;
+    private int mBorderStoke;
     private final int mDisplayId;
     private static final Region sEmptyRegion = new Region();
     private ValueAnimator mShowHideBorderAnimator;
@@ -86,16 +91,20 @@
         }
     };
     private final long mLongAnimationTimeMs;
+    private final DisplayManager mDisplayManager;
+    private final DisplayManager.DisplayListener mDisplayListener;
+    private String mCurrentDisplayUniqueId;
 
     public FullscreenMagnificationController(
             @UiContext Context context,
             @Main Handler handler,
             @Main Executor executor,
+            DisplayManager displayManager,
             AccessibilityManager accessibilityManager,
             WindowManager windowManager,
             IWindowManager iWindowManager,
             Supplier<SurfaceControlViewHost> scvhSupplier) {
-        this(context, handler, executor, accessibilityManager,
+        this(context, handler, executor, displayManager, accessibilityManager,
                 windowManager, iWindowManager, scvhSupplier,
                 new SurfaceControl.Transaction(), null);
     }
@@ -105,6 +114,7 @@
             @UiContext Context context,
             @Main Handler handler,
             @Main Executor executor,
+            DisplayManager displayManager,
             AccessibilityManager accessibilityManager,
             WindowManager windowManager,
             IWindowManager iWindowManager,
@@ -120,10 +130,7 @@
         mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mTransaction = transaction;
         mScvhSupplier = scvhSupplier;
-        mBorderOffset = mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnifier_border_width_fullscreen_with_offset)
-                - mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnifier_border_width_fullscreen);
+        updateDimensions();
         mDisplayId = mContext.getDisplayId();
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mLongAnimationTimeMs = mContext.getResources().getInteger(
@@ -140,6 +147,31 @@
                 }
             }
         });
+        mCurrentDisplayUniqueId = mContext.getDisplayNoVerify().getUniqueId();
+        mDisplayManager = displayManager;
+        mDisplayListener = new DisplayManager.DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+                // Do nothing
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                // Do nothing
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                final String uniqueId = mContext.getDisplayNoVerify().getUniqueId();
+                if (uniqueId.equals(mCurrentDisplayUniqueId)) {
+                    // Same unique ID means the physical display doesn't change. Early return.
+                    return;
+                }
+
+                mCurrentDisplayUniqueId = uniqueId;
+                applyCornerRadiusToBorder();
+            }
+        };
     }
 
     private ValueAnimator createNullTargetObjectAnimator() {
@@ -180,10 +212,15 @@
         }
         mContext.unregisterComponentCallbacks(this);
 
+
         mShowHideBorderAnimator.reverse();
     }
 
     private void cleanUpBorder() {
+        if (Flags.updateCornerRadiusOnDisplayChanged()) {
+            mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        }
+
         if (mSurfaceControlViewHost != null) {
             mSurfaceControlViewHost.release();
             mSurfaceControlViewHost = null;
@@ -226,6 +263,9 @@
             } catch (Exception e) {
                 Log.w(TAG, "Failed to register rotation watcher", e);
             }
+            if (Flags.updateCornerRadiusOnDisplayChanged()) {
+                mHandler.post(this::applyCornerRadiusToBorder);
+            }
         }
 
         mTransaction
@@ -247,6 +287,9 @@
 
         mAccessibilityManager.attachAccessibilityOverlayToDisplay(
                 mDisplayId, mBorderSurfaceControl);
+        if (Flags.updateCornerRadiusOnDisplayChanged()) {
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+        }
 
         applyTouchableRegion();
     }
@@ -304,6 +347,11 @@
             final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
             final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
             mSurfaceControlViewHost.relayout(newWidth, newHeight);
+            if (Flags.updateCornerRadiusOnDisplayChanged()) {
+                // Recenter the border
+                mTransaction.setPosition(
+                        mBorderSurfaceControl, -mBorderOffset, -mBorderOffset).apply();
+            }
         }
 
         // Rotating from Landscape to ReverseLandscape will not trigger the config changes in
@@ -352,6 +400,22 @@
                 R.dimen.magnifier_border_width_fullscreen_with_offset)
                 - mContext.getResources().getDimensionPixelSize(
                         R.dimen.magnifier_border_width_fullscreen);
+        mBorderStoke = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen_with_offset);
+    }
+
+    private void applyCornerRadiusToBorder() {
+        if (!isActivated()) {
+            return;
+        }
+
+        float cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+        GradientDrawable backgroundDrawable = (GradientDrawable) mFullscreenBorder.getBackground();
+        backgroundDrawable.setStroke(
+                mBorderStoke,
+                mContext.getResources().getColor(
+                        R.color.magnification_border_color, mContext.getTheme()));
+        backgroundDrawable.setCornerRadius(cornerRadius);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index e9c9bc7..93c4630 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -149,6 +149,7 @@
         private final Context mContext;
         private final Handler mHandler;
         private final Executor mExecutor;
+        private final DisplayManager mDisplayManager;
         private final IWindowManager mIWindowManager;
 
         FullscreenMagnificationControllerSupplier(Context context,
@@ -159,6 +160,7 @@
             mContext = context;
             mHandler = handler;
             mExecutor = executor;
+            mDisplayManager = displayManager;
             mIWindowManager = iWindowManager;
         }
 
@@ -173,6 +175,7 @@
                     windowContext,
                     mHandler,
                     mExecutor,
+                    mDisplayManager,
                     windowContext.getSystemService(AccessibilityManager.class),
                     windowContext.getSystemService(WindowManager.class),
                     mIWindowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index cbd535b..530ae15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -25,6 +25,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -36,7 +37,10 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
@@ -54,6 +58,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
 
@@ -61,6 +66,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -76,6 +82,12 @@
     private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
     private static final long ANIMATION_TIMEOUT_MS =
             5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
+
+    private static final String UNIQUE_DISPLAY_ID_PRIMARY = "000";
+    private static final String UNIQUE_DISPLAY_ID_SECONDARY = "111";
+    private static final int CORNER_RADIUS_PRIMARY = 10;
+    private static final int CORNER_RADIUS_SECONDARY = 20;
+
     private FullscreenMagnificationController mFullscreenMagnificationController;
     private SurfaceControlViewHost mSurfaceControlViewHost;
     private ValueAnimator mShowHideBorderAnimator;
@@ -83,10 +95,35 @@
     private TestableWindowManager mWindowManager;
     @Mock
     private IWindowManager mIWindowManager;
+    @Mock
+    private DisplayManager mDisplayManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext = spy(mContext);
+        Display display = mock(Display.class);
+        when(display.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_PRIMARY);
+        when(mContext.getDisplayNoVerify()).thenReturn(display);
+
+        // Override the resources to Display Primary
+        mContext.getOrCreateTestableResources()
+                .addOverride(
+                        com.android.internal.R.dimen.rounded_corner_radius,
+                        CORNER_RADIUS_PRIMARY);
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.dimen.rounded_corner_radius_adjustment, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.dimen.rounded_corner_radius_top, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(
+                        com.android.internal.R.dimen.rounded_corner_radius_top_adjustment, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(
+                        com.android.internal.R.dimen.rounded_corner_radius_bottom_adjustment, 0);
+
         getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
                 spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
                         new InputTransferToken(), "FullscreenMagnification")));
@@ -101,6 +138,7 @@
                 mContext,
                 mContext.getMainThreadHandler(),
                 mContext.getMainExecutor(),
+                mDisplayManager,
                 mContext.getSystemService(AccessibilityManager.class),
                 mContext.getSystemService(WindowManager.class),
                 mIWindowManager,
@@ -259,6 +297,87 @@
         verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
     }
 
+    @EnableFlags(Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED)
+    @Test
+    public void enableFullscreenMagnification_applyPrimaryCornerRadius()
+            throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch animationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animationEndLatch.countDown();
+            }
+        });
+
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for animation to be finished")
+                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
+
+        // Verify the initial corner radius is applied
+        GradientDrawable backgroundDrawable =
+                (GradientDrawable) mSurfaceControlViewHost.getView().getBackground();
+        assertThat(backgroundDrawable.getCornerRadius()).isEqualTo(CORNER_RADIUS_PRIMARY);
+    }
+
+    @EnableFlags(Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED)
+    @Test
+    public void onDisplayChanged_updateCornerRadiusToSecondary() throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch animationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animationEndLatch.countDown();
+            }
+        });
+
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for animation to be finished")
+                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
+
+        ArgumentCaptor<DisplayManager.DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayManager.DisplayListener.class);
+        verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), any());
+
+        Display newDisplay = mock(Display.class);
+        when(newDisplay.getUniqueId()).thenReturn(UNIQUE_DISPLAY_ID_SECONDARY);
+        when(mContext.getDisplayNoVerify()).thenReturn(newDisplay);
+        // Override the resources to Display Secondary
+        mContext.getOrCreateTestableResources()
+                .removeOverride(com.android.internal.R.dimen.rounded_corner_radius);
+        mContext.getOrCreateTestableResources()
+                .addOverride(
+                        com.android.internal.R.dimen.rounded_corner_radius,
+                        CORNER_RADIUS_SECONDARY);
+        getInstrumentation().runOnMainSync(() ->
+                displayListenerCaptor.getValue().onDisplayChanged(Display.DEFAULT_DISPLAY));
+        waitForIdleSync();
+        // Verify the corner radius is updated
+        GradientDrawable backgroundDrawable2 =
+                (GradientDrawable) mSurfaceControlViewHost.getView().getBackground();
+        assertThat(backgroundDrawable2.getCornerRadius()).isEqualTo(CORNER_RADIUS_SECONDARY);
+    }
+
+
     private ValueAnimator newNullTargetObjectAnimator() {
         final ValueAnimator animator =
                 ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e46ab8f..03fbfd37 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -144,6 +144,7 @@
 import com.android.server.power.stats.PowerStatsStore;
 import com.android.server.power.stats.PowerStatsUidResolver;
 import com.android.server.power.stats.ScreenPowerStatsProcessor;
+import com.android.server.power.stats.SensorPowerStatsProcessor;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.server.power.stats.VideoPowerStatsProcessor;
 import com.android.server.power.stats.WifiPowerStatsProcessor;
@@ -595,6 +596,17 @@
                 .setProcessor(
                         new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
 
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(new SensorPowerStatsProcessor(
+                        () -> mContext.getSystemService(SensorManager.class)));
+
         config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new)
                 .trackDeviceStates(
                         AggregatedPowerStatsConfig.STATE_POWER,
@@ -706,6 +718,10 @@
                 BatteryConsumer.POWER_COMPONENT_GNSS,
                 Flags.streamlinedMiscBatteryStats());
 
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+                BatteryConsumer.POWER_COMPONENT_SENSORS,
+                Flags.streamlinedMiscBatteryStats());
+
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
                 Flags.streamlinedMiscBatteryStats());
         mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a1dffc6..9757582 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1452,6 +1452,13 @@
             }
 
             if (!ArrayUtils.isEmpty(after.splitNames)) {
+                if (beforeSplitNames.length != beforeSplitRevisionCodes.length) {
+                    throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                            "Current split names and the split revision codes are not 1:1 mapping."
+                                    + "This indicates that the package info data has been"
+                                    + " corrupted.");
+                }
+
                 for (int i = 0; i < after.splitNames.length; i++) {
                     final String splitName = after.splitNames[i];
                     final int j = ArrayUtils.indexOf(beforeSplitNames, splitName);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9177e2b..b7dfd8d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4489,10 +4489,24 @@
         String splitName = parser.getAttributeValue(null, ATTR_NAME);
         int splitRevision = parser.getAttributeInt(null, ATTR_VERSION, -1);
         if (splitName != null && splitRevision >= 0) {
+            final int beforeSplitNamesLength = outPs.getSplitNames().length;
+            // If the split name already exists in the outPs#getSplitNames, don't add it
+            // into the array and update its revision code below
             outPs.setSplitNames(ArrayUtils.appendElement(String.class,
                     outPs.getSplitNames(), splitName));
-            outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
-                    outPs.getSplitRevisionCodes(), splitRevision));
+
+            // If the same split name has already been added before, update the latest
+            // revision code
+            final int afterSplitNamesLength = outPs.getSplitNames().length;
+            if (beforeSplitNamesLength == afterSplitNamesLength) {
+                final int index = ArrayUtils.indexOf(outPs.getSplitNames(), splitName);
+                final int[] splitRevisionCodes = outPs.getSplitRevisionCodes();
+                splitRevisionCodes[index] = splitRevision;
+                outPs.setSplitRevisionCodes(splitRevisionCodes);
+            } else {
+                outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
+                        outPs.getSplitRevisionCodes(), splitRevision, /* allowDuplicates= */ true));
+            }
         }
 
         XmlUtils.skipCurrentTag(parser);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index c4b37c69..143b3ff 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -5431,8 +5431,6 @@
         }
     }
 
-    int mSensorNesting;
-
     @GuardedBy("this")
     public void noteStartSensorLocked(int uid, int sensor) {
         noteStartSensorLocked(uid, sensor, mClock.elapsedRealtime(), mClock.uptimeMillis());
@@ -5441,11 +5439,8 @@
     @GuardedBy("this")
     public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
-        if (mSensorNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_SENSOR_ON_FLAG);
-        }
-        mSensorNesting++;
+        mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+                HistoryItem.STATE_SENSOR_ON_FLAG, uid, "sensor:0x" + Integer.toHexString(sensor));
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteStartSensor(sensor, elapsedRealtimeMs);
     }
@@ -5458,11 +5453,8 @@
     @GuardedBy("this")
     public void noteStopSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
-        mSensorNesting--;
-        if (mSensorNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_SENSOR_ON_FLAG);
-        }
+        mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+                HistoryItem.STATE_SENSOR_ON_FLAG, uid, "sensor:0x" + Integer.toHexString(sensor));
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteStopSensor(sensor, elapsedRealtimeMs);
     }
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 a5e4cf5..b308f38 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -93,8 +93,10 @@
                 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) {
                     mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile));
                 }
-                mPowerCalculators.add(new SensorPowerCalculator(
-                        mContext.getSystemService(SensorManager.class)));
+                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SENSORS)) {
+                    mPowerCalculators.add(new SensorPowerCalculator(
+                            mContext.getSystemService(SensorManager.class)));
+                }
                 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
                     mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
                 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 7d7b3c2..c81c7ff 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -257,8 +257,8 @@
             for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
                 deviceStateEstimations.get(i).intermediates = null;
             }
-            for (int i = deviceStateEstimations.size() - 1; i >= 0; i--) {
-                deviceStateEstimations.get(i).intermediates = null;
+            for (int i = combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+                combinedDeviceStateEstimations.get(i).intermediates = null;
             }
             for (int i = uidStateEstimates.size() - 1; i >= 0; i--) {
                 UidStateEstimate uidStateEstimate = uidStateEstimates.get(i);
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
index e203e4a..908c751 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
@@ -119,6 +119,7 @@
         if (!uids.isEmpty()) {
             computeUidPowerEstimates(stats, uids);
         }
+        mPlan.resetIntermediates();
     }
 
     private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
new file mode 100644
index 0000000..e66cd39
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+public class SensorPowerStatsLayout extends PowerStatsLayout {
+    private static final String TAG = "SensorPowerStatsLayout";
+    private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dsh";
+    private static final String EXTRA_UID_SENSOR_POSITIONS = "usp";
+
+    private final SparseIntArray mSensorPositions = new SparseIntArray();
+
+    void addUidSensorSection(int handle, String label) {
+        mSensorPositions.put(handle, addUidSection(1, label, FLAG_OPTIONAL));
+    }
+
+    /**
+     * Returns the position in the uid stats array of the duration element corresponding
+     * to the specified sensor identified by its handle.
+     */
+    public int getUidSensorDurationPosition(int handle) {
+        return mSensorPositions.get(handle, UNSUPPORTED);
+    }
+
+    /**
+     * Adds the specified duration to the accumulated timer for the specified sensor.
+     */
+    public void addUidSensorDuration(long[] stats, int handle, long durationMs) {
+        int position = mSensorPositions.get(handle, UNSUPPORTED);
+        if (position == UNSUPPORTED) {
+            Slog.e(TAG, "Unknown sensor: " + handle);
+            return;
+        }
+        stats[position] += durationMs;
+    }
+
+    @Override
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+
+        int[] handlers = new int[mSensorPositions.size()];
+        int[] uidDurationPositions = new int[mSensorPositions.size()];
+
+        for (int i = 0; i < mSensorPositions.size(); i++) {
+            handlers[i] = mSensorPositions.keyAt(i);
+            uidDurationPositions[i] = mSensorPositions.valueAt(i);
+        }
+
+        extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers);
+        extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
+    }
+
+    @Override
+    public void fromExtras(PersistableBundle extras) {
+        super.fromExtras(extras);
+
+        int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES);
+        int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS);
+
+        for (int i = 0; i < handlers.length; i++) {
+            mSensorPositions.put(handlers[i], uidDurationPositions[i]);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
new file mode 100644
index 0000000..5bd3288
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class SensorPowerStatsProcessor extends PowerStatsProcessor {
+    private static final String TAG = "SensorPowerStatsProcessor";
+    private static final String ANDROID_SENSOR_TYPE_PREFIX = "android.sensor.";
+
+    private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+    private static final String SENSOR_EVENT_TAG_PREFIX = "sensor:0x";
+    private final Supplier<SensorManager> mSensorManagerSupplier;
+
+    private static final long INITIAL_TIMESTAMP = -1;
+    private SensorManager mSensorManager;
+    private SensorPowerStatsLayout mStatsLayout;
+    private PowerStats mPowerStats;
+    private boolean mIsInitialized;
+    private PowerStats.Descriptor mDescriptor;
+    private long mLastUpdateTimestamp;
+    private PowerEstimationPlan mPlan;
+
+    private static class SensorState {
+        public int sensorHandle;
+        public boolean stateOn;
+        public int uid;
+        public long startTime = INITIAL_TIMESTAMP;
+    }
+
+    private static class Intermediates {
+        public double power;
+    }
+
+    private final SparseArray<SensorState> mSensorStates = new SparseArray<>();
+    private long[] mTmpDeviceStatsArray;
+    private long[] mTmpUidStatsArray;
+
+    public SensorPowerStatsProcessor(Supplier<SensorManager> sensorManagerSupplier) {
+        mSensorManagerSupplier = sensorManagerSupplier;
+    }
+
+    private boolean ensureInitialized() {
+        if (mIsInitialized) {
+            return true;
+        }
+
+        mSensorManager = mSensorManagerSupplier.get();
+        if (mSensorManager == null) {
+            return false;
+        }
+
+        mStatsLayout = new SensorPowerStatsLayout();
+        List<Sensor> sensorList = new ArrayList<>(mSensorManager.getSensorList(Sensor.TYPE_ALL));
+        sensorList.sort(Comparator.comparingInt(Sensor::getId));
+        for (int i = 0; i < sensorList.size(); i++) {
+            Sensor sensor = sensorList.get(i);
+            String label = makeLabel(sensor, sensorList);
+            mStatsLayout.addUidSensorSection(sensor.getHandle(), label);
+        }
+        mStatsLayout.addUidSectionPowerEstimate();
+        mStatsLayout.addDeviceSectionPowerEstimate();
+
+        PersistableBundle extras = new PersistableBundle();
+        mStatsLayout.toExtras(extras);
+        mDescriptor = new PowerStats.Descriptor(
+                BatteryConsumer.POWER_COMPONENT_SENSORS, mStatsLayout.getDeviceStatsArrayLength(),
+                null, 0, mStatsLayout.getUidStatsArrayLength(),
+                extras);
+
+        mPowerStats = new PowerStats(mDescriptor);
+        mTmpUidStatsArray = new long[mDescriptor.uidStatsArrayLength];
+        mTmpDeviceStatsArray = new long[mDescriptor.statsArrayLength];
+
+        mIsInitialized = true;
+        return true;
+    }
+
+    private String makeLabel(Sensor sensor, List<Sensor> sensorList) {
+        int type = sensor.getType();
+        String label = sensor.getStringType();
+
+        boolean isSingleton = true;
+        for (int i = sensorList.size() - 1; i >= 0; i--) {
+            Sensor s = sensorList.get(i);
+            if (s == sensor) {
+                continue;
+            }
+            if (s.getType() == type) {
+                isSingleton = false;
+                break;
+            }
+        }
+        if (!isSingleton) {
+            StringBuilder sb = new StringBuilder(label).append('.');
+            if (sensor.getId() > 0) { // 0 and -1 are reserved
+                sb.append(sensor.getId());
+            } else {
+                sb.append(sensor.getName());
+            }
+            label = sb.toString();
+        }
+        if (label.startsWith(ANDROID_SENSOR_TYPE_PREFIX)) {
+            label = label.substring(ANDROID_SENSOR_TYPE_PREFIX.length());
+        }
+        return label.replace(' ', '_');
+    }
+
+    @Override
+    void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+        if (!ensureInitialized()) {
+            return;
+        }
+
+        // Establish a baseline at the beginning of an accumulation pass
+        mLastUpdateTimestamp = timestampMs;
+        flushPowerStats(stats, timestampMs);
+    }
+
+    @Override
+    void noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item) {
+        if (!mIsInitialized) {
+            return;
+        }
+
+        if (item.eventTag == null || item.eventTag.string == null
+                || !item.eventTag.string.startsWith(SENSOR_EVENT_TAG_PREFIX)) {
+            return;
+        }
+
+        int sensorHandle;
+        try {
+            sensorHandle = Integer.parseInt(item.eventTag.string, SENSOR_EVENT_TAG_PREFIX.length(),
+                    item.eventTag.string.length(), 16);
+        } catch (NumberFormatException e) {
+            Slog.wtf(TAG, "Bad format of event tag: " + item.eventTag.string);
+            return;
+        }
+
+        SensorState sensor = mSensorStates.get(sensorHandle);
+        if (sensor == null) {
+            sensor = new SensorState();
+            sensor.sensorHandle = sensorHandle;
+            mSensorStates.put(sensorHandle, sensor);
+        }
+
+        int uid = item.eventTag.uid;
+        boolean sensorOn = (item.states & BatteryStats.HistoryItem.STATE_SENSOR_ON_FLAG) != 0;
+        if (sensorOn) {
+            if (!sensor.stateOn) {
+                sensor.stateOn = true;
+                sensor.uid = uid;
+                sensor.startTime = item.time;
+            } else if (sensor.uid != uid) {
+                recordUsageDuration(sensor, item.time);
+                sensor.uid = uid;
+            }
+        } else {
+            if (sensor.stateOn) {
+                recordUsageDuration(sensor, item.time);
+                sensor.stateOn = false;
+            }
+        }
+    }
+
+    @Override
+    void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+        if (!mIsInitialized) {
+            return;
+        }
+
+        for (int i = mSensorStates.size() - 1; i >= 0; i--) {
+            SensorState sensor = mSensorStates.valueAt(i);
+            if (sensor.stateOn) {
+                recordUsageDuration(sensor, timestampMs);
+            }
+        }
+        flushPowerStats(stats, timestampMs);
+
+        if (mPlan == null) {
+            mPlan = new PowerEstimationPlan(stats.getConfig());
+        }
+
+        List<Integer> uids = new ArrayList<>();
+        stats.collectUids(uids);
+
+        computeUidPowerEstimates(stats, uids);
+        computeDevicePowerEstimates(stats);
+
+        mPlan.resetIntermediates();
+    }
+
+    protected void recordUsageDuration(SensorState sensorState, long time) {
+        long durationMs = Math.max(0, time - sensorState.startTime);
+        if (durationMs > 0) {
+            long[] uidStats = mPowerStats.uidStats.get(sensorState.uid);
+            if (uidStats == null) {
+                uidStats = new long[mDescriptor.uidStatsArrayLength];
+                mPowerStats.uidStats.put(sensorState.uid, uidStats);
+            }
+            mStatsLayout.addUidSensorDuration(uidStats, sensorState.sensorHandle, durationMs);
+        }
+        sensorState.startTime = time;
+    }
+
+    private void flushPowerStats(PowerComponentAggregatedPowerStats stats, long timestamp) {
+        mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
+        stats.addPowerStats(mPowerStats, timestamp);
+
+        Arrays.fill(mPowerStats.stats, 0);
+        mPowerStats.uidStats.clear();
+        mLastUpdateTimestamp = timestamp;
+    }
+
+    private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+            List<Integer> uids) {
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        int[] uidSensorDurationPositions = new int[sensorList.size()];
+        double[] sensorPower = new double[sensorList.size()];
+        for (int i = sensorList.size() - 1; i >= 0; i--) {
+            Sensor sensor = sensorList.get(i);
+            uidSensorDurationPositions[i] =
+                    mStatsLayout.getUidSensorDurationPosition(sensor.getHandle());
+            sensorPower[i] = sensor.getPower() / MILLIS_IN_HOUR;
+        }
+
+        for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+            UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+            List<UidStateProportionalEstimate> proportionalEstimates =
+                    uidStateEstimate.proportionalEstimates;
+            for (int j = proportionalEstimates.size() - 1; j >= 0; j--) {
+                UidStateProportionalEstimate proportionalEstimate = proportionalEstimates.get(j);
+                for (int k = uids.size() - 1; k >= 0; k--) {
+                    int uid = uids.get(k);
+                    if (!stats.getUidStats(mTmpUidStatsArray, uid,
+                            proportionalEstimate.stateValues)) {
+                        continue;
+                    }
+                    double power = 0;
+                    for (int m = 0; m < uidSensorDurationPositions.length; m++) {
+                        int position = uidSensorDurationPositions[m];
+                        if (position == PowerStatsLayout.UNSUPPORTED
+                                || mTmpUidStatsArray[position] == 0) {
+                            continue;
+                        }
+                        power += sensorPower[m] * mTmpUidStatsArray[position];
+                    }
+                    if (power == 0) {
+                        continue;
+                    }
+
+                    mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+                    stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+
+                    Intermediates intermediates = (Intermediates) uidStateEstimate
+                            .combinedDeviceStateEstimate.intermediates;
+                    if (intermediates == null) {
+                        intermediates = new Intermediates();
+                        uidStateEstimate.combinedDeviceStateEstimate.intermediates = intermediates;
+                    }
+                    intermediates.power += power;
+                }
+            }
+        }
+    }
+
+    private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+        for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+            CombinedDeviceStateEstimate estimation =
+                    mPlan.combinedDeviceStateEstimations.get(i);
+            if (estimation.intermediates == null) {
+                continue;
+            }
+
+            if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+                continue;
+            }
+
+            mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+                    ((Intermediates) estimation.intermediates).power);
+            stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 444097a..291eab1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -325,11 +325,6 @@
                 : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
     }
 
-    float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
-        return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
-                .getFixedOrientationLetterboxAspectRatio(parentConfiguration);
-    }
-
     boolean isLetterboxEducationEnabled() {
         return mAppCompatConfiguration.getIsEducationEnabled();
     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceUtilsTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceUtilsTest.java
new file mode 100644
index 0000000..4a2396d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceUtilsTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 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.pm;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageInfoLite;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.UUID;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerServiceUtilsTest {
+
+    private static final String PACKAGE_NAME = "com.android.app";
+    private static final File CODE_PATH =
+            InstrumentationRegistry.getInstrumentation().getContext().getFilesDir();
+
+    @Test
+    public void testCheckDowngrade_packageSetting_versionCodeSmaller_throwException()
+            throws Exception {
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(2);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_baseRevisionCodeSmaller_throwException()
+            throws Exception {
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setBaseRevisionCode(2);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.baseRevisionCode = 1;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_splitArraySizeIsDifferent_throwException()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne };
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionTwo };
+
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setSplitNames(splitNames);
+        before.setSplitRevisionCodes(beforeSplitRevisionCodes);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_splitRevisionCodeSmaller_throwException()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionTwo, revisionThree};
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionTwo };
+
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setSplitNames(splitNames);
+        before.setSplitRevisionCodes(beforeSplitRevisionCodes);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_sameSplitNameRevisionsBigger()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne, revisionTwo};
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionThree };
+
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setSplitNames(splitNames);
+        before.setSplitRevisionCodes(beforeSplitRevisionCodes);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_hasDifferentSplitNames() throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] beforeSplitNames = new String[] { splitOne, splitTwo };
+        final String[] afterSplitNames = new String[] { splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne, revisionTwo};
+        final int[] afterSplitRevisionCodes = new int[] { revisionThree };
+
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setSplitNames(beforeSplitNames);
+        before.setSplitRevisionCodes(beforeSplitRevisionCodes);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = afterSplitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    @Test
+    public void testCheckDowngrade_packageSetting_newSplitName() throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final String[] beforeSplitNames = new String[] { splitOne };
+        final String[] afterSplitNames = new String[] { splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionTwo };
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne };
+
+        final PackageSetting before = createPackageSetting();
+        before.setLongVersionCode(1);
+        before.setSplitNames(beforeSplitNames);
+        before.setSplitRevisionCodes(beforeSplitRevisionCodes);
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = afterSplitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_versionCodeSmaller_throwException()
+            throws Exception {
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME).hideAsParsed()
+                .setVersionCode(2).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_baseRevisionCodeSmaller_throwException()
+            throws Exception {
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME).setBaseRevisionCode(2)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.baseRevisionCode = 1;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_splitArraySizeIsDifferent_throwException()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne };
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionTwo };
+
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME)
+                .asSplit(splitNames, /* splitCodePaths= */ null,
+                        beforeSplitRevisionCodes, /* splitDependencies= */ null)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_splitRevisionCodeSmaller_throwException()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionTwo, revisionThree};
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionTwo };
+
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME)
+                .asSplit(splitNames, /* splitCodePaths= */ null,
+                        beforeSplitRevisionCodes, /* splitDependencies= */ null)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        assertThrows(PackageManagerException.class,
+                () -> PackageManagerServiceUtils.checkDowngrade(before, after));
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_sameSplitNameRevisionsBigger()
+            throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] splitNames = new String[] { splitOne, splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne, revisionTwo};
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne, revisionThree };
+
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME)
+                .asSplit(splitNames, /* splitCodePaths= */ null,
+                        beforeSplitRevisionCodes, /* splitDependencies= */ null)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = splitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_hasDifferentSplitNames() throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final int revisionThree = 360;
+        final String[] beforeSplitNames = new String[] { splitOne, splitTwo };
+        final String[] afterSplitNames = new String[] { splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionOne, revisionTwo};
+        final int[] afterSplitRevisionCodes = new int[] { revisionThree };
+
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME)
+                .asSplit(beforeSplitNames, /* splitCodePaths= */ null,
+                        beforeSplitRevisionCodes, /* splitDependencies= */ null)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = afterSplitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    @Test
+    public void testCheckDowngrade_androidPackage_newSplitName() throws Exception {
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        final String[] beforeSplitNames = new String[] { splitOne };
+        final String[] afterSplitNames = new String[] { splitTwo };
+        final int[] beforeSplitRevisionCodes = new int[] { revisionTwo };
+        final int[] afterSplitRevisionCodes = new int[] { revisionOne };
+
+        final AndroidPackage before = PackageImpl.forTesting(PACKAGE_NAME)
+                .asSplit(beforeSplitNames, /* splitCodePaths= */ null,
+                        beforeSplitRevisionCodes, /* splitDependencies= */ null)
+                .hideAsParsed().setVersionCode(1).hideAsFinal();
+        final PackageInfoLite after = new PackageInfoLite();
+        after.versionCode = 1;
+        after.splitNames = afterSplitNames;
+        after.splitRevisionCodes = afterSplitRevisionCodes;
+
+        PackageManagerServiceUtils.checkDowngrade(before, after);
+    }
+
+    private PackageSetting createPackageSetting() {
+        return new PackageSetting(PACKAGE_NAME, PACKAGE_NAME, CODE_PATH, /* pkgFlags= */ 0,
+                /* privateFlags= */ 0 , UUID.randomUUID());
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index dec4634..d7af443 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1092,7 +1092,7 @@
     }
 
     @Test
-    public void testNoPkg_writeReadSplitVersions() {
+    public void testNoPkgDifferentRevisions_writeReadSplitVersions() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
         packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
@@ -1117,6 +1117,54 @@
     }
 
     @Test
+    public void testNoPkgSameRevisions_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String splitOne = "one";
+        final String splitTwo = "two";
+        final int revisionOne = 311;
+        packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionOne});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+        assertThat(resultSetting.getSplitNames()[1], is(splitTwo));
+        assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionOne));
+        assertThat(resultSetting.getSplitRevisionCodes()[1], is(revisionOne));
+    }
+
+    @Test
+    public void testNoPkgSameSplitNames_writeReadSplitVersions() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+        final String splitOne = "one";
+        final int revisionOne = 311;
+        final int revisionTwo = 330;
+        packageSetting.setSplitNames(new String[] { splitOne, splitOne});
+        packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+        assertThat(resultSetting.getSplitNames().length, is(1));
+        assertThat(resultSetting.getSplitRevisionCodes().length, is(1));
+        assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+        assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionTwo));
+    }
+
+    @Test
     public void testWriteReadArchiveState() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
new file mode 100644
index 0000000..7000487
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class SensorPowerStatsProcessorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Rule(order = 1)
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .initMeasuredEnergyStatsLocked();
+
+    private static final double PRECISION = 0.00001;
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+    private static final int SENSOR_HANDLE_1 = 77;
+    private static final int SENSOR_HANDLE_2 = 88;
+    private static final int SENSOR_HANDLE_3 = 99;
+
+    @Mock
+    private SensorManager mSensorManager;
+
+    private MonotonicClock mMonotonicClock;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+        Sensor sensor1 = createSensor(SENSOR_HANDLE_1, Sensor.TYPE_STEP_COUNTER,
+                Sensor.STRING_TYPE_STEP_COUNTER, "dancing", 100);
+        Sensor sensor2 = createSensor(SENSOR_HANDLE_2, Sensor.TYPE_MOTION_DETECT,
+                "com.example", "tango", 200);
+        Sensor sensor3 = createSensor(SENSOR_HANDLE_3, Sensor.TYPE_MOTION_DETECT,
+                "com.example", "waltz", 300);
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(
+                List.of(sensor1, sensor2, sensor3));
+    }
+
+    @Test
+    public void testPowerEstimation() {
+        SensorPowerStatsProcessor processor = new SensorPowerStatsProcessor(() -> mSensorManager);
+
+        PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+        processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1, SENSOR_HANDLE_1));
+
+        // Turn the screen off after 2.5 seconds
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+        processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1, SENSOR_HANDLE_1));
+        processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2, SENSOR_HANDLE_1));
+        processor.noteStateChange(stats, buildHistoryItem(8000, true, APP_UID2, SENSOR_HANDLE_2));
+        processor.noteStateChange(stats, buildHistoryItem(9000, false, APP_UID2, SENSOR_HANDLE_1));
+
+        processor.finish(stats, 10000);
+
+        PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+        SensorPowerStatsLayout statsLayout = new SensorPowerStatsLayout();
+        statsLayout.fromExtras(descriptor.extras);
+
+        String dump = stats.toString();
+        assertThat(dump).contains(" step_counter: ");
+        assertThat(dump).contains(" com.example.tango: ");
+
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+
+        // For UID1:
+        // SENSOR1 was on for 6000 ms.
+        //   Estimated power: 6000 * 100 = 0.167 mAh
+        //     split between three different states
+        //          fg screen-on: 6000 * 2500/10000
+        //          bg screen-off: 6000 * 2500/10000
+        //          fgs screen-off: 6000 * 5000/10000
+        double expectedPower1 = 0.166666;
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 10000);
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 10000);
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 5000 / 10000);
+
+        // For UID2:
+        // SENSOR1 was on for 2000 ms.
+        //   Estimated power: 2000 * 100 = 0.0556 mAh
+        //     split between three different states
+        //          cached screen-on: 2000 * 2500/10000
+        //          cached screen-off: 2000 * 7500/10000
+        // SENSOR2 was on for 2000 ms.
+        //   Estimated power: 2000 * 200 = 0.11111 mAh
+        //     split between three different states
+        //          cached screen-on: 2000 * 2500/10000
+        //          cached screen-off: 2000 * 7500/10000
+        double expectedPower2 = 0.05555 + 0.11111;
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower2 * 2500 / 10000);
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower2 * 7500 / 10000);
+
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of((expectedPower1 + expectedPower2) * 2500 / 10000);
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of((expectedPower1 + expectedPower2) * 7500 / 10000);
+    }
+
+    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+            int uid, int sensor) {
+        mStatsRule.setTime(timestamp, timestamp);
+        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+        historyItem.time = mMonotonicClock.monotonicTime();
+        historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_SENSOR_ON_FLAG : 0;
+        if (stateOn) {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_START;
+        } else {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+        }
+        historyItem.eventTag = historyItem.localEventTag;
+        historyItem.eventTag.uid = uid;
+        historyItem.eventTag.string = "sensor:0x" + Integer.toHexString(sensor);
+        return historyItem;
+    }
+
+    private int[] states(int... states) {
+        return states;
+    }
+
+    private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+            SensorPowerStatsProcessor processor) {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(processor);
+
+        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+        PowerComponentAggregatedPowerStats powerComponentStats =
+                aggregatedPowerStats.getPowerComponentStats(
+                        BatteryConsumer.POWER_COMPONENT_SENSORS);
+        processor.start(powerComponentStats, 0);
+
+        powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+        powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+        powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+        powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+        return powerComponentStats;
+    }
+
+    private Sensor createSensor(int handle, int type, String stringType, String name, float power) {
+        if (RavenwoodRule.isOnRavenwood()) {
+            Sensor sensor = mock(Sensor.class);
+            when(sensor.getHandle()).thenReturn(handle);
+            when(sensor.getType()).thenReturn(type);
+            when(sensor.getStringType()).thenReturn(stringType);
+            when(sensor.getName()).thenReturn(name);
+            when(sensor.getPower()).thenReturn(power);
+            return sensor;
+        } else {
+            return new Sensor(new InputSensorInfo(name, "vendor", 0 /* version */,
+                    handle, type, 100.0f /*maxRange */, 0.02f /* resolution */,
+                    (float) power, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
+                    0 /* fifoMaxEventCount */, stringType /* stringType */,
+                    "" /* requiredPermission */, 0 /* maxDelay */, 0 /* flags */, 0 /* id */));
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index ddd6d56..a6fd112 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -26,6 +26,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.platform.test.annotations.Presubmit;
@@ -246,7 +247,6 @@
         });
     }
 
-
     @Test
     @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
     public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() {
@@ -269,6 +269,24 @@
         });
     }
 
+    @Test
+    public void testGetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
+        runTestScenario((robot)-> {
+            robot.applyOnConf((c) -> {
+                c.enableCameraCompatTreatment(/* enabled */ true);
+                c.enableCameraCompatTreatmentAtBuildTime(/* enabled */ true);
+                c.enableCameraCompatSplitScreenAspectRatio(/* enabled */ true);
+                c.enableDisplayAspectRatioEnabledForFixedOrientationLetterbox(/* enabled */ false);
+                c.setFixedOrientationLetterboxAspectRatio(/* aspectRatio */ 1.5f);
+            });
+            robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+            robot.checkFixedOrientationLetterboxAspectRatioForTopParent(/* expected */ 1.5f);
+
+            robot.activity().enableTreatmentForTopActivity(/* enabled */ true);
+            robot.checkAspectRatioForTopParentIsSplitScreenRatio(/* expected */ true);
+        });
+    }
+
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -308,6 +326,28 @@
         }
 
         @NonNull
+        void checkFixedOrientationLetterboxAspectRatioForTopParent(float expected) {
+            assertEquals(expected,
+                    getTopActivityAppCompatAspectRatioOverrides()
+                            .getFixedOrientationLetterboxAspectRatio(
+                                    activity().top().getParent().getConfiguration()),
+                                        FLOAT_TOLLERANCE);
+        }
+
+        void checkAspectRatioForTopParentIsSplitScreenRatio(boolean expected) {
+            final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                    getTopActivityAppCompatAspectRatioOverrides();
+            if (expected) {
+                assertEquals(aspectRatioOverrides.getSplitScreenAspectRatio(),
+                        aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(
+                                activity().top().getParent().getConfiguration()), FLOAT_TOLLERANCE);
+            } else {
+                assertNotEquals(aspectRatioOverrides.getSplitScreenAspectRatio(),
+                        aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(
+                                activity().top().getParent().getConfiguration()), FLOAT_TOLLERANCE);
+            }
+        }
+
         private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
             return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 00a8771..6592f26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -70,4 +70,14 @@
     void enableCompatFakeFocus(boolean enabled) {
         doReturn(enabled).when(mAppCompatConfiguration).isCompatFakeFocusEnabled();
     }
+
+    void enableDisplayAspectRatioEnabledForFixedOrientationLetterbox(boolean enabled) {
+        doReturn(enabled).when(mAppCompatConfiguration)
+                .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+    }
+
+    void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
+        doReturn(aspectRatio).when(mAppCompatConfiguration)
+                .getFixedOrientationLetterboxAspectRatio();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 92f246b..6939f97 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -28,6 +28,8 @@
     private static final int DEFAULT_DISPLAY_WIDTH = 1000;
     private static final int DEFAULT_DISPLAY_HEIGHT = 2000;
 
+    static final float FLOAT_TOLLERANCE = 0.01f;
+
     @NonNull
     private final AppCompatActivityRobot mActivityRobot;
     @NonNull
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index e2c0f6c2..61a6f31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,7 +19,6 @@
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -297,37 +296,6 @@
     }
 
     @Test
-    public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
-        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
-                .isCameraCompatTreatmentEnabled();
-        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
-                .isCameraCompatTreatmentEnabledAtBuildTime();
-        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
-                .isCameraCompatSplitScreenAspectRatioEnabled();
-        doReturn(false).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
-        doReturn(1.5f).when(mActivity.mWmService.mAppCompatConfiguration)
-                .getFixedOrientationLetterboxAspectRatio();
-
-        // Recreate DisplayContent with DisplayRotationCompatPolicy
-        mActivity = setUpActivityWithComponent();
-        mController = new LetterboxUiController(mWm, mActivity);
-
-        assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
-                mActivity.getParent().getConfiguration()), /* delta */ 0.01);
-
-        spyOn(mDisplayContent.mAppCompatCameraPolicy);
-        doReturn(true).when(mDisplayContent.mAppCompatCameraPolicy)
-                .isTreatmentEnabledForActivity(eq(mActivity));
-
-        final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
-        assertEquals(aspectRatioOverrides.getSplitScreenAspectRatio(),
-                aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(
-                        mActivity.getParent().getConfiguration()), /* delta */  0.01);
-    }
-
-    @Test
     public void testIsVerticalThinLetterboxed() {
         // Vertical thin letterbox disabled
         doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)