1 /* 2 * Copyright (C) 2023 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 android.scopedstorage.cts.device; 18 19 import static android.scopedstorage.cts.device.OtherAppFilesRule.GrantModifications.GRANT; 20 import static android.scopedstorage.cts.device.OwnedAndOtherFilesRule.getResultForFilesQuery; 21 import static android.scopedstorage.cts.device.OwnedFilesRule.RESOURCE_ID_WITH_METADATA; 22 import static android.scopedstorage.cts.lib.FilePathAccessTestUtils.assertFileAccess_listFiles; 23 import static android.scopedstorage.cts.lib.FilePathAccessTestUtils.assertFileAccess_readWrite; 24 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch; 25 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile; 26 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource; 27 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_canReadThumbnail; 28 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_listFiles; 29 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_readWrite; 30 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_uriDoesNotExist; 31 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_uriIsFavorite; 32 import static android.scopedstorage.cts.lib.ResolverAccessTestUtils.assertResolver_uriIsNotFavorite; 33 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; 34 import static android.scopedstorage.cts.lib.TestUtils.doEscalation; 35 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; 36 import static android.scopedstorage.cts.lib.TestUtils.getDcimDir; 37 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; 38 39 import static com.google.common.truth.Truth.assertThat; 40 import static com.google.common.truth.Truth.assertWithMessage; 41 42 import android.Manifest; 43 import android.content.ContentResolver; 44 import android.content.ContentUris; 45 import android.database.Cursor; 46 import android.net.Uri; 47 import android.os.Build; 48 import android.os.Bundle; 49 import android.os.Environment; 50 import android.os.ParcelFileDescriptor; 51 import android.platform.test.annotations.RequiresFlagsEnabled; 52 import android.platform.test.flag.junit.CheckFlagsRule; 53 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 54 import android.provider.MediaStore; 55 import android.scopedstorage.cts.lib.ResolverAccessTestUtils; 56 import android.system.Os; 57 import android.util.Log; 58 59 import androidx.test.core.app.ApplicationProvider; 60 import androidx.test.filters.SdkSuppress; 61 62 import org.junit.BeforeClass; 63 import org.junit.ClassRule; 64 import org.junit.Rule; 65 import org.junit.Test; 66 67 import java.io.File; 68 import java.io.IOException; 69 import java.nio.ByteBuffer; 70 import java.util.Arrays; 71 import java.util.Collections; 72 import java.util.HashMap; 73 import java.util.HashSet; 74 import java.util.Set; 75 76 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") 77 public class StorageOwnedFilesTest { 78 79 private static final String TAG = "StorageOwnedFilesTest"; 80 private static final ContentResolver sContentResolver = getContentResolver(); 81 @ClassRule 82 public static final OwnedFilesRule sFilesRule = new OwnedFilesRule(sContentResolver); 83 84 @Rule 85 public final CheckFlagsRule mCheckFlagsRule = 86 DeviceFlagsValueProvider.createCheckFlagsRule(); 87 private static final String STR_DATA1 = "Random test data"; 88 private static final byte[] BYTES_DATA1 = STR_DATA1.getBytes(); 89 private static final File IMAGE_FILE_1 = OwnedFilesRule.getImageFile1(); 90 private static final File IMAGE_FILE_2_METADATA = OwnedFilesRule.getImageFile2Metadata(); 91 private static final File VIDEO_FILE_1 = OwnedFilesRule.getVideoFile1(); 92 93 // Cannot be static as the underlying resource isn't 94 private final Uri mImageUri1 = sFilesRule.getImageUri1(); 95 private final Uri mImageUri2 = sFilesRule.getImageUri2_Metadata(); 96 private final Uri mVideoUri1 = sFilesRule.getVideoUri1(); 97 98 @BeforeClass init()99 public static void init() throws Exception { 100 pollForPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, true); 101 } 102 103 @Test owned_listOwnership()104 public void owned_listOwnership() throws Exception { 105 Set<File> expectedValues = Set.of(IMAGE_FILE_1, IMAGE_FILE_2_METADATA, VIDEO_FILE_1); 106 Set<File> notExpected = Collections.emptySet(); 107 // File access 108 assertFileAccess_listFiles(IMAGE_FILE_1.getParentFile(), expectedValues, notExpected); 109 // Query DCIM 110 assertResolver_listFiles(Environment.DIRECTORY_DCIM, expectedValues, notExpected, 111 sContentResolver); 112 } 113 114 @Test owned_canRead()115 public void owned_canRead() throws Exception { 116 assertResolver_readWrite(mImageUri1, sContentResolver); 117 assertResolver_readWrite(mImageUri2, sContentResolver); 118 assertResolver_readWrite(mVideoUri1, sContentResolver); 119 assertFileAccess_readWrite(IMAGE_FILE_1); 120 assertFileAccess_readWrite(IMAGE_FILE_2_METADATA); 121 assertFileAccess_readWrite(VIDEO_FILE_1); 122 } 123 124 @Test owned_canReadThumbnail()125 public void owned_canReadThumbnail() throws Exception { 126 assertResolver_canReadThumbnail(mImageUri1, sContentResolver); 127 assertResolver_canReadThumbnail(mImageUri2, sContentResolver); 128 // TODO Need a valid video to create a thumbnail 129 // assertResolver_canReadThumbnail(mVideoUri1, sContentResolver); 130 } 131 132 @Test owned_createFavoriteRequest()133 public void owned_createFavoriteRequest() throws Exception { 134 doEscalation(MediaStore.createFavoriteRequest(sContentResolver, Arrays.asList(mImageUri1, 135 mImageUri2, mVideoUri1), 136 true)); 137 assertResolver_uriIsFavorite(mImageUri1, sContentResolver); 138 assertResolver_uriIsFavorite(mImageUri2, sContentResolver); 139 assertResolver_uriIsFavorite(mVideoUri1, sContentResolver); 140 doEscalation(MediaStore.createFavoriteRequest(sContentResolver, Arrays.asList(mImageUri1, 141 mImageUri2, mVideoUri1), 142 false)); 143 assertResolver_uriIsNotFavorite(mImageUri1, sContentResolver); 144 assertResolver_uriIsNotFavorite(mImageUri2, sContentResolver); 145 assertResolver_uriIsNotFavorite(mVideoUri1, sContentResolver); 146 147 } 148 149 @Test owned_deleteRequest()150 public void owned_deleteRequest() throws Exception { 151 File fileToBeDeleted1 = new File(getDcimDir(), TAG + "_delete_1.jpg"); 152 File fileToBeDeleted2 = new File(getDcimDir(), TAG + "_delete_2.mp4"); 153 Set<File> filesToBeDeleted = new HashSet<>(); 154 filesToBeDeleted.add(fileToBeDeleted1); 155 filesToBeDeleted.add(fileToBeDeleted2); 156 try { 157 Uri uriToBeDeleted1 = sFilesRule.createFile(fileToBeDeleted1); 158 Uri uriToBeDeleted2 = sFilesRule.createFile(fileToBeDeleted2); 159 doEscalation( 160 MediaStore.createDeleteRequest(sContentResolver, 161 Arrays.asList(uriToBeDeleted1, uriToBeDeleted2))); 162 assertResolver_uriDoesNotExist(uriToBeDeleted1, sContentResolver); 163 filesToBeDeleted.remove(fileToBeDeleted1); 164 assertResolver_uriDoesNotExist(uriToBeDeleted2, sContentResolver); 165 filesToBeDeleted.remove(fileToBeDeleted2); 166 } finally { 167 for(File f: filesToBeDeleted) { 168 f.delete(); 169 } 170 } 171 } 172 173 @Test owned_insertRequest()174 public void owned_insertRequest() throws Exception { 175 assertInsertFile(TAG + "_insert_1.jpg", 176 ResolverAccessTestUtils::assertResolver_insertImage); 177 assertInsertFile(TAG + "_insert_2.mp4", 178 ResolverAccessTestUtils::assertResolver_insertVideo); 179 } 180 181 @RequiresFlagsEnabled("com.android.providers.media.flags.picker_recent_selection") 182 @Test owned_grantOwnedItemBySelection()183 public void owned_grantOwnedItemBySelection() throws IOException { 184 // A granted item should be returned when recent_selection is requested even though the item 185 // is an owned item. 186 OtherAppFilesRule.modifyReadAccess(IMAGE_FILE_1, ApplicationProvider.getApplicationContext() 187 .getPackageName(), GRANT); 188 Bundle queryArgs = new Bundle(); 189 queryArgs.putBoolean(MediaStore.QUERY_ARG_LATEST_SELECTION_ONLY, true); 190 try (Cursor c = getResultForFilesQuery(sContentResolver, queryArgs)) { 191 assertThat(c).isNotNull(); 192 // Now only recently selected item should be returned. 193 assertWithMessage("Expected number of items(only recently selected) is 1.") 194 .that(c.getCount()).isEqualTo(1); 195 final Uri expectedMediaUri = MediaStore.scanFile(sContentResolver, 196 IMAGE_FILE_1); 197 c.moveToFirst(); 198 assertWithMessage("Expected item Uri was: " + expectedMediaUri).that( 199 c.getInt(c.getColumnIndex( 200 MediaStore.Files.FileColumns._ID))).isEqualTo( 201 ContentUris.parseId(expectedMediaUri)); 202 } 203 } 204 assertInsertFile(String filename, TriFunction<String, String, ContentResolver, Uri> inserter)205 private void assertInsertFile(String filename, 206 TriFunction<String, String, ContentResolver, Uri> inserter) throws Exception { 207 File fileToBeInserted = new File(getDcimDir(), filename); 208 final Uri targetUri = inserter.apply(fileToBeInserted.getName(), 209 Environment.DIRECTORY_DCIM, sContentResolver); 210 try (ParcelFileDescriptor parcelFileDescriptor = sContentResolver.openFileDescriptor( 211 targetUri, "rw")) { 212 assertThat(parcelFileDescriptor).isNotNull(); 213 Os.write(parcelFileDescriptor.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1)); 214 assertFileContent(parcelFileDescriptor.getFileDescriptor(), BYTES_DATA1); 215 } finally { 216 fileToBeInserted.delete(); 217 } 218 } 219 220 @FunctionalInterface 221 interface TriFunction<A, B, C, R> { apply(A a, B b, C c)222 R apply(A a, B b, C c); 223 } 224 225 @Test owned_accessLocationMetadata()226 public void owned_accessLocationMetadata() throws Exception { 227 HashMap<String, String> originalExif = getExifMetadataFromRawResource( 228 RESOURCE_ID_WITH_METADATA); 229 230 HashMap<String, String> exif = getExifMetadataFromFile(IMAGE_FILE_2_METADATA); 231 assertExifMetadataMatch(exif, originalExif); 232 //TODO do we have videos with metadata? 233 } 234 } 235