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