1 /* 2 * Copyright (C) 2022 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.app.Instrumentation 20 import android.os.SystemClock 21 import android.tools.PlatformConsts 22 import android.tools.device.apphelpers.StandardAppHelper 23 import android.tools.helpers.FIND_TIMEOUT 24 import android.tools.traces.component.ComponentNameMatcher 25 import android.tools.traces.parsers.WindowManagerStateHelper 26 import android.tools.traces.parsers.toFlickerComponent 27 import android.util.Log 28 import androidx.test.uiautomator.By 29 import androidx.test.uiautomator.Direction 30 import androidx.test.uiautomator.Until 31 import androidx.window.extensions.WindowExtensions 32 import androidx.window.extensions.WindowExtensionsProvider 33 import androidx.window.extensions.embedding.ActivityEmbeddingComponent 34 import com.android.server.wm.flicker.testapp.ActivityOptions 35 import org.junit.Assume.assumeNotNull 36 37 class ActivityEmbeddingAppHelper 38 @JvmOverloads 39 constructor( 40 instr: Instrumentation, 41 launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL, 42 component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT 43 ) : StandardAppHelper(instr, launcherName, component) { 44 45 /** 46 * Clicks the button to launch the secondary activity, which should split with the main activity 47 * based on the split pair rule. 48 */ launchSecondaryActivitynull49 fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) { 50 launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_button") 51 } 52 53 /** 54 * Clicks the button to launch the secondary activity in RTL, which should split with the main 55 * activity based on the split pair rule. 56 */ launchSecondaryActivityRTLnull57 fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) { 58 launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button") 59 } 60 61 /** Clicks the button to launch the secondary activity in a horizontal split. */ launchSecondaryActivityHorizontallynull62 fun launchSecondaryActivityHorizontally(wmHelper: WindowManagerStateHelper) { 63 launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_horizontally_button") 64 } 65 66 /** Clicks the button to launch a third activity over a secondary activity. */ launchThirdActivitynull67 fun launchThirdActivity(wmHelper: WindowManagerStateHelper) { 68 val launchButton = 69 uiDevice.wait( 70 Until.findObject(By.res(packageName, "launch_third_activity_button")), 71 FIND_TIMEOUT 72 ) 73 require(launchButton != null) { "Can't find launch third activity button on screen." } 74 launchButton.click() 75 wmHelper 76 .StateSyncBuilder() 77 .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 78 .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED) 79 .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 80 .waitForAndVerify() 81 } 82 83 /** 84 * Clicks the button to launch the trampoline activity, which should launch the secondary 85 * activity and finish itself. 86 */ launchTrampolineActivitynull87 fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) { 88 scrollToBottom() 89 val launchButton = 90 uiDevice.wait( 91 Until.findObject(By.res(packageName, "launch_trampoline_button")), 92 FIND_TIMEOUT 93 ) 94 require(launchButton != null) { "Can't find launch trampoline activity button on screen." } 95 launchButton.click() 96 wmHelper 97 .StateSyncBuilder() 98 .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 99 .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT) 100 .waitForAndVerify() 101 } 102 103 /** 104 * Clicks the button to finishes the secondary activity launched through 105 * [launchSecondaryActivity], waits for the main activity to resume. 106 */ finishSecondaryActivitynull107 fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) { 108 val finishButton = 109 uiDevice.wait( 110 Until.findObject(By.res(packageName, "finish_secondary_activity_button")), 111 FIND_TIMEOUT 112 ) 113 require(finishButton != null) { "Can't find finish secondary activity button on screen." } 114 finishButton.click() 115 wmHelper 116 .StateSyncBuilder() 117 .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT) 118 .waitForAndVerify() 119 } 120 121 /** Clicks the button to toggle the split ratio of secondary activity. */ changeSecondaryActivityRationull122 fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) { 123 val launchButton = 124 uiDevice.wait( 125 Until.findObject(By.res(packageName, "toggle_split_ratio_button")), 126 FIND_TIMEOUT 127 ) 128 require(launchButton != null) { 129 "Can't find toggle ratio for secondary activity button on screen." 130 } 131 launchButton.click() 132 wmHelper 133 .StateSyncBuilder() 134 .withAppTransitionIdle() 135 .withTransitionSnapshotGone() 136 .waitForAndVerify() 137 } 138 secondaryActivityEnterPipnull139 fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) { 140 val pipButton = 141 uiDevice.wait( 142 Until.findObject(By.res(packageName, "secondary_enter_pip_button")), 143 FIND_TIMEOUT 144 ) 145 require(pipButton != null) { "Can't find enter pip button on screen." } 146 pipButton.click() 147 wmHelper.StateSyncBuilder().withAppTransitionIdle().withPipShown().waitForAndVerify() 148 } 149 150 /** 151 * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch 152 * a fullscreen window on top of the visible region. 153 */ launchAlwaysExpandActivitynull154 fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) { 155 val launchButton = 156 uiDevice.wait( 157 Until.findObject(By.res(packageName, "launch_always_expand_activity_button")), 158 FIND_TIMEOUT 159 ) 160 require(launchButton != null) { 161 "Can't find launch always expand activity button on screen." 162 } 163 launchButton.click() 164 wmHelper 165 .StateSyncBuilder() 166 .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 167 .withActivityState( 168 MAIN_ACTIVITY_COMPONENT, 169 PlatformConsts.STATE_PAUSED, 170 PlatformConsts.STATE_STOPPED 171 ) 172 .waitForAndVerify() 173 } 174 launchSecondaryActivityFromButtonnull175 private fun launchSecondaryActivityFromButton( 176 wmHelper: WindowManagerStateHelper, 177 buttonName: String 178 ) { 179 val launchButton = 180 uiDevice.wait(Until.findObject(By.res(packageName, buttonName)), FIND_TIMEOUT) 181 require(launchButton != null) { 182 "Can't find launch secondary activity button : " + buttonName + "on screen." 183 } 184 launchButton.click() 185 wmHelper 186 .StateSyncBuilder() 187 .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 188 .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) 189 .waitForAndVerify() 190 } 191 192 /** 193 * Clicks the button to launch the placeholder primary activity, which should launch the 194 * placeholder secondary activity based on the placeholder rule. 195 */ launchPlaceholderSplitnull196 fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) { 197 val launchButton = 198 uiDevice.wait( 199 Until.findObject(By.res(packageName, "launch_placeholder_split_button")), 200 FIND_TIMEOUT 201 ) 202 require(launchButton != null) { "Can't find launch placeholder split button on screen." } 203 launchButton.click() 204 wmHelper 205 .StateSyncBuilder() 206 .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED) 207 .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED) 208 .waitForAndVerify() 209 } 210 211 /** 212 * Clicks the button to launch the placeholder primary activity in RTL, which should launch the 213 * placeholder secondary activity based on the placeholder rule. 214 */ launchPlaceholderSplitRTLnull215 fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) { 216 scrollToBottom() 217 val launchButton = 218 uiDevice.wait( 219 Until.findObject(By.res(packageName, "launch_placeholder_split_rtl_button")), 220 FIND_TIMEOUT 221 ) 222 require(launchButton != null) { "Can't find launch placeholder split button on screen." } 223 launchButton.click() 224 wmHelper 225 .StateSyncBuilder() 226 .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED) 227 .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED) 228 .waitForAndVerify() 229 } 230 231 /** 232 * Scrolls to the bottom of the launch options. This is needed if the launch button is at the 233 * bottom. Otherwise the click may trigger touch on navBar. 234 */ scrollToBottomnull235 private fun scrollToBottom() { 236 val launchOptionsList = uiDevice.wait( 237 Until.findObject(By.res(packageName, "launch_options_list")), 238 FIND_TIMEOUT 239 ) 240 requireNotNull(launchOptionsList) { "Unable to find the list of launch options" } 241 launchOptionsList.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN)) 242 // Wait a bit after scrolling, otherwise the immediate click may not be treated as "click". 243 SystemClock.sleep(1000L) 244 } 245 246 companion object { 247 private const val TAG = "ActivityEmbeddingAppHelper" 248 249 val MAIN_ACTIVITY_COMPONENT = 250 ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent() 251 252 val SECONDARY_ACTIVITY_COMPONENT = 253 ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent() 254 255 val THIRD_ACTIVITY_COMPONENT = 256 ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent() 257 258 val ALWAYS_EXPAND_ACTIVITY_COMPONENT = 259 ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent() 260 261 val PLACEHOLDER_PRIMARY_COMPONENT = 262 ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT 263 .toFlickerComponent() 264 265 val PLACEHOLDER_SECONDARY_COMPONENT = 266 ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT 267 .toFlickerComponent() 268 269 val TRAMPOLINE_ACTIVITY_COMPONENT = 270 ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent() 271 272 @JvmStatic getWindowExtensionsnull273 fun getWindowExtensions(): WindowExtensions? { 274 try { 275 return WindowExtensionsProvider.getWindowExtensions() 276 } catch (e: NoClassDefFoundError) { 277 Log.d(TAG, "Extension implementation not found") 278 } catch (e: UnsupportedOperationException) { 279 Log.d(TAG, "Stub Extension") 280 } 281 return null 282 } 283 284 @JvmStatic getActivityEmbeddingComponentnull285 fun getActivityEmbeddingComponent(): ActivityEmbeddingComponent? { 286 return getWindowExtensions()?.activityEmbeddingComponent 287 } 288 289 @JvmStatic assumeActivityEmbeddingSupportedDevicenull290 fun assumeActivityEmbeddingSupportedDevice() { 291 assumeNotNull(getActivityEmbeddingComponent()) 292 } 293 } 294 } 295