1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm.flicker.helpers 18 19 import android.graphics.Rect 20 import android.tools.device.apphelpers.IStandardAppHelper 21 import android.tools.helpers.SYSTEMUI_PACKAGE 22 import android.tools.traces.parsers.WindowManagerStateHelper 23 import android.tools.traces.wm.WindowingMode 24 import androidx.test.uiautomator.By 25 import androidx.test.uiautomator.BySelector 26 import androidx.test.uiautomator.UiDevice 27 import androidx.test.uiautomator.UiObject2 28 import androidx.test.uiautomator.Until 29 30 /** 31 * Wrapper class around App helper classes. This class adds functionality to the apps that the 32 * desktop apps would have. 33 */ 34 open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : <lambda>null35 IStandardAppHelper by innerHelper { 36 37 enum class Corners { 38 LEFT_TOP, 39 RIGHT_TOP, 40 LEFT_BOTTOM, 41 RIGHT_BOTTOM 42 } 43 44 private val TIMEOUT_MS = 3_000L 45 private val CAPTION = "desktop_mode_caption" 46 private val CAPTION_HANDLE = "caption_handle" 47 private val MAXIMIZE_BUTTON = "maximize_window" 48 private val MAXIMIZE_BUTTON_VIEW = "maximize_button_view" 49 private val CLOSE_BUTTON = "close_window" 50 51 private val caption: BySelector 52 get() = By.res(SYSTEMUI_PACKAGE, CAPTION) 53 54 /** Wait for an app moved to desktop to finish its transition. */ 55 private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) { 56 wmHelper 57 .StateSyncBuilder() 58 .withWindowSurfaceAppeared(innerHelper) 59 .withFreeformApp(innerHelper) 60 .withAppTransitionIdle() 61 .waitForAndVerify() 62 } 63 64 /** Move an app to Desktop by dragging the app handle at the top. */ 65 fun enterDesktopWithDrag( 66 wmHelper: WindowManagerStateHelper, 67 device: UiDevice, 68 ) { 69 innerHelper.launchViaIntent(wmHelper) 70 dragToDesktop(wmHelper, device) 71 waitForAppToMoveToDesktop(wmHelper) 72 } 73 74 private fun dragToDesktop(wmHelper: WindowManagerStateHelper, device: UiDevice) { 75 val windowRect = wmHelper.getWindowRegion(innerHelper).bounds 76 val startX = windowRect.centerX() 77 78 // Start dragging a little under the top to prevent dragging the notification shade. 79 val startY = 10 80 81 val displayRect = 82 wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect 83 ?: throw IllegalStateException("Default display is null") 84 85 // The position we want to drag to 86 val endY = displayRect.centerY() / 2 87 88 // drag the window to move to desktop 89 device.drag(startX, startY, startX, endY, 100) 90 } 91 92 /** Click maximise button on the app header for the given app. */ 93 fun maximiseDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) { 94 val caption = getCaptionForTheApp(wmHelper, device) 95 val maximizeButton = 96 caption 97 ?.children 98 ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) } 99 ?.children 100 ?.get(0) 101 maximizeButton?.click() 102 wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() 103 } 104 /** Click close button on the app header for the given app. */ 105 fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) { 106 val caption = getCaptionForTheApp(wmHelper, device) 107 val closeButton = caption?.children?.find { it.resourceName.endsWith(CLOSE_BUTTON) } 108 closeButton?.click() 109 wmHelper 110 .StateSyncBuilder() 111 .withAppTransitionIdle() 112 .withWindowSurfaceDisappeared(innerHelper) 113 .waitForAndVerify() 114 } 115 116 private fun getCaptionForTheApp( 117 wmHelper: WindowManagerStateHelper, 118 device: UiDevice 119 ): UiObject2? { 120 if ( 121 wmHelper.getWindow(innerHelper)?.windowingMode != 122 WindowingMode.WINDOWING_MODE_FREEFORM.value 123 ) 124 error("expected a freeform window with caption but window is not in freeform mode") 125 val captions = 126 device.wait(Until.findObjects(caption), TIMEOUT_MS) 127 ?: error("Unable to find view $caption\n") 128 129 return captions.find { 130 wmHelper.getWindowRegion(innerHelper).bounds.contains(it.visibleBounds) 131 } 132 } 133 134 /** Resize a desktop app from its corners. */ 135 fun cornerResize( 136 wmHelper: WindowManagerStateHelper, 137 device: UiDevice, 138 corner: Corners, 139 horizontalChange: Int, 140 verticalChange: Int 141 ) { 142 val windowRect = wmHelper.getWindowRegion(innerHelper).bounds 143 val (startX, startY) = getStartCoordinatesForCornerResize(windowRect, corner) 144 145 // The position we want to drag to 146 val endY = startY + verticalChange 147 val endX = startX + horizontalChange 148 149 // drag the specified corner of the window to the end coordinate. 150 device.drag(startX, startY, endX, endY, 100) 151 wmHelper 152 .StateSyncBuilder() 153 .withAppTransitionIdle() 154 .waitForAndVerify() 155 } 156 157 private fun getStartCoordinatesForCornerResize( 158 windowRect: Rect, 159 corner: Corners 160 ): Pair<Int, Int> { 161 return when (corner) { 162 Corners.LEFT_TOP -> Pair(windowRect.left, windowRect.top) 163 Corners.RIGHT_TOP -> Pair(windowRect.right, windowRect.top) 164 Corners.LEFT_BOTTOM -> Pair(windowRect.left, windowRect.bottom) 165 Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom) 166 } 167 } 168 } 169