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