1 /* 2 * Copyright (C) 2015 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 com.android.documentsui.services; 18 19 import static com.android.documentsui.services.FileOperationService.OPERATION_COPY; 20 import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; 21 import static com.android.documentsui.services.FileOperations.createBaseIntent; 22 import static com.android.documentsui.services.FileOperations.createJobId; 23 24 import static org.junit.Assert.fail; 25 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.Uri; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.test.ServiceTestCase; 32 33 import androidx.test.InstrumentationRegistry; 34 import androidx.test.filters.MediumTest; 35 36 import com.android.documentsui.R; 37 import com.android.documentsui.base.DocumentInfo; 38 import com.android.documentsui.base.DocumentStack; 39 import com.android.documentsui.base.Features; 40 import com.android.documentsui.clipping.UrisSupplier; 41 import com.android.documentsui.services.FileOperationService.OpType; 42 import com.android.documentsui.testing.DocsProviders; 43 import com.android.documentsui.testing.TestFeatures; 44 import com.android.documentsui.testing.TestHandler; 45 import com.android.documentsui.testing.TestScheduledExecutorService; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 51 @MediumTest 52 public class FileOperationServiceTest extends ServiceTestCase<FileOperationService> { 53 54 private static final Uri SRC_PARENT = 55 Uri.parse("content://com.android.documentsui.testing/parent"); 56 private static final DocumentInfo ALPHA_DOC = createDoc("alpha"); 57 private static final DocumentInfo BETA_DOC = createDoc("alpha"); 58 private static final DocumentInfo GAMMA_DOC = createDoc("gamma"); 59 private static final DocumentInfo DELTA_DOC = createDoc("delta"); 60 61 private final List<TestJob> mCopyJobs = new ArrayList<>(); 62 private final List<TestJob> mDeleteJobs = new ArrayList<>(); 63 64 private FileOperationService mService; 65 private TestScheduledExecutorService mExecutor; 66 private TestScheduledExecutorService mDeletionExecutor; 67 private TestHandler mHandler; 68 private TestForegroundManager mForegroundManager; 69 private TestNotificationManager mTestNotificationManager; 70 FileOperationServiceTest()71 public FileOperationServiceTest() { 72 super(FileOperationService.class); 73 } 74 75 @Override setUp()76 protected void setUp() throws Exception { 77 super.setUp(); 78 setupService(); // must be called first for our test setup to work correctly. 79 80 mExecutor = new TestScheduledExecutorService(); 81 mDeletionExecutor = new TestScheduledExecutorService(); 82 mHandler = new TestHandler(); 83 mTestNotificationManager = new TestNotificationManager(); 84 mForegroundManager = new TestForegroundManager(mTestNotificationManager); 85 TestFeatures features = new TestFeatures(); 86 features.notificationChannel = InstrumentationRegistry.getTargetContext() 87 .getResources().getBoolean(R.bool.feature_notification_channel); 88 89 mCopyJobs.clear(); 90 mDeleteJobs.clear(); 91 92 // Install test doubles. 93 mService = getService(); 94 95 assertNull(mService.executor); 96 mService.executor = mExecutor; 97 98 assertNull(mService.deletionExecutor); 99 mService.deletionExecutor = mDeletionExecutor; 100 101 assertNull(mService.handler); 102 mService.handler = mHandler; 103 104 assertNull(mService.foregroundManager); 105 mService.foregroundManager = mForegroundManager; 106 107 assertNull(mService.notificationManager); 108 mService.notificationManager = mTestNotificationManager.createNotificationManager(); 109 110 assertNull(mService.features); 111 mService.features = features; 112 } 113 114 @Override tearDown()115 protected void tearDown() { 116 // Release all possibly held wake lock here 117 mExecutor.runAll(); 118 mDeletionExecutor.runAll(); 119 120 // There are lots of progress notifications generated in this test case. 121 // Dismiss all of them here. 122 mHandler.dispatchAllMessages(); 123 } 124 testRunsCopyJobs()125 public void testRunsCopyJobs() throws Exception { 126 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 127 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 128 129 mExecutor.runAll(); 130 assertAllCopyJobsStarted(); 131 } 132 testRunsCopyJobs_AfterExceptionInJobCreation()133 public void testRunsCopyJobs_AfterExceptionInJobCreation() throws Exception { 134 try { 135 startService(createCopyIntent(new ArrayList<>(), BETA_DOC)); 136 fail("Should have throw exception."); 137 } catch (IllegalArgumentException expected) { 138 // We're sending a naughty empty list that should result in an IllegalArgumentException. 139 } 140 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 141 142 assertJobsCreated(1); 143 144 mExecutor.runAll(); 145 assertAllCopyJobsStarted(); 146 } 147 testRunsCopyJobs_AfterFailure()148 public void testRunsCopyJobs_AfterFailure() throws Exception { 149 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 150 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 151 152 mCopyJobs.get(0).fail(ALPHA_DOC); 153 154 mExecutor.runAll(); 155 assertAllCopyJobsStarted(); 156 } 157 testRunsCopyJobs_notRunsDeleteJobs()158 public void testRunsCopyJobs_notRunsDeleteJobs() throws Exception { 159 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 160 startService(createDeleteIntent(Arrays.asList(GAMMA_DOC))); 161 162 mExecutor.runAll(); 163 assertNoDeleteJobsStarted(); 164 } 165 testRunsDeleteJobs()166 public void testRunsDeleteJobs() throws Exception { 167 startService(createDeleteIntent(Arrays.asList(ALPHA_DOC))); 168 169 mDeletionExecutor.runAll(); 170 assertAllDeleteJobsStarted(); 171 } 172 testRunsDeleteJobs_NotRunsCopyJobs()173 public void testRunsDeleteJobs_NotRunsCopyJobs() throws Exception { 174 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 175 startService(createDeleteIntent(Arrays.asList(GAMMA_DOC))); 176 177 mDeletionExecutor.runAll(); 178 assertNoCopyJobsStarted(); 179 } 180 testUpdatesNotification()181 public void testUpdatesNotification() throws Exception { 182 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 183 mExecutor.runAll(); 184 185 // Assert monitoring continues until job is done 186 assertTrue(mHandler.hasScheduledMessage()); 187 // Two notifications -- one for setup; one for progress 188 assertEquals(2, mCopyJobs.get(0).getNumOfNotifications()); 189 } 190 testStopsUpdatingNotificationAfterFinished()191 public void testStopsUpdatingNotificationAfterFinished() throws Exception { 192 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 193 mExecutor.runAll(); 194 195 mHandler.dispatchNextMessage(); 196 // Assert monitoring stops once job is completed. 197 assertFalse(mHandler.hasScheduledMessage()); 198 199 // Assert no more notification is generated after finish. 200 assertEquals(2, mCopyJobs.get(0).getNumOfNotifications()); 201 202 } 203 testHoldsWakeLockWhileWorking()204 public void testHoldsWakeLockWhileWorking() throws Exception { 205 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 206 207 assertTrue(mService.holdsWakeLock()); 208 } 209 testReleasesWakeLock_AfterSuccess()210 public void testReleasesWakeLock_AfterSuccess() throws Exception { 211 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 212 213 assertTrue(mService.holdsWakeLock()); 214 mExecutor.runAll(); 215 assertFalse(mService.holdsWakeLock()); 216 } 217 testReleasesWakeLock_AfterFailure()218 public void testReleasesWakeLock_AfterFailure() throws Exception { 219 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 220 221 assertTrue(mService.holdsWakeLock()); 222 mExecutor.runAll(); 223 assertFalse(mService.holdsWakeLock()); 224 } 225 testShutdownStopsExecutor_AfterSuccess()226 public void testShutdownStopsExecutor_AfterSuccess() throws Exception { 227 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 228 229 mExecutor.assertAlive(); 230 mDeletionExecutor.assertAlive(); 231 232 mExecutor.runAll(); 233 shutdownService(); 234 235 assertExecutorsShutdown(); 236 } 237 testShutdownStopsExecutor_AfterMixedFailures()238 public void testShutdownStopsExecutor_AfterMixedFailures() throws Exception { 239 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 240 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 241 242 mCopyJobs.get(0).fail(ALPHA_DOC); 243 244 mExecutor.runAll(); 245 shutdownService(); 246 247 assertExecutorsShutdown(); 248 } 249 testShutdownStopsExecutor_AfterTotalFailure()250 public void testShutdownStopsExecutor_AfterTotalFailure() throws Exception { 251 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 252 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 253 254 mCopyJobs.get(0).fail(ALPHA_DOC); 255 mCopyJobs.get(1).fail(GAMMA_DOC); 256 257 mExecutor.runAll(); 258 shutdownService(); 259 260 assertExecutorsShutdown(); 261 } 262 testRunsInForeground_MultipleJobs()263 public void testRunsInForeground_MultipleJobs() throws Exception { 264 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 265 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 266 267 mExecutor.run(0); 268 mForegroundManager.assertInForeground(); 269 270 mHandler.dispatchAllMessages(); 271 mForegroundManager.assertInForeground(); 272 } 273 testFinishesInBackground_MultipleJobs()274 public void testFinishesInBackground_MultipleJobs() throws Exception { 275 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 276 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 277 278 mExecutor.run(0); 279 mForegroundManager.assertInForeground(); 280 281 mHandler.dispatchAllMessages(); 282 mForegroundManager.assertInForeground(); 283 284 mExecutor.run(0); 285 mHandler.dispatchAllMessages(); 286 mForegroundManager.assertInBackground(); 287 } 288 testAllNotificationsDismissedAfterShutdown()289 public void testAllNotificationsDismissedAfterShutdown() throws Exception { 290 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 291 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 292 293 mExecutor.runAll(); 294 295 mHandler.dispatchAllMessages(); 296 mTestNotificationManager.assertNumberOfNotifications(0); 297 } 298 testNotificationUpdateAfterForegroundJobSwitch()299 public void testNotificationUpdateAfterForegroundJobSwitch() throws Exception { 300 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 301 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 302 Job job1 = mCopyJobs.get(0); 303 Job job2 = mCopyJobs.get(1); 304 305 mService.onStart(job1); 306 mTestNotificationManager.assertHasNotification( 307 FileOperationService.NOTIFICATION_ID_PROGRESS, null); 308 309 mService.onStart(job2); 310 mTestNotificationManager.assertHasNotification( 311 FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id); 312 313 job1.cancel(); 314 mService.onFinished(job1); 315 mTestNotificationManager.assertHasNotification( 316 FileOperationService.NOTIFICATION_ID_PROGRESS, null); 317 mTestNotificationManager.assertNoNotification( 318 FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id); 319 320 job2.cancel(); 321 mService.onFinished(job2); 322 mTestNotificationManager.assertNumberOfNotifications(0); 323 } 324 createCopyIntent(List<DocumentInfo> files, DocumentInfo dest)325 private Intent createCopyIntent(List<DocumentInfo> files, DocumentInfo dest) 326 throws Exception { 327 DocumentStack stack = new DocumentStack(); 328 stack.push(dest); 329 330 List<Uri> uris = new ArrayList<>(files.size()); 331 for (DocumentInfo file : files) { 332 uris.add(file.derivedUri); 333 } 334 335 UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris); 336 TestFileOperation operation = new TestFileOperation(OPERATION_COPY, urisSupplier, stack); 337 338 return createBaseIntent(getContext(), createJobId(), operation); 339 } 340 createDeleteIntent(List<DocumentInfo> files)341 private Intent createDeleteIntent(List<DocumentInfo> files) { 342 DocumentStack stack = new DocumentStack(); 343 344 List<Uri> uris = new ArrayList<>(files.size()); 345 for (DocumentInfo file : files) { 346 uris.add(file.derivedUri); 347 } 348 349 UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris); 350 TestFileOperation operation = new TestFileOperation(OPERATION_DELETE, urisSupplier, stack); 351 352 return createBaseIntent(getContext(), createJobId(), operation); 353 } 354 createDoc(String name)355 private static DocumentInfo createDoc(String name) { 356 // Doesn't need to be valid content Uri, just some urly looking thing. 357 Uri uri = new Uri.Builder() 358 .scheme("content") 359 .authority("com.android.documentsui.testing") 360 .path(name) 361 .build(); 362 363 return createDoc(uri); 364 } 365 assertAllCopyJobsStarted()366 void assertAllCopyJobsStarted() { 367 for (TestJob job : mCopyJobs) { 368 job.assertStarted(); 369 } 370 } 371 assertAllDeleteJobsStarted()372 void assertAllDeleteJobsStarted() { 373 for (TestJob job : mDeleteJobs) { 374 job.assertStarted(); 375 } 376 } 377 assertNoCopyJobsStarted()378 void assertNoCopyJobsStarted() { 379 for (TestJob job : mCopyJobs) { 380 job.assertNotStarted(); 381 } 382 } 383 assertNoDeleteJobsStarted()384 void assertNoDeleteJobsStarted() { 385 for (TestJob job : mDeleteJobs) { 386 job.assertNotStarted(); 387 } 388 } 389 assertJobsCreated(int expected)390 void assertJobsCreated(int expected) { 391 assertEquals(expected, mCopyJobs.size() + mDeleteJobs.size()); 392 } 393 createDoc(Uri destination)394 private static DocumentInfo createDoc(Uri destination) { 395 DocumentInfo destDoc = new DocumentInfo(); 396 destDoc.derivedUri = destination; 397 return destDoc; 398 } 399 assertExecutorsShutdown()400 private void assertExecutorsShutdown() { 401 mExecutor.assertShutdown(); 402 mDeletionExecutor.assertShutdown(); 403 } 404 405 private final class TestFileOperation extends FileOperation { 406 407 private final Runnable mJobRunnable = () -> { 408 // The following statement is executed concurrently to Job.start() in real situation. 409 // Call it in TestJob.start() to mimic this behavior. 410 mHandler.dispatchNextMessage(); 411 }; 412 private final @OpType int mOpType; 413 private final UrisSupplier mSrcs; 414 private final DocumentStack mDestination; 415 TestFileOperation( @pType int opType, UrisSupplier srcs, DocumentStack destination)416 private TestFileOperation( 417 @OpType int opType, UrisSupplier srcs, DocumentStack destination) { 418 super(opType, srcs, destination); 419 mOpType = opType; 420 mSrcs = srcs; 421 mDestination = destination; 422 } 423 424 @Override createJob(Context service, Job.Listener listener, String id, Features features)425 public Job createJob(Context service, Job.Listener listener, String id, Features features) { 426 TestJob job = new TestJob( 427 service, listener, id, mOpType, mDestination, mSrcs, mJobRunnable, features); 428 429 if (mOpType == OPERATION_COPY) { 430 mCopyJobs.add(job); 431 } 432 433 if (mOpType == OPERATION_DELETE) { 434 mDeleteJobs.add(job); 435 } 436 437 return job; 438 } 439 440 /** 441 * CREATOR is required for Parcelables, but we never pass this class via parcel. 442 */ 443 public Parcelable.Creator<TestFileOperation> CREATOR = 444 new Parcelable.Creator<TestFileOperation>() { 445 446 @Override 447 public TestFileOperation createFromParcel(Parcel source) { 448 throw new UnsupportedOperationException("Can't create from a parcel."); 449 } 450 451 @Override 452 public TestFileOperation[] newArray(int size) { 453 throw new UnsupportedOperationException("Can't create a new array."); 454 } 455 }; 456 } 457 } 458