2/ Update app helpers for new apps

Migrate helpers from WMShell app helpers to flicker test packages

Test: atest FlickerLibTest && atest FlickerTests
Change-Id: I98cd3b850e8b873f09c3df2ac5f079b3209a6b44
This commit is contained in:
Nataniel Borges
2022-09-19 16:14:11 +00:00
parent 9c3d628683
commit 2c0caa0083
28 changed files with 621 additions and 719 deletions

View File

@@ -0,0 +1,55 @@
/*
* 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
import android.app.Instrumentation
import android.content.Context
import android.provider.Settings
import android.util.Log
import com.android.compatibility.common.util.SystemUtil
import java.io.IOException
object MultiWindowUtils {
private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
try {
SystemUtil.runShellCommand(instrumentation, cmd)
} catch (e: IOException) {
Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
}
}
fun getDevEnableNonResizableMultiWindow(context: Context): Int =
Settings.Global.getInt(context.contentResolver,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
Settings.Global.putInt(context.contentResolver,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
executeShellCommand(
instrumentation,
createConfigSupportsNonResizableMultiWindowCommand(configValue))
fun resetMultiWindowConfig(instrumentation: Instrumentation) =
executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
"wm set-multi-window-config --supportsNonResizable $configValue"
private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
}

View File

@@ -1,67 +0,0 @@
/*
* 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.content.pm.PackageManager.FEATURE_LEANBACK
import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.util.Log
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.traces.common.IComponentNameMatcher
import java.io.IOException
abstract class BaseAppHelper(
instrumentation: Instrumentation,
launcherName: String,
component: IComponentNameMatcher
) : StandardAppHelper(
instrumentation,
launcherName,
component,
LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
) {
private val appSelector = By.pkg(`package`).depth(0)
protected val isTelevision: Boolean
get() = context.packageManager.run {
hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
}
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
fun waitUntilClosed(): Boolean {
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
companion object {
private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
try {
SystemUtil.runShellCommand(instrumentation, cmd)
} catch (e: IOException) {
Log.e("BaseAppHelper", "executeShellCommand error! $e")
}
}
}
}

View File

@@ -1,27 +0,0 @@
/*
* 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 com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.FixedActivity.LABEL,
Components.FixedActivity.COMPONENT.toFlickerComponent()
)

View File

@@ -1,76 +0,0 @@
/*
* 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 androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.testapp.Components
open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.ImeActivity.LABEL,
Components.ImeActivity.COMPONENT.toFlickerComponent()
) {
/**
* Opens the IME and wait for it to be displayed
*
* @param wmHelper Helper used to wait for WindowManager states
*/
open fun openIME(wmHelper: WindowManagerStateHelper) {
if (!isTelevision) {
val editText = uiDevice.wait(
Until.findObject(By.res(getPackage(), "plain_text_input")),
FIND_TIMEOUT)
require(editText != null) {
"Text field not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
wmHelper.StateSyncBuilder()
.withImeShown()
.waitForAndVerify()
} else {
// If we do the same thing as above - editText.click() - on TV, that's going to force TV
// into the touch mode. We really don't want that.
launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME)
}
}
/**
* Opens the IME and wait for it to be gone
*
* @param wmHelper Helper used to wait for WindowManager states
*/
open fun closeIME(wmHelper: WindowManagerStateHelper) {
if (!isTelevision) {
uiDevice.pressBack()
// Using only the AccessibilityInfo it is not possible to identify if the IME is active
wmHelper.StateSyncBuilder()
.withImeGone()
.waitForAndVerify()
} else {
// While pressing the back button should close the IME on TV as well, it may also lead
// to the app closing. So let's instead just ask the app to close the IME.
launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
}
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (C) 2021 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 com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.LaunchBubbleActivity.LABEL,
Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
) {
companion object {
const val TIMEOUT_MS = 3_000L
}
}

View File

