blob: baeca9ceea61b87cbb8c92538ea0eedd2ea324bb [file] [log] [blame]
/*
* 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.provider.cts.ProviderTestUtils;
import android.provider.cts.R;
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<? extends Object> data() {
return ProviderTestUtils.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<String> 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<String> 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 = ProviderTestUtils.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 <T> void assertEmpty(String message, T[] array) {
if (array != null && array.length > 0) {
fail(message);
}
}
}