1 /*
2  * Copyright 2020 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.work.integration.testapp
18 
19 import android.annotation.SuppressLint
20 import android.app.Service
21 import android.content.Context
22 import android.content.Intent
23 import android.util.Log
24 import androidx.concurrent.futures.await
25 import androidx.lifecycle.LifecycleService
26 import androidx.work.Constraints
27 import androidx.work.ExistingPeriodicWorkPolicy
28 import androidx.work.NetworkType
29 import androidx.work.OneTimeWorkRequestBuilder
30 import androidx.work.PeriodicWorkRequestBuilder
31 import androidx.work.WorkInfo
32 import androidx.work.WorkQuery
33 import androidx.work.multiprocess.RemoteWorkManager
34 import java.util.concurrent.TimeUnit
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.Dispatchers
37 import kotlinx.coroutines.cancel
38 import kotlinx.coroutines.launch
39 
40 class RemoteService : LifecycleService() {
41 
42     private var mStopped: Boolean = true
43     private lateinit var mScope: CoroutineScope
44 
onCreatenull45     override fun onCreate() {
46         super.onCreate()
47         initService()
48     }
49 
onStartCommandnull50     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
51         super.onStartCommand(intent, flags, startId)
52         if (mStopped) {
53             initService()
54         }
55         handleIntent(intent)
56         // If the service were to crash, we want all unacknowledged Intents to get redelivered.
57         return Service.START_REDELIVER_INTENT
58     }
59 
onDestroynull60     override fun onDestroy() {
61         super.onDestroy()
62         mStopped = true
63         mScope.cancel()
64     }
65 
initServicenull66     private fun initService() {
67         mScope = CoroutineScope(Dispatchers.Default)
68     }
69 
handleIntentnull70     private fun handleIntent(intent: Intent?) {
71         when (intent?.action) {
72             ACTION_ENQUEUE_WORK -> {
73                 mScope.launch { enqueueWorkRequest() }
74             }
75             ACTION_ENQUEUE_CONTINUATION -> {
76                 mScope.launch { enqueueContinuation() }
77             }
78             ACTION_CANCEL_WORK_BY_TAG -> {
79                 mScope.launch { cancelAllWorkByTag() }
80             }
81             ACTION_CANCEL_ALL_WORK -> {
82                 mScope.launch { cancelAllWork() }
83             }
84             ACTION_QUERY_WORK_INFO -> {
85                 mScope.launch { queryWorkInfo() }
86             }
87             ACTION_ENQUEUE_UNIQUE_PERIODIC -> {
88                 mScope.launch { enqueuePeriodicWorkRequestWithInitialDelay() }
89             }
90             ACTION_UPDATE_UNIQUE_PERIODIC -> {
91                 mScope.launch { updateUniquePeriodicWork() }
92             }
93             else -> Log.d(TAG, "Unknown intent")
94         }
95     }
96 
enqueueWorkRequestnull97     private suspend fun enqueueWorkRequest() {
98         val request = OneTimeWorkRequestBuilder<TestWorker>().build()
99         Log.d(TAG, "Enqueue-ing TestWorker")
100         val remoteWorkManager = RemoteWorkManager.getInstance(this)
101         remoteWorkManager.enqueue(listOf(request)).await()
102     }
103 
enqueuePeriodicWorkRequestWithInitialDelaynull104     private suspend fun enqueuePeriodicWorkRequestWithInitialDelay() {
105         val request =
106             PeriodicWorkRequestBuilder<TestWorker>(15, TimeUnit.MINUTES)
107                 .setInitialDelay(15L, TimeUnit.MINUTES)
108                 .build()
109         Log.d(TAG, "Enqueue-ing PeriodicWorker ${request.id}")
110         val remoteWorkManager = RemoteWorkManager.getInstance(this)
111         remoteWorkManager
112             .enqueueUniquePeriodicWork("unique-periodic", ExistingPeriodicWorkPolicy.KEEP, request)
113             .await()
114     }
115 
updateUniquePeriodicWorknull116     private suspend fun updateUniquePeriodicWork() {
117         val request =
118             PeriodicWorkRequestBuilder<TestWorker>(15, TimeUnit.MINUTES)
119                 .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
120                 .build()
121         Log.d(TAG, "Enqueue-ing PeriodicWorker ${request.id}")
122         val remoteWorkManager = RemoteWorkManager.getInstance(this)
123         remoteWorkManager
124             .enqueueUniquePeriodicWork(
125                 "unique-periodic",
126                 ExistingPeriodicWorkPolicy.UPDATE,
127                 request
128             )
129             .await()
130     }
131 
132     @SuppressLint("EnqueueWork")
enqueueContinuationnull133     private suspend fun enqueueContinuation() {
134         val request =
135             OneTimeWorkRequestBuilder<TestWorker>()
136                 .setInitialDelay(1, TimeUnit.MINUTES)
137                 .addTag(WORK_TAG)
138                 .build()
139 
140         Log.d(TAG, "Enqueue-ing a Continuation")
141         RemoteWorkManager.getInstance(this).beginWith(request).enqueue().await()
142     }
143 
cancelAllWorkByTagnull144     private suspend fun cancelAllWorkByTag() {
145         Log.d(TAG, "Cancelling work by tag")
146         val remoteWorkManager = RemoteWorkManager.getInstance(this)
147         remoteWorkManager.cancelAllWorkByTag(WORK_TAG).await()
148     }
149 
cancelAllWorknull150     private suspend fun cancelAllWork() {
151         Log.d(TAG, "Cancelling all work")
152         val remoteWorkManager = RemoteWorkManager.getInstance(this)
153         remoteWorkManager.cancelAllWork().await()
154     }
155 
queryWorkInfonull156     private suspend fun queryWorkInfo() {
157         Log.d(TAG, "Querying work info")
158         val query = WorkQuery.Builder.fromStates(listOf(WorkInfo.State.ENQUEUED)).build()
159 
160         val remoteWorkManager = RemoteWorkManager.getInstance(this)
161         val workInfoList: List<WorkInfo> = remoteWorkManager.getWorkInfos(query).await()
162         workInfoList.forEach { Log.d(TAG, "Found Worker with tags ${it.tags}") }
163     }
164 
165     companion object {
166         const val TAG: String = "WM-RemoteService"
167         const val WORK_TAG = "RemoteWorkTag"
168         const val ACTION_ENQUEUE_WORK = "ACTION_ENQUEUE_WORK"
169         const val ACTION_ENQUEUE_CONTINUATION = "ACTION_ENQUEUE_CONTINUATION"
170         const val ACTION_CANCEL_WORK_BY_TAG = "ACTION_CANCEL_WORK_BY_TAG"
171         const val ACTION_CANCEL_ALL_WORK = "ACTION_CANCEL_ALL_WORK"
172         const val ACTION_QUERY_WORK_INFO = "ACTION_QUERY_WORK_INFO"
173         const val ACTION_ENQUEUE_UNIQUE_PERIODIC = "ACTION_ENQUEUE_UNIQUE_PERIODIC"
174         const val ACTION_UPDATE_UNIQUE_PERIODIC = "ACTION_UPDATE_UNIQUE_PERIODIC"
175 
enqueueIntentnull176         fun enqueueIntent(context: Context): Intent {
177             val intent = Intent(context.applicationContext, RemoteService::class.java)
178             intent.action = ACTION_ENQUEUE_WORK
179             return intent
180         }
181 
enqueueContinuationIntentnull182         fun enqueueContinuationIntent(context: Context): Intent {
183             val intent = Intent(context.applicationContext, RemoteService::class.java)
184             intent.action = ACTION_ENQUEUE_CONTINUATION
185             return intent
186         }
187 
enqueueUniquePeriodicIntentnull188         fun enqueueUniquePeriodicIntent(context: Context): Intent {
189             val intent = Intent(context.applicationContext, RemoteService::class.java)
190             intent.action = ACTION_ENQUEUE_UNIQUE_PERIODIC
191             return intent
192         }
193 
updateUniquePeriodicIntentnull194         fun updateUniquePeriodicIntent(context: Context): Intent {
195             val intent = Intent(context.applicationContext, RemoteService::class.java)
196             intent.action = ACTION_UPDATE_UNIQUE_PERIODIC
197             return intent
198         }
199 
cancelWorkByTagIntentnull200         fun cancelWorkByTagIntent(context: Context): Intent {
201             val intent = Intent(context.applicationContext, RemoteService::class.java)
202             intent.action = ACTION_CANCEL_WORK_BY_TAG
203             return intent
204         }
205 
cancelAllWorkIntentnull206         fun cancelAllWorkIntent(context: Context): Intent {
207             val intent = Intent(context.applicationContext, RemoteService::class.java)
208             intent.action = ACTION_CANCEL_ALL_WORK
209             return intent
210         }
211 
queryWorkInfoIntentnull212         fun queryWorkInfoIntent(context: Context): Intent {
213             val intent = Intent(context.applicationContext, RemoteService::class.java)
214             intent.action = ACTION_QUERY_WORK_INFO
215             return intent
216         }
217     }
218 }
219