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