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 package com.android.launcher3.util 17 18 import android.content.Context 19 import android.content.Intent 20 import android.os.SystemClock 21 import android.view.InputDevice 22 import android.view.KeyCharacterMap 23 import android.view.KeyEvent 24 import android.view.MotionEvent 25 import android.view.View 26 import android.view.ViewGroup 27 import androidx.core.view.children 28 import androidx.lifecycle.Lifecycle.State.RESUMED 29 import androidx.test.core.app.ActivityScenario 30 import androidx.test.core.app.ActivityScenario.ActivityAction 31 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 32 import androidx.test.uiautomator.UiDevice 33 import com.android.launcher3.Launcher 34 import com.android.launcher3.LauncherAppState 35 import com.android.launcher3.LauncherState 36 import com.android.launcher3.R 37 import com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST 38 import com.android.launcher3.tapl.TestHelpers 39 import com.android.launcher3.util.ModelTestExtensions.loadModelSync 40 import com.android.launcher3.util.Wait.atMost 41 import java.util.function.Function 42 import java.util.function.Predicate 43 import java.util.function.Supplier 44 import org.junit.After 45 46 /** 47 * Base class for tests which use Launcher activity with some utility methods. 48 * 49 * This should instead be a rule, but is kept as a base class for easier migration from TAPL 50 */ 51 open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> { 52 53 private var currentScenario: ActivityScenario<LAUNCHER_TYPE>? = null 54 55 val scenario: ActivityScenario<LAUNCHER_TYPE> 56 get() = 57 currentScenario 58 ?: ActivityScenario.launch<LAUNCHER_TYPE>( 59 TestHelpers.getHomeIntentInPackage(targetContext()), 60 null, 61 ) <lambda>null62 .also { currentScenario = it } 63 64 @JvmField val uiDevice = UiDevice.getInstance(getInstrumentation()) 65 66 @After closeCurrentActivitynull67 fun closeCurrentActivity() { 68 currentScenario?.close() 69 currentScenario = null 70 } 71 loadLauncherSyncnull72 protected fun loadLauncherSync() { 73 LauncherAppState.getInstance(targetContext()).model.loadModelSync() 74 scenario.moveToState(RESUMED) 75 } 76 targetContextnull77 protected fun targetContext(): Context = getInstrumentation().targetContext 78 79 protected fun goToState(state: LauncherState) { 80 executeOnLauncher { it.stateManager.goToState(state, 0) } 81 UiDevice.getInstance(getInstrumentation()).waitForIdle() 82 } 83 executeOnLaunchernull84 protected fun executeOnLauncher(f: ActivityAction<LAUNCHER_TYPE>) = scenario.onActivity(f) 85 86 protected fun <T> getFromLauncher(f: Function<in LAUNCHER_TYPE, out T?>): T? { 87 var result: T? = null 88 executeOnLauncher { result = f.apply(it) } 89 return result 90 } 91 isInStatenull92 protected fun isInState(state: Supplier<LauncherState>): Boolean = 93 getFromLauncher { it.stateManager.state == state.get() }!! 94 waitForStatenull95 protected fun waitForState(message: String, state: Supplier<LauncherState>) = 96 waitForLauncherCondition(message) { it.stateManager.currentStableState === state.get() } 97 waitForLauncherConditionnull98 protected fun waitForLauncherCondition( 99 message: String, 100 condition: Function<LAUNCHER_TYPE, Boolean>, 101 ) = atMost(message, { getFromLauncher(condition)!! }) 102 waitForLauncherConditionnull103 protected fun waitForLauncherCondition( 104 message: String, 105 condition: Function<LAUNCHER_TYPE, Boolean>, 106 timeout: Long, 107 ) = atMost(message, { getFromLauncher(condition)!! }, null, timeout) 108 getOnceNotNullnull109 protected fun <T> getOnceNotNull(message: String, f: Function<LAUNCHER_TYPE, T?>): T? { 110 var output: T? = null 111 atMost( 112 message, 113 { 114 val fromLauncher = getFromLauncher<T>(f) 115 output = fromLauncher 116 fromLauncher != null 117 }, 118 ) 119 return output 120 } 121 getAllAppsScrollnull122 protected fun getAllAppsScroll(launcher: LAUNCHER_TYPE) = 123 launcher.appsView.activeRecyclerView.computeVerticalScrollOffset() 124 125 @JvmOverloads 126 protected fun injectKeyEvent(keyCode: Int, actionDown: Boolean, metaState: Int = 0) { 127 uiDevice.waitForIdle() 128 val eventTime = SystemClock.uptimeMillis() 129 val event = 130 KeyEvent( 131 eventTime, 132 eventTime, 133 if (actionDown) KeyEvent.ACTION_DOWN else MotionEvent.ACTION_UP, 134 keyCode, 135 /* repeat= */ 0, 136 metaState, 137 KeyCharacterMap.VIRTUAL_KEYBOARD, 138 /* scancode= */ 0, 139 /* flags= */ 0, 140 InputDevice.SOURCE_KEYBOARD, 141 ) 142 executeOnLauncher { it.dispatchKeyEvent(event) } 143 } 144 145 @JvmOverloads startAppFastnull146 fun startAppFast( 147 packageName: String, 148 intent: Intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!, 149 ) { 150 intent.addCategory(Intent.CATEGORY_LAUNCHER) 151 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) 152 targetContext().startActivity(intent) 153 uiDevice.waitForIdle() 154 } 155 <lambda>null156 fun freezeAllApps() = executeOnLauncher { 157 it.appsView.appsStore.enableDeferUpdates(DEFER_UPDATES_TEST) 158 } 159 executeShellCommandnull160 fun executeShellCommand(cmd: String) = uiDevice.executeShellCommand(cmd) 161 162 fun addToWorkspace(view: View) { 163 TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { 164 view.accessibilityDelegate.performAccessibilityAction( 165 view, 166 R.id.action_add_to_workspace, 167 null, 168 ) 169 } 170 UiDevice.getInstance(getInstrumentation()).waitForIdle() 171 } 172 173 /** 174 * Match the behavior with how widget is added in reality with "tap to add" (even with screen 175 * readers). 176 */ addWidgetToWorkspacenull177 fun addWidgetToWorkspace(view: View) { 178 executeOnLauncher { 179 view.performClick() 180 UiDevice.getInstance(getInstrumentation()).waitForIdle() 181 view.findViewById<View>(R.id.widget_add_button).performClick() 182 } 183 } 184 searchViewnull185 fun ViewGroup.searchView(filter: Predicate<View>): View? { 186 if (filter.test(this)) return this 187 for (child in children) { 188 if (filter.test(child)) return child 189 if (child is ViewGroup) 190 child.searchView(filter)?.let { 191 return it 192 } 193 } 194 return null 195 } 196 } 197