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

Commit 632a1e17 authored by Hongwei Wang's avatar Hongwei Wang Committed by Android (Google) Code Review
Browse files

Merge "[PIP2] Send isTransitioningToPip callback" into main

parents d5252ef2 c6bd9f68
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.pip2.phone.PipUiStateChangeController;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -73,10 +74,11 @@ public abstract class Pip2Module {
            Optional<PipController> pipController,
            PipTouchHandler pipTouchHandler,
            @NonNull PipScheduler pipScheduler,
            @NonNull PipTransitionState pipStackListenerController) {
            @NonNull PipTransitionState pipStackListenerController,
            @NonNull PipUiStateChangeController pipUiStateChangeController) {
        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
                pipStackListenerController);
                pipStackListenerController, pipUiStateChangeController);
    }

    @WMSingleton
@@ -181,4 +183,11 @@ public abstract class Pip2Module {
    static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
        return new PipTransitionState(handler);
    }

    @WMSingleton
    @Provides
    static PipUiStateChangeController providePipUiStateChangeController(
            PipTransitionState pipTransitionState) {
        return new PipUiStateChangeController(pipTransitionState);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -119,7 +119,8 @@ public class PipTransition extends PipTransitionController implements
            PipMenuController pipMenuController,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipScheduler pipScheduler,
            PipTransitionState pipTransitionState) {
            PipTransitionState pipTransitionState,
            PipUiStateChangeController pipUiStateChangeController) {
        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                pipBoundsAlgorithm);

+83 −0
Original line number Diff line number Diff line
/*
 * 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.wm.shell.pip2.phone;

import android.app.ActivityTaskManager;
import android.app.Flags;
import android.app.PictureInPictureUiState;
import android.os.Bundle;
import android.os.RemoteException;

import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.util.function.Consumer;

/**
 * Controller class manages the {@link android.app.PictureInPictureUiState} callbacks sent to app.
 */
public class PipUiStateChangeController implements
        PipTransitionState.PipTransitionStateChangedListener {

    private final PipTransitionState mPipTransitionState;

    private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer;

    public PipUiStateChangeController(PipTransitionState pipTransitionState) {
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this);
        mPictureInPictureUiStateConsumer = pictureInPictureUiState -> {
            try {
                ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
                        pictureInPictureUiState);
            } catch (RemoteException | IllegalStateException e) {
                ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                        "Failed to send PictureInPictureUiState.");
            }
        };
    }

    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
        if (newState == PipTransitionState.SWIPING_TO_PIP) {
            onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */);
        } else if (newState == PipTransitionState.ENTERING_PIP
                && !mPipTransitionState.isInSwipePipToHomeTransition()) {
            onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */);
        } else if (newState == PipTransitionState.ENTERED_PIP) {
            onIsTransitioningToPipUiStateChange(false /* isTransitioningToPip */);
        }
    }

    @VisibleForTesting
    void setPictureInPictureUiStateConsumer(Consumer<PictureInPictureUiState> consumer) {
        mPictureInPictureUiStateConsumer = consumer;
    }

    private void onIsTransitioningToPipUiStateChange(boolean isTransitioningToPip) {
        if (Flags.enablePipUiStateCallbackOnEntering()
                && mPictureInPictureUiStateConsumer != null) {
            mPictureInPictureUiStateConsumer.accept(new PictureInPictureUiState.Builder()
                    .setTransitioningToPip(isTransitioningToPip)
                    .build());
        }
    }
}
+2 −4
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

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

import android.os.Bundle;
import android.os.Handler;
@@ -22,8 +22,6 @@ import android.os.Parcelable;
import android.testing.AndroidTestingRunner;

import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.pip2.phone.PipTransitionState;

import junit.framework.Assert;

@@ -33,7 +31,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;

/**
 * Unit test against {@link PhoneSizeSpecSource}.
 * Unit test against {@link PipTransitionState}.
 *
 * This test mocks the PiP2 flag to be true.
 */
+123 −0
Original line number Diff line number Diff line
/*
 * 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.wm.shell.pip2.phone;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.app.Flags;
import android.app.PictureInPictureUiState;
import android.os.Bundle;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;

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;

import java.util.function.Consumer;

/**
 * Unit test against {@link PipUiStateChangeController}.
 */
@RunWith(AndroidTestingRunner.class)
public class PipUiStateChangeControllerTests {

    @Mock
    private PipTransitionState mPipTransitionState;

    private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer;
    private ArgumentCaptor<PictureInPictureUiState> mArgumentCaptor;

    private PipUiStateChangeController mPipUiStateChangeController;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mPipUiStateChangeController = new PipUiStateChangeController(mPipTransitionState);
        mPictureInPictureUiStateConsumer = spy(pictureInPictureUiState -> {});
        mPipUiStateChangeController.setPictureInPictureUiStateConsumer(
                mPictureInPictureUiStateConsumer);
        mArgumentCaptor = ArgumentCaptor.forClass(PictureInPictureUiState.class);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
    public void onPipTransitionStateChanged_swipePipStart_callbackIsTransitioningToPipTrue() {
        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);

        mPipUiStateChangeController.onPipTransitionStateChanged(
                PipTransitionState.UNDEFINED, PipTransitionState.SWIPING_TO_PIP, Bundle.EMPTY);

        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
        assertTrue(mArgumentCaptor.getValue().isTransitioningToPip());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
    public void onPipTransitionStateChanged_swipePipOngoing_noCallbackIsTransitioningToPip() {
        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);

        mPipUiStateChangeController.onPipTransitionStateChanged(
                PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);

        verifyZeroInteractions(mPictureInPictureUiStateConsumer);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
    public void onPipTransitionStateChanged_swipePipFinish_callbackIsTransitioningToPipFalse() {
        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);

        mPipUiStateChangeController.onPipTransitionStateChanged(
                PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY);

        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
        assertFalse(mArgumentCaptor.getValue().isTransitioningToPip());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
    public void onPipTransitionStateChanged_tapHomeStart_callbackIsTransitioningToPipTrue() {
        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false);

        mPipUiStateChangeController.onPipTransitionStateChanged(
                PipTransitionState.UNDEFINED, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);

        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
        assertTrue(mArgumentCaptor.getValue().isTransitioningToPip());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
    public void onPipTransitionStateChanged_tapHomeFinish_callbackIsTransitioningToPipFalse() {
        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false);

        mPipUiStateChangeController.onPipTransitionStateChanged(
                PipTransitionState.ENTERING_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY);

        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
        assertFalse(mArgumentCaptor.getValue().isTransitioningToPip());
    }
}