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