@@ -1,354 +0,0 @@
/*
* 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.graphics.Point
import android.os.SystemClock
import android.view.InputDevice
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.IComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
class SplitScreenHelper(
instrumentation: Instrumentation,
activityLabel: String,
componentInfo: IComponentNameMatcher
) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
companion object {
const val TIMEOUT_MS = 3_000L
const val DRAG_DURATION_MS = 1_000L
const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
const val DIVIDER_BAR = "docked_divider_handle"
const val GESTURE_STEP_MS = 16L
const val LONG_PRESS_TIME_MS = 100L
private val notificationScrollerSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
private val notificationContentSelector: BySelector
get() = By.text("Notification content")
private val dividerBarSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
instrumentation,
Components.SplitScreenActivity.LABEL,
Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
)
fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
instrumentation,
Components.SplitScreenSecondaryActivity.LABEL,
Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
)
fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
instrumentation,
Components.NonResizeableActivity.LABEL,
Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
)
fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
instrumentation,
Components.SendNotificationActivity.LABEL,
Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
)
fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
instrumentation,
Components.ImeActivity.LABEL,
Components.ImeActivity.COMPONENT.toFlickerComponent()
)
fun waitForSplitComplete(
wmHelper: WindowManagerStateHelper,
primaryApp: IComponentMatcher,
secondaryApp: IComponentMatcher,
) {
wmHelper.StateSyncBuilder()
.withWindowSurfaceAppeared(primaryApp)
.withWindowSurfaceAppeared(secondaryApp)
.withSplitDividerVisible()
.waitForAndVerify()
}
fun enterSplit(
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
primaryApp: SplitScreenHelper,
secondaryApp: SplitScreenHelper
) {
tapl.workspace.switchToOverview().dismissAllTasks()
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
splitFromOverview(tapl)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
fun splitFromOverview(tapl: LauncherInstrumentation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
if (tapl.isTablet) {
tapl.workspace.switchToOverview().overviewActions
.clickSplit()
.currentTask
.open()
} else {
tapl.workspace.switchToOverview().currentTask
.tapMenu()
.tapSplitMenuItem()
.currentTask
.open()
}
SystemClock.sleep(TIMEOUT_MS)
}
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
wmHelper: WindowManagerStateHelper
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
// Pull down the notifications
device.swipe(
displayBounds.centerX(), 5,
displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
)
SystemClock.sleep(TIMEOUT_MS)
// Find the target notification
val notificationScroller = device.wait(
Until.findObject(notificationScrollerSelector), TIMEOUT_MS
)
var notificationContent = notificationScroller.findObject(notificationContentSelector)
while (notificationContent == null) {
device.swipe(
displayBounds.centerX(), displayBounds.centerY(),
displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
)
notificationContent = notificationScroller.findObject(notificationContentSelector)
}
// Drag to split
val dragStart = notificationContent.visibleCenter
val dragMiddle = Point(dragStart.x + 50, dragStart.y)
val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
val downTime = SystemClock.uptimeMillis()
touch(
instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
TIMEOUT_MS, dragStart
)
// It needs a horizontal movement to trigger the drag
touchMove(
instrumentation, downTime, SystemClock.uptimeMillis(),
DRAG_DURATION_MS, dragStart, dragMiddle
)
touchMove(
instrumentation, downTime, SystemClock.uptimeMillis(),
DRAG_DURATION_MS, dragMiddle, dragEnd
)
// Wait for a while to start splitting
SystemClock.sleep(TIMEOUT_MS)
touch(
instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
GESTURE_STEP_MS, dragEnd
)
SystemClock.sleep(TIMEOUT_MS)
}
fun touch(
instrumentation: Instrumentation,
action: Int,
downTime: Long,
eventTime: Long,
duration: Long,
point: Point
) {
val motionEvent = MotionEvent.obtain(
downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
)
motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
motionEvent.recycle()
SystemClock.sleep(duration)
}
fun touchMove(
instrumentation: Instrumentation,
downTime: Long,
eventTime: Long,
duration: Long,
from: Point,
to: Point
) {
val steps: Long = duration / GESTURE_STEP_MS
var currentTime = eventTime
var currentX = from.x.toFloat()
var currentY = from.y.toFloat()
val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
for (i in 1..steps) {
val motionMove = MotionEvent.obtain(
downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
)
motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
instrumentation.uiAutomation.injectInputEvent(motionMove, true)
motionMove.recycle()
currentTime += GESTURE_STEP_MS
if (i == steps - 1) {
currentX = to.x.toFloat()
currentY = to.y.toFloat()
} else {
currentX += stepX
currentY += stepY
}
SystemClock.sleep(GESTURE_STEP_MS)
}
}
fun longPress(
instrumentation: Instrumentation,
point: Point
) {
val downTime = SystemClock.uptimeMillis()
touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
SystemClock.sleep(LONG_PRESS_TIME_MS)
touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
}
fun createShortcutOnHotseatIfNotExist(
tapl: LauncherInstrumentation,
appName: String
) {
tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
val allApps = tapl.workspace.switchToAllApps()
allApps.freeze()
try {
allApps.getAppIcon(appName).dragToHotseat(0)
} finally {
allApps.unfreeze()
}
}
fun dragDividerToResizeAndWait(
device: UiDevice,
wmHelper: WindowManagerStateHelper
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
wmHelper.StateSyncBuilder()
.withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
.waitForAndVerify()
}
fun dragDividerToDismissSplit(
device: UiDevice,
wmHelper: WindowManagerStateHelper,
dragToRight: Boolean,
dragToBottom: Boolean
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
dividerBar.drag(Point(
if (dragToRight) {
displayBounds.width * 4 / 5
} else {
displayBounds.width * 1 / 5
},
if (dragToBottom) {
displayBounds.height * 4 / 5
} else {
displayBounds.height * 1 / 5
}))
}
fun doubleTapDividerToSwitch(device: UiDevice) {
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
val interval = (ViewConfiguration.getDoubleTapTimeout() +
ViewConfiguration.getDoubleTapMinTime()) / 2
dividerBar.click()
SystemClock.sleep(interval.toLong())
dividerBar.click()
}
fun copyContentInSplit(
instrumentation: Instrumentation,
device: UiDevice,
sourceApp: IComponentNameMatcher,
destinationApp: IComponentNameMatcher,
) {
// Copy text from sourceApp
val textView = device.wait(Until.findObject(
By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
longPress(instrumentation, textView.getVisibleCenter())
val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
copyBtn.click()
// Paste text to destinationApp
val editText = device.wait(Until.findObject(
By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
longPress(instrumentation, editText.getVisibleCenter())
val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
pasteBtn.click()
// Verify text
if (!textView.getText().contentEquals(editText.getText())) {
error("Fail to copy content in split")
}
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.tv
import android.app.Instrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
/**
* Helper class for PIP app on AndroidTV
*/
open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
private val appSelector = By.pkg(`package`).depth(0)
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
private fun focusOnObject(selector: BySelector): Boolean {
// We expect all the focusable UI elements to be arranged in a way so that it is possible
// to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
// from "the bottom".
repeat(FOCUS_ATTEMPTS) {
uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
?: error("The object we try to focus on is gone.")
uiDevice.pressDPadDown()
uiDevice.waitForIdle()
}
return false
}
override fun clickObject(resId: String) {
val selector = By.res(`package`, resId)
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
@Deprecated(
"Use PipAppHelper.closePipWindow(wmHelper) instead",
ReplaceWith("closePipWindow(wmHelper)")
)
override fun closePipWindow() {
uiDevice.closeTvPipWindow()
}
/**
* Taps the pip window and dismisses it by clicking on the X button.
*/
override fun closePipWindow(wmHelper: WindowManagerStateHelper) {
uiDevice.closeTvPipWindow()
// Wait for animation to complete.
wmHelper.StateSyncBuilder()
.withPipGone()
.withHomeActivityVisible()
.waitForAndVerify()
}
fun waitUntilClosed(): Boolean {
val appSelector = By.pkg(`package`).depth(0)
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
companion object {
private const val FOCUS_ATTEMPTS = 20
private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
}
}

View File

@@ -0,0 +1,341 @@
/*
* Copyright (C) 2022 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.splitscreen
import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
import android.view.InputDevice
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.IComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
internal object SplitScreenHelper {
internal const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
private const val DIVIDER_BAR = "docked_divider_handle"
private const val GESTURE_STEP_MS = 16L
private const val LONG_PRESS_TIME_MS = 100L
private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
private val notificationScrollerSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
private val notificationContentSelector: BySelector
get() = By.text("Notification content")
private val dividerBarSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
SimpleAppHelper(
instrumentation,
ActivityOptions.SplitScreen.Primary.LABEL,
ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
)
fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
SimpleAppHelper(
instrumentation,
ActivityOptions.SplitScreen.Secondary.LABEL,
ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
)
fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
NonResizeableAppHelper(instrumentation)
fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
NotificationAppHelper(instrumentation)
fun getIme(instrumentation: Instrumentation): ImeAppHelper =
ImeAppHelper(instrumentation)
fun waitForSplitComplete(
wmHelper: WindowManagerStateHelper,
primaryApp: IComponentMatcher,
secondaryApp: IComponentMatcher,
) {
wmHelper.StateSyncBuilder()
.withWindowSurfaceAppeared(primaryApp)
.withWindowSurfaceAppeared(secondaryApp)
.withSplitDividerVisible()
.waitForAndVerify()
}
fun enterSplit(
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
primaryApp: StandardAppHelper,
secondaryApp: StandardAppHelper
) {
tapl.workspace.switchToOverview().dismissAllTasks()
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
splitFromOverview(tapl)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
fun splitFromOverview(tapl: LauncherInstrumentation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
if (tapl.isTablet) {
tapl.workspace.switchToOverview().overviewActions
.clickSplit()
.currentTask
.open()
} else {
tapl.workspace.switchToOverview().currentTask
.tapMenu()
.tapSplitMenuItem()
.currentTask
.open()
}
SystemClock.sleep(TIMEOUT_MS)
}
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
wmHelper: WindowManagerStateHelper
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
// Pull down the notifications
device.swipe(
displayBounds.centerX(), 5,
displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
)
SystemClock.sleep(TIMEOUT_MS)
// Find the target notification
val notificationScroller = device.wait(
Until.findObject(notificationScrollerSelector), TIMEOUT_MS
)
var notificationContent = notificationScroller.findObject(notificationContentSelector)
while (notificationContent == null) {
device.swipe(
displayBounds.centerX(), displayBounds.centerY(),
displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
)
notificationContent = notificationScroller.findObject(notificationContentSelector)
}
// Drag to split
val dragStart = notificationContent.visibleCenter
val dragMiddle = Point(dragStart.x + 50, dragStart.y)
val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
val downTime = SystemClock.uptimeMillis()
touch(
instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
TIMEOUT_MS, dragStart
)
// It needs a horizontal movement to trigger the drag
touchMove(
instrumentation, downTime, SystemClock.uptimeMillis(),
DRAG_DURATION_MS, dragStart, dragMiddle
)
touchMove(
instrumentation, downTime, SystemClock.uptimeMillis(),
DRAG_DURATION_MS, dragMiddle, dragEnd
)
// Wait for a while to start splitting
SystemClock.sleep(TIMEOUT_MS)
touch(
instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
GESTURE_STEP_MS, dragEnd
)
SystemClock.sleep(TIMEOUT_MS)
}
fun touch(
instrumentation: Instrumentation,
action: Int,
downTime: Long,
eventTime: Long,
duration: Long,
point: Point
) {
val motionEvent = MotionEvent.obtain(
downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
)
motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
motionEvent.recycle()
SystemClock.sleep(duration)
}
fun touchMove(
instrumentation: Instrumentation,
downTime: Long,
eventTime: Long,
duration: Long,
from: Point,
to: Point
) {
val steps: Long = duration / GESTURE_STEP_MS
var currentTime = eventTime
var currentX = from.x.toFloat()
var currentY = from.y.toFloat()
val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
for (i in 1..steps) {
val motionMove = MotionEvent.obtain(
downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
)
motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
instrumentation.uiAutomation.injectInputEvent(motionMove, true)
motionMove.recycle()
currentTime += GESTURE_STEP_MS
if (i == steps - 1) {
currentX = to.x.toFloat()
currentY = to.y.toFloat()
} else {
currentX += stepX
currentY += stepY
}
SystemClock.sleep(GESTURE_STEP_MS)
}
}
fun longPress(
instrumentation: Instrumentation,
point: Point
) {
val downTime = SystemClock.uptimeMillis()
touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
SystemClock.sleep(LONG_PRESS_TIME_MS)
touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
}
fun createShortcutOnHotseatIfNotExist(
tapl: LauncherInstrumentation,
appName: String
) {
tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
val allApps = tapl.workspace.switchToAllApps()
allApps.freeze()
try {
allApps.getAppIcon(appName).dragToHotseat(0)
} finally {
allApps.unfreeze()
}
}
fun dragDividerToResizeAndWait(
device: UiDevice,
wmHelper: WindowManagerStateHelper
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
wmHelper.StateSyncBuilder()
.withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
.waitForAndVerify()
}
fun dragDividerToDismissSplit(
device: UiDevice,
wmHelper: WindowManagerStateHelper,
dragToRight: Boolean,
dragToBottom: Boolean
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
dividerBar.drag(Point(
if (dragToRight) {
displayBounds.width * 4 / 5
} else {
displayBounds.width * 1 / 5
},
if (dragToBottom) {
displayBounds.height * 4 / 5
} else {
displayBounds.height * 1 / 5
}))
}
fun doubleTapDividerToSwitch(device: UiDevice) {
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
val interval = (ViewConfiguration.getDoubleTapTimeout() +
ViewConfiguration.getDoubleTapMinTime()) / 2
dividerBar.click()
SystemClock.sleep(interval.toLong())
dividerBar.click()
}
fun copyContentInSplit(
instrumentation: Instrumentation,
device: UiDevice,
sourceApp: IComponentNameMatcher,
destinationApp: IComponentNameMatcher,
) {
// Copy text from sourceApp
val textView = device.wait(Until.findObject(
By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
longPress(instrumentation, textView.visibleCenter)
val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
copyBtn.click()
// Paste text to destinationApp
val editText = device.wait(Until.findObject(
By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
longPress(instrumentation, editText.visibleCenter)
val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
pasteBtn.click()
// Verify text
if (!textView.text.contentEquals(editText.text)) {
error("Fail to copy content in split")
}
}
}

View File

@@ -33,12 +33,12 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
import org.junit.Assume.assumeNotNull
class ActivityEmbeddingAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME,
component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
instr: Instrumentation,
launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
/**
@@ -47,8 +47,8 @@ class ActivityEmbeddingAppHelper @JvmOverloads constructor(
*/
fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
val launchButton = uiDevice.wait(
Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
FIND_TIMEOUT)
Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
FIND_TIMEOUT)
require(launchButton != null) {
"Can't find launch placeholder split button on screen."
}
@@ -62,14 +62,15 @@ class ActivityEmbeddingAppHelper @JvmOverloads constructor(
companion object {
private const val TAG = "ActivityEmbeddingAppHelper"
val MAIN_ACTIVITY_COMPONENT = ActivityOptions
.ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
val MAIN_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
val PLACEHOLDER_PRIMARY_COMPONENT = ActivityOptions
.ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
val PLACEHOLDER_SECONDARY_COMPONENT = ActivityOptions
.ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME
val PLACEHOLDER_SECONDARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
.toFlickerComponent()
@JvmStatic

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.wm.shell.flicker.helpers
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -23,4 +23,4 @@ class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
component: ComponentNameMatcher
) : BaseAppHelper(instrumentation, activityLabel, component)
) : StandardAppHelper(instrumentation, activityLabel, component)

