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