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 getVerb()47 private String getVerb() { 48 switch(mOpType) { 49 case FileOperationService.OPERATION_COPY: 50 case FileOperationService.OPERATION_EXTRACT: 51 return "Copying"; 52 case FileOperationService.OPERATION_COMPRESS: 53 return "Zipping"; 54 case FileOperationService.OPERATION_MOVE: 55 return "Moving"; 56 case FileOperationService.OPERATION_DELETE: 57 // DeleteJob does not inherit from CopyJob 58 case FileOperationService.OPERATION_UNKNOWN: 59 default: 60 return ""; 61 } 62 } 63 runCopyFilesTest()64 public void runCopyFilesTest() throws Exception { 65 Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt"); 66 mDocs.writeDocument(testFile1, HAM_BYTES); 67 68 Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt"); 69 mDocs.writeDocument(testFile2, FRUITY_BYTES); 70 71 CopyJob job = createJob(newArrayList(testFile1, testFile2)); 72 JobProgress progress = job.getJobProgress(); 73 assertEquals(Job.STATE_CREATED, progress.state); 74 75 job.run(); 76 mJobListener.waitForFinished(); 77 78 mDocs.assertChildCount(mDestRoot, 2); 79 mDocs.assertHasFile(mDestRoot, "test1.txt"); 80 mDocs.assertHasFile(mDestRoot, "test2.txt"); 81 mDocs.assertFileContents(mDestRoot.documentId, "test1.txt", HAM_BYTES); 82 mDocs.assertFileContents(mDestRoot.documentId, "test2.txt", FRUITY_BYTES); 83 84 progress = job.getJobProgress(); 85 assertEquals(Job.STATE_COMPLETED, progress.state); 86 assertFalse(progress.hasFailures); 87 assertEquals(getVerb() + " 2 files to " + mDestRoot.title, progress.msg); 88 assertEquals(HAM_BYTES.length + FRUITY_BYTES.length, progress.currentBytes); 89 assertEquals(HAM_BYTES.length + FRUITY_BYTES.length, progress.requiredBytes); 90 } 91 runCopyVirtualTypedFileTest()92 public void runCopyVirtualTypedFileTest() throws Exception { 93 Uri testFile = mDocs.createVirtualFile( 94 mSrcRoot, "/virtual.sth", "virtual/mime-type", 95 FRUITY_BYTES, "application/pdf", "text/html"); 96 97 CopyJob job = createJob(newArrayList(testFile)); 98 job.run(); 99 waitForJobFinished(); 100 101 mDocs.assertChildCount(mDestRoot, 1); 102 mDocs.assertHasFile(mDestRoot, "virtual.sth.pdf"); // copy should convert file to PDF. 103 mDocs.assertFileContents(mDestRoot.documentId, "virtual.sth.pdf", FRUITY_BYTES); 104 105 JobProgress progress = job.getJobProgress(); 106 assertEquals(Job.STATE_COMPLETED, progress.state); 107 assertFalse(progress.hasFailures); 108 assertEquals("Copying virtual.sth to " + mDestRoot.title, progress.msg); 109 assertEquals(FRUITY_BYTES.length, progress.currentBytes); 110 assertEquals(FRUITY_BYTES.length, progress.requiredBytes); 111 } 112 runCopyVirtualNonTypedFileTest()113 public void runCopyVirtualNonTypedFileTest() throws Exception { 114 Uri testFile = mDocs.createVirtualFile( 115 mSrcRoot, "/virtual.sth", "virtual/mime-type", 116 FRUITY_BYTES); 117 118 CopyJob job = createJob(newArrayList(testFile)); 119 job.run(); 120 waitForJobFinished(); 121 122 mJobListener.assertFailed(); 123 mJobListener.assertFilesFailed(newArrayList("virtual.sth")); 124 125 mDocs.assertChildCount(mDestRoot, 0); 126 127 JobProgress progress = job.getJobProgress(); 128 assertEquals(Job.STATE_COMPLETED, progress.state); 129 assertTrue(progress.hasFailures); 130 assertEquals(getVerb() + " virtual.sth to " + mDestRoot.title, progress.msg); 131 assertEquals(0, progress.currentBytes); 132 assertEquals(FRUITY_BYTES.length, progress.requiredBytes); 133 } 134 runCopyEmptyDirTest()135 public void runCopyEmptyDirTest() throws Exception { 136 Uri testDir = mDocs.createFolder(mSrcRoot, "emptyDir"); 137 138 CopyJob job = createJob(newArrayList(testDir)); 139 job.run(); 140 waitForJobFinished(); 141 142 Notification progressNotification = job.getProgressNotification(); 143 String copyPercentage = progressNotification.extras.getString(Notification.EXTRA_SUB_TEXT); 144 145 // the percentage representation should not be NaN. 146 assertNotEquals(copyPercentage.equals(NumberFormat.getPercentInstance().format(Double.NaN)), 147 "Percentage representation should not be NaN."); 148 149 mDocs.assertChildCount(mDestRoot, 1); 150 mDocs.assertHasDirectory(mDestRoot, "emptyDir"); 151 152 JobProgress progress = job.getJobProgress(); 153 assertEquals(Job.STATE_COMPLETED, progress.state); 154 assertFalse(progress.hasFailures); 155 assertEquals(getVerb() + " emptyDir to " + mDestRoot.title, progress.msg); 156 assertEquals(-1, progress.currentBytes); 157 assertEquals(-1, progress.requiredBytes); 158 } 159 runCopyDirRecursivelyTest()160 public void runCopyDirRecursivelyTest() throws Exception { 161 162 Uri testDir1 = mDocs.createFolder(mSrcRoot, "dir1"); 163 mDocs.createDocument(testDir1, "text/plain", "test1.txt"); 164 165 Uri testDir2 = mDocs.createFolder(testDir1, "dir2"); 166 mDocs.createDocument(testDir2, "text/plain", "test2.txt"); 167 168 createJob(newArrayList(testDir1)).run(); 169 waitForJobFinished(); 170 171 DocumentInfo dir1Copy = mDocs.findDocument(mDestRoot.documentId, "dir1"); 172 173 mDocs.assertChildCount(dir1Copy.derivedUri, 2); 174 mDocs.assertHasDirectory(dir1Copy.derivedUri, "dir2"); 175 mDocs.assertHasFile(dir1Copy.derivedUri, "test1.txt"); 176 177 DocumentInfo dir2Copy = mDocs.findDocument(dir1Copy.documentId, "dir2"); 178 mDocs.assertChildCount(dir2Copy.derivedUri, 1); 179 mDocs.assertHasFile(dir2Copy.derivedUri, "test2.txt"); 180 } 181 runNoCopyDirToSelfTest()182 public void runNoCopyDirToSelfTest() throws Exception { 183 Uri testDir = mDocs.createFolder(mSrcRoot, "someDir"); 184 185 createJob(mOpType, 186 newArrayList(testDir), 187 DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId), 188 testDir).run(); 189 190 waitForJobFinished(); 191 mJobListener.assertFailed(); 192 mJobListener.assertFilesFailed(newArrayList("someDir")); 193 194 mDocs.assertChildCount(mDestRoot, 0); 195 } 196 runNoCopyDirToDescendentTest()197 public void runNoCopyDirToDescendentTest() throws Exception { 198 Uri testDir = mDocs.createFolder(mSrcRoot, "someDir"); 199 Uri destDir = mDocs.createFolder(testDir, "theDescendent"); 200 201 createJob(mOpType, 202 newArrayList(testDir), 203 DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId), 204 destDir).run(); 205 206 waitForJobFinished(); 207 mJobListener.assertFailed(); 208 mJobListener.assertFilesFailed(newArrayList("someDir")); 209 210 mDocs.assertChildCount(mDestRoot, 0); 211 } 212 runCopyFileWithReadErrorsTest()213 public void runCopyFileWithReadErrorsTest() throws Exception { 214 Uri testFile = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt"); 215 mDocs.writeDocument(testFile, HAM_BYTES); 216 217 String testId = DocumentsContract.getDocumentId(testFile); 218 mDocs.simulateReadErrorsForFile(testId, null); 219 220 createJob(newArrayList(testFile)).run(); 221 222 waitForJobFinished(); 223 mJobListener.assertFailed(); 224 mJobListener.assertFilesFailed(newArrayList("test1.txt")); 225 226 mDocs.assertChildCount(mDestRoot, 0); 227 } 228 runCopyProgressForFileCountTest()229 public void runCopyProgressForFileCountTest() throws Exception { 230 // Init FileCountProgressTracker with 10 docs required to copy. 231 TestCopyJobProcessTracker<CopyJob.FileCountProgressTracker> tracker = 232 new TestCopyJobProcessTracker(CopyJob.FileCountProgressTracker.class, 10, 233 createJob(newArrayList(mDocs.createFolder(mSrcRoot, "tempDir"))), 234 (completed) -> NumberFormat.getPercentInstance().format(completed), 235 (time) -> mContext.getString(R.string.copy_remaining, 236 DateUtils.formatDuration((Long) time))); 237 238 // Assert init progress is 0 & default remaining time is -1. 239 tracker.getProcessTracker().start(); 240 tracker.assertProgressTrackStarted(); 241 tracker.assertStartedProgressEquals(0); 242 tracker.assertStartedRemainingTimeEquals(-1); 243 244 // Progress 20%: 2 docs processed after 1 sec, no remaining time since first sample. 245 IntStream.range(0, 2).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted()); 246 tracker.updateProgressAndRemainingTime(1000); 247 tracker.assertProgressEquals(0.2); 248 tracker.assertNoRemainingTime(); 249 250 // Progress 40%: 4 docs processed after 2 secs, expect remaining time is 3 secs. 251 IntStream.range(2, 4).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted()); 252 tracker.updateProgressAndRemainingTime(2000); 253 tracker.assertProgressEquals(0.4); 254 tracker.assertReminingTimeEquals(3000L); 255 256 // progress 100%: 10 doc processed after 5 secs, expect no remaining time shown. 257 IntStream.range(4, 10).forEach(__ -> tracker.getProcessTracker().onDocumentCompleted()); 258 tracker.updateProgressAndRemainingTime(5000); 259 tracker.assertProgressEquals(1.0); 260 tracker.assertNoRemainingTime(); 261 } 262 runCopyProgressForByteCountTest()263 public void runCopyProgressForByteCountTest() throws Exception { 264 // Init ByteCountProgressTracker with 100 KBytes required to copy. 265 TestCopyJobProcessTracker<CopyJob.ByteCountProgressTracker> tracker = 266 new TestCopyJobProcessTracker(CopyJob.ByteCountProgressTracker.class, 100000, 267 createJob(newArrayList(mDocs.createFolder(mSrcRoot, "tempDir"))), 268 (completed) -> NumberFormat.getPercentInstance().format(completed), 269 (time) -> mContext.getString(R.string.copy_remaining, 270 DateUtils.formatDuration((Long) time))); 271 272 // Assert init progress is 0 & default remaining time is -1. 273 tracker.getProcessTracker().start(); 274 tracker.assertProgressTrackStarted(); 275 tracker.assertStartedProgressEquals(0); 276 tracker.assertStartedRemainingTimeEquals(-1); 277 278 // Progress 25%: 25 KBytes processed after 1 sec, no remaining time since first sample. 279 tracker.getProcessTracker().onBytesCopied(25000); 280 tracker.updateProgressAndRemainingTime(1000); 281 tracker.assertProgressEquals(0.25); 282 tracker.assertNoRemainingTime(); 283 284 // Progress 50%: 50 KBytes processed after 2 secs, expect remaining time is 2 secs. 285 tracker.getProcessTracker().onBytesCopied(25000); 286 tracker.updateProgressAndRemainingTime(2000); 287 tracker.assertProgressEquals(0.5); 288 tracker.assertReminingTimeEquals(2000L); 289 290 // Progress 100%: 100 KBytes processed after 4 secs, expect no remaining time shown. 291 tracker.getProcessTracker().onBytesCopied(50000); 292 tracker.updateProgressAndRemainingTime(4000); 293 tracker.assertProgressEquals(1.0); 294 tracker.assertNoRemainingTime(); 295 } 296 waitForJobFinished()297 void waitForJobFinished() throws Exception { 298 mJobListener.waitForFinished(); 299 mDocs.waitForWrite(); 300 } 301 302 /** 303 * Creates a job with a stack consisting to the default source and destination. 304 * TODO: Clean up, as mDestRoot.documentInfo may not really be the parent of 305 * srcs. 306 */ createJob(List<Uri> srcs)307 final T createJob(List<Uri> srcs) throws Exception { 308 Uri srcParent = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId); 309 return createJob(srcs, srcParent); 310 } 311 createJob(List<Uri> srcs, Uri srcParent)312 final T createJob(List<Uri> srcs, Uri srcParent) throws Exception { 313 Uri destination = DocumentsContract.buildDocumentUri(AUTHORITY, mDestRoot.documentId); 314 return createJob(mOpType, srcs, srcParent, destination); 315 } 316 } 317