1 /*
<lambda>null2 * Copyright (C) 2017 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 androidx.work.integration.testapp
17
18 import android.app.PendingIntent
19 import android.app.job.JobInfo
20 import android.app.job.JobScheduler
21 import android.content.ComponentName
22 import android.content.Intent
23 import android.net.NetworkCapabilities
24 import android.net.NetworkRequest
25 import android.os.Build
26 import android.os.Bundle
27 import android.provider.MediaStore
28 import android.util.Log
29 import android.view.View
30 import android.widget.Button
31 import android.widget.CheckBox
32 import android.widget.EditText
33 import android.widget.Toast
34 import androidx.appcompat.app.AppCompatActivity
35 import androidx.lifecycle.Observer
36 import androidx.work.Constraints
37 import androidx.work.Constraints.ContentUriTrigger
38 import androidx.work.ExistingWorkPolicy
39 import androidx.work.NetworkType
40 import androidx.work.OneTimeWorkRequest
41 import androidx.work.OneTimeWorkRequest.Companion.from
42 import androidx.work.OutOfQuotaPolicy
43 import androidx.work.PeriodicWorkRequest
44 import androidx.work.WorkContinuation
45 import androidx.work.WorkInfo
46 import androidx.work.WorkManager
47 import androidx.work.WorkRequest
48 import androidx.work.impl.background.systemjob.SystemJobService
49 import androidx.work.impl.workers.ConstraintTrackingWorker
50 import androidx.work.integration.testapp.RemoteService.Companion.cancelAllWorkIntent
51 import androidx.work.integration.testapp.RemoteService.Companion.cancelWorkByTagIntent
52 import androidx.work.integration.testapp.RemoteService.Companion.enqueueContinuationIntent
53 import androidx.work.integration.testapp.RemoteService.Companion.enqueueIntent
54 import androidx.work.integration.testapp.RemoteService.Companion.enqueueUniquePeriodicIntent
55 import androidx.work.integration.testapp.RemoteService.Companion.queryWorkInfoIntent
56 import androidx.work.integration.testapp.RemoteService.Companion.updateUniquePeriodicIntent
57 import androidx.work.integration.testapp.imageprocessing.ImageProcessingActivity
58 import androidx.work.integration.testapp.sherlockholmes.AnalyzeSherlockHolmesActivity
59 import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_CLASS_NAME
60 import androidx.work.multiprocess.RemoteListenableWorker.ARGUMENT_PACKAGE_NAME
61 import androidx.work.multiprocess.RemoteWorkerService
62 import androidx.work.workDataOf
63 import java.util.concurrent.TimeUnit
64
65 /** Main Activity */
66 class MainActivity : AppCompatActivity() {
67 private var lastForegroundWorkRequest: WorkRequest? = null
68 private var lastNotificationId = 10
69 private val workManager: WorkManager by lazy { WorkManager.getInstance(this) }
70
71 override fun onCreate(savedInstanceState: Bundle?) {
72 super.onCreate(savedInstanceState)
73 setContentView(R.layout.activity_main)
74 findViewById<View>(R.id.enqueue_infinite_work_charging).setOnClickListener {
75 workManager.enqueue(
76 OneTimeWorkRequest.Builder(InfiniteWorker::class.java)
77 .setConstraints(Constraints(requiresCharging = true))
78 .build()
79 )
80 }
81 findViewById<View>(R.id.enqueue_infinite_work_network).setOnClickListener {
82 workManager.enqueue(
83 OneTimeWorkRequest.Builder(InfiniteWorker::class.java)
84 .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
85 .build()
86 )
87 }
88 findViewById<View>(R.id.enqueue_battery_not_low).setOnClickListener {
89 workManager.enqueue(
90 OneTimeWorkRequest.Builder(TestWorker::class.java)
91 .setConstraints(Constraints(requiresBatteryNotLow = true))
92 .build()
93 )
94 }
95 findViewById<View>(R.id.sherlock_holmes).setOnClickListener {
96 startActivity(Intent(this@MainActivity, AnalyzeSherlockHolmesActivity::class.java))
97 }
98 findViewById<View>(R.id.image_processing).setOnClickListener {
99 startActivity(Intent(this@MainActivity, ImageProcessingActivity::class.java))
100 }
101 findViewById<View>(R.id.image_uri)
102 .setOnClickListener(
103 View.OnClickListener {
104 if (Build.VERSION.SDK_INT < 24) {
105 return@OnClickListener
106 }
107 val constraints =
108 Constraints(
109 contentUriTriggers =
110 setOf(
111 ContentUriTrigger(
112 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
113 true
114 )
115 )
116 )
117 workManager.enqueue(
118 ToastWorker.create("Image URI Updated!").setConstraints(constraints).build()
119 )
120 }
121 )
122 val delayInMs = findViewById<EditText>(R.id.delay_in_ms)
123 findViewById<View>(R.id.schedule_delay).setOnClickListener {
124 val delayString = delayInMs.text.toString()
125 val delay = delayString.toLong()
126 Log.d(TAG, "Enqueuing job with delay of $delay ms")
127 workManager.enqueue(
128 ToastWorker.create("Delayed Job Ran!")
129 .setInitialDelay(delay, TimeUnit.MILLISECONDS)
130 .build()
131 )
132 }
133 findViewById<View>(R.id.coroutine_sleep).setOnClickListener {
134 val delayString = delayInMs.text.toString()
135 val delay = delayString.toLong()
136 Log.d(TAG, "Enqueuing job with delay of $delay ms")
137 val inputData = workDataOf("sleep_time" to delay)
138 workManager.enqueue(
139 OneTimeWorkRequest.Builder(CoroutineSleepWorker::class.java)
140 .setInputData(inputData)
141 .addTag("coroutine_sleep")
142 .build()
143 )
144 }
145 findViewById<View>(R.id.coroutine_cancel).setOnClickListener {
146 workManager.cancelAllWorkByTag("coroutine_sleep")
147 }
148 findViewById<View>(R.id.enqueue_periodic_work).setOnClickListener {
149 val input = workDataOf(ToastWorker.ARG_MESSAGE to "Periodic work")
150 val request =
151 PeriodicWorkRequest.Builder(ToastWorker::class.java, 15, TimeUnit.MINUTES)
152 .setInputData(input)
153 .build()
154 workManager.enqueue(request)
155 }
156 findViewById<View>(R.id.enqueue_periodic_work_flex).setOnClickListener {
157 val input = workDataOf(ToastWorker.ARG_MESSAGE to "Periodic work with Flex")
158 val request =
159 PeriodicWorkRequest.Builder(
160 ToastWorker::class.java,
161 15,
162 TimeUnit.MINUTES,
163 10,
164 TimeUnit.MINUTES
165 )
166 .setInputData(input)
167 .build()
168 workManager.enqueue(request)
169 }
170 findViewById<View>(R.id.enqueue_periodic_initial_delay).setOnClickListener {
171 val input = workDataOf(ToastWorker.ARG_MESSAGE to "Periodic work")
172 val request =
173 PeriodicWorkRequest.Builder(ToastWorker::class.java, 15, TimeUnit.MINUTES)
174 .setInitialDelay(1, TimeUnit.MINUTES)
175 .setInputData(input)
176 .build()
177 workManager.enqueue(request)
178 }
179 findViewById<View>(R.id.begin_unique_work_loop).setOnClickListener {
180 val keep = findViewById<CheckBox>(R.id.keep)
181 val policy = if (keep.isChecked) ExistingWorkPolicy.KEEP else ExistingWorkPolicy.REPLACE
182 repeat(50) {
183 workManager
184 .beginUniqueWork(UNIQUE_WORK_NAME, policy, from(SleepWorker::class.java))
185 .enqueue()
186 }
187 }
188 findViewById<View>(R.id.enqueue_lots_of_work).setOnClickListener {
189 repeat(NUM_WORKERS) {
190 // Exceed Scheduler.MAX_SCHEDULER_LIMIT (100)
191 workManager.beginWith(from(SleepWorker::class.java)).enqueue()
192 }
193 }
194 findViewById<View>(R.id.exploding_work).setOnClickListener {
195 val leaves = mutableListOf<WorkContinuation>()
196 repeat(10) {
197 val workRequest = from(TestWorker::class.java)
198 val continuation = workManager.beginWith(workRequest)
199 repeat(10) {
200 val primaryDependent = from(TestWorker::class.java)
201 val primaryContinuation = continuation.then(primaryDependent)
202 repeat(10) {
203 val secondaryDependent = from(TestWorker::class.java)
204 leaves.add(primaryContinuation.then(secondaryDependent))
205 }
206 }
207 }
208 WorkContinuation.combine(leaves).then(from(TestWorker::class.java)).enqueue()
209 }
210 findViewById<View>(R.id.replace_completed_work).setOnClickListener {
211 workManager
212 .getWorkInfosForUniqueWorkLiveData(REPLACE_COMPLETED_WORK)
213 .observe(
214 this,
215 object : Observer<List<WorkInfo>> {
216 private var count = 0
217
218 override fun onChanged(value: List<WorkInfo>) {
219 if (value.isNotEmpty()) {
220 val status = value[0]
221 if (status.state.isFinished && count < NUM_WORKERS) {
222 // Enqueue another worker.
223 workManager
224 .beginUniqueWork(
225 REPLACE_COMPLETED_WORK,
226 ExistingWorkPolicy.REPLACE,
227 from(TestWorker::class.java)
228 )
229 .enqueue()
230 count += 1
231 }
232 }
233 }
234 }
235 )
236 workManager
237 .beginUniqueWork(
238 REPLACE_COMPLETED_WORK,
239 ExistingWorkPolicy.REPLACE,
240 from(TestWorker::class.java)
241 )
242 .enqueue()
243 }
244 findViewById<View>(R.id.run_retry_worker).setOnClickListener {
245 val request = from(RetryWorker::class.java)
246 workManager.enqueueUniqueWork(RetryWorker.TAG, ExistingWorkPolicy.REPLACE, request)
247 workManager
248 .getWorkInfoByIdLiveData(request.id)
249 .observe(
250 this,
251 Observer<WorkInfo?> { value ->
252 if (value == null) return@Observer
253
254 Toast.makeText(
255 this@MainActivity,
256 "Run attempt count #${value.runAttemptCount}",
257 Toast.LENGTH_SHORT
258 )
259 .show()
260 }
261 )
262 }
263 findViewById<View>(R.id.run_recursive_worker).setOnClickListener {
264 val request =
265 OneTimeWorkRequest.Builder(RecursiveWorker::class.java)
266 .addTag(RecursiveWorker.TAG)
267 .build()
268 workManager.enqueue(request)
269 }
270 findViewById<View>(R.id.run_constraint_tracking_worker).setOnClickListener {
271 val inputData =
272 workDataOf(
273 CONSTRAINT_WORKER_ARGUMENT_CLASS_NAME to ForegroundWorker::class.java.name
274 )
275
276 val request =
277 OneTimeWorkRequest.Builder(ConstraintTrackingWorker::class.java)
278 .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
279 .setInputData(inputData)
280 .addTag(CONSTRAINT_TRACKING_TAG)
281 .build()
282 workManager.enqueue(request)
283 }
284 findViewById<View>(R.id.cancel_constraint_tracking_worker).setOnClickListener {
285 workManager.cancelAllWorkByTag(CONSTRAINT_TRACKING_TAG)
286 }
287 findViewById<View>(R.id.run_foreground_worker).setOnClickListener {
288 lastNotificationId += 1
289 val inputData = workDataOf(ForegroundWorker.InputNotificationId to lastNotificationId)
290
291 val request =
292 OneTimeWorkRequest.Builder(ForegroundWorker::class.java)
293 .setInputData(inputData)
294 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
295 .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
296 .build()
297 lastForegroundWorkRequest = request
298 workManager.enqueue(request)
299 }
300 findViewById<View>(R.id.run_foreground_worker_location).setOnClickListener {
301 lastNotificationId += 1
302 val inputData =
303 workDataOf(ForegroundLocationWorker.InputNotificationId to lastNotificationId)
304
305 val request =
306 OneTimeWorkRequest.Builder(ForegroundLocationWorker::class.java)
307 .setInputData(inputData)
308 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
309 .build()
310 lastForegroundWorkRequest = request
311 workManager.enqueue(request)
312 }
313 findViewById<View>(R.id.run_foreground_worker_network_request).setOnClickListener {
314 lastNotificationId += 1
315 val inputData = workDataOf(ForegroundWorker.InputNotificationId to lastNotificationId)
316 val networkRequest =
317 NetworkRequest.Builder()
318 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
319 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
320 .build()
321 val constraints =
322 Constraints.Builder()
323 .setRequiredNetworkRequest(networkRequest, NetworkType.CONNECTED)
324 .build()
325
326 val request =
327 OneTimeWorkRequest.Builder(ForegroundWorker::class.java)
328 .setInputData(inputData)
329 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
330 .setConstraints(constraints)
331 .build()
332 lastForegroundWorkRequest = request
333 workManager.enqueue(request)
334 }
335 findViewById<View>(R.id.cancel_foreground_worker).setOnClickListener {
336 if (lastForegroundWorkRequest != null) {
337 workManager.cancelWorkById(lastForegroundWorkRequest!!.id)
338 lastForegroundWorkRequest = null
339 } else {
340 workManager.cancelAllWorkByTag(ForegroundWorker::class.java.name)
341 }
342 }
343 findViewById<View>(R.id.cancel_foreground_worker_intent).setOnClickListener {
344 if (lastForegroundWorkRequest != null) {
345 val pendingIntent =
346 workManager.createCancelPendingIntent(lastForegroundWorkRequest!!.id)
347 try {
348 pendingIntent.send(0)
349 lastForegroundWorkRequest = null
350 } catch (exception: PendingIntent.CanceledException) {
351 Log.e(TAG, "Pending intent was cancelled.", exception)
352 }
353 } else {
354 Log.d(TAG, "No work to cancel")
355 }
356 }
357 findViewById<View>(R.id.enqueue_work_multi_process).setOnClickListener {
358 startService(enqueueIntent(this))
359 }
360 findViewById<View>(R.id.enqueue_continuation_multi_process).setOnClickListener {
361 startService(enqueueContinuationIntent(this))
362 }
363 findViewById<View>(R.id.cancel_work_tag_multiprocess).setOnClickListener {
364 startService(cancelWorkByTagIntent(this))
365 }
366 findViewById<View>(R.id.cancel_all_work_multiprocess).setOnClickListener {
367 startService(cancelAllWorkIntent(this))
368 }
369 findViewById<View>(R.id.query_work_multiprocess).setOnClickListener {
370 startService(queryWorkInfoIntent(this))
371 }
372 findViewById<View>(R.id.enqueue_periodic_work_multiprocess).setOnClickListener {
373 startService(enqueueUniquePeriodicIntent(this))
374 }
375 findViewById<View>(R.id.update_periodic_work_multiprocess).setOnClickListener {
376 startService(updateUniquePeriodicIntent(this))
377 }
378 findViewById<View>(R.id.enqueue_remote_worker_1).setOnClickListener {
379 val serviceName = RemoteWorkerService::class.java.name
380 val componentName = ComponentName(PACKAGE_NAME, serviceName)
381 val request = oneTimeWorkRemoteWorkRequest(componentName)
382 workManager.enqueue(request)
383 }
384 findViewById<View>(R.id.enqueue_remote_worker_2).setOnClickListener {
385 val serviceName = RemoteWorkerService2::class.java.name
386 val componentName = ComponentName(PACKAGE_NAME, serviceName)
387 val request = oneTimeWorkRemoteWorkRequest(componentName)
388 workManager.enqueue(request)
389 }
390 findViewById<View>(R.id.cancel_remote_workers).setOnClickListener {
391 workManager.cancelAllWorkByTag(RemoteWorker::class.java.name)
392 }
393 findViewById<View>(R.id.crash_app).setOnClickListener {
394 throw RuntimeException("Crashed app")
395 }
396 findViewById<View>(R.id.stress_test).setOnClickListener { queueLotsOfWorkers(workManager) }
397 findViewById<View>(R.id.enqueue_network_request).setOnClickListener {
398 enqueueWithNetworkRequest(workManager)
399 }
400 val hundredJobExceptionButton = findViewById<Button>(R.id.create_hundred_job_exception)
401 // 100 Job limits are only enforced on API 24+.
402 if (Build.VERSION.SDK_INT >= 24) {
403 hundredJobExceptionButton.setOnClickListener {
404 val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
405 workManager.cancelAllWork()
406 jobScheduler.cancelAll()
407 repeat(101) { i ->
408 jobScheduler.schedule(
409 JobInfo.Builder(
410 100000 + i,
411 ComponentName(this, SystemJobService::class.java)
412 )
413 .setMinimumLatency((10 * 60 * 1000).toLong())
414 .build()
415 )
416 }
417 repeat(100) {
418 workManager.enqueue(
419 OneTimeWorkRequest.Builder(TestWorker::class.java)
420 .setInitialDelay(10L, TimeUnit.MINUTES)
421 .build()
422 )
423 }
424 }
425 } else {
426 hundredJobExceptionButton.visibility = View.GONE
427 }
428 }
429
430 private fun oneTimeWorkRemoteWorkRequest(componentName: ComponentName): OneTimeWorkRequest {
431 val data =
432 workDataOf(
433 ARGUMENT_PACKAGE_NAME to componentName.packageName,
434 ARGUMENT_CLASS_NAME to componentName.className,
435 )
436 return OneTimeWorkRequest.Builder(RemoteWorker::class.java)
437 .setInputData(data)
438 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
439 .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
440 .build()
441 }
442 }
443
enqueueWithNetworkRequestnull444 private fun enqueueWithNetworkRequest(workManager: WorkManager) {
445 if (Build.VERSION.SDK_INT < 21) {
446 Log.w(TAG, "Ignoring enqueueWithNetworkRequest on old API levels")
447 return
448 }
449 val networkRequest =
450 NetworkRequest.Builder()
451 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
452 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
453 .build()
454 val constraints =
455 Constraints.Builder()
456 .setRequiredNetworkRequest(networkRequest, NetworkType.UNMETERED)
457 .build()
458 val request =
459 OneTimeWorkRequest.Builder(TestWorker::class.java).setConstraints(constraints).build()
460 workManager.enqueue(request)
461 }
462
463 private const val PACKAGE_NAME = "androidx.work.integration.testapp"
464 private const val TAG = "MainActivity"
465 private const val CONSTRAINT_TRACKING_TAG = "ConstraintTrackingWorker"
466 private const val UNIQUE_WORK_NAME = "importantUniqueWork"
467 private const val REPLACE_COMPLETED_WORK = "replaceCompletedWork"
468 private const val NUM_WORKERS = 150
469 private const val CONSTRAINT_WORKER_ARGUMENT_CLASS_NAME =
470 "androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME"
471