Merge "Simplify IMMS#updateInputMethodsFromSettingsLocked() a bit" into main
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 2d78317..73ac263 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -199,3 +199,11 @@
   description: "redacts notifications on the lockscreen if they have the 'sensitiveContent' flag"
   bug: "343631648"
 }
+
+flag {
+  name: "api_rich_ongoing"
+  is_exported: true
+  namespace: "systemui"
+  description: "Guards new android.app.richongoingnotification api"
+  bug: "337261753"
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 579a794..a09720d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -22,6 +22,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
+import android.graphics.Rect
 import android.os.RemoteException
 import android.os.SystemProperties
 import android.util.DisplayMetrics
@@ -138,6 +139,30 @@
         }
     }
 
+
+    /**
+     * Returns a fake source rect hint for animation purposes when app-provided one is invalid.
+     * Resulting adjusted source rect hint lets the app icon in the content overlay to stay visible.
+     */
+    @JvmStatic
+    fun getEnterPipWithOverlaySrcRectHint(appBounds: Rect, aspectRatio: Float): Rect {
+        val appBoundsAspRatio = appBounds.width().toFloat() / appBounds.height()
+        val width: Int
+        val height: Int
+        var left = 0
+        var top = 0
+        if (appBoundsAspRatio < aspectRatio) {
+            width = appBounds.width()
+            height = Math.round(width / aspectRatio)
+            top = (appBounds.height() - height) / 2
+        } else {
+            height = appBounds.height()
+            width = Math.round(height * aspectRatio)
+            left = (appBounds.width() - width) / 2
+        }
+        return Rect(left, top, left + width, top + height)
+    }
+
     private var isPip2ExperimentEnabled: Boolean? = null
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 57c0732..0a3c15b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -40,6 +40,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.Transitions;
 
