• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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