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

Commit 0f352465 authored by Eric Lin's avatar Eric Lin Committed by Android (Google) Code Review
Browse files

Merge "Refactor BubblesTransitionObserverTest for clarity." into main

parents b09515cb 80bea642
Loading
Loading
Loading
Loading
+0 −251
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bubbles;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.IWindowContainerToken;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;

import androidx.test.filters.SmallTest;

import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.transition.TransitionInfoBuilder;

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

/**
 * Tests of {@link BubblesTransitionObserver}.
 */
@SmallTest
public class BubblesTransitionObserverTest extends ShellTestCase {

    @Mock
    private BubbleController mBubbleController;
    @Mock
    private BubbleData mBubbleData;

    @Mock
    private IBinder mTransition;
    @Mock
    private SurfaceControl.Transaction mStartT;
    @Mock
    private SurfaceControl.Transaction mFinishT;

    @Mock
    private Bubble mBubble;

    private BubblesTransitionObserver mTransitionObserver;


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

        mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData);
    }

    @Test
    public void testOnTransitionReady_open_collapsesStack() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_openOnAnotherDisplay_doesNotCollapseStack() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(2);
        taskInfo.displayId = 1; // not DEFAULT_DISPLAY
        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, taskInfo);

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_toFront_collapsesStack() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_noTaskInfo_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        // Null task info
        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */);

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_noTaskId_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        // Invalid task id
        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT,
                createTaskInfo(INVALID_TASK_ID));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_notOpening_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        // Transits that aren't opening
        TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2));
        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3));
        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4));
        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_stackAnimating_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating

        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_stackNotExpanded_skip() {
        when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_noSelectedBubble_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    @Test
    public void testOnTransitionReady_openingMatchesExpanded_skip() {
        when(mBubbleData.isExpanded()).thenReturn(true);
        when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
        when(mBubble.getTaskId()).thenReturn(1);
        when(mBubbleController.isStackAnimating()).thenReturn(false);

        // What's moving to front is same as the opened bubble
        TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1));

        mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);

        verify(mBubbleData, never()).setExpanded(eq(false));
    }

    private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) {
        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        taskInfo.taskId = taskId;
        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        return taskInfo;
    }

    private TransitionInfo createTransitionInfo(int changeType,
            ActivityManager.RunningTaskInfo info) {
        final TransitionInfo.Change change = new TransitionInfo.Change(
                new WindowContainerToken(mock(IWindowContainerToken.class)),
                mock(SurfaceControl.class));
        change.setMode(changeType);
        change.setTaskInfo(info);

        return new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                .addChange(change).build();
    }

}
+210 −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.bubbles

import android.app.ActivityManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.platform.test.annotations.EnableFlags
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.view.WindowManager.TransitionType
import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub

/**
 * Unit tests of [BubblesTransitionObserver].
 *
 * Build/Install/Run:
 * atest WMShellUnitTests:BubblesTransitionObserverTest
 */
@SmallTest
@RunWith(TestParameterInjector::class)
class BubblesTransitionObserverTest : ShellTestCase() {

    private val bubble = mock<Bubble> {
        on { taskId } doReturn 1
    }
    private val bubbleData = mock<BubbleData> {
        on { isExpanded } doReturn true
        on { selectedBubble } doReturn bubble
    }
    private val bubbleController = mock<BubbleController> {
        on { isStackAnimating } doReturn false
    }
    private val transitionObserver = BubblesTransitionObserver(bubbleController, bubbleData)

    @Test
    fun testOnTransitionReady_openWithTaskTransition_collapsesStack() {
        val info = createTaskTransition(TRANSIT_OPEN, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_openTaskOnAnotherDisplay_doesNotCollapseStack() {
        val taskInfo = createTaskInfo(taskId = 2).apply {
            displayId = 1 // not DEFAULT_DISPLAY
        }
        val info = createTaskTransition(TRANSIT_OPEN, taskInfo)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE)
    @Test
    fun testOnTransitionReady_openTaskByBubble_doesNotCollapseStack() {
        val taskInfo = createTaskInfo(taskId = 2)
        bubbleController.stub {
            on { shouldBeAppBubble(taskInfo) } doReturn true // Launched by another bubble.
        }
        val info = createTaskTransition(TRANSIT_OPEN, taskInfo)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_toFront_collapsesStack() {
        val info = createTaskTransition(TRANSIT_TO_FRONT, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_noTaskInfoNoActivityInfo_skip() {
        val info = createTaskTransition(TRANSIT_TO_FRONT, taskInfo = null) // Null task info

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_noTaskId_skip() {
        val info = createTaskTransition(TRANSIT_OPEN, taskId = INVALID_TASK_ID) // Invalid task id

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_notOpening_skip(@TestParameter tc: TransitNotOpeningTestCase) {
        transitionObserver.onTransitionReady(mock(), tc.info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_stackAnimating_skip() {
        bubbleController.stub {
            on { isStackAnimating } doReturn true // Stack is animating
        }
        val info = createTaskTransition(TRANSIT_OPEN, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_stackNotExpanded_skip() {
        bubbleData.stub {
            on { isExpanded } doReturn false // Stack is not expanded
        }
        val info = createTaskTransition(TRANSIT_TO_FRONT, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_noSelectedBubble_skip() {
        bubbleData.stub {
            on { selectedBubble } doReturn null // No selected bubble
        }
        val info = createTaskTransition(TRANSIT_OPEN, taskId = 2)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    @Test
    fun testOnTransitionReady_openingMatchesExpanded_skip() {
        // What's moving to front is the same as the opened bubble.
        val info = createTaskTransition(TRANSIT_TO_FRONT, taskId = 1)

        transitionObserver.onTransitionReady(mock(), info, mock(), mock())

        verify(bubbleData, never()).setExpanded(false)
    }

    // Transits that aren't opening.
    enum class TransitNotOpeningTestCase(
        @TransitionType private val changeType: Int,
        private val taskId: Int,
    ) {
        CHANGE(TRANSIT_CHANGE, taskId = 2),
        CLOSE(TRANSIT_CLOSE, taskId = 3),
        BACK(TRANSIT_TO_BACK, taskId = 4);

        val info: TransitionInfo
            get() = createTaskTransition(changeType, taskId)
    }

    companion object {
        private fun createTaskTransition(@TransitionType changeType: Int, taskId: Int) =
            createTaskTransition(changeType, taskInfo = createTaskInfo(taskId))

        private fun createTaskTransition(
            @TransitionType changeType: Int,
            taskInfo: ActivityManager.RunningTaskInfo?,
        ) = TransitionInfoBuilder(TRANSIT_OPEN).addChange(changeType, taskInfo).build()

        private fun createTaskInfo(taskId: Int) = ActivityManager.RunningTaskInfo().apply {
            this.taskId = taskId
            this.token = WindowContainerToken(mock() /* realToken */)
            this.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
        }
    }
}