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