@@ -619,19 +620,8 @@
                 // This is done for entering case only.
                 if (isInPipDirection(direction)) {
                     final float aspectRatio = endValue.width() / (float) endValue.height();
-                    if ((startValue.width() / (float) startValue.height()) > aspectRatio) {
-                        // use the full height.
-                        adjustedSourceRectHint.set(0, 0,
-                                (int) (startValue.height() * aspectRatio), startValue.height());
-                        adjustedSourceRectHint.offset(
-                                (startValue.width() - adjustedSourceRectHint.width()) / 2, 0);
-                    } else {
-                        // use the full width.
-                        adjustedSourceRectHint.set(0, 0,
-                                startValue.width(), (int) (startValue.width() / aspectRatio));
-                        adjustedSourceRectHint.offset(
-                                0, (startValue.height() - adjustedSourceRectHint.height()) / 2);
-                    }
+                    adjustedSourceRectHint.set(PipUtils.getEnterPipWithOverlaySrcRectHint(
+                            startValue, aspectRatio));
                 }
             } else {
                 adjustedSourceRectHint.set(sourceRectHint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8c9206c..e4420d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -999,7 +999,6 @@
 
     private void onEndOfSwipePipToHomeTransition() {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mPipTransitionController.setEnterAnimationType(ANIM_TYPE_BOUNDS);
             return;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index dbf18fa..3cae72d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1083,6 +1083,8 @@
             mSurfaceTransactionHelper
                     .crop(finishTransaction, leash, destinationBounds)
                     .round(finishTransaction, leash, true /* applyCornerRadius */);
+            // Always reset to bounds animation type afterwards.
+            setEnterAnimationType(ANIM_TYPE_BOUNDS);
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index 3e298e5..895c2ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -19,11 +19,13 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.content.Context;
 import android.view.SurfaceControl;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 
 import java.lang.annotation.Retention;
@@ -45,6 +47,7 @@
     public static final int FADE_IN = 0;
     public static final int FADE_OUT = 1;
 
+    private final int mEnterAnimationDuration;
     private final SurfaceControl mLeash;
     private final SurfaceControl.Transaction mStartTransaction;
 
@@ -55,7 +58,8 @@
     private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
 
-    public PipAlphaAnimator(SurfaceControl leash,
+    public PipAlphaAnimator(Context context,
+            SurfaceControl leash,
             SurfaceControl.Transaction tx,
             @Fade int direction) {
         mLeash = leash;
@@ -67,6 +71,9 @@
         }
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+        mEnterAnimationDuration = context.getResources()
+                .getInteger(R.integer.config_pipEnterAnimationDuration);
+        setDuration(mEnterAnimationDuration);
         addListener(this);
         addUpdateListener(this);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 3e215d9..0b2db6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -293,37 +293,32 @@
             return false;
         }
 
+        SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
         PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
-        Rect srcRectHint = params.getSourceRectHint();
-        Rect startBounds = pipChange.getStartAbsBounds();
+
+        Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds();
         Rect destinationBounds = pipChange.getEndAbsBounds();
 
+        float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat();
+
+        // We fake the source rect hint when the one prvided by the app is invalid for
+        // the animation with an app icon overlay.
+        Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint()
+                : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio);
+
         WindowContainerTransaction finishWct = new WindowContainerTransaction();
         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
 
-        if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
-            final float scale = (float) destinationBounds.width() / srcRectHint.width();
-            startTransaction.setWindowCrop(pipLeash, srcRectHint);
-            startTransaction.setPosition(pipLeash,
-                    destinationBounds.left - srcRectHint.left * scale,
-                    destinationBounds.top - srcRectHint.top * scale);
+        final float scale = (float) destinationBounds.width() / animationSrcRectHint.width();
+        startTransaction.setWindowCrop(pipLeash, animationSrcRectHint);
+        startTransaction.setPosition(pipLeash,
+                destinationBounds.left - animationSrcRectHint.left * scale,
+                destinationBounds.top - animationSrcRectHint.top * scale);
+        startTransaction.setScale(pipLeash, scale, scale);
 
-            // Reset the scale in case we are in the multi-activity case.
-            // TO_FRONT transition already scales down the task in single-activity case, but
-            // in multi-activity case, reparenting yields new reset scales coming from pinned task.
-            startTransaction.setScale(pipLeash, scale, scale);
-        } else {
-            final float scaleX = (float) destinationBounds.width() / startBounds.width();
-            final float scaleY = (float) destinationBounds.height() / startBounds.height();
+        if (overlayLeash != null) {
             final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
                     mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
-            SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
-
-            startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
-                    .setScale(pipLeash, scaleX, scaleY)
-                    .setWindowCrop(pipLeash, startBounds)
-                    .reparent(overlayLeash, pipLeash)
-                    .setLayer(overlayLeash, Integer.MAX_VALUE);
 
             // Overlay needs to be adjusted once a new draw comes in resetting surface transform.
             tx.setScale(overlayLeash, 1f, 1f);
@@ -390,15 +385,23 @@
         if (pipChange == null) {
             return false;
         }
-        // cache the PiP task token and leash
-        WindowContainerToken pipTaskToken = pipChange.getContainer();
 
-        Preconditions.checkNotNull(mPipLeash, "Leash is null for alpha transition.");
-        // start transition with 0 alpha
-        startTransaction.setAlpha(mPipLeash, 0f);
-        PipAlphaAnimator animator = new PipAlphaAnimator(mPipLeash,
-                startTransaction, PipAlphaAnimator.FADE_IN);
-        animator.setAnimationEndCallback(() -> finishCallback.onTransitionFinished(null));
+        Rect destinationBounds = pipChange.getEndAbsBounds();
+        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");
+
+        // Start transition with 0 alpha at the entry bounds.
+        startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+                .setWindowCrop(pipLeash, destinationBounds.width(), destinationBounds.height())
+                .setAlpha(pipLeash, 0f);
+
+        PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
+                PipAlphaAnimator.FADE_IN);
+        animator.setAnimationEndCallback(() -> {
+            finishCallback.onTransitionFinished(null);
+            // This should update the pip transition state accordingly after we stop playing.
+            onClientDrawAtTransitionEnd();
+        });
 
         animator.start();
         return true;
@@ -480,10 +483,10 @@
 
     private boolean isLegacyEnter(@NonNull TransitionInfo info) {
         TransitionInfo.Change pipChange = getPipChange(info);
-        // If the only change in the changes list is a TO_FRONT mode PiP task,
+        // If the only change in the changes list is a opening type PiP task,
         // then this is legacy-enter PiP.
-        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
-                && info.getChanges().size() == 1;
+        return pipChange != null && info.getChanges().size() == 1
+                && (pipChange.getMode() == TRANSIT_TO_FRONT || pipChange.getMode() == TRANSIT_OPEN);
     }
 
     private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 01a479f..3dcdc0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -831,12 +831,12 @@
         // We want handle to remain pressed if the pointer moves outside of it during a drag.
         handle.setPressed((inHandle && action == ACTION_DOWN)
                 || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
-        if (isHandleMenuActive() && !isMenuAboveStatusBar()) {
+        if (isHandleMenuActive() && !isHandleMenuAboveStatusBar()) {
             mHandleMenu.checkMotionEvent(ev);
         }
     }
 
-    private boolean isMenuAboveStatusBar() {
+    private boolean isHandleMenuAboveStatusBar() {
         return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index bfc4e0d..df0836c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -24,6 +24,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,6 +36,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -70,8 +72,14 @@
     private final DesktopModeWindowDecoration mParentDecor;
     @VisibleForTesting
     AdditionalViewContainer mHandleMenuViewContainer;
+    // Position of the handle menu used for laying out the handle view.
     @VisibleForTesting
     final PointF mHandleMenuPosition = new PointF();
+    // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
+    // may be in a different coordinate space than the input coordinates. Therefore, we still care
+    // about the menu's coordinates relative to the display as a whole, so we need to maintain
+    // those as well.
+    final Point mGlobalMenuPosition = new Point();
     private final boolean mShouldShowWindowingPill;
     private final Bitmap mAppIconBitmap;
     private final CharSequence mAppName;
@@ -244,39 +252,23 @@
     private void updateHandleMenuPillPositions() {
         int menuX;
         final int menuY;
+        final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
+        updateGlobalMenuPosition(taskBounds);
         if (mLayoutResId == R.layout.desktop_mode_app_header) {
             // Align the handle menu to the left side of the caption.
             menuX = mMarginMenuStart;
             menuY = mMarginMenuTop;
         } else {
-            final int handleWidth = loadDimensionPixelSize(mContext.getResources(),
-                    R.dimen.desktop_mode_fullscreen_decor_caption_width);
-            final int handleOffset = (mMenuWidth / 2) - (handleWidth / 2);
-            final int captionX = mParentDecor.getCaptionX();
-            // TODO(b/343561161): This needs to be calculated differently if the task is in
-            //  top/bottom split.
             if (Flags.enableAdditionalWindowsAboveStatusBar()) {
-                final Rect leftOrTopStageBounds = new Rect();
-                if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
-                        == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
-                    mSplitScreenController.getStageBounds(leftOrTopStageBounds, new Rect());
-                }
                 // In a focused decor, we use global coordinates for handle menu. Therefore we
                 // need to account for other factors like split stage and menu/handle width to
                 // center the menu.
                 final DisplayLayout layout = mDisplayController
                         .getDisplayLayout(mTaskInfo.displayId);
-                menuX = captionX + handleOffset - (layout.width() / 2);
-                if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
-                        == SPLIT_POSITION_BOTTOM_OR_RIGHT && layout.isLandscape()) {
-                    // If this task in the right stage, we need to offset by left stage's width
-                    menuX += leftOrTopStageBounds.width();
-                }
-                menuY = mMarginMenuStart - ((layout.height() - mMenuHeight) / 2);
+                menuX = mGlobalMenuPosition.x + ((mMenuWidth - layout.width()) / 2);
+                menuY = mGlobalMenuPosition.y + ((mMenuHeight - layout.height()) / 2);
             } else {
-                final int captionWidth = mTaskInfo.getConfiguration()
-                        .windowConfiguration.getBounds().width();
-                menuX = (captionWidth / 2) - (mMenuWidth / 2);
+                menuX = (taskBounds.width() / 2) - (mMenuWidth / 2);
                 menuY = mMarginMenuTop;
             }
         }
@@ -284,6 +276,36 @@
         mHandleMenuPosition.set(menuX, menuY);
     }
 
+    private void updateGlobalMenuPosition(Rect taskBounds) {
+        if (mTaskInfo.isFreeform()) {
+            mGlobalMenuPosition.set(taskBounds.left + mMarginMenuStart,
+                    taskBounds.top + mMarginMenuTop);
+        } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+            mGlobalMenuPosition.set(
+                    (taskBounds.width() / 2) - (mMenuWidth / 2) + mMarginMenuStart,
+                    mMarginMenuTop
+            );
+        } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+            final int splitPosition = mSplitScreenController.getSplitPosition(mTaskInfo.taskId);
+            final Rect leftOrTopStageBounds = new Rect();
+            final Rect rightOrBottomStageBounds = new Rect();
+            mSplitScreenController.getStageBounds(leftOrTopStageBounds,
+                    rightOrBottomStageBounds);
+            // TODO(b/343561161): This needs to be calculated differently if the task is in
+            //  top/bottom split.
+            if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+                mGlobalMenuPosition.set(leftOrTopStageBounds.width()
+                                + (rightOrBottomStageBounds.width() / 2)
+                                - (mMenuWidth / 2) + mMarginMenuStart,
+                        mMarginMenuTop);
+            } else if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+                mGlobalMenuPosition.set((leftOrTopStageBounds.width() / 2)
+                                - (mMenuWidth / 2) + mMarginMenuStart,
+                        mMarginMenuTop);
+            }
+        }
+    }
+
     /**
      * Update pill layout, in case task changes have caused positioning to change.
      */
