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