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

Commit 74a1163a authored by Eric Lin's avatar Eric Lin
Browse files

Refactor and share TransitionInfoBuilder for shell tests.

This commit refactors the `TransitionInfoBuilder` utility by moving it
from the `WMShellUnitTests` module to the shared `WMShellTests-utils`
library, located at `WindowManager/Shell/tests/util`. This change
facilitates the sharing of the builder between the `WMShellUnitTests`
and `CarWMShellUnitTests` modules, promoting code reuse for future
updates.

The `TransitionInfoBuilder` has been rewritten in Kotlin with detailed
KDoc, improving its understandability and maintainability. Additionally,
the API for constructing `TransitionInfo.Change` objects has been
streamlined. Specifically, verbose calls like `addChange(mode, flags,
null, null, null)` have been replaced with a more concise and expressive
overloaded method `addChange(mode, flags)`. This simplification enhances
the clarity and readability of the test code.

BUG: 387193964
Test: atest CarWMShellUnitTests WMShellUnitTests
Test: atest WMShellRobolectricTests
Flag: EXEMPT refactor
Change-Id: I6604db50f8e6e10497e3d84db16c8658253b3c2d
parent 990bafd3
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -44,14 +44,14 @@ android_robolectric_test {
    // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
    exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
    static_libs: [
        "WMShellTests-utils",
        "junit",
        "androidx.core_core-animation-testing",
        "androidx.test.runner",
        "androidx.test.rules",
        "androidx.test.ext.junit",
        "mockito-robolectric-prebuilt",
        "mockito-robolectric-prebuilt", // mockito deps order matters!
        "mockito-kotlin2",
        "WMShellTests-utils", // order matters because it depends on mockito-kotlin2
        "platform-parametric-runner-lib",
        "truth",
        "flag-junit-base",
+1 −2
Original line number Diff line number Diff line
@@ -168,8 +168,7 @@ public class StartingWindowControllerTests extends ShellTestCase {
        st.clear();
        final IBinder secondToken = new Binder();
        final TransitionInfo secondInfo = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(TRANSIT_OPEN, FLAG_IS_BEHIND_STARTING_WINDOW,
                        null, null, null).build();
                .addChange(TRANSIT_OPEN, FLAG_IS_BEHIND_STARTING_WINDOW).build();
        observer.onAddingWindow(taskId, token, appToken);
        observer.onTransitionReady(token, info, st, st);
        waitTransactionCommit(st);
+0 −115
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.transition;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;

import static org.mockito.Mockito.mock;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;

/**
 * Utility for creating/editing synthetic TransitionInfos for tests.
 */
public class TransitionInfoBuilder {
    final TransitionInfo mInfo;
    static final int DISPLAY_ID = 0;

    public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
        this(type, 0 /* flags */);
    }

    public TransitionInfoBuilder(@WindowManager.TransitionType int type,
            @WindowManager.TransitionFlags int flags) {
        this(type, flags, false /* asNoOp */);
    }

    public TransitionInfoBuilder(@WindowManager.TransitionType int type,
            @WindowManager.TransitionFlags int flags, boolean asNoOp) {
        mInfo = new TransitionInfo(type, flags);
        if (!asNoOp) {
            mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
        }
    }

    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
            @TransitionInfo.ChangeFlags int flags,
            @Nullable ActivityManager.RunningTaskInfo taskInfo,
            @Nullable ComponentName activityComponent, @Nullable IBinder taskFragmentToken) {
        final TransitionInfo.Change change = new TransitionInfo.Change(
                taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */));
        change.setMode(mode);
        change.setFlags(flags);
        change.setTaskInfo(taskInfo);
        change.setActivityComponent(activityComponent);
        change.setTaskFragmentToken(taskFragmentToken);
        return addChange(change);
    }

    /** Add a change to the TransitionInfo */
    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
            @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
        return addChange(mode, flags, taskInfo, null /* activityComponent */,
                null /* taskFragmentToken */);
    }

    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
            ActivityManager.RunningTaskInfo taskInfo) {
        return addChange(mode, TransitionInfo.FLAG_NONE, taskInfo);
    }

    /** Add a change to the TransitionInfo */
    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
            ComponentName activityComponent) {
        return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskinfo */, activityComponent,
                null /* taskFragmentToken */);
    }

    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
        return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */);
    }

    /** Add a change with a TaskFragment token to the TransitionInfo */
    public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
            @Nullable IBinder taskFragmentToken) {
        return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */,
                null /* activityComponent */, taskFragmentToken);
    }

    public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
        change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
        mInfo.addChange(change);
        return this;
    }

    public TransitionInfo build() {
        return mInfo;
    }

    private static SurfaceControl createMockSurface(boolean valid) {
        SurfaceControl sc = mock(SurfaceControl.class);
        doReturn(valid).when(sc).isValid();
        doReturn("TestSurface").when(sc).toString();
        return sc;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ java_library {
    ],
    static_libs: [
        "WindowManager-Shell",
        "mockito-kotlin2",
        "truth",
    ],
}
+149 −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.transition