@@ -302,6 +324,8 @@
      * @param ev the MotionEvent to compare against.
      */
     void checkMotionEvent(MotionEvent ev) {
+        // If the menu view is above status bar, we can let the views handle input directly.
+        if (isViewAboveStatusBar()) return;
         final View handleMenu = mHandleMenuViewContainer.getView();
         final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
         final PointF inputPoint = translateInputToLocalSpace(ev);
@@ -314,6 +338,11 @@
         }
     }
 
+    private boolean isViewAboveStatusBar() {
+        return Flags.enableAdditionalWindowsAboveStatusBar()
+                && !mTaskInfo.isFreeform();
+    }
+
     // Translate the input point from display coordinates to the same space as the handle menu.
     private PointF translateInputToLocalSpace(MotionEvent ev) {
         return new PointF(ev.getX() - mHandleMenuPosition.x,
@@ -329,10 +358,33 @@
      */
     boolean isValidMenuInput(PointF inputPoint) {
         if (!viewsLaidOut()) return true;
-        return pointInView(
-                mHandleMenuViewContainer.getView(),
-                inputPoint.x - mHandleMenuPosition.x,
-                inputPoint.y - mHandleMenuPosition.y);
+        if (!isViewAboveStatusBar()) {
+            return pointInView(
+                    mHandleMenuViewContainer.getView(),
+                    inputPoint.x - mHandleMenuPosition.x,
+                    inputPoint.y - mHandleMenuPosition.y);
+        } else {
+            // Handle menu exists in a different coordinate space when added to WindowManager.
+            // Therefore we must compare the provided input coordinates to global menu coordinates.
+            // This includes factoring for split stage as input coordinates are relative to split
+            // stage position, not relative to the display as a whole.
+            PointF inputRelativeToMenu = new PointF(
+                    inputPoint.x - mGlobalMenuPosition.x,
+                    inputPoint.y - mGlobalMenuPosition.y
+            );
+            if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
+                    == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+                // TODO(b/343561161): This also needs to be calculated differently if
+                //  the task is in top/bottom split.
+                Rect leftStageBounds = new Rect();
+                mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
+                inputRelativeToMenu.x += leftStageBounds.width();
+            }
+            return pointInView(
+                    mHandleMenuViewContainer.getView(),
+                    inputRelativeToMenu.x,
+                    inputRelativeToMenu.y);
+        }
     }
 
     private boolean pointInView(View v, float x, float y) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 9891b5b..3295dde 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
 
 import android.view.Gravity
+import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
@@ -71,7 +72,8 @@
     }
 
     @Composable
