1 /*
2  * Copyright (C) 2009 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.provider.cts.media;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.Manifest;
30 import android.app.AppOpsManager;
31 import android.content.ContentResolver;
32 import android.content.Context;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.ProviderInfo;
36 import android.database.Cursor;
37 import android.net.Uri;
38 import android.os.Build;
39 import android.os.Process;
40 import android.os.storage.StorageManager;
41 import android.os.storage.StorageVolume;
42 import android.provider.BaseColumns;
43 import android.provider.MediaStore;
44 import android.provider.MediaStore.MediaColumns;
45 import android.provider.cts.ProviderTestUtils;
46 import android.provider.cts.R;
47 import android.util.Log;
48 
49 import androidx.annotation.NonNull;
50 import androidx.test.InstrumentationRegistry;
51 import androidx.test.filters.SdkSuppress;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.junit.runners.Parameterized;
58 import org.junit.runners.Parameterized.Parameter;
59 import org.junit.runners.Parameterized.Parameters;
60 
61 import java.util.Set;
62 
63 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
64 @RunWith(Parameterized.class)
65 public class MediaStoreTest {
66     static final String TAG = "MediaStoreTest";
67 
68     private static final long SIZE_DELTA = 32_000;
69     private static final String[] SYSTEM_GALERY_APPOPS = {
70             AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
71 
72     private Context mContext;
73     private ContentResolver mContentResolver;
74 
75     private Uri mExternalImages;
76 
77     @Parameter(0)
78     public String mVolumeName;
79 
80     @Parameters
data()81     public static Iterable<? extends Object> data() {
82         return ProviderTestUtils.getSharedVolumeNames();
83     }
84 
getContext()85     private Context getContext() {
86         return InstrumentationRegistry.getTargetContext();
87     }
88 
89     @Before
setUp()90     public void setUp() throws Exception {
91         mContext = InstrumentationRegistry.getTargetContext();
92         mContentResolver = mContext.getContentResolver();
93 
94         Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId());
95         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
96     }
97 
98     @After
tearDown()99     public void tearDown() throws Exception {
100         InstrumentationRegistry.getInstrumentation().getUiAutomation()
101                 .dropShellPermissionIdentity();
102     }
103 
104     /**
105      * Sure this is pointless, but czars demand test coverage.
106      */
107     @Test
testConstructors()108     public void testConstructors() {
109         new MediaStore();
110         new MediaStore.Audio();
111         new MediaStore.Audio.Albums();
112         new MediaStore.Audio.Artists();
113         new MediaStore.Audio.Artists.Albums();
114         new MediaStore.Audio.Genres();
115         new MediaStore.Audio.Genres.Members();
116         new MediaStore.Audio.Media();
117         new MediaStore.Audio.Playlists();
118         new MediaStore.Audio.Playlists.Members();
119         new MediaStore.Files();
120         new MediaStore.Images();
121         new MediaStore.Images.Media();
122         new MediaStore.Images.Thumbnails();
123         new MediaStore.Video();
124         new MediaStore.Video.Media();
125         new MediaStore.Video.Thumbnails();
126     }
127 
128     @Test
testRequireOriginal()129     public void testRequireOriginal() {
130         assertFalse(MediaStore.getRequireOriginal(mExternalImages));
131         assertTrue(MediaStore.getRequireOriginal(MediaStore.setRequireOriginal(mExternalImages)));
132     }
133 
134     @Test
testGetMediaScannerUri()135     public void testGetMediaScannerUri() {
136         // query
137         Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
138                 null, null, null);
139         assertEquals(1, c.getCount());
140         c.close();
141     }
142 
143     @Test
testGetVersion()144     public void testGetVersion() {
145         // We should have valid versions to help detect data wipes
146         assertNotNull(MediaStore.getVersion(getContext()));
147         assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL));
148         assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL));
149         assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY));
150     }
151 
152     @Test
testGetExternalVolumeNames()153     public void testGetExternalVolumeNames() {
154         Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext());
155 
156         assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
157         assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
158         assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
159     }
160 
161     @Test
testGetRecentExternalVolumeNames()162     public void testGetRecentExternalVolumeNames() {
163         Set<String> volumeNames = MediaStore.getRecentExternalVolumeNames(getContext());
164 
165         assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
166         assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
167         assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
168     }
169 
170     @Test
testGetStorageVolume()171     public void testGetStorageVolume() throws Exception {
172         final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
173 
174         final StorageManager sm = mContext.getSystemService(StorageManager.class);
175         final StorageVolume sv = sm.getStorageVolume(uri);
176 
177         // We should always have a volume for media we just created
178         assertNotNull(sv);
179 
180         if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) {
181             assertEquals(sm.getPrimaryStorageVolume(), sv);
182         }
183     }
184 
185     @Test
testGetStorageVolume_Unrelated()186     public void testGetStorageVolume_Unrelated() throws Exception {
187         final StorageManager sm = mContext.getSystemService(StorageManager.class);
188         try {
189             sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/"));
190             fail("getStorageVolume unrelated should throw exception");
191         } catch (IllegalArgumentException expected) {
192         }
193     }
194 
195     @Test
testRewriteToLegacy()196     public void testRewriteToLegacy() throws Exception {
197         final Uri before = MediaStore.Images.Media
198                 .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
199         final Uri after = MediaStore.rewriteToLegacy(before);
200 
201         assertEquals(MediaStore.AUTHORITY, before.getAuthority());
202         assertEquals(MediaStore.AUTHORITY_LEGACY, after.getAuthority());
203     }
204 
205     /**
206      * When upgrading from an older device, we really need our legacy provider
207      * to be present to ensure that we don't lose user data like
208      * {@link BaseColumns#_ID} and {@link MediaColumns#IS_FAVORITE}.
209      */
210     @Test
testLegacy()211     public void testLegacy() throws Exception {
212         final ProviderInfo legacy = getContext().getPackageManager()
213                 .resolveContentProvider(MediaStore.AUTHORITY_LEGACY, 0);
214         if (legacy == null) {
215             if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R) {
216                 // If we're a brand new device, we don't require a legacy
217                 // provider, since there's nothing to upgrade
218                 return;
219             } else {
220                 fail("Upgrading devices must have a legacy MediaProvider at "
221                         + "MediaStore.AUTHORITY_LEGACY to upgrade user data from");
222             }
223         }
224 
225         // Verify that legacy provider is protected
226         assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data",
227                 android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.readPermission);
228         assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data",
229                 android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.writePermission);
230 
231         // And finally verify that legacy provider is headless
232         final PackageInfo legacyPackage = getContext().getPackageManager().getPackageInfo(
233                 legacy.packageName, PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS
234                         | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES);
235         assertEmpty("Headless legacy MediaProvider must have no activities",
236                 legacyPackage.activities);
237         assertEquals("Headless legacy MediaProvider must have exactly one provider",
238                 1, legacyPackage.providers.length);
239         assertEmpty("Headless legacy MediaProvider must have no receivers",
240                 legacyPackage.receivers);
241         assertEmpty("Headless legacy MediaProvider must have no services",
242                 legacyPackage.services);
243     }
244 
245     @Test
testIsCurrentSystemGallery()246     public void testIsCurrentSystemGallery() throws Exception {
247         assertThat(
248                 MediaStore.isCurrentSystemGallery(
249                         mContentResolver, Process.myUid(), getContext().getPackageName()))
250                 .isFalse();
251 
252         try {
253             setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
254             assertThat(
255                     MediaStore.isCurrentSystemGallery(
256                             mContentResolver, Process.myUid(), getContext().getPackageName()))
257                     .isTrue();
258         } finally {
259             setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
260         }
261 
262         assertThat(
263                 MediaStore.isCurrentSystemGallery(
264                         mContentResolver, Process.myUid(), getContext().getPackageName()))
265                 .isFalse();
266     }
267 
268     @Test
269     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testCanManageMedia()270     public void testCanManageMedia() throws Exception {
271         final String opString = AppOpsManager.permissionToOp(Manifest.permission.MANAGE_MEDIA);
272 
273         // no access
274         assertThat(MediaStore.canManageMedia(getContext())).isFalse();
275         try {
276             // grant access
277             setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, opString);
278 
279             assertThat(MediaStore.canManageMedia(getContext())).isTrue();
280         } finally {
281             setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, opString);
282         }
283         // no access
284         assertThat(MediaStore.canManageMedia(getContext())).isFalse();
285     }
286 
setAppOpsModeForUid(int uid, int mode, @NonNull String... ops)287     private void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
288         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(null);
289         try {
290             for (String op : ops) {
291                 getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
292             }
293         } finally {
294             getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
295         }
296     }
297 
assertEmpty(String message, T[] array)298     private static <T> void assertEmpty(String message, T[] array) {
299         if (array != null && array.length > 0) {
300             fail(message);
301         }
302     }
303 }
304