import android.app.ActivityManager
import android.content.ComponentName
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

/**
 * Utility for creating/editing synthetic [TransitionInfo] for tests.
 *
 * @param type the type of the transition. See [WindowManager.TransitionType].
 * @param flags the flags for the transition. See [WindowManager.TransitionFlags].
 * @param asNoOp if true, the root leash will not be added.
 */
class TransitionInfoBuilder @JvmOverloads constructor(
    @WindowManager.TransitionType type: Int,
    @WindowManager.TransitionFlags flags: Int = 0,
    asNoOp: Boolean = false,
) {
    // The underlying TransitionInfo object being built.
    private val info: TransitionInfo = TransitionInfo(type, flags).apply {
        if (asNoOp) {
            return@apply
        }
        // Add a root leash by default, unless asNoOp is true.
        addRootLeash(
            DISPLAY_ID,
            createMockSurface(), /* leash */
            0, /* offsetLeft */
            0, /* offsetTop */
        )
    }

    /** Adds a change to the [TransitionInfo]. */
    @JvmOverloads
    fun addChange(
        @WindowManager.TransitionType mode: Int,
        @TransitionInfo.ChangeFlags flags: Int = TransitionInfo.FLAG_NONE,
    ) = addChange(mode, flags, activityComponent = null, taskInfo = null)

    /** Adds a change to the [TransitionInfo] for task transition with [flags]. */
    fun addChange(
        @WindowManager.TransitionType mode: Int,
        @TransitionInfo.ChangeFlags flags: Int,
        taskInfo: ActivityManager.RunningTaskInfo?,
    ) = addChange(mode, flags, activityComponent = null, taskInfo = taskInfo)

    /** Adds a change to the [TransitionInfo] for task transition. */
    fun addChange(
        @WindowManager.TransitionType mode: Int,
        taskInfo: ActivityManager.RunningTaskInfo?,
    ) = addChange(mode, activityComponent = null, taskInfo = taskInfo)

    /** Adds a change to the [TransitionInfo] for activity transition. */
    fun addChange(@WindowManager.TransitionType mode: Int, activityComponent: ComponentName?) =
        addChange(mode, activityComponent = activityComponent, taskInfo = null)

    /** Add a change to the [TransitionInfo] for task fragment. */
    fun addChange(@WindowManager.TransitionType mode: Int, taskFragmentToken: IBinder?) =
        addChange(
            mode,
            activityComponent = null,
            taskInfo = null,
            taskFragmentToken = taskFragmentToken,
        )

    /**
     * Adds a change to the [TransitionInfo].
     *
     * @param mode the mode of the change. See [WindowManager.TransitionType].
     * @param flags the flags for this change. See [TransitionInfo.ChangeFlags].
     * @param activityComponent the component associated with this change for activity transition.
     * @param taskInfo the task info associated with this change for task transition.
     * @param taskFragmentToken the task fragment token associated with this change.
     * @return this [TransitionInfoBuilder] instance for chaining.
     */
    private fun addChange(
        @WindowManager.TransitionType mode: Int,
        @TransitionInfo.ChangeFlags flags: Int = TransitionInfo.FLAG_NONE,
        activityComponent: ComponentName? = null,
        taskInfo: ActivityManager.RunningTaskInfo? = null,
        taskFragmentToken: IBinder? = null,
    ): TransitionInfoBuilder {
        val container = taskInfo?.token
        val leash = createMockSurface()
        val change = TransitionInfo.Change(container, leash).apply {
            setMode(mode)
            setFlags(flags)
            setActivityComponent(activityComponent)
            setTaskInfo(taskInfo)
            setTaskFragmentToken(taskFragmentToken)
        }
        return addChange(change)
    }

    /**
     * Adds a pre-configured change to the [TransitionInfo].
     *
     * @param change the TransitionInfo.Change object to add.
     * @return this TransitionInfoBuilder instance for chaining.
     */
    fun addChange(change: TransitionInfo.Change): TransitionInfoBuilder {
        // Set the display ID for the change.
        change.setDisplayId(DISPLAY_ID /* start */, DISPLAY_ID /* end */)
        // Add the change to the internal TransitionInfo object.
        info.addChange(change)
        return this // Return this for fluent builder pattern.
    }

    /**
     * Builds and returns the configured [TransitionInfo] object.
     *
     * @return the constructed [TransitionInfo].
     */
    fun build(): TransitionInfo {
        return info
    }

    companion object {
        // Default display ID for root leashes and changes.
        const val DISPLAY_ID = 0

        // Create a mock SurfaceControl for testing.
        private fun createMockSurface() = mock<SurfaceControl> {
            on { isValid } doReturn true
            on { toString() } doReturn "TestSurface"
        }
    }
}