1 /*
<lambda>null2 * Copyright 2019 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 androidx.testutils
18
19 import android.app.Activity
20 import android.content.Intent
21 import android.os.Looper
22 import androidx.test.core.app.ActivityScenario
23 import androidx.test.platform.app.InstrumentationRegistry
24 import androidx.test.runner.AndroidJUnitRunner
25 import org.junit.rules.ExternalResource
26 import org.junit.runner.Description
27 import org.junit.runners.model.Statement
28
29 @Suppress("unused")
30 open class ActivityRecyclingAndroidJUnitRunner : AndroidJUnitRunner() {
31 override fun waitForActivitiesToComplete() {}
32 }
33
34 /**
35 * Implement this interface on your Activity to allow HackyActivityScenarioRule to launch
36 * once-per-test-class.
37 */
38 interface Resettable {
setFinishEnablednull39 fun setFinishEnabled(finishEnabled: Boolean)
40 }
41
42 /**
43 * Copy of ActivityScenarioRule, but which works around AndroidX test infra trying to finish
44 * activities in between each test.
45 */
46 class ResettableActivityScenarioRule<A> : ExternalResource where A : Activity, A : Resettable {
47 private val scenarioSupplier: () -> ActivityScenario<A>
48 private lateinit var _scenario: ActivityScenario<A>
49 private var finishEnabled: Boolean = true
50 private var initialTouchMode: Boolean = false
51
52 val scenario: ActivityScenario<A>
53 get() = _scenario
54
55 override fun apply(base: Statement?, description: Description): Statement {
56 // Running as a ClassRule? Disable activity finish
57 finishEnabled = (description.methodName != null)
58 return super.apply(base, description)
59 }
60
61 @JvmOverloads
62 constructor(activityClass: Class<A>, initialTouchMode: Boolean = false) {
63 this.initialTouchMode = initialTouchMode
64 InstrumentationRegistry.getInstrumentation().setInTouchMode(initialTouchMode)
65 scenarioSupplier = { ActivityScenario.launch(activityClass) }
66 }
67
68 @JvmOverloads
69 constructor(startActivityIntent: Intent, initialTouchMode: Boolean = false) {
70 InstrumentationRegistry.getInstrumentation().setInTouchMode(initialTouchMode)
71 scenarioSupplier = { ActivityScenario.launch(startActivityIntent) }
72 }
73
74 @Throws(Throwable::class)
75 override fun before() {
76 _scenario = scenarioSupplier.invoke()
77 _activity = internalGetActivity()
78 if (!finishEnabled) {
79 // TODO: Correct approach inside test lib would be removing activity from
80 // cleanup list
81 scenario.onActivity { it.setFinishEnabled(false) }
82 }
83 }
84
85 override fun after() {
86 if (!finishEnabled) {
87 scenario.onActivity { it.setFinishEnabled(true) }
88 }
89 scenario.close()
90 InstrumentationRegistry.getInstrumentation().setInTouchMode(initialTouchMode)
91 }
92
93 // Below are compat hacks to get RecyclerView ActivityTestRule tests up and running quickly
94
95 fun runOnUiThread(runnable: Runnable) {
96 if (Looper.myLooper() == Looper.getMainLooper()) {
97 runnable.run()
98 } else {
99 InstrumentationRegistry.getInstrumentation().runOnMainSync { runnable.run() }
100 }
101 }
102
103 fun runOnUiThread(action: () -> Unit) {
104 runOnUiThread(Runnable(action))
105 }
106
107 private fun internalGetActivity(): A {
108 val activityReturn = mutableListOf<A?>(null)
109 scenario.onActivity { activity -> activityReturn[0] = activity }
110 return activityReturn[0]!!
111 }
112
113 private lateinit var _activity: A
114
115 fun getActivity(): A {
116 return _activity
117 }
118 }
119
120 @Suppress("FunctionName") /* Acts as constructor */
ResettableActivityScenarioRulenull121 inline fun <reified A> ResettableActivityScenarioRule(
122 initialTouchMode: Boolean = false
123 ): ResettableActivityScenarioRule<A> where A : Activity, A : Resettable {
124 return ResettableActivityScenarioRule(A::class.java, initialTouchMode)
125 }
126