View File

@@ -24,11 +24,11 @@ import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
class FixedOrientationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
component: ComponentNameMatcher =
ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
instr: Instrumentation,
launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -29,9 +29,8 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
class GameAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME,
component: ComponentNameMatcher =
ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherName: String = ActivityOptions.Game.LABEL,
component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy,
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {

View File

@@ -32,9 +32,9 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
instr: Instrumentation,
private val rotation: Int,
private val imePackageName: String = IME_PACKAGE,
launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
override fun openIME(wmHelper: WindowManagerStateHelper) {
// do nothing (the app is focused automatically)
@@ -62,21 +62,22 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
"start_dialog_themed_activity_btn")), FIND_TIMEOUT)
"start_dialog_themed_activity_btn")), FIND_TIMEOUT)
requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. Screen turned off)"
"was left in an unknown state (e.g. Screen turned off)"
}
button.click()
wmHelper.StateSyncBuilder()
.withFullScreenApp(
ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
.waitForAndVerify()
}
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(
Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
// Pressing back key to dismiss the dialog
if (dialog != null) {
@@ -86,9 +87,10 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
.waitForAndVerify()
}
}
fun getInsetsVisibleFromDialog(type: Int): Boolean {
val insetsVisibilityTextView = uiDevice.wait(
Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
if (insetsVisibilityTextView != null) {
val visibility = insetsVisibilityTextView.text.toString()
val matcher = when (type) {

View File

@@ -28,12 +28,11 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
open class ImeAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.Ime.Default.LABEL,
component: ComponentNameMatcher =
ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
/**
* Opens the IME and wait for it to be displayed
@@ -73,8 +72,8 @@ open class ImeAppHelper @JvmOverloads constructor(
open fun finishActivity(wmHelper: WindowManagerStateHelper) {
val finishButton = uiDevice.wait(
Until.findObject(By.res(getPackage(), "finish_activity_btn")),
FIND_TIMEOUT)
Until.findObject(By.res(getPackage(), "finish_activity_btn")),
FIND_TIMEOUT)
requireNotNull(finishButton) {
"Finish activity button not found, probably IME activity is not on the screen?"
}

View File

@@ -26,16 +26,16 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
override fun openIME(wmHelper: WindowManagerStateHelper) {
val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
require(editText != null) {
requireNotNull(editText) {
"Text field not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
waitIMEShown(wmHelper)
@@ -43,7 +43,7 @@ class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dismissButton = uiDevice.wait(
Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
// Pressing back key to dismiss the dialog
if (dismissButton != null) {

View File

@@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent
class ImeStateInitializeHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
package com.android.wm.shell.flicker.helpers
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
class LaunchBubbleHelper(instrumentation: Instrumentation) : StandardAppHelper(
instrumentation,
Components.SimpleActivity.LABEL,
Components.SimpleActivity.COMPONENT.toFlickerComponent()
)
ActivityOptions.Bubbles.LaunchBubble.LABEL,
ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent()
)

View File

@@ -28,13 +28,13 @@ import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
class MailAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME,
component: ComponentNameMatcher =
ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
instr: Instrumentation,
launcherName: String = ActivityOptions.Mail.LABEL,
component: ComponentNameMatcher =
ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
fun openMail(rowIdx: Int) {
@@ -46,7 +46,7 @@ class MailAppHelper @JvmOverloads constructor(
if (row != null) break
scrollDown()
}
require(row != null) {""}
require(row != null) { "" }
row.click()
uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT)
}
@@ -57,9 +57,9 @@ class MailAppHelper @JvmOverloads constructor(
}
fun waitForMailList(): UiObject2 {
var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT)
require(ret != null) {""}
requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" }
return ret
}

View File

@@ -14,20 +14,31 @@
* limitations under the License.
*/
package com.android.wm.shell.flicker.helpers
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.content.Context
import android.provider.Settings
import android.util.Log
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.traces.common.ComponentNameMatcher
import java.io.IOException
class MultiWindowHelper(
class MultiWindowUtils(
instrumentation: Instrumentation,
activityLabel: String,
componentsInfo: ComponentNameMatcher
) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) {
companion object {
fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
try {
SystemUtil.runShellCommand(instrumentation, cmd)
} catch (e: IOException) {
Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
}
}
fun getDevEnableNonResizableMultiWindow(context: Context): Int =
Settings.Global.getInt(context.contentResolver,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)

View File

@@ -29,9 +29,9 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
class NewTasksAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
component: ComponentNameMatcher =
ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
@@ -43,7 +43,7 @@ class NewTasksAppHelper @JvmOverloads constructor(
requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
"was left in an unknown state (e.g. in split screen)"
}
button.click()
wmHelper.StateSyncBuilder()

View File

@@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent
class NonResizeableAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -28,25 +28,28 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
class NotificationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.Notification.LABEL,
component: ComponentNameMatcher =
ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
fun postNotification(wmHelper: WindowManagerStateHelper) {
val button = uiDevice.wait(
Until.findObject(By.res(getPackage(), "post_notification")),
FIND_TIMEOUT)
Until.findObject(By.res(getPackage(), "post_notification")),
FIND_TIMEOUT)
require(button != null) {
requireNotNull(button) {
"Post notification button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
"was left in an unknown state (e.g. in split screen)"
}
button.click()
uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
?: error("Flicker Notification not found")
?: error("Flicker Notification not found")
wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
.waitForAndVerify()
}
}

View File

@@ -14,28 +14,23 @@
* limitations under the License.
*/
package com.android.wm.shell.flicker.helpers
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
import com.android.wm.shell.flicker.testapp.Components
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper(
instrumentation,
Components.PipActivity.LABEL,
Components.PipActivity.COMPONENT.toFlickerComponent()
ActivityOptions.Pip.LABEL,
ActivityOptions.Pip.COMPONENT.toFlickerComponent()
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
@@ -46,16 +41,11 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
it.packageName == `package`
}
fun clickObject(resId: String) {
open fun clickObject(resId: String) {
val selector = By.res(`package`, resId)
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
if (!isTelevision) {
obj.click()
} else {
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
obj.click()
}
/**
@@ -85,20 +75,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
launchViaIntentAndWaitShown(wmHelper)
private fun focusOnObject(selector: BySelector): Boolean {
// We expect all the focusable UI elements to be arranged in a way so that it is possible
// to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
// from "the bottom".
repeat(FOCUS_ATTEMPTS) {
uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
?: error("The object we try to focus on is gone.")
uiDevice.pressDPadDown()
uiDevice.waitForIdle()
}
return false
}
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
clickObject(ENTER_PIP_BUTTON_ID)
@@ -140,12 +116,8 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
"Use PipAppHelper.closePipWindow(wmHelper) instead",
ReplaceWith("closePipWindow(wmHelper)")
)
fun closePipWindow() {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
open fun closePipWindow() {
closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
@@ -159,20 +131,16 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
/**
* Taps the pip window and dismisses it by clicking on the X button.
*/
fun closePipWindow(wmHelper: WindowManagerStateHelper) {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// search and interact with the dismiss button
val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
val dismissPipObject = uiDevice.findObject(dismissSelector)
?: error("PIP window dismiss button not found")
val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// search and interact with the dismiss button
val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
val dismissPipObject = uiDevice.findObject(dismissSelector)
?: error("PIP window dismiss button not found")
val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
// Wait for animation to complete.
wmHelper.StateSyncBuilder()
@@ -213,7 +181,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
companion object {
private const val FOCUS_ATTEMPTS = 20
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"

View File

@@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent
class SeamlessRotationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
component: ComponentNameMatcher =
ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent
class ShowWhenLockedAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -25,10 +25,9 @@ import com.android.server.wm.traces.parser.toFlickerComponent
class SimpleAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.SimpleActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy)

View File

@@ -29,16 +29,15 @@ import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelpe
class TwoActivitiesAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
component: ComponentNameMatcher =
ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
launcherStrategy: ILauncherStrategy =
LauncherStrategyFactory.getInstance(instr).launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
private val secondActivityComponent =
ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
@@ -46,7 +45,7 @@ class TwoActivitiesAppHelper @JvmOverloads constructor(
requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
"was left in an unknown state (e.g. in split screen)"
}
button.click()

View File

@@ -136,7 +136,7 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".LaunchNewActivity");
}
public static class PipActivity {
public static class Pip {
// Test App > Pip Activity
public static final String LABEL = "PipActivity";
public static final String MENU_ACTION_NO_OP = "No-Op";
@@ -163,7 +163,7 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".PipActivity");
}
public static class SplitScreenActivity {
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,