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