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

Commit 47082296 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[PIP][jank] Add jank CUJs for expand transitions" into main

parents 856618dc 9ddfa1a5
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