blob: 32627662d48253503ff87d840d09baf6405eacb5 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.scopedstorage.cts.legacy;
import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.installApp;
import static android.scopedstorage.cts.lib.TestUtils.listAs;
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.scopedstorage.cts.lib.TestUtils;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.install.lib.TestApp;
import com.google.common.io.Files;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Test app targeting Q and requesting legacy storage - tests legacy file path access.
* Designed to be run by LegacyAccessHostTest.
*
* <p> Test cases that assume we have WRITE_EXTERNAL_STORAGE only are appended with hasW,
* those that assume we have READ_EXTERNAL_STORAGE only are appended with hasR, those who assume we
* have both are appended with hasRW.
*/
@RunWith(AndroidJUnit4.class)
public class LegacyStorageTest {
private static final String TAG = "LegacyFileAccessTest";
static final String THIS_PACKAGE_NAME = InstrumentationRegistry.getContext().getPackageName();
static final String IMAGE_FILE_NAME = "LegacyStorageTest_file.jpg";
static final String VIDEO_FILE_NAME = "LegacyStorageTest_file.mp4";
static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file.pdf";
private static final TestApp TEST_APP_A = new TestApp("TestAppA",
"android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
/**
* This method needs to be called once before running the whole test.
*/
@Test
public void setupExternalStorage() {
setupDefaultDirectories();
}
@Before
public void setUp() throws Exception {
pollForExternalStorageState();
}
/**
* Tests that legacy apps bypass the type-path conformity restrictions imposed by
* MediaProvider. <p> Assumes we have WRITE_EXTERNAL_STORAGE.
*/
@Test
public void testCreateFilesInRandomPlaces_hasW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
// Can create file under root dir
assertCanCreateFile(new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest.txt"));
// Can create music file under DCIM
assertCanCreateFile(new File(TestUtils.getDcimDir(), "LegacyFileAccessTest.mp3"));
// Can create random file under external files dir
assertCanCreateFile(new File(TestUtils.getExternalFilesDir(),
"LegacyFileAccessTest"));
// However, even legacy apps can't create files under other app's directories
final File otherAppDir = new File(TestUtils.getAndroidDataDir(), "com.android.shell");
final File file = new File(otherAppDir, "LegacyFileAccessTest.txt");
// otherAppDir was already created by the host test
try {
file.createNewFile();
fail("File creation expected to fail: " + file);
} catch (IOException expected) {
}
}
/**
* Tests that legacy apps bypass dir creation/deletion restrictions imposed by MediaProvider.
* <p> Assumes we have WRITE_EXTERNAL_STORAGE.
*/
@Test
public void testMkdirInRandomPlaces_hasW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
// Can create a top-level direcotry
final File topLevelDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
assertCanCreateDir(topLevelDir);
final File otherAppDir = new File(TestUtils.getAndroidDataDir(), "com.android.shell");
// However, even legacy apps can't create dirs under other app's directories
final File subDir = new File(otherAppDir, "LegacyFileAccessTest");
// otherAppDir was already created by the host test
assertThat(subDir.mkdir()).isFalse();
// Try to list a directory and fail because it requires READ permission
assertThat(TestUtils.getMusicDir().list()).isNull();
}
/**
* Tests that an app can't access external storage without permissions.
*/
@Test
public void testCantAccessExternalStorage() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
// Can't create file under root dir
final File newTxtFile = new File(TestUtils.getExternalStorageDir(),
"LegacyFileAccessTest.txt");
try {
newTxtFile.createNewFile();
fail("File creation expected to fail: " + newTxtFile);
} catch (IOException expected) {
}
// Can't create music file under /MUSIC
final File newMusicFile = new File(TestUtils.getMusicDir(), "LegacyFileAccessTest.mp3");
try {
newMusicFile.createNewFile();
fail("File creation expected to fail: " + newMusicFile);
} catch (IOException expected) {
}
// Can't create a top-level direcotry
final File topLevelDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
assertThat(topLevelDir.mkdir()).isFalse();
// Can't read existing file
final File existingFile = new File(TestUtils.getExternalStorageDir(),
"LegacyAccessHostTest_shell");
try {
Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
fail("Opening file for read expected to fail: " + existingFile);
} catch (ErrnoException expected) {
}
// Can't delete file
assertThat(existingFile.delete()).isFalse();
// try to list a directory and fail
assertThat(TestUtils.getMusicDir().list()).isNull();
assertThat(TestUtils.getExternalStorageDir().list()).isNull();
// However, even without permissions, we can access our own external dir
final File fileInDataDir =
new File(TestUtils.getExternalFilesDir(),
"LegacyFileAccessTest");
try {
assertThat(fileInDataDir.createNewFile()).isTrue();
assertThat(Arrays.asList(fileInDataDir.getParentFile().list()))
.contains("LegacyFileAccessTest");
} finally {
fileInDataDir.delete();
}
// we can access our own external media directory without permissions.
final File fileInMediaDir =
new File(TestUtils.getExternalMediaDir(),
"LegacyFileAccessTest");
try {
assertThat(fileInMediaDir.createNewFile()).isTrue();
assertThat(Arrays.asList(fileInMediaDir.getParentFile().list()))
.contains("LegacyFileAccessTest");
} finally {
fileInMediaDir.delete();
}
}
// test read storage permission
@Test
public void testReadOnlyExternalStorage_hasR() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
// can list directory content
assertThat(TestUtils.getMusicDir().list()).isNotNull();
// try to write a file and fail
final File existingFile = new File(TestUtils.getExternalStorageDir(),
"LegacyAccessHostTest_shell");
// can open file for read
FileDescriptor fd = null;
try {
fd = Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
} finally {
if (fd != null) {
Os.close(fd);
}
}
try {
fd = Os.open(existingFile.getPath(), OsConstants.O_WRONLY, /*mode*/ 0);
Os.close(fd);
fail("Opening file for write expected to fail: " + existingFile);
} catch (ErrnoException expected) {
}
// try to create file and fail, because it requires WRITE
final File newFile = new File(TestUtils.getMusicDir(), "LegacyFileAccessTest.mp3");
try {
newFile.createNewFile();
fail("Creating file expected to fail: " + newFile);
} catch (IOException expected) {
}
// try to mkdir and fail, because it requires WRITE
final File newDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
try {
assertThat(newDir.mkdir()).isFalse();
} finally {
newDir.delete();
}
}
/**
* Test that legacy app with storage permission can list all files
*/
@Test
public void testListFiles_hasR() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
// can list a non-media file created by other package.
assertThat(Arrays.asList(TestUtils.getExternalStorageDir().list()))
.contains("LegacyAccessHostTest_shell");
}
/**
* Test that rename for legacy app with WRITE_EXTERNAL_STORAGE permission bypasses rename
* restrictions imposed by MediaProvider
*/
@Test
public void testCanRename_hasRW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File musicFile1 = new File(TestUtils.getDcimDir(), "LegacyFileAccessTest.mp3");
final File musicFile2 = new File(TestUtils.getExternalStorageDir(),
"LegacyFileAccessTest.mp3");
final File musicFile3 = new File(TestUtils.getMoviesDir(), "LegacyFileAccessTest.mp3");
final File nonMediaDir1 = new File(TestUtils.getDcimDir(), "LegacyFileAccessTest");
final File nonMediaDir2 = new File(TestUtils.getExternalStorageDir(),
"LegacyFileAccessTest");
final File pdfFile1 = new File(nonMediaDir1, "LegacyFileAccessTest.pdf");
final File pdfFile2 = new File(nonMediaDir2, "LegacyFileAccessTest.pdf");
try {
// can rename a file to root directory.
assertThat(musicFile1.createNewFile()).isTrue();
assertCanRenameFile(musicFile1, musicFile2);
// can rename a music file to Movies directory.
assertCanRenameFile(musicFile2, musicFile3);
assertThat(nonMediaDir1.mkdir()).isTrue();
assertThat(pdfFile1.createNewFile()).isTrue();
// can rename directory to root directory.
assertCanRenameDirectory(
nonMediaDir1, nonMediaDir2, new File[] {pdfFile1}, new File[] {pdfFile2});
} finally {
musicFile1.delete();
musicFile2.delete();
musicFile3.delete();
pdfFile1.delete();
pdfFile2.delete();
nonMediaDir1.delete();
nonMediaDir2.delete();
}
}
/**
* Test that legacy app with only READ_EXTERNAL_STORAGE can only rename files in app external
* directories.
*/
@Test
public void testCantRename_hasR() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
final File shellFile1 = new File(TestUtils.getExternalStorageDir(),
"LegacyAccessHostTest_shell");
final File shellFile2 = new File(TestUtils.getDownloadDir(), "LegacyFileAccessTest_shell");
final File mediaFile1 =
new File(TestUtils.getExternalMediaDir(),
"LegacyFileAccessTest1");
final File mediaFile2 =
new File(TestUtils.getExternalMediaDir(),
"LegacyFileAccessTest2");
try {
// app can't rename shell file.
assertCantRenameFile(shellFile1, shellFile2);
// app can't move shell file to its media directory.
assertCantRenameFile(shellFile1, mediaFile1);
// However, even without permissions, app can rename files in its own external media
// directory.
assertThat(mediaFile1.createNewFile()).isTrue();
assertThat(mediaFile1.renameTo(mediaFile2)).isTrue();
assertThat(mediaFile2.exists()).isTrue();
} finally {
mediaFile1.delete();
mediaFile2.delete();
}
}
/**
* Test that legacy app with no storage permission can only rename files in app external
* directories.
*/
@Test
public void testCantRename_noStoragePermission() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
final File shellFile1 = new File(TestUtils.getExternalStorageDir(),
"LegacyAccessHostTest_shell");
final File shellFile2 = new File(TestUtils.getDownloadDir(), "LegacyFileAccessTest_shell");
final File mediaFile1 =
new File(TestUtils.getExternalMediaDir(),
"LegacyFileAccessTest1");
final File mediaFile2 =
new File(TestUtils.getExternalMediaDir(),
"LegacyFileAccessTest2");
try {
// app can't rename shell file.
assertCantRenameFile(shellFile1, shellFile2);
// app can't move shell file to its media directory.
assertCantRenameFile(shellFile1, mediaFile1);
// However, even without permissions, app can rename files in its own external media
// directory.
assertThat(mediaFile1.createNewFile()).isTrue();
assertThat(mediaFile1.renameTo(mediaFile2)).isTrue();
assertThat(mediaFile2.exists()).isTrue();
} finally {
mediaFile1.delete();
mediaFile2.delete();
}
}
/**
* b/156046098, Test that MediaProvider doesn't throw UNIQUE constraint error while updating db
* rows corresponding to renamed directory.
*/
@Test
public void testRenameDirectoryAndUpdateDB_hasW() throws Exception {
final String testDirectoryName = "LegacyFileAccessTestDirectory";
File directoryOldPath = new File(TestUtils.getDcimDir(), testDirectoryName);
File directoryNewPath = new File(TestUtils.getMoviesDir(), testDirectoryName);
try {
if (directoryOldPath.exists()) {
executeShellCommand("rm -r " + directoryOldPath.getPath());
}
assertThat(directoryOldPath.mkdirs()).isTrue();
assertCanRenameDirectory(directoryOldPath, directoryNewPath, null, null);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, directoryNewPath.getPath());
// Verify that updating directoryOldPath to directoryNewPath doesn't throw
// UNIQUE constraint error.
getContentResolver().update(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
values, /*where*/ MediaStore.MediaColumns.DATA + "=?",
/*whereArgs*/ new String[] {directoryOldPath.getPath()});
} finally {
directoryOldPath.delete();
directoryNewPath.delete();
}
}
/**
* Test that legacy app with WRITE_EXTERNAL_STORAGE can delete all files, and corresponding
* database entry is deleted on deleting the file.
*/
@Test
public void testCanDeleteAllFiles_hasRW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File videoFile = new File(TestUtils.getExternalStorageDir(), VIDEO_FILE_NAME);
final File otherAppPdfFile = new File(TestUtils.getDownloadDir(), NONMEDIA_FILE_NAME);
try {
assertThat(videoFile.createNewFile()).isTrue();
assertDirectoryContains(videoFile.getParentFile(), videoFile);
assertThat(getFileRowIdFromDatabase(videoFile)).isNotEqualTo(-1);
// Legacy app can delete its own file.
assertThat(videoFile.delete()).isTrue();
// Deleting the file will remove videoFile entry from database.
assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
installApp(TEST_APP_A, false);
assertThat(createFileAs(TEST_APP_A, otherAppPdfFile.getAbsolutePath())).isTrue();
assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isNotEqualTo(-1);
// Legacy app with write permission can delete the pdfFile owned by TestApp.
assertThat(otherAppPdfFile.delete()).isTrue();
// Deleting the pdfFile also removes pdfFile from database.
assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
} finally {
deleteFileAsNoThrow(TEST_APP_A, otherAppPdfFile.getAbsolutePath());
uninstallApp(TEST_APP_A);
videoFile.delete();
}
}
/**
* Test that file created by legacy app is inserted to MediaProvider database. And,
* MediaColumns.OWNER_PACKAGE_NAME is updated with calling package's name.
*/
@Test
public void testLegacyAppCanOwnAFile_hasW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File videoFile = new File(TestUtils.getExternalStorageDir(), VIDEO_FILE_NAME);
try {
assertThat(videoFile.createNewFile()).isTrue();
installApp(TEST_APP_A, true);
// videoFile is inserted to database, non-legacy app can see this videoFile on 'ls'.
assertThat(listAs(TEST_APP_A, TestUtils.getExternalStorageDir().getAbsolutePath()))
.contains(VIDEO_FILE_NAME);
// videoFile is in database, row ID for videoFile can not be -1.
assertNotEquals(-1, getFileRowIdFromDatabase(videoFile));
assertEquals(THIS_PACKAGE_NAME, getFileOwnerPackageFromDatabase(videoFile));
assertTrue(videoFile.delete());
// videoFile is removed from database on delete, hence row ID is -1.
assertEquals(-1, getFileRowIdFromDatabase(videoFile));
} finally {
videoFile.delete();
uninstallApp(TEST_APP_A);
}
}
@Test
public void testCreateAndRenameDoesntLeaveStaleDBRow_hasRW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File videoFile = new File(TestUtils.getDcimDir(), VIDEO_FILE_NAME);
final File renamedVideoFile = new File(TestUtils.getDcimDir(), "Renamed_" + VIDEO_FILE_NAME);
final ContentResolver cr = getContentResolver();
try {
assertThat(videoFile.createNewFile()).isTrue();
assertThat(videoFile.renameTo(renamedVideoFile)).isTrue();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, renamedVideoFile.getAbsolutePath());
// Insert new renamedVideoFile to database
final Uri uri = cr.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values, null);
assertNotNull(uri);
// Query for all images/videos in the device.
// This shouldn't list videoFile which was renamed to renamedVideoFile.
final ArrayList<String> imageAndVideoFiles = getImageAndVideoFilesFromDatabase();
assertThat(imageAndVideoFiles).contains(renamedVideoFile.getName());
assertThat(imageAndVideoFiles).doesNotContain(videoFile.getName());
} finally {
videoFile.delete();
renamedVideoFile.delete();
}
}
/**
* b/150147690,b/150193381: Test that file rename doesn't delete any existing Uri.
*/
@Test
public void testRenameDoesntInvalidateUri_hasRW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File imageFile = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
final File temporaryImageFile = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME + "_.tmp");
final ContentResolver cr = getContentResolver();
try {
assertThat(imageFile.createNewFile()).isTrue();
try (final FileOutputStream fos = new FileOutputStream(imageFile)) {
fos.write(BYTES_DATA1);
}
// Insert this file to database.
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
assertNotNull(uri);
Files.copy(imageFile, temporaryImageFile);
// Write more bytes to temporaryImageFile
try (final FileOutputStream fos = new FileOutputStream(temporaryImageFile, true)) {
fos.write(BYTES_DATA2);
}
assertThat(imageFile.delete()).isTrue();
temporaryImageFile.renameTo(imageFile);
// Previous uri of imageFile is unaltered after delete & rename.
final Uri scannedUri = MediaStore.scanFile(cr, imageFile);
assertThat(scannedUri.getLastPathSegment()).isEqualTo(uri.getLastPathSegment());
final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
assertFileContent(imageFile, expected);
} finally {
imageFile.delete();
temporaryImageFile.delete();
}
}
/**
* b/150498564,b/150274099: Test that apps can rename files that are not in database.
*/
@Test
public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
final File directoryNoMedia = new File(TestUtils.getDcimDir(), ".directoryNoMedia");
final File imageInNoMediaDir = new File(directoryNoMedia, IMAGE_FILE_NAME);
final File renamedImageInDCIM = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
final File noMediaFile = new File(directoryNoMedia, ".nomedia");
final ContentResolver cr = getContentResolver();
try {
if (!directoryNoMedia.exists()) {
assertThat(directoryNoMedia.mkdirs()).isTrue();
}
assertThat(noMediaFile.createNewFile()).isTrue();
assertThat(imageInNoMediaDir.createNewFile()).isTrue();
// Remove imageInNoMediaDir from database.
MediaStore.scanFile(cr, directoryNoMedia);
// Query for all images/videos in the device. This shouldn't list imageInNoMediaDir
assertThat(getImageAndVideoFilesFromDatabase())
.doesNotContain(imageInNoMediaDir.getName());
// Rename shouldn't throw error even if imageInNoMediaDir is not in database.
assertThat(imageInNoMediaDir.renameTo(renamedImageInDCIM)).isTrue();
// We can insert renamedImageInDCIM to database
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, renamedImageInDCIM.getAbsolutePath());
final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
assertNotNull(uri);
} finally {
imageInNoMediaDir.delete();
renamedImageInDCIM.delete();
noMediaFile.delete();
directoryNoMedia.delete();
}
}
private static void assertCanCreateFile(File file) throws IOException {
if (file.exists()) {
file.delete();
}
try {
if (!file.createNewFile()) {
fail("Could not create file: " + file);
}
} finally {
file.delete();
}
}
private static void assertCanCreateDir(File dir) throws IOException {
if (dir.exists()) {
if (!dir.delete()) {
Log.w(TAG,
"Can't create dir " + dir + " because it already exists and we can't "
+ "delete it!");
return;
}
}
try {
if (!dir.mkdir()) {
fail("Could not mkdir: " + dir);
}
} finally {
dir.delete();
}
}
/**
* Queries {@link ContentResolver} for all image and video files, returns display name of
* corresponding files.
*/
private static ArrayList<String> getImageAndVideoFilesFromDatabase() {
ArrayList<String> mediaFiles = new ArrayList<>();
final String selection = "is_pending = 0 AND is_trashed = 0 AND "
+ "(media_type = ? OR media_type = ?)";
final String[] selectionArgs =
new String[] {String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
try (Cursor c = getContentResolver().query(
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
/* projection */ new String[] {MediaStore.MediaColumns.DISPLAY_NAME},
selection, selectionArgs, null)) {
while (c.moveToNext()) {
mediaFiles.add(c.getString(0));
}
}
return mediaFiles;
}
}