Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ddfa1a5 authored by Perry Wu's avatar Perry Wu
Browse files

[PIP][jank] Add jank CUJs for expand transitions

As part of improving jank cuj coverage, add cujs for expanding pip
in PIP2. This also adds PipInteractionHandler which will handle all
other jank CUJ tracking.

Flag: EXEMPT jank
Bug: b/352738878
Test: atest WMShellUnitTests:PipInteractionHandlerTest
Test: Manually check events are being logged
Change-Id: Iad2f4874f199479cfccd35b561c2d223672bc5ea
parent f2f0c768
Loading
Loading
Loading
Loading
+15 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -45,6 +46,7 @@ import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTaskListener;
@@ -88,12 +90,13 @@ public abstract class Pip2Module {
            @NonNull PipUiStateChangeController pipUiStateChangeController,
            DisplayController displayController,
            Optional<SplitScreenController> splitScreenControllerOptional,
            PipDesktopState pipDesktopState) {
            PipDesktopState pipDesktopState,
            PipInteractionHandler pipInteractionHandler) {
        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
                pipScheduler, pipStackListenerController, pipDisplayLayoutState,
                pipUiStateChangeController, displayController, splitScreenControllerOptional,
                pipDesktopState);
                pipDesktopState, pipInteractionHandler);
    }

    @WMSingleton
@@ -249,4 +252,14 @@ public abstract class Pip2Module {

    @BindsOptionalOf
    abstract DragToDesktopTransitionHandler optionalDragToDesktopTransitionHandler();

    @WMSingleton
    @Provides
    static PipInteractionHandler providePipInteractionHandler(
            Context context,
            @ShellMainThread Handler mainHandler
    ) {
        return new PipInteractionHandler(context, mainHandler,
                InteractionJankMonitor.getInstance());
    }
}
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.pip2.phone;

import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;

import android.annotation.IntDef;
import android.content.Context;
import android.os.Handler;
import android.view.SurfaceControl;

import com.android.internal.jank.InteractionJankMonitor;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Helps track PIP CUJ interactions
 */
public class PipInteractionHandler {
    @IntDef(prefix = {"INTERACTION_"}, value = {
            INTERACTION_EXIT_PIP,
            INTERACTION_EXIT_PIP_TO_SPLIT
    })

    @Retention(RetentionPolicy.SOURCE)
    public @interface Interaction {}

    public static final int INTERACTION_EXIT_PIP = 0;
    public static final int INTERACTION_EXIT_PIP_TO_SPLIT = 1;

    private final Context mContext;
    private final Handler mHandler;
    private final InteractionJankMonitor mInteractionJankMonitor;

    public PipInteractionHandler(Context context, Handler handler,
            InteractionJankMonitor interactionJankMonitor) {
        mContext = context;
        mHandler = handler;
        mInteractionJankMonitor = interactionJankMonitor;
    }

    /**
     * Begin tracking PIP CUJ.
     *
     * @param leash PIP leash.
     * @param interaction Tag for interaction.
     */
    public void begin(SurfaceControl leash, @Interaction int interaction) {
        mInteractionJankMonitor.begin(leash, mContext, mHandler, CUJ_PIP_TRANSITION,
                pipInteractionToString(interaction));
    }

    /**
     * End tracking CUJ.
     */
    public void end() {
        mInteractionJankMonitor.end(CUJ_PIP_TRANSITION);
    }

    /**
     * Converts an interaction to a string representation used for tagging.
     *
     * @param interaction Interaction to track.
     * @return String representation of the interaction.
     */
    public static String pipInteractionToString(@Interaction int interaction) {
        return switch (interaction) {
            case INTERACTION_EXIT_PIP -> "EXIT_PIP";
            case INTERACTION_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
            default -> "";
        };
    }
}
+6 −2
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ public class PipTransition extends PipTransitionController implements
    private final DisplayController mDisplayController;
    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
    private final PipDesktopState mPipDesktopState;
    private final PipInteractionHandler mPipInteractionHandler;

    //
    // Transition caches
@@ -154,7 +155,8 @@ public class PipTransition extends PipTransitionController implements
            PipUiStateChangeController pipUiStateChangeController,
            DisplayController displayController,
            Optional<SplitScreenController> splitScreenControllerOptional,
            PipDesktopState pipDesktopState) {
            PipDesktopState pipDesktopState,
            PipInteractionHandler pipInteractionHandler) {
        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                pipBoundsAlgorithm);

