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

Commit 5b81951e authored by Ikram Gabiyev's avatar Ikram Gabiyev Committed by Android (Google) Code Review
Browse files

Merge "[PiP2] Track display change transitions in PiP" into main

parents afb53107 6d937c27
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -479,6 +479,7 @@ public class PipController implements ConfigurationChangeListener,
                mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
            });
        } else {
            mPipTransitionState.setIsPipBoundsChangingWithDisplay(true);
            t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
        }
        // Update the size spec in PipBoundsState afterwards.
+5 −0
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.phone.transition.PipDisplayChangeObserver;
import com.android.wm.shell.pip2.phone.transition.PipExpandHandler;
import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -143,6 +144,7 @@ public class PipTransition extends PipTransitionController implements
    // Internal state and relevant cached info
    //
    private final PipExpandHandler mExpandHandler;
    private final PipDisplayChangeObserver mPipDisplayChangeObserver;

    private Transitions.TransitionFinishCallback mFinishCallback;

@@ -189,12 +191,15 @@ public class PipTransition extends PipTransitionController implements
                pipBoundsState, pipBoundsAlgorithm,
                pipTransitionState, pipDisplayLayoutState, pipDesktopState, pipInteractionHandler,
                splitScreenControllerOptional);
        mPipDisplayChangeObserver = new PipDisplayChangeObserver(pipTransitionState,
                pipBoundsState);
    }

    @Override
    protected void onInit() {
        if (PipFlags.isPip2ExperimentEnabled()) {
            mTransitions.addHandler(this);
            mTransitions.registerObserver(mPipDisplayChangeObserver);
        }
    }

+30 −3
Original line number Diff line number Diff line
@@ -169,6 +169,8 @@ public class PipTransitionState {

    private boolean mInFixedRotation = false;

    private boolean mIsPipBoundsChangingWithDisplay = false;

    /**
     * An interface to track state updates as we progress through PiP transitions.
     */
@@ -373,6 +375,25 @@ public class PipTransitionState {
        }
    }

    /**
     * @return true if a display change is ungoing with a PiP bounds change.
     */
    public boolean isPipBoundsChangingWithDisplay() {
        return mIsPipBoundsChangingWithDisplay;
    }

    /**
     * Sets the PiP bounds change with display change flag.
     */
    public void setIsPipBoundsChangingWithDisplay(boolean isPipBoundsChangingWithDisplay) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: Set mIsPipBoundsChangingWithDisplay=%b", TAG, isPipBoundsChangingWithDisplay);
        mIsPipBoundsChangingWithDisplay = isPipBoundsChangingWithDisplay;
        if (!isPipBoundsChangingWithDisplay) {
            maybeRunOnIdlePipTransitionStateCallback();
        }
    }

    /**
     * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
     */
@@ -438,13 +459,16 @@ public class PipTransitionState {

    public boolean isPipStateIdle() {
        // This needs to be a valid in-PiP state that isn't a transient state.
        return (mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS) && !isInFixedRotation();
        return (mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS)
                && !isInFixedRotation() && !isPipBoundsChangingWithDisplay();
    }

    @Override
    public String toString() {
        return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
                stateToString(mState), mInSwipePipToHomeTransition);
        return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b, "
                + "mIsPipBoundsChangingWithDisplay=%b, mInFixedRotation=%b",
                stateToString(mState), mInSwipePipToHomeTransition, mIsPipBoundsChangingWithDisplay,
                        mInFixedRotation);
    }

    /** Dumps internal state. */
@@ -452,5 +476,8 @@ public class PipTransitionState {
        final String innerPrefix = prefix + "  ";
        pw.println(prefix + TAG);
        pw.println(innerPrefix + "mState=" + stateToString(mState));
        pw.println(innerPrefix + "mInFixedRotation=" + mInFixedRotation);
        pw.println(innerPrefix + "mIsPipBoundsChangingWithDisplay="
                + mIsPipBoundsChangingWithDisplay);
    }
}
+93 −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.transition;

import android.graphics.Rect;
import android.os.IBinder;
import android.util.Pair;
import android.view.SurfaceControl;
import android.window.TransitionInfo;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;

/**
 * An implementation of {@link Transitions.TransitionObserver} to track of external transitions
 * that might affect a PiP task as well.
 */
public class PipDisplayChangeObserver implements Transitions.TransitionObserver {
    private final PipTransitionState mPipTransitionState;
    private final PipBoundsState mPipBoundsState;

    @Nullable
    private Pair<IBinder, TransitionInfo> mDisplayChangeTransition;

    public PipDisplayChangeObserver(PipTransitionState pipTransitionState,
            PipBoundsState pipBoundsState) {
        mPipTransitionState = pipTransitionState;
        mPipBoundsState = pipBoundsState;
    }

    @Override
    public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction) {
        if (TransitionUtil.hasDisplayChange(info)) {
            mDisplayChangeTransition = new Pair<>(transition, info);
        }
    }

    @Override
    public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
        if (mDisplayChangeTransition != null
                && mDisplayChangeTransition.first == merged) {
            maybeUpdatePipStateOnDisplayChange(mDisplayChangeTransition.second /* info */);
            mPipTransitionState.setIsPipBoundsChangingWithDisplay(false);
            mDisplayChangeTransition = null;
        }
    }

    @Override
    public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
        if (mDisplayChangeTransition != null
                && mDisplayChangeTransition.first == transition) {
            maybeUpdatePipStateOnDisplayChange(mDisplayChangeTransition.second /* info */);
            mPipTransitionState.setIsPipBoundsChangingWithDisplay(false);
            mDisplayChangeTransition = null;
        }
    }

    @VisibleForTesting
    @Nullable
    Pair<IBinder, TransitionInfo> getDisplayChangeTransition() {
        return mDisplayChangeTransition;
    }

    private void maybeUpdatePipStateOnDisplayChange(@NonNull TransitionInfo info) {
        final TransitionInfo.Change pipChange = PipTransitionUtils.getPipChange(info);
        if (pipChange == null) return;

        final Rect endBounds = pipChange.getEndAbsBounds();
        mPipBoundsState.setBounds(endBounds);
    }
}
+21 −0
Original line number Diff line number Diff line
@@ -267,6 +267,27 @@ public class PipTransitionStateTest extends ShellTestCase {
                mPipTransitionState.getOnIdlePipTransitionStateRunnable());
    }

    @Test
    public void testSetIsPipBoundsChangingWithDisplay_toFalse_thenIdle() {
        when(mMainHandler.obtainMessage(anyInt())).thenAnswer(invocation ->
                new Message().setWhat(invocation.getArgument(0)));

        // Pick an initially idle ENTERED_PIP state
        mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
        // Enter an non-idle state as PiP bounds change with the display
        mPipTransitionState.setIsPipBoundsChangingWithDisplay(true);

        final Runnable onIdleRunnable = mock(Runnable.class);
        mPipTransitionState.setOnIdlePipTransitionStateRunnable(onIdleRunnable);

        // We are supposed to be in a non-idle state, so the runnable should not be posted yet.
        verify(mMainHandler, never()).sendMessage(any());

        mPipTransitionState.setIsPipBoundsChangingWithDisplay(false);
        verify(mMainHandler, times(1))
                .sendMessage(argThat(msg -> msg.getCallback() == onIdleRunnable));
    }

    @Test
    public void testPostState_noImmediateStateChange_postedOnHandler() {
        mPipTransitionState.setState(PipTransitionState.UNDEFINED);
Loading