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