1 package com.android.onboarding.nodes.testing.testapp
2
3 import android.content.Context
4 import android.content.Intent
5 import android.content.Intent.CATEGORY_DEFAULT
6 import android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT
7 import android.content.pm.PackageManager.ResolveInfoFlags
8 import android.graphics.Color
9 import android.os.Bundle
10 import android.widget.ArrayAdapter
11 import android.widget.Button
12 import android.widget.EditText
13 import android.widget.LinearLayout
14 import android.widget.Spinner
15 import android.widget.TextView
16 import androidx.activity.result.ActivityResult
17 import com.android.onboarding.activity.CompositeOnboardingActivity
18 import com.android.onboarding.common.TEST_APP
19 import com.android.onboarding.contracts.IdentifyExecutingContractByAction
20 import com.android.onboarding.contracts.OnboardingActivityApiContract
21 import com.android.onboarding.contracts.annotations.DiscouragedOnboardingApi
22 import com.android.onboarding.contracts.annotations.InternalOnboardingApi
23 import com.android.onboarding.contracts.annotations.OnboardingNode
24 import com.android.onboarding.contracts.failNode
25 import com.android.onboarding.contracts.registerForActivityLaunch
26
27 @OptIn(DiscouragedOnboardingApi::class, InternalOnboardingApi::class)
28 class MainActivity : CompositeOnboardingActivity<ContractArg, String, ColourContract>() {
29 private val defaultContract = RedContract()
30 private val contracts = arrayOf(defaultContract, BlueContract(), GreenContract())
31
selectContractnull32 override fun selectContract(intent: Intent): ColourContract =
33 contracts.firstOrNull { it.isExecuting(intent.action) } ?: defaultContract
34
onCreatenull35 override fun onCreate(savedInstanceState: Bundle?) {
36 super.onCreate(savedInstanceState)
37
38 setContentView(R.layout.activity_main)
39
40 if (attachResult.valid) {
41 @Suppress("SetTextI18n")
42 checkNotNull(findViewById<TextView>(R.id.status)).text = "Received argument: ${argument.arg}"
43 }
44
45 val processSpinner = checkNotNull(findViewById<Spinner>(R.id.processSpinner))
46 val packages =
47 packageManager
48 .queryIntentActivities(
49 Intent("com.android.onboarding.nodes.testing.testapp.red"),
50 ResolveInfoFlags.of(0),
51 )
52 .map { it.activityInfo.packageName }
53 .toSet()
54 .toList()
55 val packageNames =
56 packages
57 .map { packageManager.getApplicationLabel(packageManager.getApplicationInfo(it, 0)) }
58 .toList()
59 processSpinner.adapter =
60 ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, packageNames)
61 processSpinner.setSelection(packages.indexOf(packageName))
62
63 val contractSpinner = checkNotNull(findViewById<Spinner>(R.id.contractSpinner))
64 contractSpinner.adapter =
65 ArrayAdapter(
66 this,
67 android.R.layout.simple_spinner_dropdown_item,
68 contracts.map(OnboardingActivityApiContract<*, *>::metadata).map(OnboardingNode::name),
69 )
70 contractSpinner.setSelection(contracts.indexOf(contract))
71
72 val noResultLaunchers = contracts.map { registerForActivityLaunch(it) }
73 val resultLaunchers = contracts.map { registerForActivityResult(it, this::onResult) }
74
75 checkNotNull(findViewById<TextView>(R.id.packageName)).text =
76 packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName, 0))
77
78 checkNotNull(findViewById<LinearLayout>(R.id.bg)).setBackgroundColor(contract.colour)
79
80 when (contract) {
81 is RedContract -> {
82 checkNotNull(findViewById<LinearLayout>(R.id.bg)).setBackgroundColor(Color.RED)
83 }
84 is BlueContract -> {
85 checkNotNull(findViewById<LinearLayout>(R.id.bg)).setBackgroundColor(Color.BLUE)
86 }
87 is GreenContract -> {
88 checkNotNull(findViewById<LinearLayout>(R.id.bg)).setBackgroundColor(Color.GREEN)
89 }
90 }
91
92 checkNotNull(findViewById<Button>(R.id.startActivityAndFinishButton)).setOnClickListener {
93 val arg = checkNotNull(findViewById<EditText>(R.id.argument)).text.toString()
94 val contractId =
95 checkNotNull(findViewById<Spinner>(R.id.contractSpinner)).selectedItemId.toInt()
96 val contractLauncher = noResultLaunchers[contractId]
97 val targetPackageName = checkNotNull(packages[processSpinner.selectedItemPosition]).toString()
98
99 contractLauncher.launch(ContractArg(arg, targetPackageName))
100 finish()
101 }
102
103 checkNotNull(findViewById<Button>(R.id.startActivityAndForwardButton)).setOnClickListener {
104 val arg = checkNotNull(findViewById<EditText>(R.id.argument)).text.toString()
105 val contractId =
106 checkNotNull(findViewById<Spinner>(R.id.contractSpinner)).selectedItemId.toInt()
107 val contractLauncher = noResultLaunchers[contractId]
108 val targetPackageName = checkNotNull(packages[processSpinner.selectedItemPosition]).toString()
109
110 contractLauncher.launch(ContractArg(arg, targetPackageName, shouldForward = true))
111 finish()
112 }
113
114 checkNotNull(findViewById<Button>(R.id.startActivityButton)).setOnClickListener {
115 val arg = checkNotNull(findViewById<EditText>(R.id.argument)).text.toString()
116 val contractId =
117 checkNotNull(findViewById<Spinner>(R.id.contractSpinner)).selectedItemId.toInt()
118 val contractLauncher = noResultLaunchers[contractId]
119 val targetPackageName = checkNotNull(packages[processSpinner.selectedItemPosition]).toString()
120
121 contractLauncher.launch(ContractArg(arg, targetPackageName))
122 }
123
124 checkNotNull(findViewById<Button>(R.id.startActivityForResultButton)).setOnClickListener {
125 val arg = checkNotNull(findViewById<EditText>(R.id.argument)).text.toString()
126 val contractId =
127 checkNotNull(findViewById<Spinner>(R.id.contractSpinner)).selectedItemId.toInt()
128 val contractLauncher = resultLaunchers[contractId]
129 val targetPackageName = checkNotNull(packages[processSpinner.selectedItemPosition]).toString()
130
131 contractLauncher.launch(ContractArg(arg, targetPackageName))
132 }
133
134 checkNotNull(findViewById<Button>(R.id.finishButton)).setOnClickListener { finish() }
135 checkNotNull(findViewById<Button>(R.id.crashButton)).setOnClickListener {
136 error("Crashing, as requested...")
137 }
138 checkNotNull(findViewById<Button>(R.id.failNodeButton)).setOnClickListener {
139 failNode(checkNotNull(findViewById<EditText>(R.id.argument)).text.toString())
140 }
141 checkNotNull(findViewById<Button>(R.id.setResultAndFinishButton)).setOnClickListener {
142 setResult(checkNotNull(findViewById<EditText>(R.id.argument)).text.toString())
143 finish()
144 }
145
146 checkNotNull(findViewById<Button>(R.id.startServiceButton)).setOnClickListener {
147 startService(
148 Intent(this, BackgroundTaskService::class.java).putExtra("usingKeepAlive", false)
149 )
150 }
151
152 checkNotNull(findViewById<Button>(R.id.startServiceUsingKeepAliveButton)).setOnClickListener {
153 startService(Intent(this, BackgroundTaskService::class.java).putExtra("usingKeepAlive", true))
154 }
155
156 checkNotNull(findViewById<Button>(R.id.stopServiceButton)).setOnClickListener {
157 stopService(Intent(this, BackgroundTaskService::class.java))
158 }
159 }
160
onResultnull161 fun onResult(result: String) {
162 checkNotNull(findViewById<TextView>(R.id.status)).text = "Received result: $result"
163 }
164
onDestroynull165 override fun onDestroy() {
166 super.onDestroy()
167 stopService(Intent(this, BackgroundTaskService::class.java))
168 }
169 }
170
171 data class ContractArg(
172 val arg: String,
173 val targetPackageName: String,
174 val shouldForward: Boolean = false,
175 )
176
177 sealed class ColourContract(val colour: Int, private val name: String) :
178 OnboardingActivityApiContract<ContractArg, String>(), IdentifyExecutingContractByAction {
isExecutingnull179 override fun isExecuting(action: String?) = action?.endsWith(".$name") ?: false
180
181 override fun performCreateIntent(context: Context, arg: ContractArg): Intent =
182 Intent("com.android.onboarding.nodes.testing.testapp.$name").apply {
183 setPackage(arg.targetPackageName)
184 putExtra("KEY", arg.arg)
185 addCategory(CATEGORY_DEFAULT)
186
187 if (arg.shouldForward) {
188 addFlags(FLAG_ACTIVITY_FORWARD_RESULT)
189 }
190 }
191
performExtractArgumentnull192 override fun performExtractArgument(intent: Intent): ContractArg =
193 ContractArg(
194 checkNotNull(intent.getStringExtra("KEY")),
195 intent.`package` ?: "",
196 intent.hasFlag(FLAG_ACTIVITY_FORWARD_RESULT),
197 )
198
199 override fun performParseResult(result: ActivityResult): String =
200 checkNotNull(result.data?.getStringExtra("KEY"))
201
202 override fun performSetResult(result: String): ActivityResult =
203 ActivityResult(1, Intent().apply { putExtra("KEY", result) })
204 }
205
206 @OnboardingNode(component = TEST_APP, name = "Red", uiType = OnboardingNode.UiType.OTHER)
207 class RedContract : ColourContract(Color.RED, "red")
208
209 @OnboardingNode(component = TEST_APP, name = "Blue", uiType = OnboardingNode.UiType.OTHER)
210 class BlueContract : ColourContract(Color.BLUE, "blue")
211
212 @OnboardingNode(component = TEST_APP, name = "Green", uiType = OnboardingNode.UiType.OTHER)
213 class GreenContract : ColourContract(Color.GREEN, "green")
214
hasFlagnull215 fun Intent.hasFlag(flag: Int) = (this.flags and flag) == flag
216