-    private fun Content(dialog: SystemUIDialog) {
+    @VisibleForTesting
+    fun Content(dialog: SystemUIDialog) {
         val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle()
 
         if (!isAvailable) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 04b930e..07d8890 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -25,12 +25,15 @@
 import static org.mockito.Mockito.when;
 
 import android.app.DreamManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -87,7 +90,7 @@
         assertThat(captured).isTrue();
     }
 
-    // Verifies that a swipe in the upward direction is not catpured.
+    // Verifies that a swipe in the upward direction is not captured.
     @Test
     public void testSwipeUp_notCaptured() {
         final boolean captured = swipe(Direction.UP);
@@ -98,34 +101,58 @@
 
     // Verifies that a swipe down forwards captured touches to central surfaces for handling.
     @Test
-    public void testSwipeDown_sentToCentralSurfaces() {
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    public void testSwipeDown_communalEnabled_sentToCentralSurfaces() {
         swipe(Direction.DOWN);
 
-        // Both motion events are sent for the shade window to process.
+        // Both motion events are sent for central surfaces to process.
         verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
     }
 
-    // Verifies that a swipe down forwards captured touches to central surfaces for handling.
+    // Verifies that a swipe down forwards captured touches to the shade view for handling.
+    @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    public void testSwipeDown_communalDisabled_sentToShadeView() {
+        swipe(Direction.DOWN);
+
+        // Both motion events are sent for the shade view to process.
+        verify(mShadeViewController, times(2)).handleExternalTouch(any());
+    }
+
+    // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
+    // handling.
     @Test
     public void testSwipeDown_dreaming_sentToShadeView() {
         when(mDreamManager.isDreaming()).thenReturn(true);
 
         swipe(Direction.DOWN);
 
-        // Both motion events are sent for the shade window to process.
+        // Both motion events are sent for the shade view to process.
         verify(mShadeViewController, times(2)).handleExternalTouch(any());
     }
 
-    // Verifies that a swipe down is not forwarded to the shade window.
+    // Verifies that a swipe up is not forwarded to central surfaces.
     @Test
-    public void testSwipeUp_touchesNotSent() {
+    @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+    public void testSwipeUp_communalEnabled_touchesNotSent() {
         swipe(Direction.UP);
 
-        // Motion events are not sent for the shade window to process as the swipe is going in the
+        // Motion events are not sent for central surfaces to process as the swipe is going in the
         // wrong direction.
         verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
     }
 
+    // Verifies that a swipe up is not forwarded to the shade view.
+    @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    public void testSwipeUp_communalDisabled_touchesNotSent() {
+        swipe(Direction.UP);
+
+        // Motion events are not sent for the shade view to process as the swipe is going in the
+        // wrong direction.
+        verify(mShadeViewController, never()).handleExternalTouch(any());
+    }
+
     /**
      * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
      * touch handler's gesture listener.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 6c6a1cc..324579d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -57,7 +57,7 @@
                     .toViewModel(
                         isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
                         isHeadTrackingAvailable =
-                            isAvailable is SpatialAudioAvailabilityModel.SpatialAudio,
+                            isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
                     )
                     .copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
             }
@@ -69,7 +69,7 @@
                 // head tracking availability means there are three possible states for the spatial
                 // audio: disabled, enabled regular, enabled with head tracking.
                 // Show popup in this case instead of a togglealbe button.
-                it is SpatialAudioAvailabilityModel.SpatialAudio
+                it is SpatialAudioAvailabilityModel.HeadTracking
             }
             .stateIn(scope, SharingStarted.Eagerly, false)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 5a092f3..62e56be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.animation
 
+import android.content.applicationContext
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testCase
 
 val Kosmos.dialogTransitionAnimator by Fixture {
     fakeDialogTransitionAnimator(
         // The main thread is checked in a bunch of places inside the different transitions
         // animators, so we have to pass the real main executor here.
-        mainExecutor = testCase.context.mainExecutor,
+        mainExecutor = applicationContext.mainExecutor,
         interactionJankMonitor = interactionJankMonitor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopupKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopupKosmos.kt
new file mode 100644
index 0000000..49170d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopupKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.systemui.volume.panel.component.popup.ui.composable
+
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+
+val Kosmos.volumePanelPopup: VolumePanelPopup by
+    Kosmos.Fixture { VolumePanelPopup(systemUIDialogFactory, dialogTransitionAnimator) }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 167c384..53730e3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -227,6 +227,7 @@
         "connectivity_flags_lib",
         "dreams_flags_lib",
         "aconfig_new_storage_flags_lib",
+        "powerstats_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 60290bc..e424ffa 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3892,9 +3892,10 @@
                 return;
             }
 
-            final long lastTopTime = sr.app.mState.getLastTopTime();
-            final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
+            final boolean currentlyTop = sr.app.mState.getCurProcState() <= PROCESS_STATE_TOP;
             final long nowUptime = SystemClock.uptimeMillis();
+            final long lastTopTime = currentlyTop ? nowUptime : sr.app.mState.getLastTopTime();
+            final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
             if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
                 // Discard any other messages for this service
                 mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8764f51..fd8f0fa 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -382,12 +382,6 @@
     @MultiUserUnawareField
     private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
-    /**
-     * Tracks how many times {@link #mSettings} was updated.
-     */
-    @GuardedBy("ImfLock.class")
-    private int mMethodMapUpdateCount = 0;
-
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
     private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
@@ -5246,7 +5240,6 @@
             Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
             return;
         }
-        mMethodMapUpdateCount++;
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
@@ -6072,7 +6065,7 @@
             p.println("Current Input Method Manager state:");
             final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
-            p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
+            p.println("  Input Methods:");
             for (int i = 0; i < numImes; i++) {
                 InputMethodInfo info = methodList.get(i);
                 p.println("  InputMethod #" + i + ":");
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 2081f73..0acadb1 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,7 +16,12 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -25,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.admin.SecurityLog;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.Flags;
 import android.content.pm.PackageManager;
@@ -376,7 +382,30 @@
             mCallingUid = callingUid;
         }
 
-        public boolean isSameComponent(ActivityInfo activityInfo) {
+        public boolean isLauncherActivity(@NonNull Computer computer, @UserIdInt int userId) {
+            if (mIsForWholeApp) {
+                return false;
+            }
+            // Query the launcher activities with the package name.
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+            intent.setPackage(mPackageName);
+            List<ResolveInfo> launcherActivities = computer.queryIntentActivitiesInternal(
+                    intent, null,
+                    MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | GET_RESOLVED_FILTER
+                            | MATCH_DISABLED_COMPONENTS, SYSTEM_UID, userId);
+            final int launcherActivitiesSize =
+                    launcherActivities != null ? launcherActivities.size() : 0;
+            for (int i = 0; i < launcherActivitiesSize; i++) {
+                ResolveInfo resolveInfo = launcherActivities.get(i);
+                if (isSameComponent(resolveInfo.activityInfo)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean isSameComponent(ActivityInfo activityInfo) {
             if (activityInfo == null) {
                 return false;
             }
@@ -395,25 +424,13 @@
             Slog.d(TAG, "Fail to report component state due to metrics is empty");
             return;
         }
-        boolean isLauncher = false;
-        final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
-                computer, userId);
-        final int resolveInfosForLauncherSize =
-                resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
         final int metricsSize = componentStateMetricsList.size();
         for (int i = 0; i < metricsSize; i++) {
             final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
-            for (int j = 0; j < resolveInfosForLauncherSize; j++) {
-                ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
-                if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
-                    isLauncher = true;
-                    break;
-                }
-            }
             reportComponentStateChanged(componentStateMetrics.mUid,
                     componentStateMetrics.mComponentOldState,
                     componentStateMetrics.mComponentNewState,
-                    isLauncher,
+                    componentStateMetrics.isLauncherActivity(computer, userId),
                     componentStateMetrics.mIsForWholeApp,
                     componentStateMetrics.mCallingUid);
         }
@@ -424,10 +441,4 @@
         FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
                 uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid);
     }
-
-    private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
-            @UserIdInt int userId) {
-        return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
-                null, /* flags */ 0, userId);
-    }
 }
