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