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; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.app.usage.StorageStatsManager; 26 import android.content.ContentResolver; 27 import android.content.ContentUris; 28 import android.content.Context; 29 import android.content.res.AssetFileDescriptor; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.SystemClock; 33 import android.os.storage.StorageManager; 34 import android.os.storage.StorageVolume; 35 import android.platform.test.annotations.Presubmit; 36 import android.provider.MediaStore; 37 import android.provider.MediaStore.MediaColumns; 38 import android.util.Log; 39 40 import androidx.test.InstrumentationRegistry; 41 42 import org.junit.After; 43 import org.junit.Assume; 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.junit.runners.Parameterized; 48 import org.junit.runners.Parameterized.Parameter; 49 import org.junit.runners.Parameterized.Parameters; 50 51 import java.io.File; 52 import java.util.HashSet; 53 import java.util.Set; 54 import java.util.UUID; 55 56 @Presubmit 57 @RunWith(Parameterized.class) 58 public class MediaStoreTest { 59 static final String TAG = "MediaStoreTest"; 60 61 private static final long SIZE_DELTA = 32_000; 62 63 private Context mContext; 64 private ContentResolver mContentResolver; 65 66 private Uri mExternalImages; 67 68 @Parameter(0) 69 public String mVolumeName; 70 71 @Parameters data()72 public static Iterable<? extends Object> data() { 73 return ProviderTestUtils.getSharedVolumeNames(); 74 } 75 getContext()76 private Context getContext() { 77 return InstrumentationRegistry.getTargetContext(); 78 } 79 80 @Before setUp()81 public void setUp() throws Exception { 82 mContext = InstrumentationRegistry.getTargetContext(); 83 mContentResolver = mContext.getContentResolver(); 84 85 Log.d(TAG, "Using volume " + mVolumeName); 86 mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName); 87 } 88 89 @After tearDown()90 public void tearDown() throws Exception { 91 InstrumentationRegistry.getInstrumentation().getUiAutomation() 92 .dropShellPermissionIdentity(); 93 } 94 95 @Test testGetMediaScannerUri()96 public void testGetMediaScannerUri() { 97 // query 98 Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null, 99 null, null, null); 100 assertEquals(1, c.getCount()); 101 c.close(); 102 } 103 104 @Test testGetVersion()105 public void testGetVersion() { 106 // We should have valid versions to help detect data wipes 107 assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL)); 108 assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL)); 109 assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY)); 110 } 111 112 @Test testGetExternalVolumeNames()113 public void testGetExternalVolumeNames() { 114 Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext()); 115 116 assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL)); 117 assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL)); 118 assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY)); 119 } 120 121 @Test testGetStorageVolume()122 public void testGetStorageVolume() throws Exception { 123 Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)); 124 125 final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages); 126 127 final StorageManager sm = mContext.getSystemService(StorageManager.class); 128 final StorageVolume sv = sm.getStorageVolume(uri); 129 130 // We should always have a volume for media we just created 131 assertNotNull(sv); 132 133 if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) { 134 assertEquals(sm.getPrimaryStorageVolume(), sv); 135 } 136 } 137 138 @Test testGetStorageVolume_Unrelated()139 public void testGetStorageVolume_Unrelated() throws Exception { 140 final StorageManager sm = mContext.getSystemService(StorageManager.class); 141 try { 142 sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/")); 143 fail("getStorageVolume unrelated should throw exception"); 144 } catch (IllegalArgumentException expected) { 145 } 146 } 147 148 @Test testContributedMedia()149 public void testContributedMedia() throws Exception { 150 // STOPSHIP: remove this once isolated storage is always enabled 151 Assume.assumeTrue(StorageManager.hasIsolatedStorage()); 152 Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)); 153 154 InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 155 android.Manifest.permission.CLEAR_APP_USER_DATA, 156 android.Manifest.permission.PACKAGE_USAGE_STATS); 157 158 // Start by cleaning up contributed items 159 MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(), 160 android.os.Process.myUserHandle()); 161 162 // Force sync to try updating other views 163 ProviderTestUtils.executeShellCommand("sync"); 164 SystemClock.sleep(500); 165 166 // Measure usage before 167 final long beforePackage = getExternalPackageSize(); 168 final long beforeTotal = getExternalTotalSize(); 169 final long beforeContributed = MediaStore.getContributedMediaSize(getContext(), 170 getContext().getPackageName(), android.os.Process.myUserHandle()); 171 172 final long stageSize; 173 try (AssetFileDescriptor fd = getContext().getResources() 174 .openRawResourceFd(R.raw.volantis)) { 175 stageSize = fd.getLength(); 176 } 177 178 // Create media both inside and outside sandbox 179 final Uri inside; 180 final Uri outside; 181 final File file = new File(ProviderTestUtils.stageDir(mVolumeName), 182 "cts" + System.nanoTime() + ".jpg"); 183 ProviderTestUtils.stageFile(R.raw.volantis, file); 184 inside = ProviderTestUtils.scanFileFromShell(file); 185 outside = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages); 186 187 { 188 final HashSet<Long> visible = getVisibleIds(mExternalImages); 189 assertTrue(visible.contains(ContentUris.parseId(inside))); 190 assertTrue(visible.contains(ContentUris.parseId(outside))); 191 192 // Force sync to try updating other views 193 ProviderTestUtils.executeShellCommand("sync"); 194 SystemClock.sleep(500); 195 196 final long afterPackage = getExternalPackageSize(); 197 final long afterTotal = getExternalTotalSize(); 198 final long afterContributed = MediaStore.getContributedMediaSize(getContext(), 199 getContext().getPackageName(), android.os.Process.myUserHandle()); 200 201 assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA); 202 assertMostlyEquals(beforeTotal + stageSize + stageSize, afterTotal, SIZE_DELTA); 203 assertMostlyEquals(beforeContributed + stageSize, afterContributed, SIZE_DELTA); 204 } 205 206 // Delete only contributed items 207 MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(), 208 android.os.Process.myUserHandle()); 209 { 210 final HashSet<Long> visible = getVisibleIds(mExternalImages); 211 assertTrue(visible.contains(ContentUris.parseId(inside))); 212 assertFalse(visible.contains(ContentUris.parseId(outside))); 213 214 // Force sync to try updating other views 215 ProviderTestUtils.executeShellCommand("sync"); 216 SystemClock.sleep(500); 217 218 final long afterPackage = getExternalPackageSize(); 219 final long afterTotal = getExternalTotalSize(); 220 final long afterContributed = MediaStore.getContributedMediaSize(getContext(), 221 getContext().getPackageName(), android.os.Process.myUserHandle()); 222 223 assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA); 224 assertMostlyEquals(beforeTotal + stageSize, afterTotal, SIZE_DELTA); 225 assertMostlyEquals(0, afterContributed, SIZE_DELTA); 226 } 227 } 228 getExternalPackageSize()229 private long getExternalPackageSize() throws Exception { 230 final StorageManager storage = getContext().getSystemService(StorageManager.class); 231 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 232 233 final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName)); 234 return stats.queryStatsForPackage(externalUuid, getContext().getPackageName(), 235 android.os.Process.myUserHandle()).getDataBytes(); 236 } 237 getExternalTotalSize()238 private long getExternalTotalSize() throws Exception { 239 final StorageManager storage = getContext().getSystemService(StorageManager.class); 240 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 241 242 final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName)); 243 return stats.queryExternalStatsForUser(externalUuid, android.os.Process.myUserHandle()) 244 .getTotalBytes(); 245 } 246 getVisibleIds(Uri collectionUri)247 private HashSet<Long> getVisibleIds(Uri collectionUri) { 248 final HashSet<Long> res = new HashSet<>(); 249 try (Cursor c = mContentResolver.query(collectionUri, 250 new String[] { MediaColumns._ID }, null, null)) { 251 while (c.moveToNext()) { 252 res.add(c.getLong(0)); 253 } 254 } 255 return res; 256 } 257 assertMostlyEquals(long expected, long actual, long delta)258 private static void assertMostlyEquals(long expected, long actual, long delta) { 259 if (Math.abs(expected - actual) > delta) { 260 fail("Expected roughly " + expected + " but was " + actual); 261 } 262 } 263 } 264