diff --git a/services/core/java/com/android/server/powerstats/Android.bp b/services/core/java/com/android/server/powerstats/Android.bp
new file mode 100644
index 0000000..7f3b091
--- /dev/null
+++ b/services/core/java/com/android/server/powerstats/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+    name: "powerstats_flags",
+    package: "com.android.server.powerstats",
+    container: "system",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "powerstats_flags_lib",
+    aconfig_declarations: "powerstats_flags",
+}
diff --git a/services/core/java/com/android/server/powerstats/TimerTrigger.java b/services/core/java/com/android/server/powerstats/TimerTrigger.java
index f8a4135..817a40d 100644
--- a/services/core/java/com/android/server/powerstats/TimerTrigger.java
+++ b/services/core/java/com/android/server/powerstats/TimerTrigger.java
@@ -16,8 +16,10 @@
 
 package com.android.server.powerstats;
 
+import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.util.Slog;
 
 /**
@@ -33,37 +35,53 @@
     private static final long LOG_PERIOD_MS_HIGH_FREQUENCY = 2 * 60 * 1000; // 2 minutes
 
     private final Handler mHandler;
+    private final AlarmManager mAlarmManager;
 
-    private Runnable mLogDataLowFrequency = new Runnable() {
+    class PeriodicTimer implements Runnable, AlarmManager.OnAlarmListener {
+        private final String mName;
+        private final long mPeriodMs;
+        private final int mMsgType;
+
+        PeriodicTimer(String name, long periodMs, int msgType) {
+            mName = name;
+            mPeriodMs = periodMs;
+            mMsgType = msgType;
+        }
+
+        @Override
+        public void onAlarm() {
+            run();
+        }
+
         @Override
         public void run() {
-            // Do not wake the device for these messages.  Opportunistically log rail data every
-            // LOG_PERIOD_MS_LOW_FREQUENCY.
-            mHandler.postDelayed(mLogDataLowFrequency, LOG_PERIOD_MS_LOW_FREQUENCY);
-            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data low frequency");
-            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
+            if (Flags.alarmBasedPowerstatsLogging()) {
+                final long nextAlarmMs = SystemClock.elapsedRealtime() + mPeriodMs;
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmMs,
+                        AlarmManager.WINDOW_EXACT, 0, mName, this, mHandler, null);
+            } else {
+                mHandler.postDelayed(this, mPeriodMs);
+            }
+            if (DEBUG) Slog.d(TAG, "Received delayed message (" + mName + ").  Logging rail data");
+            logPowerStatsData(mMsgType);
         }
-    };
-
-    private Runnable mLogDataHighFrequency = new Runnable() {
-        @Override
-        public void run() {
-            // Do not wake the device for these messages.  Opportunistically log rail data every
-            // LOG_PERIOD_MS_HIGH_FREQUENCY.
-            mHandler.postDelayed(mLogDataHighFrequency, LOG_PERIOD_MS_HIGH_FREQUENCY);
-            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data high frequency");
-            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
-        }
-    };
+    }
 
     public TimerTrigger(Context context, PowerStatsLogger powerStatsLogger,
             boolean triggerEnabled) {
         super(context, powerStatsLogger);
         mHandler = mContext.getMainThreadHandler();
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
 
         if (triggerEnabled) {
-            mLogDataLowFrequency.run();
-            mLogDataHighFrequency.run();
+            final PeriodicTimer logDataLowFrequency = new PeriodicTimer("PowerStatsLowFreqLog",
+                    LOG_PERIOD_MS_LOW_FREQUENCY,
+                    PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
+            final PeriodicTimer logDataHighFrequency = new PeriodicTimer("PowerStatsHighFreqLog",
+                    LOG_PERIOD_MS_HIGH_FREQUENCY,
+                    PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
+            logDataLowFrequency.run();
+            logDataHighFrequency.run();
         }
     }
 }
diff --git a/services/core/java/com/android/server/powerstats/flags.aconfig b/services/core/java/com/android/server/powerstats/flags.aconfig
new file mode 100644
index 0000000..0a4a751
--- /dev/null
+++ b/services/core/java/com/android/server/powerstats/flags.aconfig
@@ -0,0 +1,13 @@
+
+package: "com.android.server.powerstats"
+container: "system"
+
+flag {
+    name: "alarm_based_powerstats_logging"
+    namespace: "backstage_power"
+    description: "Utilize new OomAdjuster implementation"
+    bug: "294598168"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file