1 /* <lambda>null2 * Copyright 2021 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.impl 18 19 import android.app.job.JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY 20 import android.content.ComponentName 21 import android.content.Context 22 import android.content.ContextWrapper 23 import android.content.Intent 24 import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE 25 import androidx.core.app.NotificationChannelCompat 26 import androidx.core.app.NotificationCompat 27 import androidx.core.app.NotificationManagerCompat 28 import androidx.test.core.app.ApplicationProvider 29 import androidx.test.ext.junit.runners.AndroidJUnit4 30 import androidx.test.filters.MediumTest 31 import androidx.work.Configuration 32 import androidx.work.DatabaseTest 33 import androidx.work.ForegroundInfo 34 import androidx.work.OneTimeWorkRequest 35 import androidx.work.PeriodicWorkRequest 36 import androidx.work.impl.foreground.SystemForegroundDispatcher.createStartForegroundIntent 37 import androidx.work.impl.foreground.SystemForegroundDispatcher.createStopForegroundIntent 38 import androidx.work.impl.model.WorkGenerationalId 39 import androidx.work.impl.model.generationalId 40 import androidx.work.impl.testutils.TrackingWorkerFactory 41 import androidx.work.impl.utils.SerialExecutorImpl 42 import androidx.work.impl.utils.taskexecutor.TaskExecutor 43 import androidx.work.worker.LatchWorker 44 import androidx.work.worker.StopAwareWorker 45 import androidx.work.worker.StopLatchWorker 46 import java.util.concurrent.CountDownLatch 47 import java.util.concurrent.Executor 48 import java.util.concurrent.ExecutorService 49 import java.util.concurrent.Executors 50 import java.util.concurrent.TimeUnit 51 import org.junit.After 52 import org.junit.Assert.assertEquals 53 import org.junit.Assert.assertFalse 54 import org.junit.Assert.assertTrue 55 import org.junit.Before 56 import org.junit.Test 57 import org.junit.runner.RunWith 58 59 @RunWith(AndroidJUnit4::class) 60 class ProcessorTests : DatabaseTest() { 61 val factory = TrackingWorkerFactory() 62 lateinit var processor: Processor 63 lateinit var defaultExecutor: ExecutorService 64 lateinit var backgroundExecutor: ExecutorService 65 lateinit var serialExecutor: SerialExecutorImpl 66 private val context = 67 TrackingContext(ApplicationProvider.getApplicationContext<Context>().applicationContext) 68 69 private val foregroundInfo: ForegroundInfo 70 get() { 71 val channel = 72 NotificationChannelCompat.Builder( 73 "test", 74 NotificationManagerCompat.IMPORTANCE_DEFAULT 75 ) 76 .setName("hello") 77 .build() 78 NotificationManagerCompat.from(context).createNotificationChannel(channel) 79 val notification = 80 NotificationCompat.Builder(context, "test") 81 .setOngoing(true) 82 .setTicker("ticker") 83 .setContentText("content text") 84 .setSmallIcon(androidx.core.R.drawable.notification_bg) 85 .build() 86 return ForegroundInfo(1, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE) 87 } 88 89 @Before 90 fun setUp() { 91 // first worker will take over the first thread with its doWork 92 // second worker will execute on the second thread 93 defaultExecutor = Executors.newFixedThreadPool(2) 94 backgroundExecutor = Executors.newSingleThreadExecutor() 95 serialExecutor = SerialExecutorImpl(backgroundExecutor) 96 val taskExecutor = 97 object : TaskExecutor { 98 val mainExecutor = Executor { runnable -> runnable.run() } 99 100 override fun getMainThreadExecutor(): Executor { 101 return mainExecutor 102 } 103 104 override fun getSerialTaskExecutor(): SerialExecutorImpl { 105 return serialExecutor 106 } 107 } 108 val configuration = 109 Configuration.Builder().setWorkerFactory(factory).setExecutor(defaultExecutor).build() 110 processor = Processor(context, configuration, taskExecutor, mDatabase) 111 } 112 113 @Test 114 @MediumTest 115 fun testInterruptNotInCriticalSection() { 116 val request1 = OneTimeWorkRequest.Builder(StopLatchWorker::class.java).build() 117 val request2 = OneTimeWorkRequest.Builder(StopLatchWorker::class.java).build() 118 insertWork(request1) 119 insertWork(request2) 120 class CountDownListener(val expectedId: String) : ExecutionListener { 121 val latch = CountDownLatch(1) 122 123 override fun onExecuted(id: WorkGenerationalId, needsReschedule: Boolean) { 124 if (id.workSpecId == expectedId) { 125 latch.countDown() 126 } 127 } 128 } 129 val firstListener = CountDownListener(request1.workSpec.id) 130 processor.addExecutionListener(firstListener) 131 val startStopToken = StartStopToken(WorkGenerationalId(request1.workSpec.id, 0)) 132 processor.startWork(startStopToken) 133 134 val firstWorker = factory.awaitWorker(request1.id) 135 Executors.newSingleThreadExecutor().execute { 136 // wil result in long running onStop call, but it will block task thread 137 processor.stopWork(startStopToken, 0) 138 } 139 assertTrue((firstWorker as StopLatchWorker).awaitOnStopCall()) 140 141 val secondListener = CountDownListener(request2.workSpec.id) 142 processor.addExecutionListener(secondListener) 143 // This would have previously failed trying to acquire a lock 144 processor.startWork(StartStopToken(WorkGenerationalId(request2.workSpec.id, 0))) 145 firstWorker.countDown() 146 val secondWorker = factory.awaitWorker(request2.id) 147 assertTrue(firstListener.latch.await(3, TimeUnit.SECONDS)) 148 (secondWorker as StopLatchWorker).countDown() 149 assertTrue(secondListener.latch.await(3, TimeUnit.SECONDS)) 150 firstWorker.countDown() 151 assertTrue(context.intents.isEmpty()) 152 } 153 154 @Test 155 @MediumTest 156 fun testStartForegroundStopWork() { 157 val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build() 158 insertWork(request) 159 val startStopToken = StartStopToken(request.workSpec.generationalId()) 160 val executionFinished = CountDownLatch(1) 161 processor.addExecutionListener { _, _ -> executionFinished.countDown() } 162 processor.startWork(startStopToken) 163 164 processor.startForeground(startStopToken.id.workSpecId, foregroundInfo) 165 // won't actually stopWork, because stopForeground should be used 166 processor.stopWork(startStopToken, 0) 167 // follow-up startWork shouldn't fail 168 processor.startWork(StartStopToken(request.workSpec.generationalId())) 169 assertTrue(processor.isEnqueued(startStopToken.id.workSpecId)) 170 val firstWorker = factory.awaitWorker(request.id) 171 (firstWorker as LatchWorker).mLatch.countDown() 172 assertTrue(executionFinished.await(3, TimeUnit.SECONDS)) 173 } 174 175 @Test 176 @MediumTest 177 fun testInterruptStopsService() { 178 val request = OneTimeWorkRequest.Builder(StopAwareWorker::class.java).build() 179 insertWork(request) 180 val id = request.workSpec.generationalId() 181 val startStopToken = StartStopToken(id) 182 val executionFinished = CountDownLatch(1) 183 processor.addExecutionListener { _, _ -> executionFinished.countDown() } 184 processor.startWork(startStopToken) 185 processor.startForeground(startStopToken.id.workSpecId, foregroundInfo) 186 val expected = createStartForegroundIntent(context, id, foregroundInfo) 187 assertTrue(context.intents[0].filterEquals(expected)) 188 // won't actually stopWork, because stopForeground should be used 189 processor.stopForegroundWork(startStopToken, STOP_REASON_CONSTRAINT_CONNECTIVITY) 190 assertFalse(processor.isEnqueued(startStopToken.id.workSpecId)) 191 assertTrue(executionFinished.await(3, TimeUnit.SECONDS)) 192 val stopIntentExpected = createStopForegroundIntent(context) 193 194 val intent = context.intents.getOrNull(1) ?: throw AssertionError("Stop Intent wasn't sent") 195 assertTrue(intent.filterEquals(stopIntentExpected)) 196 } 197 198 @Test 199 @MediumTest 200 fun testStartOldGenerationDoesntStopCurrentWorker() { 201 val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build() 202 insertWork(request) 203 mDatabase.workSpecDao().incrementGeneration(request.stringId) 204 val token = StartStopToken(WorkGenerationalId(request.workSpec.id, 1)) 205 processor.startWork(token) 206 val firstWorker = factory.awaitWorker(request.id) 207 var called = false 208 val oldGenerationListener = ExecutionListener { id, needsReschedule -> 209 called = true 210 assertEquals(WorkGenerationalId(request.workSpec.id, 0), id) 211 assertFalse(needsReschedule) 212 } 213 processor.addExecutionListener(oldGenerationListener) 214 processor.startWork(StartStopToken(WorkGenerationalId(request.workSpec.id, 0))) 215 assertTrue(called) 216 processor.removeExecutionListener(oldGenerationListener) 217 val executionFinished = CountDownLatch(1) 218 processor.addExecutionListener { _, _ -> executionFinished.countDown() } 219 (firstWorker as LatchWorker).mLatch.countDown() 220 assertTrue(executionFinished.await(3, TimeUnit.SECONDS)) 221 } 222 223 @Test 224 @MediumTest 225 fun testStartNewGenerationDoesntStopCurrentWorker() { 226 val request = 227 PeriodicWorkRequest.Builder(LatchWorker::class.java, 10, TimeUnit.DAYS).build() 228 insertWork(request) 229 val token = StartStopToken(WorkGenerationalId(request.workSpec.id, 0)) 230 processor.startWork(token) 231 val firstWorker = factory.awaitWorker(request.id) 232 var called = false 233 val oldGenerationListener = ExecutionListener { id, needsReschedule -> 234 called = true 235 assertEquals(WorkGenerationalId(request.workSpec.id, 1), id) 236 assertFalse(needsReschedule) 237 } 238 processor.addExecutionListener(oldGenerationListener) 239 mDatabase.workSpecDao().incrementGeneration(request.stringId) 240 processor.startWork(StartStopToken(WorkGenerationalId(request.workSpec.id, 1))) 241 assertTrue(called) 242 processor.removeExecutionListener(oldGenerationListener) 243 val executionFinished = CountDownLatch(1) 244 processor.addExecutionListener { _, _ -> executionFinished.countDown() } 245 (firstWorker as LatchWorker).mLatch.countDown() 246 assertTrue(executionFinished.await(3, TimeUnit.SECONDS)) 247 } 248 249 @Test 250 @MediumTest 251 fun testOldGenerationDoesntStart() { 252 val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build() 253 insertWork(request) 254 mDatabase.workSpecDao().incrementGeneration(request.stringId) 255 val oldToken = StartStopToken(WorkGenerationalId(request.workSpec.id, 0)) 256 var called = false 257 val oldGenerationListener = ExecutionListener { id, needsReschedule -> 258 called = true 259 // worker shouldn't have been created 260 assertEquals(0, factory.createdWorkers.value.size) 261 assertEquals(WorkGenerationalId(request.workSpec.id, 0), id) 262 assertFalse(needsReschedule) 263 } 264 processor.addExecutionListener(oldGenerationListener) 265 assertFalse(processor.startWork(oldToken)) 266 assertTrue(called) 267 } 268 269 @After 270 fun tearDown() { 271 defaultExecutor.shutdownNow() 272 backgroundExecutor.shutdownNow() 273 assertTrue(defaultExecutor.awaitTermination(3, TimeUnit.SECONDS)) 274 assertTrue(backgroundExecutor.awaitTermination(3, TimeUnit.SECONDS)) 275 } 276 277 private class TrackingContext(base: Context) : ContextWrapper(base) { 278 val intents = mutableListOf<Intent>() 279 280 override fun startService(service: Intent): ComponentName? { 281 // don't start anything, simply track requests 282 intents.add(service) 283 // result isn't used so simply return null 284 return null 285 } 286 287 override fun startForegroundService(service: Intent): ComponentName? { 288 // simply track it 289 return startService(service) 290 } 291 } 292 } 293