/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.provider.cts.media; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.Manifest; import android.app.AppOpsManager; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Process; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.MediaColumns; import android.util.Log; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SdkSuppress; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.util.Set; @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) @RunWith(Parameterized.class) public class MediaStoreTest { static final String TAG = "MediaStoreTest"; private static final long SIZE_DELTA = 32_000; private static final String[] SYSTEM_GALERY_APPOPS = { AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO}; private Context mContext; private ContentResolver mContentResolver; private Uri mExternalImages; @Parameter(0) public String mVolumeName; @Parameters public static Iterable data() { return MediaProviderTestUtils.getSharedVolumeNames(); } private Context getContext() { return InstrumentationRegistry.getTargetContext(); } @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); mContentResolver = mContext.getContentResolver(); Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId()); mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName); } @After public void tearDown() throws Exception { InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } /** * Sure this is pointless, but czars demand test coverage. */ @Test public void testConstructors() { new MediaStore(); new MediaStore.Audio(); new MediaStore.Audio.Albums(); new MediaStore.Audio.Artists(); new MediaStore.Audio.Artists.Albums(); new MediaStore.Audio.Genres(); new MediaStore.Audio.Genres.Members(); new MediaStore.Audio.Media(); new MediaStore.Audio.Playlists(); new MediaStore.Audio.Playlists.Members(); new MediaStore.Files(); new MediaStore.Images(); new MediaStore.Images.Media(); new MediaStore.Images.Thumbnails(); new MediaStore.Video(); new MediaStore.Video.Media(); new MediaStore.Video.Thumbnails(); } @Test public void testRequireOriginal() { assertFalse(MediaStore.getRequireOriginal(mExternalImages)); assertTrue(MediaStore.getRequireOriginal(MediaStore.setRequireOriginal(mExternalImages))); } @Test public void testGetMediaScannerUri() { // query Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null, null, null, null); assertEquals(1, c.getCount()); c.close(); } @Test public void testGetVersion() { // We should have valid versions to help detect data wipes assertNotNull(MediaStore.getVersion(getContext())); assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL)); assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL)); assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY)); } @Test public void testGetExternalVolumeNames() { Set volumeNames = MediaStore.getExternalVolumeNames(getContext()); assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL)); assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL)); assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY)); } @Test public void testGetRecentExternalVolumeNames() { Set volumeNames = MediaStore.getRecentExternalVolumeNames(getContext()); assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL)); assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL)); assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY)); } @Test public void testGetStorageVolume() throws Exception { final Uri uri = MediaProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages); final StorageManager sm = mContext.getSystemService(StorageManager.class); final StorageVolume sv = sm.getStorageVolume(uri); // We should always have a volume for media we just created assertNotNull(sv); if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) { assertEquals(sm.getPrimaryStorageVolume(), sv); } } @Test public void testGetStorageVolume_Unrelated() throws Exception { final StorageManager sm = mContext.getSystemService(StorageManager.class); try { sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/")); fail("getStorageVolume unrelated should throw exception"); } catch (IllegalArgumentException expected) { } } @Test public void testRewriteToLegacy() throws Exception { final Uri before = MediaStore.Images.Media .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); final Uri after = MediaStore.rewriteToLegacy(before); assertEquals(MediaStore.AUTHORITY, before.getAuthority()); assertEquals(MediaStore.AUTHORITY_LEGACY, after.getAuthority()); } /** * When upgrading from an older device, we really need our legacy provider * to be present to ensure that we don't lose user data like * {@link BaseColumns#_ID} and {@link MediaColumns#IS_FAVORITE}. */ @Test public void testLegacy() throws Exception { final ProviderInfo legacy = getContext().getPackageManager() .resolveContentProvider(MediaStore.AUTHORITY_LEGACY, 0); if (legacy == null) { if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R) { // If we're a brand new device, we don't require a legacy // provider, since there's nothing to upgrade return; } else { fail("Upgrading devices must have a legacy MediaProvider at " + "MediaStore.AUTHORITY_LEGACY to upgrade user data from"); } } // Verify that legacy provider is protected assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data", android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.readPermission); assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data", android.Manifest.permission.WRITE_MEDIA_STORAGE, legacy.writePermission); // And finally verify that legacy provider is headless final PackageInfo legacyPackage = getContext().getPackageManager().getPackageInfo( legacy.packageName, PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES); assertEmpty("Headless legacy MediaProvider must have no activities", legacyPackage.activities); assertEquals("Headless legacy MediaProvider must have exactly one provider", 1, legacyPackage.providers.length); assertEmpty("Headless legacy MediaProvider must have no receivers", legacyPackage.receivers); assertEmpty("Headless legacy MediaProvider must have no services", legacyPackage.services); } @Test public void testIsCurrentSystemGallery() throws Exception { assertThat( MediaStore.isCurrentSystemGallery( mContentResolver, Process.myUid(), getContext().getPackageName())) .isFalse(); try { setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); assertThat( MediaStore.isCurrentSystemGallery( mContentResolver, Process.myUid(), getContext().getPackageName())) .isTrue(); } finally { setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); } assertThat( MediaStore.isCurrentSystemGallery( mContentResolver, Process.myUid(), getContext().getPackageName())) .isFalse(); } @Test @SdkSuppress(minSdkVersion = 31, codeName = "S") public void testCanManageMedia() throws Exception { final String opString = AppOpsManager.permissionToOp(Manifest.permission.MANAGE_MEDIA); // no access assertThat(MediaStore.canManageMedia(getContext())).isFalse(); try { // grant access setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, opString); assertThat(MediaStore.canManageMedia(getContext())).isTrue(); } finally { setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, opString); } // no access assertThat(MediaStore.canManageMedia(getContext())).isFalse(); } private void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(null); try { for (String op : ops) { getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode); } } finally { getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); } } private static void assertEmpty(String message, T[] array) { if (array != null && array.length > 0) { fail(message); } } }