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 mService.mVisualSignalsEnabled = false; 114 } 115 116 @Override tearDown()117 protected void tearDown() { 118 // Release all possibly held wake lock here 119 mExecutor.runAll(); 120 mDeletionExecutor.runAll(); 121 122 // There are lots of progress notifications generated in this test case. 123 // Dismiss all of them here. 124 mHandler.dispatchAllMessages(); 125 } 126 testRunsCopyJobs()127 public void testRunsCopyJobs() throws Exception { 128 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 129 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 130 131 mExecutor.runAll(); 132 assertAllCopyJobsStarted(); 133 } 134 testRunsCopyJobs_AfterExceptionInJobCreation()135 public void testRunsCopyJobs_AfterExceptionInJobCreation() throws Exception { 136 try { 137 startService(createCopyIntent(new ArrayList<>(), BETA_DOC)); 138 fail("Should have throw exception."); 139 } catch (IllegalArgumentException expected) { 140 // We're sending a naughty empty list that should result in an IllegalArgumentException. 141 } 142 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 143 144 assertJobsCreated(1); 145 146 mExecutor.runAll(); 147 assertAllCopyJobsStarted(); 148 } 149 testRunsCopyJobs_AfterFailure()150 public void testRunsCopyJobs_AfterFailure() throws Exception { 151 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 152 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 153 154 mCopyJobs.get(0).fail(ALPHA_DOC); 155 156 mExecutor.runAll(); 157 assertAllCopyJobsStarted(); 158 } 159 testRunsCopyJobs_notRunsDeleteJobs()160 public void testRunsCopyJobs_notRunsDeleteJobs() throws Exception { 161 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 162 startService(createDeleteIntent(Arrays.asList(GAMMA_DOC))); 163 164 mExecutor.runAll(); 165 assertNoDeleteJobsStarted(); 166 } 167 testRunsDeleteJobs()168 public void testRunsDeleteJobs() throws Exception { 169 startService(createDeleteIntent(Arrays.asList(ALPHA_DOC))); 170 171 mDeletionExecutor.runAll(); 172 assertAllDeleteJobsStarted(); 173 } 174 testRunsDeleteJobs_NotRunsCopyJobs()175 public void testRunsDeleteJobs_NotRunsCopyJobs() throws Exception { 176 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 177 startService(createDeleteIntent(Arrays.asList(GAMMA_DOC))); 178 179 mDeletionExecutor.runAll(); 180 assertNoCopyJobsStarted(); 181 } 182 testUpdatesNotification()183 public void testUpdatesNotification() throws Exception { 184 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 185 mExecutor.runAll(); 186 187 // Assert monitoring continues until job is done 188 assertTrue(mHandler.hasScheduledMessage()); 189 // Two notifications -- one for setup; one for progress 190 assertEquals(2, mCopyJobs.get(0).getNumOfNotifications()); 191 } 192 testStopsUpdatingNotificationAfterFinished()193 public void testStopsUpdatingNotificationAfterFinished() throws Exception { 194 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 195 mExecutor.runAll(); 196 197 mHandler.dispatchNextMessage(); 198 // Assert monitoring stops once job is completed. 199 assertFalse(mHandler.hasScheduledMessage()); 200 201 // Assert no more notification is generated after finish. 202 assertEquals(2, mCopyJobs.get(0).getNumOfNotifications()); 203 204 } 205 testHoldsWakeLockWhileWorking()206 public void testHoldsWakeLockWhileWorking() throws Exception { 207 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 208 209 assertTrue(mService.holdsWakeLock()); 210 } 211 testReleasesWakeLock_AfterSuccess()212 public void testReleasesWakeLock_AfterSuccess() throws Exception { 213 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 214 215 assertTrue(mService.holdsWakeLock()); 216 mExecutor.runAll(); 217 assertFalse(mService.holdsWakeLock()); 218 } 219 testReleasesWakeLock_AfterFailure()220 public void testReleasesWakeLock_AfterFailure() throws Exception { 221 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 222 223 assertTrue(mService.holdsWakeLock()); 224 mExecutor.runAll(); 225 assertFalse(mService.holdsWakeLock()); 226 } 227 testShutdownStopsExecutor_AfterSuccess()228 public void testShutdownStopsExecutor_AfterSuccess() throws Exception { 229 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 230 231 mExecutor.assertAlive(); 232 mDeletionExecutor.assertAlive(); 233 234 mExecutor.runAll(); 235 shutdownService(); 236 237 assertExecutorsShutdown(); 238 } 239 testShutdownStopsExecutor_AfterMixedFailures()240 public void testShutdownStopsExecutor_AfterMixedFailures() throws Exception { 241 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 242 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 243 244 mCopyJobs.get(0).fail(ALPHA_DOC); 245 246 mExecutor.runAll(); 247 shutdownService(); 248 249 assertExecutorsShutdown(); 250 } 251 testShutdownStopsExecutor_AfterTotalFailure()252 public void testShutdownStopsExecutor_AfterTotalFailure() throws Exception { 253 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 254 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 255 256 mCopyJobs.get(0).fail(ALPHA_DOC); 257 mCopyJobs.get(1).fail(GAMMA_DOC); 258 259 mExecutor.runAll(); 260 shutdownService(); 261 262 assertExecutorsShutdown(); 263 } 264 testRunsInForeground_MultipleJobs()265 public void testRunsInForeground_MultipleJobs() throws Exception { 266 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 267 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 268 269 mExecutor.run(0); 270 mForegroundManager.assertInForeground(); 271 272 mHandler.dispatchAllMessages(); 273 mForegroundManager.assertInForeground(); 274 } 275 testFinishesInBackground_MultipleJobs()276 public void testFinishesInBackground_MultipleJobs() throws Exception { 277 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 278 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 279 280 mExecutor.run(0); 281 mForegroundManager.assertInForeground(); 282 283 mHandler.dispatchAllMessages(); 284 mForegroundManager.assertInForeground(); 285 286 mExecutor.run(0); 287 mHandler.dispatchAllMessages(); 288 mForegroundManager.assertInBackground(); 289 } 290 testAllNotificationsDismissedAfterShutdown()291 public void testAllNotificationsDismissedAfterShutdown() throws Exception { 292 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 293 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 294 295 mExecutor.runAll(); 296 297 mHandler.dispatchAllMessages(); 298 mTestNotificationManager.assertNumberOfNotifications(0); 299 } 300 testNotificationUpdateAfterForegroundJobSwitch()301 public void testNotificationUpdateAfterForegroundJobSwitch() throws Exception { 302 startService(createCopyIntent(Arrays.asList(ALPHA_DOC), BETA_DOC)); 303 startService(createCopyIntent(Arrays.asList(GAMMA_DOC), DELTA_DOC)); 304 Job job1 = mCopyJobs.get(0); 305 Job job2 = mCopyJobs.get(1); 306 307 mService.onStart(job1); 308 mTestNotificationManager.assertHasNotification( 309 FileOperationService.NOTIFICATION_ID_PROGRESS, null); 310 311 mService.onStart(job2); 312 mTestNotificationManager.assertHasNotification( 313 FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id); 314 315 job1.cancel(); 316 mService.onFinished(job1); 317 mTestNotificationManager.assertHasNotification( 318 FileOperationService.NOTIFICATION_ID_PROGRESS, null); 319 mTestNotificationManager.assertNoNotification( 320 FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id); 321 322 job2.cancel(); 323 mService.onFinished(job2); 324 mTestNotificationManager.assertNumberOfNotifications(0); 325 } 326 createCopyIntent(List<DocumentInfo> files, DocumentInfo dest)327 private Intent createCopyIntent(List<DocumentInfo> files, DocumentInfo dest) 328 throws Exception { 329 DocumentStack stack = new DocumentStack(); 330 stack.push(dest); 331 332 List<Uri> uris = new ArrayList<>(files.size()); 333 for (DocumentInfo file : files) { 334 uris.add(file.derivedUri); 335 } 336 337 UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris); 338 TestFileOperation operation = new TestFileOperation(OPERATION_COPY, urisSupplier, stack); 339 340 return createBaseIntent(getContext(), createJobId(), operation); 341 } 342 createDeleteIntent(List<DocumentInfo> files)343 private Intent createDeleteIntent(List<DocumentInfo> files) { 344 DocumentStack stack = new DocumentStack(); 345 346 List<Uri> uris = new ArrayList<>(files.size()); 347 for (DocumentInfo file : files) { 348 uris.add(file.derivedUri); 349 } 350 351 UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris); 352 TestFileOperation operation = new TestFileOperation(OPERATION_DELETE, urisSupplier, stack); 353 354 return createBaseIntent(getContext(), createJobId(), operation); 355 } 356 createDoc(String name)357 private static DocumentInfo createDoc(String name) { 358 // Doesn't need to be valid content Uri, just some urly looking thing. 359 Uri uri = new Uri.Builder() 360 .scheme("content") 361 .authority("com.android.documentsui.testing") 362 .path(name) 363 .build(); 364 365 return createDoc(uri); 366 } 367 assertAllCopyJobsStarted()368 void assertAllCopyJobsStarted() { 369 for (TestJob job : mCopyJobs) { 370 job.assertStarted(); 371 } 372 } 373 assertAllDeleteJobsStarted()374 void assertAllDeleteJobsStarted() { 375 for (TestJob job : mDeleteJobs) { 376 job.assertStarted(); 377 } 378 } 379 assertNoCopyJobsStarted()380 void assertNoCopyJobsStarted() { 381 for (TestJob job : mCopyJobs) { 382 job.assertNotStarted(); 383 } 384 } 385 assertNoDeleteJobsStarted()386 void assertNoDeleteJobsStarted() { 387 for (TestJob job : mDeleteJobs) { 388 job.assertNotStarted(); 389 } 390 } 391 assertJobsCreated(int expected)392 void assertJobsCreated(int expected) { 393 assertEquals(expected, mCopyJobs.size() + mDeleteJobs.size()); 394 } 395 createDoc(Uri destination)396 private static DocumentInfo createDoc(Uri destination) { 397 DocumentInfo destDoc = new DocumentInfo(); 398 destDoc.derivedUri = destination; 399 return destDoc; 400 } 401 assertExecutorsShutdown()402 private void assertExecutorsShutdown() { 403 mExecutor.assertShutdown(); 404 mDeletionExecutor.assertShutdown(); 405 } 406 407 private final class TestFileOperation extends FileOperation { 408 409 private final Runnable mJobRunnable = () -> { 410 // The following statement is executed concurrently to Job.start() in real situation. 411 // Call it in TestJob.start() to mimic this behavior. 412 mHandler.dispatchNextMessage(); 413 }; 414 private final @OpType int mOpType; 415 private final UrisSupplier mSrcs; 416 private final DocumentStack mDestination; 417 TestFileOperation( @pType int opType, UrisSupplier srcs, DocumentStack destination)418 private TestFileOperation( 419 @OpType int opType, UrisSupplier srcs, DocumentStack destination) { 420 super(opType, srcs, destination); 421 mOpType = opType; 422 mSrcs = srcs; 423 mDestination = destination; 424 } 425 426 @Override createJob(Context service, Job.Listener listener, String id, Features features)427 public Job createJob(Context service, Job.Listener listener, String id, Features features) { 428 TestJob job = new TestJob( 429 service, listener, id, mOpType, mDestination, mSrcs, mJobRunnable, features); 430 431 if (mOpType == OPERATION_COPY) { 432 mCopyJobs.add(job); 433 } 434 435 if (mOpType == OPERATION_DELETE) { 436 mDeleteJobs.add(job); 437 } 438 439 return job; 440 } 441 442 /** 443 * CREATOR is required for Parcelables, but we never pass this class via parcel. 444 */ 445 public Parcelable.Creator<TestFileOperation> CREATOR = 446 new Parcelable.Creator<TestFileOperation>() { 447 448 @Override 449 public TestFileOperation createFromParcel(Parcel source) { 450 throw new UnsupportedOperationException("Can't create from a parcel."); 451 } 452 453 @Override 454 public TestFileOperation[] newArray(int size) { 455 throw new UnsupportedOperationException("Can't create a new array."); 456 } 457 }; 458 } 459 } 460