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