• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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