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.google.common.collect.Lists.newArrayList;
20 
21 import static org.junit.Assert.assertNotEquals;
22 
23 import android.app.Notification;
24 import android.net.Uri;
25 import android.provider.DocumentsContract;
26 import android.text.format.DateUtils;
27 
28 import androidx.test.filters.MediumTest;
29 
30 import com.android.documentsui.R;
31 import com.android.documentsui.base.DocumentInfo;
32 import com.android.documentsui.services.FileOperationService.OpType;
33 
34 import java.text.NumberFormat;
35 import java.util.List;
36 import java.util.stream.IntStream;
37 
38 @MediumTest
39 public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJobTest<T> {
40 
41     private final @OpType int mOpType;
42 
AbstractCopyJobTest(@pType int opType)43     AbstractCopyJobTest(@OpType int opType) {
44         mOpType = opType;
45     }
46 
runCopyFilesTest()47     public void runCopyFilesTest() throws Exception {
48         Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
49         mDocs.writeDocument(testFile1, HAM_BYTES);
50 
51         Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
52         mDocs.writeDocument(testFile2, FRUITY_BYTES);
53 
54         createJob(newArrayList(testFile1, testFile2)).run();
55         mJobListener.waitForFinished();
56 
57         mDocs.assertChildCount(mDestRoot, 2);
58         mDocs.assertHasFile(mDestRoot, "test1.txt");
59         mDocs.assertHasFile(mDestRoot, "test2.txt");
60         mDocs.assertFileContents(mDestRoot.documentId, "test1.txt", HAM_BYTES);
61         mDocs.assertFileContents(mDestRoot.documentId, "test2.txt", FRUITY_BYTES);
62     }
63 
runCopyVirtualTypedFileTest()64     public void runCopyVirtualTypedFileTest() throws Exception {
65         Uri testFile = mDocs.createVirtualFile(
66                 mSrcRoot, "/virtual.sth", "virtual/mime-type",
67                 FRUITY_BYTES, "application/pdf", "text/html");
68 
69         createJob(newArrayList(testFile)).run();
70 
71         waitForJobFinished();
72 
73         mDocs.assertChildCount(mDestRoot, 1);
74         mDocs.assertHasFile(mDestRoot, "virtual.sth.pdf");  // copy should convert file to PDF.
75         mDocs.assertFileContents(mDestRoot.documentId, "virtual.sth.pdf", FRUITY_BYTES);
76     }
77 
runCopyVirtualNonTypedFileTest()78     public void runCopyVirtualNonTypedFileTest() throws Exception {
79         Uri testFile = mDocs.createVirtualFile(
80                 mSrcRoot, "/virtual.sth", "virtual/mime-type",
81                 FRUITY_BYTES);
82 
83         createJob(newArrayList(testFile)).run();
84 
85         waitForJobFinished();
86         mJobListener.assertFailed();
87         mJobListener.assertFilesFailed(newArrayList("virtual.sth"));
88 
89         mDocs.assertChildCount(mDestRoot, 0);
90     }
91 
runCopyEmptyDirTest()92     public void runCopyEmptyDirTest() throws Exception {
93         Uri testDir = mDocs.createFolder(mSrcRoot, "emptyDir");
94 
95         CopyJob job = createJob(newArrayList(testDir));
96         job.run();
97         waitForJobFinished();
98 
99         Notification progressNotification = job.getProgressNotification();
100         String copyPercentage = progressNotification.extras.getString(Notification.EXTRA_SUB_TEXT);
101 
102         // the percentage representation should not be NaN.
103         assertNotEquals(copyPercentage.equals(NumberFormat.getPercentInstance().format(Double.NaN)),
104                 "Percentage representation should not be NaN.");
105 
106         mDocs.assertChildCount(mDestRoot, 1);
107         mDocs.assertHasDirectory(mDestRoot, "emptyDir");
108     }
109 
runCopyDirRecursivelyTest()110     public void runCopyDirRecursivelyTest() throws Exception {
111 
112         Uri testDir1 = mDocs.createFolder(mSrcRoot, "dir1");
113         mDocs.createDocument(testDir1, "text/plain", "test1.txt");
114 
115         Uri testDir2 = mDocs.createFolder(testDir1, "dir2");
116         mDocs.createDocument(testDir2, "text/plain", "test2.txt");
117 
118         createJob(newArrayList(testDir1)).run();
119         waitForJobFinished();
120 
121         DocumentInfo dir1Copy = mDocs.findDocument(mDestRoot.documentId, "dir1");
122 
123         mDocs.assertChildCount(dir1Copy.derivedUri, 2);
124         mDocs.assertHasDirectory(dir1Copy.derivedUri, "dir2");
125         mDocs.assertHasFile(dir1Copy.derivedUri, "test1.txt");
126 
127         DocumentInfo dir2Copy = mDocs.findDocument(dir1Copy.documentId, "dir2");
128         mDocs.assertChildCount(dir2Copy.derivedUri, 1);
129         mDocs.assertHasFile(dir2Copy.derivedUri, "test2.txt");
130     }
131 
runNoCopyDirToSelfTest()132     public void runNoCopyDirToSelfTest() throws Exception {
133         Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
134 
135         createJob(mOpType,
136                 newArrayList(testDir),
137                 DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
138                 testDir).run();
139 
140         waitForJobFinished();
141         mJobListener.assertFailed();
142         mJobListener.assertFilesFailed(newArrayList("someDir"));
143 
144         mDocs.assertChildCount(mDestRoot, 0);
145     }
146 
runNoCopyDirToDescendentTest()147     public void runNoCopyDirToDescendentTest() throws Exception {
148         Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
149         Uri destDir = mDocs.createFolder(testDir, "theDescendent");
150 
151         createJob(mOpType,
152                 newArrayList(testDir),
153                 DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
154                 destDir).run();
155 
156         waitForJobFinished();
157         mJobListener.assertFailed();
158         mJobListener.assertFilesFailed(newArrayList("someDir"));
159 
160         mDocs.assertChildCount(mDestRoot, 0);
161     }
162 
runCopyFileWithReadErrorsTest()163     public void runCopyFileWithReadErrorsTest() throws Exception {
164         Uri testFile = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
165         mDocs.writeDocument(testFile, HAM_BYTES);
166 
167         String testId = DocumentsContract.getDocumentId(testFile);
168         mDocs.simulateReadErrorsForFile(testId, null);
169 
170         createJob(newArrayList(testFile)).run();
171 
172         waitForJobFinished();
173         mJobListener.assertFailed();
174         mJobListener.assertFilesFailed(newArrayList("test1.txt"));
175 
176         mDocs.assertChildCount(mDestRoot, 0);
177     }
178 
runCopyProgressForFileCountTest()179     public void runCopyProgressForFileCountTest() throws Exception {
180         // Init FileCountProgressTracker with 10 docs required to copy.
181         TestCopyJobProcessTracker<CopyJob.FileCountProgressTracker> tracker =
182                 new TestCopyJobProcessTracker(CopyJob.FileCountProgressTracker.class, 10,
183                         createJob(newArrayList(mDocs.createFolder(mSrcRoot, "tempDir"))),
184                         (completed) -> NumberFormat.getPercentInstance().format(completed),
185                         (time) -> mContext.getString(R.string.copy_remaining,
186                                 DateUtils.formatDuration((Long) time)));
187 
188         // Assert init progress is 0 & default remaining time is -1.
189         tracker.getProcessTracker().start();
190         tracker.assertProgressTrackStarted();
191         tracker.assertStartedProgressEquals(0);
192         tracker.assertStartedRemainingTimeEquals(-1);
193 
194         // Progress 20%: 2 docs processed after 1 sec, no remaining time since first sample.
195         IntStream.range(0, 2).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted());
196         tracker.updateProgressAndRemainingTime(1000);
197         tracker.assertProgressEquals(0.2);
198         tracker.assertNoRemainingTime();
199 
200         // Progress 40%: 4 docs processed after 2 secs, expect remaining time is 3 secs.
201         IntStream.range(2, 4).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted());
202         tracker.updateProgressAndRemainingTime(2000);
203         tracker.assertProgressEquals(0.4);
204         tracker.assertReminingTimeEquals(3000L);
205 
206         // progress 100%: 10 doc processed after 5 secs, expect no remaining time shown.
207         IntStream.range(4, 10).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted());
208         tracker.updateProgressAndRemainingTime(5000);
209         tracker.assertProgressEquals(1.0);
210         tracker.assertNoRemainingTime();
211     }
212 
runCopyProgressForByteCountTest()213     public void runCopyProgressForByteCountTest() throws Exception {
214         // Init ByteCountProgressTracker with 100 KBytes required to copy.
215         TestCopyJobProcessTracker<CopyJob.ByteCountProgressTracker> tracker =
216                 new TestCopyJobProcessTracker(CopyJob.ByteCountProgressTracker.class, 100000,
217                         createJob(newArrayList(mDocs.createFolder(mSrcRoot, "tempDir"))),
218                         (completed) -> NumberFormat.getPercentInstance().format(completed),
219                         (time) -> mContext.getString(R.string.copy_remaining,
220                                 DateUtils.formatDuration((Long) time)));
221 
222         // Assert init progress is 0 & default remaining time is -1.
223         tracker.getProcessTracker().start();
224         tracker.assertProgressTrackStarted();
225         tracker.assertStartedProgressEquals(0);
226         tracker.assertStartedRemainingTimeEquals(-1);
227 
228         // Progress 25%: 25 KBytes processed after 1 sec, no remaining time since first sample.
229         tracker.getProcessTracker().onBytesCopied(25000);
230         tracker.updateProgressAndRemainingTime(1000);
231         tracker.assertProgressEquals(0.25);
232         tracker.assertNoRemainingTime();
233 
234         // Progress 50%: 50 KBytes processed after 2 secs, expect remaining time is 2 secs.
235         tracker.getProcessTracker().onBytesCopied(25000);
236         tracker.updateProgressAndRemainingTime(2000);
237         tracker.assertProgressEquals(0.5);
238         tracker.assertReminingTimeEquals(2000L);
239 
240         // Progress 100%: 100 KBytes processed after 4 secs, expect no remaining time shown.
241         tracker.getProcessTracker().onBytesCopied(50000);
242         tracker.updateProgressAndRemainingTime(4000);
243         tracker.assertProgressEquals(1.0);
244         tracker.assertNoRemainingTime();
245     }
246 
waitForJobFinished()247     void waitForJobFinished() throws Exception {
248         mJobListener.waitForFinished();
249         mDocs.waitForWrite();
250     }
251 
252     /**
253      * Creates a job with a stack consisting to the default source and destination.
254      * TODO: Clean up, as mDestRoot.documentInfo may not really be the parent of
255      * srcs.
256      */
createJob(List<Uri> srcs)257     final T createJob(List<Uri> srcs) throws Exception {
258         Uri srcParent = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
259         return createJob(srcs, srcParent);
260     }
261 
createJob(List<Uri> srcs, Uri srcParent)262     final T createJob(List<Uri> srcs, Uri srcParent) throws Exception {
263         Uri destination = DocumentsContract.buildDocumentUri(AUTHORITY, mDestRoot.documentId);
264         return createJob(mOpType, srcs, srcParent, destination);
265     }
266 }
267