@@ -168,9 +170,11 @@ public class PipTransition extends PipTransitionController implements
        mDisplayController = displayController;
        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
        mPipDesktopState = pipDesktopState;
        mPipInteractionHandler = pipInteractionHandler;

        mExpandHandler = new PipExpandHandler(mContext, pipBoundsState, pipBoundsAlgorithm,
                pipTransitionState, pipDisplayLayoutState, splitScreenControllerOptional);
                pipTransitionState, pipDisplayLayoutState, pipInteractionHandler,
                splitScreenControllerOptional);
    }

    @Override
+10 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -58,6 +59,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    private final PipTransitionState mPipTransitionState;
    private final PipDisplayLayoutState mPipDisplayLayoutState;
    private final PipInteractionHandler mPipInteractionHandler;
    private final Optional<SplitScreenController> mSplitScreenControllerOptional;

    @Nullable
@@ -72,12 +74,14 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipTransitionState pipTransitionState,
            PipDisplayLayoutState pipDisplayLayoutState,
            PipInteractionHandler pipInteractionHandler,
            Optional<SplitScreenController> splitScreenControllerOptional) {
        mContext = context;
        mPipBoundsState = pipBoundsState;
        mPipBoundsAlgorithm = pipBoundsAlgorithm;
        mPipTransitionState = pipTransitionState;
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mPipInteractionHandler = pipInteractionHandler;
        mSplitScreenControllerOptional = splitScreenControllerOptional;

        mPipExpandAnimatorSupplier = PipExpandAnimator::new;
@@ -183,6 +187,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
        PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
                startTransaction, finishTransaction, endBounds, startBounds, endBounds,
                sourceRectHint, delta);
        animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
                PipInteractionHandler.INTERACTION_EXIT_PIP));
        animator.setAnimationEndCallback(() -> {
            if (parentBeforePip != null) {
                // TODO b/377362511: Animate local leash instead to also handle letterbox case.
@@ -190,6 +196,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
                finishTransaction.setCrop(pipLeash, null);
            }
            finishTransition();
            mPipInteractionHandler.end();
        });
        cacheAndStartTransitionAnimator(animator);
        saveReentryState();
@@ -248,6 +255,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
            splitController.finishEnterSplitScreen(finishTransaction);
        });

        animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
                PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
        animator.setAnimationEndCallback(() -> {
            if (parentBeforePip == null) {
                // After PipExpandAnimator is done modifying finishTransaction, we need to make
@@ -256,6 +265,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
                finishTransaction.setPosition(pipLeash, 0, 0);
            }
            finishTransition();
            mPipInteractionHandler.end();
        });
        cacheAndStartTransitionAnimator(animator);
        saveReentryState();
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.pip2.phone;

import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP;
import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.kotlin.VerificationKt.times;

import android.content.Context;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;

import androidx.test.filters.SmallTest;

import com.android.internal.jank.InteractionJankMonitor;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Unit test against {@link PipInteractionHandler}.
 */
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class PipInteractionHandlerTest {
    @Mock private Context mMockContext;
    @Mock private Handler mMockHandler;
    @Mock private InteractionJankMonitor mMockInteractionJankMonitor;

    private SurfaceControl mTestLeash;

    private PipInteractionHandler mPipInteractionHandler;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mPipInteractionHandler = new PipInteractionHandler(mMockContext, mMockHandler,
                mMockInteractionJankMonitor);
        mTestLeash = new SurfaceControl.Builder()
                .setContainerLayer()
                .setName("PipInteractionHandlerTest")
                .setCallsite("PipInteractionHandlerTest")
                .build();
    }

    @Test
    public void begin_expand_startsTracking() {
        mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP);

        verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
                eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
                eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP)));
    }

    @Test
    public void begin_expandToSplit_startsTracking() {
        mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP_TO_SPLIT);

        verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
                eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
                eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP_TO_SPLIT)));
    }

    @Test
    public void end_stopsTracking() {
        mPipInteractionHandler.end();

        verify(mMockInteractionJankMonitor, times(1)).end(CUJ_PIP_TRANSITION);
    }
}
Loading