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

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

Merge "Migrate FlickerPiPTests to WmShellFlickerTests."

parents ac28321d bc0e28b4
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.flicker.helpers

import android.app.Instrumentation
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.waitForIME
import org.junit.Assert

open class ImeAppHelper(
    instr: Instrumentation,
    launcherName: String = "ImeApp",
    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
            .getInstance(instr)
            .launcherStrategy
) : FlickerAppHelper(instr, launcherName, launcherStrategy) {
    open fun openIME(device: UiDevice) {
        val editText = device.wait(
                Until.findObject(By.res(getPackage(), "plain_text_input")),
                FIND_TIMEOUT)
        Assert.assertNotNull("Text field not found, this usually happens when the device " +
                "was left in an unknown state (e.g. in split screen)", editText)
        editText.click()
        if (!device.waitForIME()) {
            Assert.fail("IME did not appear")
        }
    }

    open fun closeIME(device: UiDevice) {
        device.pressBack()
        // Using only the AccessibilityInfo it is not possible to identify if the IME is active
        device.waitForIdle(1000)
    }
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized

/**
 * Test Pip launch.
 * To run this test: `atest FlickerTests:PipToAppTest`
 * To run this test: `atest WMShellFlickerTests:PipToAppTest`
 */
@RequiresDevice
@RunWith(Parameterized::class)
+217 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.flicker.pip

import android.content.ComponentName
import android.graphics.Region
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.util.Log
import android.view.Surface
import android.view.WindowManager
import androidx.test.filters.RequiresDevice
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.dsl.runWithFlicker
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
import java.io.IOException

/**
 * Test Pip launch.
 * To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
 */
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipKeyboardTest(
    rotationName: String,
    rotation: Int
) : PipTestBase(rotationName, rotation) {
    private val windowManager: WindowManager =
            instrumentation.context.getSystemService(WindowManager::class.java)

    private val keyboardApp = ImeAppHelper(instrumentation, "ImeApp",
            LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy)

    private val KEYBOARD_ACTIVITY: ComponentName = ComponentName.createRelative(
            "com.android.wm.shell.flicker.testapp", ".ImeActivity")
    private val PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
    private val INPUT_METHOD_WINDOW_NAME = "InputMethod"

    private val testRepetitions = 10

    private val keyboardScenario: FlickerBuilder
        get() = FlickerBuilder(instrumentation).apply {
            repeat { testRepetitions }
            // disable layer tracing
            withLayerTracing { null }
            setup {
                test {
                    device.wakeUpAndGoToHomeScreen()
                    device.pressHome()
                    // launch our target pip app
                    testApp.open()
                    this.setRotation(rotation)
                    testApp.clickEnterPipButton(device)
                    // open an app with an input field and a keyboard
                    // UiAutomator doesn't support to launch the multiple Activities in a task.
                    // So use launchActivity() for the Keyboard Activity.
                    launchActivity(KEYBOARD_ACTIVITY)
                }
            }
            teardown {
                test {
                    keyboardApp.exit()

                    if (device.hasPipWindow()) {
                        device.closePipWindow()
                    }
                    testApp.exit()
                    this.setRotation(Surface.ROTATION_0)
                }
            }
        }

    /** Ensure the pip window remains visible throughout any keyboard interactions. */
    @Test
    fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
        val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
        runWithFlicker(keyboardScenario) {
            withTestName { testTag }
            transitions {
                // open the soft keyboard
                keyboardApp.openIME(device)

                // then close it again
                keyboardApp.closeIME(device)
            }
            assertions {
                windowManagerTrace {
                    all("PiP window must remain inside visible bounds") {
                        coversAtMostRegion(
                                partialWindowTitle = "PipActivity",
                                region = Region(windowManager.maximumWindowMetrics.bounds)
                        )
                    }
                }
            }
        }
    }

    /** Ensure the pip window does not obscure the keyboard. */
    @Test
    fun pipWindow_doesNotObscure_keyboard() {
        val testTag = "pipWindow_doesNotObscure_keyboard"
        runWithFlicker(keyboardScenario) {
            withTestName { testTag }
            transitions {
                // open the soft keyboard
                keyboardApp.openIME(device)
            }
            teardown {
                eachRun {
                    // close the keyboard
                    keyboardApp.closeIME(device)
                }
            }
            assertions {
                windowManagerTrace {
                    end {
                        isAboveWindow(INPUT_METHOD_WINDOW_NAME, PIP_ACTIVITY_WINDOW_NAME)
                    }
                }
            }
        }
    }

    private fun launchActivity(
        activity: ComponentName? = null,
        action: String? = null,
        flags: Set<Int> = setOf(),
        boolExtras: Map<String, Boolean> = mapOf(),
        intExtras: Map<String, Int> = mapOf(),
        stringExtras: Map<String, String> = mapOf()
    ) {
        require(activity != null || !action.isNullOrBlank()) {
            "Cannot launch an activity with neither activity name nor action!"
        }
        val command = composeCommand(
                "start", activity, action, flags, boolExtras, intExtras, stringExtras)
        executeShellCommand(command)
    }

    private fun composeCommand(
        command: String,
        activity: ComponentName?,
        action: String?,
        flags: Set<Int>,
        boolExtras: Map<String, Boolean>,
        intExtras: Map<String, Int>,
        stringExtras: Map<String, String>
    ): String = buildString {
        append("am ")
        append(command)
        activity?.let {
            append(" -n ")
            append(it.flattenToShortString())
        }
        action?.let {
            append(" -a ")
            append(it)
        }
        flags.forEach {
            append(" -f ")
            append(it)
        }
        boolExtras.forEach {
            append(it.withFlag("ez"))
        }
        intExtras.forEach {
            append(it.withFlag("ei"))
        }
        stringExtras.forEach {
            append(it.withFlag("es"))
        }
    }

    private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"

    private fun executeShellCommand(cmd: String): String {
        try {
            return SystemUtil.runShellCommand(instrumentation, cmd)
        } catch (e: IOException) {
            Log.e("FlickerTests", "Error running shell command: $cmd")
            throw e
        }
    }

    companion object {
        @Parameterized.Parameters(name = "{0}")
        @JvmStatic
        fun getParams(): Collection<Array<Any>> {
            val supportedRotations = intArrayOf(Surface.ROTATION_0)
            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
        }
    }
}
 No newline at end of file
+9 −0
Original line number Diff line number Diff line
@@ -33,5 +33,14 @@
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".ImeActivity"
             android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
             android:label="ImeApp"
             android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
+27 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
 Copyright 2018 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.
-->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusableInTouchMode="true"
    android:background="@android:color/holo_green_light">
    <EditText android:id="@+id/plain_text_input"
              android:layout_height="wrap_content"
              android:layout_width="match_parent"
              android:inputType="text"/>
</LinearLayout>
Loading