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