1 /* 2 * Copyright (C) 2008 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.providers.media.util; 18 19 import static android.os.ParcelFileDescriptor.MODE_APPEND; 20 import static android.os.ParcelFileDescriptor.MODE_CREATE; 21 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 22 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 23 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; 24 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; 25 import static android.system.OsConstants.F_OK; 26 import static android.system.OsConstants.O_APPEND; 27 import static android.system.OsConstants.O_CREAT; 28 import static android.system.OsConstants.O_RDONLY; 29 import static android.system.OsConstants.O_RDWR; 30 import static android.system.OsConstants.O_TRUNC; 31 import static android.system.OsConstants.O_WRONLY; 32 import static android.system.OsConstants.R_OK; 33 import static android.system.OsConstants.W_OK; 34 import static android.system.OsConstants.X_OK; 35 import static android.text.format.DateUtils.DAY_IN_MILLIS; 36 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 37 import static android.text.format.DateUtils.WEEK_IN_MILLIS; 38 39 import static com.android.providers.media.util.FileUtils.buildUniqueFile; 40 import static com.android.providers.media.util.FileUtils.extractDisplayName; 41 import static com.android.providers.media.util.FileUtils.extractFileExtension; 42 import static com.android.providers.media.util.FileUtils.extractFileName; 43 import static com.android.providers.media.util.FileUtils.extractOwnerPackageNameFromRelativePath; 44 import static com.android.providers.media.util.FileUtils.extractPathOwnerPackageName; 45 import static com.android.providers.media.util.FileUtils.extractRelativePath; 46 import static com.android.providers.media.util.FileUtils.extractTopLevelDir; 47 import static com.android.providers.media.util.FileUtils.extractVolumeName; 48 import static com.android.providers.media.util.FileUtils.extractVolumePath; 49 import static com.android.providers.media.util.FileUtils.fromFuseFile; 50 import static com.android.providers.media.util.FileUtils.isDataOrObbPath; 51 import static com.android.providers.media.util.FileUtils.isDataOrObbRelativePath; 52 import static com.android.providers.media.util.FileUtils.isExternalMediaDirectory; 53 import static com.android.providers.media.util.FileUtils.isObbOrChildRelativePath; 54 import static com.android.providers.media.util.FileUtils.toFuseFile; 55 import static com.android.providers.media.util.FileUtils.translateModeAccessToPosix; 56 import static com.android.providers.media.util.FileUtils.translateModePfdToPosix; 57 import static com.android.providers.media.util.FileUtils.translateModePosixToPfd; 58 import static com.android.providers.media.util.FileUtils.translateModePosixToString; 59 import static com.android.providers.media.util.FileUtils.translateModeStringToPosix; 60 61 import static com.google.common.truth.Truth.assertThat; 62 63 import static org.junit.Assert.assertEquals; 64 import static org.junit.Assert.assertFalse; 65 import static org.junit.Assert.assertNull; 66 import static org.junit.Assert.assertThrows; 67 import static org.junit.Assert.assertTrue; 68 import static org.junit.Assert.fail; 69 70 import android.content.ContentValues; 71 import android.os.Environment; 72 import android.os.SystemProperties; 73 import android.provider.MediaStore; 74 import android.provider.MediaStore.MediaColumns; 75 import android.text.TextUtils; 76 77 import androidx.test.InstrumentationRegistry; 78 import androidx.test.runner.AndroidJUnit4; 79 80 import com.google.common.collect.Range; 81 82 import org.junit.After; 83 import org.junit.Assume; 84 import org.junit.Before; 85 import org.junit.Test; 86 import org.junit.runner.RunWith; 87 88 import java.io.File; 89 import java.io.FileNotFoundException; 90 import java.io.IOException; 91 import java.io.RandomAccessFile; 92 import java.util.Arrays; 93 import java.util.HashSet; 94 import java.util.Locale; 95 import java.util.Optional; 96 97 @RunWith(AndroidJUnit4.class) 98 public class FileUtilsTest { 99 // Exposing here since it is also used by MediaProviderTest.java 100 public static final int MAX_FILENAME_BYTES = FileUtils.MAX_FILENAME_BYTES; 101 102 /** 103 * To help avoid flaky tests, give ourselves a unique nonce to be used for 104 * all filesystem paths, so that we don't risk conflicting with previous 105 * test runs. 106 */ 107 private static final String NONCE = String.valueOf(System.nanoTime()); 108 109 private static final String TEST_DIRECTORY_NAME = "FileUtilsTestDirectory" + NONCE; 110 private static final String TEST_FILE_NAME = "FileUtilsTestFile" + NONCE; 111 112 private File mTarget; 113 private File mDcimTarget; 114 private File mDeleteTarget; 115 private File mDownloadTarget; 116 private File mTestDownloadDir; 117 118 @Before setUp()119 public void setUp() throws Exception { 120 mTarget = InstrumentationRegistry.getTargetContext().getCacheDir(); 121 FileUtils.deleteContents(mTarget); 122 123 mDcimTarget = new File(mTarget, "DCIM"); 124 mDcimTarget.mkdirs(); 125 126 mDeleteTarget = mDcimTarget; 127 128 mDownloadTarget = new File(Environment.getExternalStorageDirectory(), 129 Environment.DIRECTORY_DOWNLOADS); 130 mTestDownloadDir = new File(mDownloadTarget, TEST_DIRECTORY_NAME); 131 mTestDownloadDir.mkdirs(); 132 } 133 134 @After tearDown()135 public void tearDown() throws Exception { 136 FileUtils.deleteContents(mTarget); 137 FileUtils.deleteContents(mTestDownloadDir); 138 } 139 touch(String name, long age)140 private void touch(String name, long age) throws Exception { 141 final File file = new File(mDeleteTarget, name); 142 file.createNewFile(); 143 file.setLastModified(System.currentTimeMillis() - age); 144 } 145 146 @Test testString()147 public void testString() throws Exception { 148 final File file = new File(mTarget, String.valueOf(System.nanoTime())); 149 150 // Verify initial empty state 151 assertFalse(FileUtils.readString(file).isPresent()); 152 153 // Verify simple writing and reading 154 FileUtils.writeString(file, Optional.of("meow")); 155 assertTrue(FileUtils.readString(file).isPresent()); 156 assertEquals("meow", FileUtils.readString(file).get()); 157 158 // Verify empty writing deletes file 159 FileUtils.writeString(file, Optional.empty()); 160 assertFalse(FileUtils.readString(file).isPresent()); 161 162 // Verify reading from a file with more than 4096 chars 163 try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { 164 raf.setLength(4097); 165 } 166 assertEquals(Optional.empty(), FileUtils.readString(file)); 167 168 // Verify reading from non existing file. 169 file.delete(); 170 assertEquals(Optional.empty(), FileUtils.readString(file)); 171 172 } 173 174 @Test testDeleteOlderEmptyDir()175 public void testDeleteOlderEmptyDir() throws Exception { 176 FileUtils.deleteOlderFiles(mDeleteTarget, 10, WEEK_IN_MILLIS); 177 assertDirContents(); 178 } 179 180 @Test testDeleteOlderTypical()181 public void testDeleteOlderTypical() throws Exception { 182 touch("file1", HOUR_IN_MILLIS); 183 touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 184 touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 185 touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 186 touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 187 assertTrue(FileUtils.deleteOlderFiles(mDeleteTarget, 3, DAY_IN_MILLIS)); 188 assertDirContents("file1", "file2", "file3"); 189 } 190 191 @Test testDeleteOlderInFuture()192 public void testDeleteOlderInFuture() throws Exception { 193 touch("file1", -HOUR_IN_MILLIS); 194 touch("file2", HOUR_IN_MILLIS); 195 touch("file3", WEEK_IN_MILLIS); 196 assertTrue(FileUtils.deleteOlderFiles(mDeleteTarget, 0, DAY_IN_MILLIS)); 197 assertDirContents("file1", "file2"); 198 199 touch("file1", -HOUR_IN_MILLIS); 200 touch("file2", HOUR_IN_MILLIS); 201 touch("file3", WEEK_IN_MILLIS); 202 assertTrue(FileUtils.deleteOlderFiles(mDeleteTarget, 0, DAY_IN_MILLIS)); 203 assertDirContents("file1", "file2"); 204 } 205 206 @Test testDeleteOlderOnlyAge()207 public void testDeleteOlderOnlyAge() throws Exception { 208 touch("file1", HOUR_IN_MILLIS); 209 touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 210 touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 211 touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 212 touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 213 assertTrue(FileUtils.deleteOlderFiles(mDeleteTarget, 0, DAY_IN_MILLIS)); 214 assertFalse(FileUtils.deleteOlderFiles(mDeleteTarget, 0, DAY_IN_MILLIS)); 215 assertDirContents("file1"); 216 } 217 218 @Test testDeleteOlderOnlyCount()219 public void testDeleteOlderOnlyCount() throws Exception { 220 touch("file1", HOUR_IN_MILLIS); 221 touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 222 touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 223 touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 224 touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS); 225 assertTrue(FileUtils.deleteOlderFiles(mDeleteTarget, 2, 0)); 226 assertFalse(FileUtils.deleteOlderFiles(mDeleteTarget, 2, 0)); 227 assertDirContents("file1", "file2"); 228 } 229 230 @Test testTranslateMode()231 public void testTranslateMode() throws Exception { 232 assertTranslate("r", O_RDONLY, MODE_READ_ONLY); 233 234 assertTranslate("rw", O_RDWR | O_CREAT, 235 MODE_READ_WRITE | MODE_CREATE); 236 assertTranslate("rwt", O_RDWR | O_CREAT | O_TRUNC, 237 MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE); 238 assertTranslate("rwa", O_RDWR | O_CREAT | O_APPEND, 239 MODE_READ_WRITE | MODE_CREATE | MODE_APPEND); 240 241 assertTranslate("w", O_WRONLY | O_CREAT, 242 MODE_WRITE_ONLY | MODE_CREATE | MODE_CREATE); 243 assertTranslate("wt", O_WRONLY | O_CREAT | O_TRUNC, 244 MODE_WRITE_ONLY | MODE_CREATE | MODE_TRUNCATE); 245 assertTranslate("wa", O_WRONLY | O_CREAT | O_APPEND, 246 MODE_WRITE_ONLY | MODE_CREATE | MODE_APPEND); 247 } 248 249 @Test testMalformedTransate_int()250 public void testMalformedTransate_int() throws Exception { 251 try { 252 // The non-standard Linux access mode 3 should throw 253 // an IllegalArgumentException. 254 translateModePosixToPfd(O_RDWR | O_WRONLY); 255 fail(); 256 } catch (IllegalArgumentException expected) { 257 } 258 } 259 260 @Test testMalformedTransate_string()261 public void testMalformedTransate_string() throws Exception { 262 try { 263 // The non-standard Linux access mode 3 should throw 264 // an IllegalArgumentException. 265 translateModePosixToString(O_RDWR | O_WRONLY); 266 fail(); 267 } catch (IllegalArgumentException expected) { 268 } 269 } 270 271 @Test testTranslateMode_Invalid()272 public void testTranslateMode_Invalid() throws Exception { 273 try { 274 translateModeStringToPosix("rwx"); 275 fail(); 276 } catch (IllegalArgumentException expected) { 277 } 278 try { 279 translateModeStringToPosix(""); 280 fail(); 281 } catch (IllegalArgumentException expected) { 282 } 283 } 284 285 @Test testTranslateMode_Access()286 public void testTranslateMode_Access() throws Exception { 287 assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK)); 288 assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK)); 289 assertEquals(O_WRONLY, translateModeAccessToPosix(W_OK)); 290 assertEquals(O_RDWR, translateModeAccessToPosix(R_OK | W_OK)); 291 assertEquals(O_RDWR, translateModeAccessToPosix(R_OK | W_OK | X_OK)); 292 } 293 assertTranslate(String string, int posix, int pfd)294 private static void assertTranslate(String string, int posix, int pfd) { 295 assertEquals(posix, translateModeStringToPosix(string)); 296 assertEquals(string, translateModePosixToString(posix)); 297 assertEquals(pfd, translateModePosixToPfd(posix)); 298 assertEquals(posix, translateModePfdToPosix(pfd)); 299 } 300 301 @Test testContains()302 public void testContains() throws Exception { 303 assertTrue(FileUtils.contains(new File("/"), new File("/moo.txt"))); 304 assertTrue(FileUtils.contains(new File("/"), new File("/"))); 305 306 assertTrue(FileUtils.contains(new File("/sdcard"), new File("/sdcard"))); 307 assertTrue(FileUtils.contains(new File("/sdcard/"), new File("/sdcard/"))); 308 309 assertTrue(FileUtils.contains(new File("/sdcard"), new File("/sdcard/moo.txt"))); 310 assertTrue(FileUtils.contains(new File("/sdcard/"), new File("/sdcard/moo.txt"))); 311 312 assertFalse(FileUtils.contains(new File("/sdcard"), new File("/moo.txt"))); 313 assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/moo.txt"))); 314 315 assertFalse(FileUtils.contains(new File("/sdcard"), new File("/sdcard.txt"))); 316 assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/sdcard.txt"))); 317 } 318 319 @Test testValidFatFilename()320 public void testValidFatFilename() throws Exception { 321 assertTrue(FileUtils.isValidFatFilename("a")); 322 assertTrue(FileUtils.isValidFatFilename("foo bar.baz")); 323 assertTrue(FileUtils.isValidFatFilename("foo.bar.baz")); 324 assertTrue(FileUtils.isValidFatFilename(".bar")); 325 assertTrue(FileUtils.isValidFatFilename("foo.bar")); 326 assertTrue(FileUtils.isValidFatFilename("foo bar")); 327 assertTrue(FileUtils.isValidFatFilename("foo+bar")); 328 assertTrue(FileUtils.isValidFatFilename("foo,bar")); 329 330 assertFalse(FileUtils.isValidFatFilename("foo*bar")); 331 assertFalse(FileUtils.isValidFatFilename("foo?bar")); 332 assertFalse(FileUtils.isValidFatFilename("foo<bar")); 333 assertFalse(FileUtils.isValidFatFilename(null)); 334 assertFalse(FileUtils.isValidFatFilename(".")); 335 assertFalse(FileUtils.isValidFatFilename("../foo")); 336 assertFalse(FileUtils.isValidFatFilename("/foo")); 337 338 assertEquals(".._foo", FileUtils.buildValidFatFilename("../foo")); 339 assertEquals("_foo", FileUtils.buildValidFatFilename("/foo")); 340 assertEquals(".foo", FileUtils.buildValidFatFilename(".foo")); 341 assertEquals("foo.bar", FileUtils.buildValidFatFilename("foo.bar")); 342 assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz")); 343 } 344 345 @Test testTrimFilename()346 public void testTrimFilename() throws Exception { 347 assertEquals("short.txt", FileUtils.trimFilename("short.txt", 16)); 348 assertEquals("extrem...eme.txt", FileUtils.trimFilename("extremelylongfilename.txt", 16)); 349 350 final String unicode = "a\u03C0\u03C0\u03C0\u03C0z"; 351 assertEquals("a\u03C0\u03C0\u03C0\u03C0z", FileUtils.trimFilename(unicode, 10)); 352 assertEquals("a\u03C0...\u03C0z", FileUtils.trimFilename(unicode, 9)); 353 assertEquals("a...\u03C0z", FileUtils.trimFilename(unicode, 8)); 354 assertEquals("a...\u03C0z", FileUtils.trimFilename(unicode, 7)); 355 assertEquals("a...z", FileUtils.trimFilename(unicode, 6)); 356 } 357 358 @Test testBuildUniqueFile_normal()359 public void testBuildUniqueFile_normal() throws Exception { 360 assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test")); 361 assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); 362 assertNameEquals("test.jpeg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpeg")); 363 assertNameEquals("TEst.JPeg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "TEst.JPeg")); 364 assertNameEquals(".test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", ".test")); 365 assertNameEquals("test.png.jpg", 366 FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.png.jpg")); 367 assertNameEquals("test.png.jpg", 368 FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.png")); 369 370 assertNameEquals("test.flac", FileUtils.buildUniqueFile(mTarget, "audio/flac", "test")); 371 assertNameEquals("test.flac", FileUtils.buildUniqueFile(mTarget, "audio/flac", "test.flac")); 372 assertNameEquals("test.flac", 373 FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test")); 374 assertNameEquals("test.flac", 375 FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test.flac")); 376 } 377 378 @Test testBuildUniqueFile_unknown()379 public void testBuildUniqueFile_unknown() throws Exception { 380 assertNameEquals("test", 381 FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test")); 382 assertNameEquals("test.jpg", 383 FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test.jpg")); 384 assertNameEquals(".test", 385 FileUtils.buildUniqueFile(mTarget, "application/octet-stream", ".test")); 386 387 assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test")); 388 assertNameEquals("test.lolz", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test.lolz")); 389 } 390 391 @Test testBuildUniqueFile_increment()392 public void testBuildUniqueFile_increment() throws Exception { 393 assertNameEquals("test.jpg", 394 FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); 395 new File(mTarget, "test.jpg").createNewFile(); 396 assertNameEquals("test (1).jpg", 397 FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); 398 new File(mTarget, "test (1).jpg").createNewFile(); 399 assertNameEquals("test (2).jpg", 400 FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); 401 } 402 403 @Test testBuildUniqueFile_increment_hidden()404 public void testBuildUniqueFile_increment_hidden() throws Exception { 405 assertNameEquals(".hidden.jpg", 406 FileUtils.buildUniqueFile(mTarget, "image/jpeg", ".hidden.jpg")); 407 new File(mTarget, ".hidden.jpg").createNewFile(); 408 assertNameEquals(".hidden (1).jpg", 409 FileUtils.buildUniqueFile(mTarget, "image/jpeg", ".hidden.jpg")); 410 } 411 412 @Test testBuildUniqueFile_mimeless()413 public void testBuildUniqueFile_mimeless() throws Exception { 414 assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg")); 415 new File(mTarget, "test.jpg").createNewFile(); 416 assertNameEquals("test (1).jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg")); 417 418 assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, "test")); 419 new File(mTarget, "test").createNewFile(); 420 assertNameEquals("test (1)", FileUtils.buildUniqueFile(mTarget, "test")); 421 422 assertNameEquals("test.foo.bar", FileUtils.buildUniqueFile(mTarget, "test.foo.bar")); 423 new File(mTarget, "test.foo.bar").createNewFile(); 424 assertNameEquals("test.foo (1).bar", FileUtils.buildUniqueFile(mTarget, "test.foo.bar")); 425 } 426 427 /** 428 * Verify that we generate unique filenames that meet the JEITA DCF 429 * specification when writing into directories like {@code DCIM}. 430 */ 431 @Test testBuildUniqueFile_DCF_strict()432 public void testBuildUniqueFile_DCF_strict() throws Exception { 433 assertNameEquals("IMG_0100.JPG", 434 buildUniqueFile(mDcimTarget, "IMG_0100.JPG")); 435 436 touch(mDcimTarget, "IMG_0999.JPG"); 437 assertNameEquals("IMG_0998.JPG", 438 buildUniqueFile(mDcimTarget, "IMG_0998.JPG")); 439 assertNameEquals("IMG_1000.JPG", 440 buildUniqueFile(mDcimTarget, "IMG_0999.JPG")); 441 assertNameEquals("IMG_1000.JPG", 442 buildUniqueFile(mDcimTarget, "IMG_1000.JPG")); 443 444 touch(mDcimTarget, "IMG_1000.JPG"); 445 assertNameEquals("IMG_1001.JPG", 446 buildUniqueFile(mDcimTarget, "IMG_0999.JPG")); 447 448 // We can't step beyond standard numbering 449 touch(mDcimTarget, "IMG_9999.JPG"); 450 try { 451 buildUniqueFile(mDcimTarget, "IMG_9999.JPG"); 452 fail(); 453 } catch (FileNotFoundException expected) { 454 } 455 } 456 457 /** 458 * Verify that we generate unique filenames that meet the JEITA DCF 459 * specification when writing into directories like {@code DCIM}. 460 * 461 * See b/174120008 for context. 462 */ 463 @Test testBuildUniqueFile_DCF_strict_differentLocale()464 public void testBuildUniqueFile_DCF_strict_differentLocale() throws Exception { 465 Locale defaultLocale = Locale.getDefault(); 466 try { 467 Locale.setDefault(new Locale("ar", "SA")); 468 testBuildUniqueFile_DCF_strict(); 469 } 470 finally { 471 Locale.setDefault(defaultLocale); 472 } 473 } 474 475 /** 476 * Verify that we generate unique filenames that look valid compared to other 477 * {@code DCIM} filenames. These technically aren't part of the official 478 * JEITA DCF specification. 479 */ 480 @Test testBuildUniqueFile_DCF_relaxed()481 public void testBuildUniqueFile_DCF_relaxed() throws Exception { 482 touch(mDcimTarget, "IMG_20190102_030405.jpg"); 483 assertNameEquals("IMG_20190102_030405~2.jpg", 484 buildUniqueFile(mDcimTarget, "IMG_20190102_030405.jpg")); 485 486 touch(mDcimTarget, "IMG_20190102_030405~2.jpg"); 487 assertNameEquals("IMG_20190102_030405~3.jpg", 488 buildUniqueFile(mDcimTarget, "IMG_20190102_030405.jpg")); 489 assertNameEquals("IMG_20190102_030405~3.jpg", 490 buildUniqueFile(mDcimTarget, "IMG_20190102_030405~2.jpg")); 491 } 492 493 /** 494 * Verify that we generate unique filenames that look valid compared to other 495 * {@code DCIM} filenames. These technically aren't part of the official 496 * JEITA DCF specification. 497 * 498 * See b/174120008 for context. 499 */ 500 @Test testBuildUniqueFile_DCF_relaxed_differentLocale()501 public void testBuildUniqueFile_DCF_relaxed_differentLocale() throws Exception { 502 Locale defaultLocale = Locale.getDefault(); 503 try { 504 Locale.setDefault(new Locale("ar", "SA")); 505 testBuildUniqueFile_DCF_relaxed(); 506 } finally { 507 Locale.setDefault(defaultLocale); 508 } 509 } 510 511 @Test testGetAbsoluteExtendedPath()512 public void testGetAbsoluteExtendedPath() throws Exception { 513 assertEquals("/storage/emulated/0/DCIM/.trashed-1888888888-test.jpg", 514 FileUtils.getAbsoluteExtendedPath( 515 "/storage/emulated/0/DCIM/.trashed-1621147340-test.jpg", 1888888888)); 516 } 517 518 @Test testExtractVolumePath()519 public void testExtractVolumePath() throws Exception { 520 assertEquals("/storage/emulated/0/", 521 extractVolumePath("/storage/emulated/0/foo.jpg")); 522 assertEquals("/storage/0000-0000/", 523 extractVolumePath("/storage/0000-0000/foo.jpg")); 524 } 525 526 @Test testExtractVolumeName()527 public void testExtractVolumeName() throws Exception { 528 assertEquals(MediaStore.VOLUME_EXTERNAL_PRIMARY, 529 extractVolumeName("/storage/emulated/0/foo.jpg")); 530 assertEquals("0000-0000", 531 extractVolumeName("/storage/0000-0000/foo.jpg")); 532 } 533 534 @Test testExtractRelativePath()535 public void testExtractRelativePath() throws Exception { 536 for (String prefix : new String[] { 537 "/storage/emulated/0/", 538 "/storage/0000-0000/" 539 }) { 540 assertEquals("/", 541 extractRelativePath(prefix + "foo.jpg")); 542 assertEquals("DCIM/", 543 extractRelativePath(prefix + "DCIM/foo.jpg")); 544 assertEquals("DCIM/My Vacation/", 545 extractRelativePath(prefix + "DCIM/My Vacation/foo.jpg")); 546 assertEquals("Pictures/", 547 extractRelativePath(prefix + "DCIM/../Pictures/.//foo.jpg")); 548 assertEquals("/", 549 extractRelativePath(prefix + "DCIM/Pictures/./..//..////foo.jpg")); 550 assertEquals("Android/data/", 551 extractRelativePath(prefix + "DCIM/foo.jpg/.//../../Android/data/poc")); 552 } 553 554 assertEquals(null, extractRelativePath("/sdcard/\\\u0000")); 555 } 556 557 @Test testExtractTopLevelDir()558 public void testExtractTopLevelDir() throws Exception { 559 for (String prefix : new String[] { 560 "/storage/emulated/0/", 561 "/storage/0000-0000/" 562 }) { 563 assertEquals(null, 564 extractTopLevelDir(prefix + "foo.jpg")); 565 assertEquals("DCIM", 566 extractTopLevelDir(prefix + "DCIM/foo.jpg")); 567 assertEquals("DCIM", 568 extractTopLevelDir(prefix + "DCIM/My Vacation/foo.jpg")); 569 } 570 } 571 572 @Test testExtractTopLevelDirWithRelativePathSegments()573 public void testExtractTopLevelDirWithRelativePathSegments() throws Exception { 574 assertEquals(null, 575 extractTopLevelDir(new String[] { null })); 576 assertEquals("DCIM", 577 extractTopLevelDir(new String[] { "DCIM" })); 578 assertEquals("DCIM", 579 extractTopLevelDir(new String[] { "DCIM", "My Vacation" })); 580 581 assertEquals(null, 582 extractTopLevelDir(new String[] { "AppClone" }, "AppClone")); 583 assertEquals("DCIM", 584 extractTopLevelDir(new String[] { "AppClone", "DCIM" }, "AppClone")); 585 assertEquals("DCIM", 586 extractTopLevelDir(new String[] { "AppClone", "DCIM", "My Vacation" }, "AppClone")); 587 588 assertEquals("Test", 589 extractTopLevelDir(new String[] { "Test" }, "AppClone")); 590 assertEquals("Test", 591 extractTopLevelDir(new String[] { "Test", "DCIM" }, "AppClone")); 592 assertEquals("Test", 593 extractTopLevelDir(new String[] { "Test", "DCIM", "My Vacation" }, "AppClone")); 594 } 595 596 @Test testExtractTopLevelDirForCrossUser()597 public void testExtractTopLevelDirForCrossUser() throws Exception { 598 Assume.assumeTrue(FileUtils.isCrossUserEnabled()); 599 600 final String crossUserRoot = SystemProperties.get("external_storage.cross_user.root", null); 601 Assume.assumeFalse(TextUtils.isEmpty(crossUserRoot)); 602 603 for (String prefix : new String[] { 604 "/storage/emulated/0/", 605 "/storage/0000-0000/" 606 }) { 607 assertEquals(null, 608 extractTopLevelDir(prefix + "foo.jpg")); 609 assertEquals("DCIM", 610 extractTopLevelDir(prefix + "DCIM/foo.jpg")); 611 assertEquals("DCIM", 612 extractTopLevelDir(prefix + "DCIM/My Vacation/foo.jpg")); 613 614 assertEquals(null, 615 extractTopLevelDir(prefix + crossUserRoot + "/foo.jpg")); 616 assertEquals("DCIM", 617 extractTopLevelDir(prefix + crossUserRoot + "/DCIM/foo.jpg")); 618 assertEquals("DCIM", 619 extractTopLevelDir(prefix + crossUserRoot + "/DCIM/My Vacation/foo.jpg")); 620 621 assertEquals("Test", 622 extractTopLevelDir(prefix + "Test/DCIM/foo.jpg")); 623 assertEquals("Test", 624 extractTopLevelDir(prefix + "Test/DCIM/My Vacation/foo.jpg")); 625 } 626 } 627 628 @Test testExtractDisplayName()629 public void testExtractDisplayName() throws Exception { 630 for (String probe : new String[] { 631 "foo.bar.baz", 632 "/foo.bar.baz", 633 "/foo.bar.baz/", 634 "/sdcard/foo.bar.baz", 635 "/sdcard/foo.bar.baz/", 636 }) { 637 assertEquals(probe, "foo.bar.baz", extractDisplayName(probe)); 638 } 639 } 640 641 @Test testExtractFileName()642 public void testExtractFileName() throws Exception { 643 for (String probe : new String[] { 644 "foo", 645 "/foo", 646 "/sdcard/foo", 647 "foo.bar", 648 "/foo.bar", 649 "/sdcard/foo.bar", 650 }) { 651 assertEquals(probe, "foo", extractFileName(probe)); 652 } 653 } 654 655 @Test testExtractFileName_empty()656 public void testExtractFileName_empty() throws Exception { 657 for (String probe : new String[] { 658 "", 659 "/", 660 ".bar", 661 "/.bar", 662 "/sdcard/.bar", 663 }) { 664 assertEquals(probe, "", extractFileName(probe)); 665 } 666 } 667 668 @Test testExtractFileExtension()669 public void testExtractFileExtension() throws Exception { 670 for (String probe : new String[] { 671 ".bar", 672 "foo.bar", 673 "/.bar", 674 "/foo.bar", 675 "/sdcard/.bar", 676 "/sdcard/foo.bar", 677 "/sdcard/foo.baz.bar", 678 "/sdcard/foo..bar", 679 }) { 680 assertEquals(probe, "bar", extractFileExtension(probe)); 681 } 682 } 683 684 @Test testExtractFileExtension_none()685 public void testExtractFileExtension_none() throws Exception { 686 for (String probe : new String[] { 687 "", 688 "/", 689 "/sdcard/", 690 "bar", 691 "/bar", 692 "/sdcard/bar", 693 }) { 694 assertEquals(probe, null, extractFileExtension(probe)); 695 } 696 } 697 698 @Test testExtractFileExtension_empty()699 public void testExtractFileExtension_empty() throws Exception { 700 for (String probe : new String[] { 701 "foo.", 702 "/foo.", 703 "/sdcard/foo.", 704 }) { 705 assertEquals(probe, "", extractFileExtension(probe)); 706 } 707 } 708 709 @Test testSanitizeValues()710 public void testSanitizeValues() throws Exception { 711 final ContentValues values = new ContentValues(); 712 values.put(MediaColumns.RELATIVE_PATH, "path/in\0valid/data/"); 713 values.put(MediaColumns.DISPLAY_NAME, "inva\0lid"); 714 FileUtils.sanitizeValues(values, /*rewriteHiddenFileName*/ true); 715 assertEquals("path/in_valid/data/", values.get(MediaColumns.RELATIVE_PATH)); 716 assertEquals("inva_lid", values.get(MediaColumns.DISPLAY_NAME)); 717 } 718 719 @Test testSanitizeValues_Root()720 public void testSanitizeValues_Root() throws Exception { 721 final ContentValues values = new ContentValues(); 722 values.put(MediaColumns.RELATIVE_PATH, "/"); 723 FileUtils.sanitizeValues(values, /*rewriteHiddenFileName*/ true); 724 assertEquals("/", values.get(MediaColumns.RELATIVE_PATH)); 725 } 726 727 @Test testSanitizeValues_HiddenFile()728 public void testSanitizeValues_HiddenFile() throws Exception { 729 final String hiddenDirectoryPath = ".hiddenDirectory/"; 730 final String hiddenFileName = ".hiddenFile"; 731 final ContentValues values = new ContentValues(); 732 values.put(MediaColumns.RELATIVE_PATH, hiddenDirectoryPath); 733 values.put(MediaColumns.DISPLAY_NAME, hiddenFileName); 734 735 FileUtils.sanitizeValues(values, /*rewriteHiddenFileName*/ false); 736 assertEquals(hiddenDirectoryPath, values.get(MediaColumns.RELATIVE_PATH)); 737 assertEquals(hiddenFileName, values.get(MediaColumns.DISPLAY_NAME)); 738 739 FileUtils.sanitizeValues(values, /*rewriteHiddenFileName*/ true); 740 assertEquals("_" + hiddenDirectoryPath, values.get(MediaColumns.RELATIVE_PATH)); 741 assertEquals("_" + hiddenFileName, values.get(MediaColumns.DISPLAY_NAME)); 742 } 743 744 @Test testComputeDateExpires_None()745 public void testComputeDateExpires_None() throws Exception { 746 final ContentValues values = new ContentValues(); 747 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 748 749 FileUtils.computeDateExpires(values); 750 assertFalse(values.containsKey(MediaColumns.DATE_EXPIRES)); 751 } 752 753 @Test testComputeDateExpires_Pending_Set()754 public void testComputeDateExpires_Pending_Set() throws Exception { 755 final ContentValues values = new ContentValues(); 756 values.put(MediaColumns.IS_PENDING, 1); 757 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 758 759 FileUtils.computeDateExpires(values); 760 final long target = (System.currentTimeMillis() 761 + FileUtils.DEFAULT_DURATION_PENDING) / 1_000; 762 assertThat(values.getAsLong(MediaColumns.DATE_EXPIRES)) 763 .isIn(Range.closed(target - 5, target + 5)); 764 } 765 766 @Test testComputeDateExpires_Pending_Clear()767 public void testComputeDateExpires_Pending_Clear() throws Exception { 768 final ContentValues values = new ContentValues(); 769 values.put(MediaColumns.IS_PENDING, 0); 770 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 771 772 FileUtils.computeDateExpires(values); 773 assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES)); 774 assertNull(values.get(MediaColumns.DATE_EXPIRES)); 775 } 776 777 @Test testComputeDateExpires_Trashed_Set()778 public void testComputeDateExpires_Trashed_Set() throws Exception { 779 final ContentValues values = new ContentValues(); 780 values.put(MediaColumns.IS_TRASHED, 1); 781 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 782 783 FileUtils.computeDateExpires(values); 784 final long target = (System.currentTimeMillis() 785 + FileUtils.DEFAULT_DURATION_TRASHED) / 1_000; 786 assertThat(values.getAsLong(MediaColumns.DATE_EXPIRES)) 787 .isIn(Range.closed(target - 5, target + 5)); 788 } 789 790 @Test testComputeDateExpires_Trashed_Clear()791 public void testComputeDateExpires_Trashed_Clear() throws Exception { 792 final ContentValues values = new ContentValues(); 793 values.put(MediaColumns.IS_TRASHED, 0); 794 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 795 796 FileUtils.computeDateExpires(values); 797 assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES)); 798 assertNull(values.get(MediaColumns.DATE_EXPIRES)); 799 } 800 801 @Test testComputeDataFromValues_Trashed_trimFileName()802 public void testComputeDataFromValues_Trashed_trimFileName() throws Exception { 803 testComputeDataFromValues_withAction_trimFileName(MediaColumns.IS_TRASHED); 804 } 805 806 @Test testComputeDataFromValues_Pending_trimFileName()807 public void testComputeDataFromValues_Pending_trimFileName() throws Exception { 808 testComputeDataFromValues_withAction_trimFileName(MediaColumns.IS_PENDING); 809 } 810 811 @Test testGetTopLevelNoMedia_CurrentDir()812 public void testGetTopLevelNoMedia_CurrentDir() throws Exception { 813 File dirInDownload = getNewDirInDownload("testGetTopLevelNoMedia_CurrentDir"); 814 File nomedia = new File(dirInDownload, ".nomedia"); 815 assertTrue(nomedia.createNewFile()); 816 817 assertThat(FileUtils.getTopLevelNoMedia(dirInDownload)) 818 .isEqualTo(dirInDownload); 819 assertThat(FileUtils.getTopLevelNoMedia(new File(dirInDownload, "foo"))) 820 .isEqualTo(dirInDownload); 821 } 822 823 @Test testGetTopLevelNoMedia_CurrentNestedDir()824 public void testGetTopLevelNoMedia_CurrentNestedDir() throws Exception { 825 File topDirInDownload = getNewDirInDownload("testGetTopLevelNoMedia_CurrentNestedDir"); 826 827 File dirInTopDirInDownload = new File(topDirInDownload, "foo"); 828 assertTrue(dirInTopDirInDownload.mkdirs()); 829 File nomedia = new File(dirInTopDirInDownload, ".nomedia"); 830 assertTrue(nomedia.createNewFile()); 831 832 assertThat(FileUtils.getTopLevelNoMedia(dirInTopDirInDownload)) 833 .isEqualTo(dirInTopDirInDownload); 834 assertThat(FileUtils.getTopLevelNoMedia(new File(dirInTopDirInDownload, "foo"))) 835 .isEqualTo(dirInTopDirInDownload); 836 } 837 838 @Test testGetTopLevelNoMedia_TopDir()839 public void testGetTopLevelNoMedia_TopDir() throws Exception { 840 File topDirInDownload = getNewDirInDownload("testGetTopLevelNoMedia_TopDir"); 841 File topNomedia = new File(topDirInDownload, ".nomedia"); 842 assertTrue(topNomedia.createNewFile()); 843 844 File dirInTopDirInDownload = new File(topDirInDownload, "foo"); 845 assertTrue(dirInTopDirInDownload.mkdirs()); 846 File nomedia = new File(dirInTopDirInDownload, ".nomedia"); 847 assertTrue(nomedia.createNewFile()); 848 849 assertThat(FileUtils.getTopLevelNoMedia(dirInTopDirInDownload)) 850 .isEqualTo(topDirInDownload); 851 assertThat(FileUtils.getTopLevelNoMedia(new File(dirInTopDirInDownload, "foo"))) 852 .isEqualTo(topDirInDownload); 853 } 854 855 @Test testGetTopLevelNoMedia_NoDir()856 public void testGetTopLevelNoMedia_NoDir() throws Exception { 857 File topDirInDownload = getNewDirInDownload("testGetTopLevelNoMedia_NoDir"); 858 File dirInTopDirInDownload = new File(topDirInDownload, "foo"); 859 assertTrue(dirInTopDirInDownload.mkdirs()); 860 861 assertEquals(null, 862 FileUtils.getTopLevelNoMedia(new File(dirInTopDirInDownload, "foo"))); 863 assertThat(FileUtils.getTopLevelNoMedia(dirInTopDirInDownload)) 864 .isNull(); 865 assertThat(FileUtils.getTopLevelNoMedia(new File(dirInTopDirInDownload, "foo"))) 866 .isNull(); 867 } 868 869 @Test testShouldFileBeHidden()870 public void testShouldFileBeHidden() throws Exception { 871 File dir = getNewDirInDownload("testDirectory2"); 872 873 // We don't create the files since shouldFileBeHidden needs to work even if the file has 874 // not been created yet. 875 876 File file = new File(dir, ".test-file"); 877 assertThat(FileUtils.shouldFileBeHidden(file)).isTrue(); 878 879 File hiddenFile = new File(dir, ".hidden-file"); 880 assertThat(FileUtils.shouldFileBeHidden(hiddenFile)).isTrue(); 881 } 882 883 @Test testShouldFileBeHidden_hiddenParent()884 public void testShouldFileBeHidden_hiddenParent() throws Exception { 885 File hiddenDirName = getNewDirInDownload(".testDirectory"); 886 887 // We don't create the file since shouldFileBeHidden needs to work even if the file has 888 // not been created yet. 889 890 File fileInHiddenParent = new File(hiddenDirName, "testDirectory.txt"); 891 assertThat(FileUtils.shouldFileBeHidden(fileInHiddenParent)).isTrue(); 892 } 893 894 // Visibility of default dirs is tested in ModernMediaScannerTest#testVisibleDefaultFolders. 895 @Test testShouldDirBeHidden()896 public void testShouldDirBeHidden() throws Exception { 897 final File root = new File("storage/emulated/0"); 898 assertThat(FileUtils.shouldDirBeHidden(root)).isFalse(); 899 900 // We don't create the dirs since shouldDirBeHidden needs to work even if the dir has 901 // not been created yet. 902 903 File visibleDir = new File(mTestDownloadDir, "testDirectory"); 904 assertThat(FileUtils.shouldDirBeHidden(visibleDir)).isFalse(); 905 906 File hiddenDir = new File(mTestDownloadDir, ".testDirectory"); 907 assertThat(FileUtils.shouldDirBeHidden(hiddenDir)).isTrue(); 908 } 909 910 @Test testShouldDirBeHidden_hiddenParent()911 public void testShouldDirBeHidden_hiddenParent() throws Exception { 912 File hiddenDirName = getNewDirInDownload(".testDirectory"); 913 914 // We don't create the dirs since shouldDirBeHidden needs to work even if the dir has 915 // not been created yet. 916 917 File dirInHiddenParent = new File(hiddenDirName, "testDirectory"); 918 assertThat(FileUtils.shouldDirBeHidden(dirInHiddenParent)).isTrue(); 919 } 920 921 // Visibility of default dirs is tested in ModernMediaScannerTest#testVisibleDefaultFolders. 922 @Test testIsDirectoryHidden()923 public void testIsDirectoryHidden() throws Exception { 924 File visibleDir = getNewDirInDownload("testDirectory"); 925 assertThat(FileUtils.isDirectoryHidden(visibleDir)).isFalse(); 926 927 File hiddenDirName = getNewDirInDownload(".testDirectory"); 928 assertThat(FileUtils.isDirectoryHidden(hiddenDirName)).isTrue(); 929 930 File hiddenDirNomedia = getNewDirInDownload("testDirectory2"); 931 File nomedia = new File(hiddenDirNomedia, ".nomedia"); 932 assertThat(nomedia.createNewFile()).isTrue(); 933 assertThat(FileUtils.isDirectoryHidden(hiddenDirNomedia)).isTrue(); 934 } 935 936 @Test testDirectoryDirty()937 public void testDirectoryDirty() throws Exception { 938 File dirInDownload = getNewDirInDownload("testDirectoryDirty"); 939 940 // Directory without nomedia is not dirty 941 assertFalse(FileUtils.isDirectoryDirty(dirInDownload)); 942 943 // Creating an empty .nomedia file makes directory dirty 944 File nomedia = new File(dirInDownload, ".nomedia"); 945 assertTrue(nomedia.createNewFile()); 946 assertTrue(FileUtils.isDirectoryDirty(dirInDownload)); 947 948 // Marking as clean with a .nomedia file works 949 FileUtils.setDirectoryDirty(dirInDownload, false); 950 assertFalse(FileUtils.isDirectoryDirty(dirInDownload)); 951 952 // Marking as dirty with a .nomedia file works 953 FileUtils.setDirectoryDirty(dirInDownload, true); 954 assertTrue(FileUtils.isDirectoryDirty(dirInDownload)); 955 } 956 957 @Test testDirectoryDirty_noMediaDirectory()958 public void testDirectoryDirty_noMediaDirectory() throws Exception { 959 File dirInDownload = getNewDirInDownload("testDirectoryDirty"); 960 961 // Directory without nomedia is clean 962 assertFalse(FileUtils.isDirectoryDirty(dirInDownload)); 963 964 // Creating a .nomedia directory makes directory dirty 965 File nomedia = new File(dirInDownload, ".nomedia"); 966 assertTrue(nomedia.mkdir()); 967 assertTrue(FileUtils.isDirectoryDirty(dirInDownload)); 968 969 // Marking as clean with a .nomedia directory has no effect 970 FileUtils.setDirectoryDirty(dirInDownload, false); 971 assertTrue(FileUtils.isDirectoryDirty(dirInDownload)); 972 } 973 974 @Test testDirectoryDirty_nullDir()975 public void testDirectoryDirty_nullDir() throws Exception { 976 assertThat(FileUtils.isDirectoryDirty(null)).isFalse(); 977 } 978 979 @Test testExtractPathOwnerPackageName()980 public void testExtractPathOwnerPackageName() { 981 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/data/foo")) 982 .isEqualTo("foo"); 983 assertThat(extractPathOwnerPackageName("/storage/emulated/0/android/data/foo")) 984 .isEqualTo("foo"); 985 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/obb/foo")) 986 .isEqualTo("foo"); 987 assertThat(extractPathOwnerPackageName("/storage/emulated/0/android/obb/foo")) 988 .isEqualTo("foo"); 989 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/media/foo")) 990 .isEqualTo("foo"); 991 assertThat(extractPathOwnerPackageName("/storage/emulated/0/android/media/foo")) 992 .isEqualTo("foo"); 993 assertThat(extractPathOwnerPackageName("/storage/ABCD-1234/Android/data/foo")) 994 .isEqualTo("foo"); 995 assertThat(extractPathOwnerPackageName("/storage/ABCD-1234/Android/obb/foo")) 996 .isEqualTo("foo"); 997 assertThat(extractPathOwnerPackageName("/storage/ABCD-1234/Android/media/foo")) 998 .isEqualTo("foo"); 999 assertThat(extractPathOwnerPackageName("/storage/ABCD-1234/android/media/foo")) 1000 .isEqualTo("foo"); 1001 1002 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/data")).isNull(); 1003 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/obb")).isNull(); 1004 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Android/media")).isNull(); 1005 assertThat(extractPathOwnerPackageName("/storage/ABCD-1234/Android/media")).isNull(); 1006 assertThat(extractPathOwnerPackageName("/storage/emulated/0/Pictures/foo")).isNull(); 1007 assertThat(extractPathOwnerPackageName("Android/data")).isNull(); 1008 assertThat(extractPathOwnerPackageName("Android/obb")).isNull(); 1009 } 1010 1011 @Test testExtractOwnerPackageNameFromRelativePath()1012 public void testExtractOwnerPackageNameFromRelativePath() { 1013 assertThat(extractOwnerPackageNameFromRelativePath("Android/data/foo")).isEqualTo("foo"); 1014 assertThat(extractOwnerPackageNameFromRelativePath("Android/obb/foo")).isEqualTo("foo"); 1015 assertThat(extractOwnerPackageNameFromRelativePath("Android/media/foo")).isEqualTo("foo"); 1016 assertThat(extractOwnerPackageNameFromRelativePath("Android/media/foo.com/files")) 1017 .isEqualTo("foo.com"); 1018 1019 assertThat(extractOwnerPackageNameFromRelativePath("/storage/emulated/0/Android/data/foo")) 1020 .isNull(); 1021 assertThat(extractOwnerPackageNameFromRelativePath("Android/data")).isNull(); 1022 assertThat(extractOwnerPackageNameFromRelativePath("Android/obb")).isNull(); 1023 assertThat(extractOwnerPackageNameFromRelativePath("Android/media")).isNull(); 1024 assertThat(extractOwnerPackageNameFromRelativePath("Pictures/foo")).isNull(); 1025 } 1026 1027 @Test testIsDataOrObbPath()1028 public void testIsDataOrObbPath() { 1029 assertThat(isDataOrObbPath("/storage/emulated/0/Android/data")).isTrue(); 1030 assertThat(isDataOrObbPath("/storage/emulated/0/Android/obb")).isTrue(); 1031 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/data")).isTrue(); 1032 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obb")).isTrue(); 1033 1034 assertThat(isDataOrObbPath("/storage/emulated/0/Android/data/foo")).isFalse(); 1035 assertThat(isDataOrObbPath("/storage/emulated/0/Android/obb/foo")).isFalse(); 1036 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/data/foo")).isFalse(); 1037 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obb/foo")).isFalse(); 1038 assertThat(isDataOrObbPath("/storage/emulated/10/Android/obb/foo")).isFalse(); 1039 assertThat(isDataOrObbPath("/storage/emulated//Android/obb/foo")).isFalse(); 1040 assertThat(isDataOrObbPath("/storage/emulated//Android/obb")).isFalse(); 1041 assertThat(isDataOrObbPath("/storage/emulated/0//Android/obb")).isFalse(); 1042 assertThat(isDataOrObbPath("/storage/emulated/0//Android/obb/foo")).isFalse(); 1043 assertThat(isDataOrObbPath("/storage/emulated/0/Android/")).isFalse(); 1044 assertThat(isDataOrObbPath("/storage/emulated/0/Android/media/")).isFalse(); 1045 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/media/")).isFalse(); 1046 assertThat(isDataOrObbPath("/storage/emulated/0/Pictures/")).isFalse(); 1047 assertThat(isDataOrObbPath("/storage/ABCD-1234/Android/obbfoo")).isFalse(); 1048 assertThat(isDataOrObbPath("/storage/emulated/0/Android/datafoo")).isFalse(); 1049 assertThat(isDataOrObbPath("Android/")).isFalse(); 1050 assertThat(isDataOrObbPath("Android/media/")).isFalse(); 1051 } 1052 1053 @Test testIsDataOrObbRelativePath()1054 public void testIsDataOrObbRelativePath() { 1055 assertThat(isDataOrObbRelativePath("Android/data")).isTrue(); 1056 assertThat(isDataOrObbRelativePath("Android/obb")).isTrue(); 1057 assertThat(isDataOrObbRelativePath("Android/data/foo")).isTrue(); 1058 assertThat(isDataOrObbRelativePath("Android/obb/foo")).isTrue(); 1059 1060 assertThat(isDataOrObbRelativePath("/storage/emulated/0/Android/data")).isFalse(); 1061 assertThat(isDataOrObbRelativePath("Android/")).isFalse(); 1062 assertThat(isDataOrObbRelativePath("Android/media/")).isFalse(); 1063 assertThat(isDataOrObbRelativePath("Pictures/")).isFalse(); 1064 } 1065 1066 @Test testIsObbOrChildRelativePath()1067 public void testIsObbOrChildRelativePath() { 1068 assertThat(isObbOrChildRelativePath("Android/obb")).isTrue(); 1069 assertThat(isObbOrChildRelativePath("Android/obb/")).isTrue(); 1070 assertThat(isObbOrChildRelativePath("Android/obb/foo.com")).isTrue(); 1071 1072 assertThat(isObbOrChildRelativePath("/storage/emulated/0/Android/obb")).isFalse(); 1073 assertThat(isObbOrChildRelativePath("/storage/emulated/0/Android/")).isFalse(); 1074 assertThat(isObbOrChildRelativePath("Android/")).isFalse(); 1075 assertThat(isObbOrChildRelativePath("Android/media/")).isFalse(); 1076 assertThat(isObbOrChildRelativePath("Pictures/")).isFalse(); 1077 assertThat(isObbOrChildRelativePath("Android/obbfoo")).isFalse(); 1078 assertThat(isObbOrChildRelativePath("Android/data")).isFalse(); 1079 } 1080 getNewDirInDownload(String name)1081 private File getNewDirInDownload(String name) { 1082 File file = new File(mTestDownloadDir, name); 1083 assertTrue(file.mkdir()); 1084 return file; 1085 } 1086 touch(File dir, String name)1087 private static File touch(File dir, String name) throws IOException { 1088 final File res = new File(dir, name); 1089 res.createNewFile(); 1090 return res; 1091 } 1092 assertNameEquals(String expected, File actual)1093 private static void assertNameEquals(String expected, File actual) { 1094 assertEquals(expected, actual.getName()); 1095 } 1096 assertDirContents(String... expected)1097 private void assertDirContents(String... expected) { 1098 final HashSet<String> expectedSet = new HashSet<>(Arrays.asList(expected)); 1099 String[] actual = mDeleteTarget.list(); 1100 if (actual == null) actual = new String[0]; 1101 1102 assertEquals( 1103 "Expected " + Arrays.toString(expected) + " but actual " + Arrays.toString(actual), 1104 expected.length, actual.length); 1105 for (String actualFile : actual) { 1106 assertTrue("Unexpected actual file " + actualFile, expectedSet.contains(actualFile)); 1107 } 1108 } 1109 createExtremeFileName(String prefix, String extension)1110 public static String createExtremeFileName(String prefix, String extension) { 1111 // create extreme long file name 1112 final int prefixLength = prefix.length(); 1113 final int extensionLength = extension.length(); 1114 StringBuilder str = new StringBuilder(prefix); 1115 for (int i = 0; i < (MAX_FILENAME_BYTES - prefixLength - extensionLength); i++) { 1116 str.append(i % 10); 1117 } 1118 return str.append(extension).toString(); 1119 } 1120 testComputeDataFromValues_withAction_trimFileName(String columnKey)1121 private void testComputeDataFromValues_withAction_trimFileName(String columnKey) { 1122 final String originalName = createExtremeFileName("test", ".jpg"); 1123 final String volumePath = "/storage/emulated/0/"; 1124 final ContentValues values = new ContentValues(); 1125 values.put(columnKey, 1); 1126 values.put(MediaColumns.RELATIVE_PATH, "DCIM/My Vacation/"); 1127 values.put(MediaColumns.DATE_EXPIRES, 1577836800L); 1128 values.put(MediaColumns.DISPLAY_NAME, originalName); 1129 1130 FileUtils.computeDataFromValues(values, new File(volumePath), false /* isForFuse */); 1131 1132 final String data = values.getAsString(MediaColumns.DATA); 1133 final String result = FileUtils.extractDisplayName(data); 1134 // after adding the prefix .pending-timestamp or .trashed-timestamp, 1135 // the largest length of the file name is MAX_FILENAME_BYTES 255 1136 assertThat(result.length()).isAtMost(MAX_FILENAME_BYTES); 1137 assertThat(result).isNotEqualTo(originalName); 1138 } 1139 1140 @Test testIsExternalMediaDirectory()1141 public void testIsExternalMediaDirectory() throws Exception { 1142 for (String prefix : new String[] { 1143 "/storage/emulated/0/", 1144 "/storage/0000-0000/", 1145 }) { 1146 assertTrue(isExternalMediaDirectory(prefix + "Android/media/foo.jpg", null)); 1147 assertTrue(isExternalMediaDirectory(prefix + "Android/media/foo.jpg", "")); 1148 assertTrue(isExternalMediaDirectory(prefix + "Android/mEdia/foo.jpg", "")); 1149 assertFalse(isExternalMediaDirectory(prefix + "Android/data/foo.jpg", "")); 1150 assertTrue(isExternalMediaDirectory(prefix + "Android/media/foo.jpg", "AppClone")); 1151 assertTrue(isExternalMediaDirectory(prefix + "android/mEdia/foo.jpg", "AppClone")); 1152 assertTrue(isExternalMediaDirectory(prefix + "AppClone/Android/media/foo.jpg", "AppClone")); 1153 assertTrue(isExternalMediaDirectory(prefix + "AppClone/Android/mEdia/foo.jpg", "AppClone")); 1154 assertTrue(isExternalMediaDirectory(prefix + "Appclone/Android/mEdia/foo.jpg", "AppClone")); 1155 assertFalse(isExternalMediaDirectory(prefix + "AppClone/Android/media/foo.jpg", null)); 1156 assertFalse(isExternalMediaDirectory(prefix + "AppClone/Android/mEdia/foo.jpg", null)); 1157 assertFalse(isExternalMediaDirectory(prefix + "AppClone/Android/media/foo.jpg", "")); 1158 assertFalse(isExternalMediaDirectory(prefix + "AppClone/Android/media/foo.jpg", "NotAppClone")); 1159 } 1160 } 1161 1162 @Test testToAndFromFuseFile()1163 public void testToAndFromFuseFile() throws Exception { 1164 final File fuseFilePrimary = new File("/mnt/user/0/emulated/0/foo"); 1165 final File fuseFileSecondary = new File("/mnt/user/0/0000-0000/foo"); 1166 1167 final File lowerFsFilePrimary = new File("/storage/emulated/0/foo"); 1168 final File lowerFsFileSecondary = new File("/storage/0000-0000/foo"); 1169 1170 final File unexpectedFile = new File("/mnt/pass_through/0/emulated/0/foo"); 1171 1172 assertThat(fromFuseFile(fuseFilePrimary)).isEqualTo(lowerFsFilePrimary); 1173 assertThat(fromFuseFile(fuseFileSecondary)).isEqualTo(lowerFsFileSecondary); 1174 assertThat(fromFuseFile(lowerFsFilePrimary)).isEqualTo(lowerFsFilePrimary); 1175 1176 assertThat(toFuseFile(lowerFsFilePrimary)).isEqualTo(fuseFilePrimary); 1177 assertThat(toFuseFile(lowerFsFileSecondary)).isEqualTo(fuseFileSecondary); 1178 assertThat(toFuseFile(fuseFilePrimary)).isEqualTo(fuseFilePrimary); 1179 1180 assertThat(toFuseFile(unexpectedFile)).isEqualTo(unexpectedFile); 1181 assertThat(fromFuseFile(unexpectedFile)).isEqualTo(unexpectedFile); 1182 } 1183 1184 @Test testComputeValuesFromData()1185 public void testComputeValuesFromData() { 1186 final ContentValues values = new ContentValues(); 1187 values.put(MediaColumns.DATA, "/storage/emulated/0/Pictures/foo.jpg"); 1188 1189 FileUtils.computeValuesFromData(values, false); 1190 1191 assertEquals("external_primary", values.getAsString(MediaColumns.VOLUME_NAME)); 1192 assertEquals("Pictures/", values.getAsString(MediaColumns.RELATIVE_PATH)); 1193 assertEquals(0, (int) values.getAsInteger(MediaColumns.IS_TRASHED)); 1194 assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES)); 1195 assertNull(values.get(MediaColumns.DATE_EXPIRES)); 1196 assertEquals("foo.jpg", values.getAsString(MediaColumns.DISPLAY_NAME)); 1197 assertTrue(values.containsKey(MediaColumns.BUCKET_DISPLAY_NAME)); 1198 assertEquals("Pictures", values.get(MediaColumns.BUCKET_DISPLAY_NAME)); 1199 } 1200 1201 @Test testComputeValuesFromData_withTopLevelFile()1202 public void testComputeValuesFromData_withTopLevelFile() { 1203 final ContentValues values = new ContentValues(); 1204 values.put(MediaColumns.DATA, "/storage/emulated/0/foo.jpg"); 1205 1206 FileUtils.computeValuesFromData(values, false); 1207 1208 assertEquals("external_primary", values.getAsString(MediaColumns.VOLUME_NAME)); 1209 assertEquals("/", values.getAsString(MediaColumns.RELATIVE_PATH)); 1210 assertEquals(0, (int) values.getAsInteger(MediaColumns.IS_TRASHED)); 1211 assertTrue(values.containsKey(MediaColumns.DATE_EXPIRES)); 1212 assertNull(values.get(MediaColumns.DATE_EXPIRES)); 1213 assertEquals("foo.jpg", values.getAsString(MediaColumns.DISPLAY_NAME)); 1214 assertTrue(values.containsKey(MediaColumns.BUCKET_DISPLAY_NAME)); 1215 assertNull(values.get(MediaColumns.BUCKET_DISPLAY_NAME)); 1216 } 1217 1218 @Test testComputeDataFromValuesForValidPath_success()1219 public void testComputeDataFromValuesForValidPath_success() { 1220 final ContentValues values = new ContentValues(); 1221 values.put(MediaColumns.RELATIVE_PATH, "Android/media/com.example"); 1222 values.put(MediaColumns.DISPLAY_NAME, "./../../abc.txt"); 1223 1224 FileUtils.computeDataFromValues(values, new File("/storage/emulated/0"), false); 1225 1226 assertThat(values.getAsString(MediaColumns.DATA)).isEqualTo( 1227 "/storage/emulated/0/Android/abc.txt"); 1228 } 1229 1230 @Test testComputeDataFromValuesForInvalidPath_throwsIllegalArgumentException()1231 public void testComputeDataFromValuesForInvalidPath_throwsIllegalArgumentException() { 1232 final ContentValues values = new ContentValues(); 1233 values.put(MediaColumns.RELATIVE_PATH, "\0"); 1234 values.put(MediaColumns.DISPLAY_NAME, "./../../abc.txt"); 1235 1236 assertThrows(IllegalArgumentException.class, 1237 () -> FileUtils.computeDataFromValues(values, new File("/storage/emulated/0"), 1238 false)); 1239 } 1240 } 1241