• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.content.cts;
18 
19 import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.workProfile;
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.assertNull;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.testng.Assert.assertThrows;
31 
32 import android.content.ContentProvider;
33 import android.content.ContentProvider.CallingIdentity;
34 import android.content.ContentResolver;
35 import android.content.ContentValues;
36 import android.content.Context;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ProviderInfo;
39 import android.content.res.AssetFileDescriptor;
40 import android.database.Cursor;
41 import android.database.sqlite.SQLiteDatabase;
42 import android.net.Uri;
43 import android.os.Binder;
44 import android.os.Bundle;
45 import android.os.ParcelFileDescriptor;
46 import android.os.UserHandle;
47 import android.platform.test.annotations.AppModeFull;
48 import android.platform.test.annotations.AppModeSdkSandbox;
49 import android.provider.MediaStore;
50 
51 import androidx.annotation.NonNull;
52 import androidx.annotation.Nullable;
53 import androidx.test.core.app.ApplicationProvider;
54 
55 import com.android.bedstead.harrier.BedsteadJUnit4;
56 import com.android.bedstead.harrier.DeviceState;
57 import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
58 import com.android.bedstead.nene.TestApis;
59 
60 import org.junit.After;
61 import org.junit.ClassRule;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 
66 import java.io.DataInputStream;
67 import java.io.DataOutputStream;
68 import java.io.File;
69 import java.io.FileInputStream;
70 import java.io.FileNotFoundException;
71 import java.io.FileOutputStream;
72 import java.io.IOException;
73 import java.io.InputStream;
74 
75 /**
76  * Test {@link ContentProvider}.
77  */
78 @RunWith(BedsteadJUnit4.class)
79 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
80 public class ContentProviderTest {
81     private static final String TEST_PACKAGE_NAME = "android.content.cts";
82     private static final String TEST_FILE_NAME = "testFile.tmp";
83     private static final String TEST_DB_NAME = "test.db";
84 
85     @ClassRule
86     @Rule
87     public static final DeviceState sDeviceState = new DeviceState();
88 
89     private static final Context sContext = ApplicationProvider.getApplicationContext();
90     @After
tearDown()91     public void tearDown() throws Exception {
92         sContext.deleteDatabase(TEST_DB_NAME);
93         sContext.deleteFile(TEST_FILE_NAME);
94     }
95 
96     @Test
testOpenAssetFile()97     public void testOpenAssetFile() throws IOException {
98         MockContentProvider mockContentProvider = new MockContentProvider();
99         Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
100                 "://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
101 
102         try {
103             mockContentProvider.openAssetFile(uri, "r");
104             fail("Should always throw out FileNotFoundException!");
105         } catch (FileNotFoundException e) {
106         }
107 
108         try {
109             mockContentProvider.openFile(null, null);
110             fail("Should always throw out FileNotFoundException!");
111         } catch (FileNotFoundException e) {
112         }
113     }
114 
115     @Test
testAttachInfo()116     public void testAttachInfo() {
117         MockContentProvider mockContentProvider = new MockContentProvider();
118 
119         ProviderInfo info1 = new ProviderInfo();
120         info1.readPermission = "android.permission.READ_SMS";
121         info1.writePermission = null; // Guarded by an app op not a permission.
122         mockContentProvider.attachInfo(sContext, info1);
123         assertSame(sContext, mockContentProvider.getContext());
124         assertEquals(info1.readPermission, mockContentProvider.getReadPermission());
125         assertEquals(info1.writePermission, mockContentProvider.getWritePermission());
126 
127         ProviderInfo info2 = new ProviderInfo();
128         info2.readPermission = "android.permission.READ_CONTACTS";
129         info2.writePermission = "android.permission.WRITE_CONTACTS";
130         mockContentProvider.attachInfo(null, info2);
131         assertSame(sContext, mockContentProvider.getContext());
132         assertEquals(info1.readPermission, mockContentProvider.getReadPermission());
133         assertEquals(info1.writePermission, mockContentProvider.getWritePermission());
134 
135         mockContentProvider = new MockContentProvider();
136         mockContentProvider.attachInfo(null, null);
137         assertNull(mockContentProvider.getContext());
138         assertNull(mockContentProvider.getReadPermission());
139         assertNull(mockContentProvider.getWritePermission());
140 
141         mockContentProvider.attachInfo(null, info2);
142         assertNull(mockContentProvider.getContext());
143         assertEquals(info2.readPermission, mockContentProvider.getReadPermission());
144         assertEquals(info2.writePermission, mockContentProvider.getWritePermission());
145 
146         mockContentProvider.attachInfo(sContext, info1);
147         assertSame(sContext, mockContentProvider.getContext());
148         assertEquals(info1.readPermission, mockContentProvider.getReadPermission());
149         assertEquals(info1.writePermission, mockContentProvider.getWritePermission());
150     }
151 
152     @Test
testBulkInsert()153     public void testBulkInsert() {
154         MockContentProvider mockContentProvider = new MockContentProvider();
155 
156         int count = 2;
157         ContentValues[] values = new ContentValues[count];
158         for (int i = 0; i < count; i++) {
159             values[i] = new ContentValues();
160         }
161         Uri uri = Uri.parse("content://browser/bookmarks");
162         assertEquals(count, mockContentProvider.bulkInsert(uri, values));
163         assertEquals(count, mockContentProvider.getInsertCount());
164 
165         mockContentProvider = new MockContentProvider();
166         try {
167             assertEquals(count, mockContentProvider.bulkInsert(null, values));
168         } finally {
169             assertEquals(count, mockContentProvider.getInsertCount());
170         }
171     }
172 
173     @Test
testGetContext()174     public void testGetContext() {
175         MockContentProvider mockContentProvider = new MockContentProvider();
176         assertNull(mockContentProvider.getContext());
177 
178         mockContentProvider.attachInfo(sContext, null);
179         assertSame(sContext, mockContentProvider.getContext());
180         mockContentProvider.attachInfo(null, null);
181         assertSame(sContext, mockContentProvider.getContext());
182     }
183 
184     @Test
testAccessReadPermission()185     public void testAccessReadPermission() {
186         MockContentProvider mockContentProvider = new MockContentProvider();
187         assertNull(mockContentProvider.getReadPermission());
188 
189         String expected = "android.permission.READ_CONTACTS";
190         mockContentProvider.setReadPermissionWrapper(expected);
191         assertEquals(expected, mockContentProvider.getReadPermission());
192 
193         expected = "android.permission.READ_SMS";
194         mockContentProvider.setReadPermissionWrapper(expected);
195         assertEquals(expected, mockContentProvider.getReadPermission());
196 
197         mockContentProvider.setReadPermissionWrapper(null);
198         assertNull(mockContentProvider.getReadPermission());
199     }
200 
201     @Test
testAccessWritePermission()202     public void testAccessWritePermission() {
203         MockContentProvider mockContentProvider = new MockContentProvider();
204         assertNull(mockContentProvider.getWritePermission());
205 
206         String expected = "android.permission.WRITE_CONTACTS";
207         mockContentProvider.setWritePermissionWrapper(expected);
208         assertEquals(expected, mockContentProvider.getWritePermission());
209 
210         mockContentProvider.setWritePermissionWrapper(null);
211         assertNull(mockContentProvider.getWritePermission());
212     }
213 
214     @Test
testIsTemporary()215     public void testIsTemporary() {
216         MockContentProvider mockContentProvider = new MockContentProvider();
217         assertFalse(mockContentProvider.isTemporary());
218     }
219 
220     @Test
testOpenFile()221     public void testOpenFile() {
222         MockContentProvider mockContentProvider = new MockContentProvider();
223 
224         try {
225             Uri uri = Uri.parse("content://test");
226             mockContentProvider.openFile(uri, "r");
227             fail("Should always throw out FileNotFoundException!");
228         } catch (FileNotFoundException e) {
229         }
230 
231         try {
232             mockContentProvider.openFile(null, null);
233             fail("Should always throw out FileNotFoundException!");
234         } catch (FileNotFoundException e) {
235         }
236     }
237 
238     @Test
testOpenFileHelper()239     public void testOpenFileHelper() throws IOException {
240         // create a temporary File
241         sContext.openFileOutput(TEST_FILE_NAME, Context.MODE_PRIVATE).close();
242         File file = sContext.getFileStreamPath(TEST_FILE_NAME);
243         assertTrue(file.exists());
244 
245         ContentProvider cp = new OpenFilePipeContentProvider(file.getAbsolutePath(), TEST_DB_NAME);
246 
247         Uri uri = Uri.parse("content://test");
248         assertNotNull(cp.openFile(uri, "r"));
249 
250         try {
251             uri = Uri.parse("content://test");
252             cp.openFile(uri, "wrong");
253             fail("Should throw IllegalArgumentException for bad mode!");
254         } catch (IllegalArgumentException e) {
255         }
256 
257         // delete the temporary file
258         file.delete();
259 
260         try {
261             uri = Uri.parse("content://test");
262             cp.openFile(uri, "r");
263             fail("Should throw FileNotFoundException!");
264         } catch (FileNotFoundException e) {
265         }
266 
267         try {
268             cp.openFile((Uri) null, "r");
269             fail("Should always throw FileNotFoundException!");
270         } catch (FileNotFoundException e) {
271         }
272     }
273 
assertAssetFileContents(AssetFileDescriptor assetFileDescriptor, String message)274     private static void assertAssetFileContents(AssetFileDescriptor assetFileDescriptor,
275             String message) throws IOException {
276         assertNotNull(assetFileDescriptor);
277         try (DataInputStream dis = new DataInputStream(
278                 new FileInputStream(assetFileDescriptor.getFileDescriptor()))) {
279             assertEquals(message, dis.readUTF());
280         }
281     }
282 
283     @Test
testOpenPipeHelper()284     public void testOpenPipeHelper() throws IOException {
285         // create a temporary File
286         sContext.openFileOutput(TEST_FILE_NAME, Context.MODE_PRIVATE).close();
287         File file = sContext.getFileStreamPath(TEST_FILE_NAME);
288         assertTrue(file.exists());
289 
290         ContentProvider cp = new OpenFilePipeContentProvider(file.getAbsolutePath(), TEST_DB_NAME);
291 
292         Uri uri = Uri.parse("content://test");
293         assertAssetFileContents(cp.openAssetFile(uri, "r"), "OK");
294 
295         uri = Uri.parse("content://test");
296         assertAssetFileContents(cp.openAssetFile(uri, "wrong"),
297                 "java.lang.IllegalArgumentException: Bad mode: wrong");
298 
299         // delete the temporary file
300         file.delete();
301 
302         uri = Uri.parse("content://test");
303         assertAssetFileContents(cp.openAssetFile(uri, "r"),
304                 "java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)");
305 
306         assertAssetFileContents(cp.openAssetFile((Uri) null, "r"),
307                 "java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)");
308     }
309 
310     @Test
testOnConfigurationChanged()311     public void testOnConfigurationChanged() {
312         // cannot trigger this callback reliably
313     }
314 
315     @Test
testOnLowMemory()316     public void testOnLowMemory() {
317         // cannot trigger this callback reliably
318     }
319 
320     @Test
testRefresh_DefaultImplReturnsFalse()321     public void testRefresh_DefaultImplReturnsFalse() {
322         MockContentProvider provider = new MockContentProvider();
323         assertFalse(provider.refresh(null, null, null));
324     }
325 
326     @Test
testGetIContentProvider()327     public void testGetIContentProvider() {
328         MockContentProvider mockContentProvider = new MockContentProvider();
329 
330         assertNotNull(mockContentProvider.getIContentProvider());
331     }
332 
333     @Test
testClearCallingIdentity()334     public void testClearCallingIdentity() {
335         final MockContentProvider provider = new MockContentProvider();
336         provider.attachInfo(sContext, new ProviderInfo());
337 
338         final CallingIdentity ident = provider.clearCallingIdentity();
339         try {
340             assertEquals(android.os.Process.myUid(), Binder.getCallingUid());
341             assertEquals(null, provider.getCallingPackage());
342         } finally {
343             provider.restoreCallingIdentity(ident);
344         }
345     }
346 
347     @Test
testCheckUriPermission()348     public void testCheckUriPermission() {
349         MockContentProvider provider = new MockContentProvider();
350         final Uri uri = Uri.parse("content://test");
351         assertEquals(PackageManager.PERMISSION_DENIED,
352                 provider.checkUriPermission(uri, android.os.Process.myUid(), 0));
353     }
354 
355     @Test
testCreateContentUriForUser_nullUri_throwsNPE()356     public void testCreateContentUriForUser_nullUri_throwsNPE() {
357         assertThrows(
358                 NullPointerException.class,
359                 () -> ContentProvider.createContentUriForUser(null, UserHandle.of(7)));
360     }
361 
362     @Test
testCreateContentUriForUser_nonContentUri_throwsIAE()363     public void testCreateContentUriForUser_nonContentUri_throwsIAE() {
364         final Uri uri = Uri.parse("notcontent://test");
365         assertThrows(
366                 IllegalArgumentException.class,
367                 () -> ContentProvider.createContentUriForUser(uri, UserHandle.of(7)));
368     }
369 
370     @Test
testCreateContentUriForUser_UriWithDifferentUserID_throwsIAE()371     public void testCreateContentUriForUser_UriWithDifferentUserID_throwsIAE() {
372         final Uri uri = Uri.parse("content://07@Test");
373         assertThrows(
374                 IllegalArgumentException.class,
375                 () -> ContentProvider.createContentUriForUser(uri, UserHandle.of(7)));
376     }
377 
378     @Test
testCreateContentUriForUser_UriWithUserID_unchanged()379     public void testCreateContentUriForUser_UriWithUserID_unchanged() {
380         final Uri uri = Uri.parse("content://7@Test");
381         assertEquals(uri, ContentProvider.createContentUriForUser(uri, UserHandle.of(7)));
382     }
383 
384     @Test
385     @EnsureHasWorkProfile
386     @AppModeFull
createContentUriForUser_returnsCorrectUri()387     public void createContentUriForUser_returnsCorrectUri() {
388         final ContentResolver profileContentResolver =
389                 TestApis.context().androidContextAsUser(workProfile(sDeviceState))
390                         .getContentResolver();
391         final String testContentDisplayName = "testContent.mp3";
392         final Uri workProfileUriWithoutUserId = createAndInsertTestAudioFile(
393                 profileContentResolver, testContentDisplayName);
394 
395         final Uri workProfileUriWithUserId = ContentProvider.createContentUriForUser(
396                 workProfileUriWithoutUserId, workProfile(sDeviceState).userHandle());
397 
398         assertThat(getAudioContentDisplayName(
399                 sContext.getContentResolver(), workProfileUriWithUserId))
400                 .isEqualTo(testContentDisplayName);
401     }
402 
createAndInsertTestAudioFile(ContentResolver resolver, String displayName)403     private Uri createAndInsertTestAudioFile(ContentResolver resolver, String displayName) {
404         final Uri audioCollection = MediaStore.Audio.Media.getContentUri(
405                 MediaStore.VOLUME_EXTERNAL_PRIMARY);
406         final ContentValues testContent = new ContentValues();
407         testContent.put(MediaStore.Audio.Media.DISPLAY_NAME, displayName);
408         return resolver.insert(audioCollection, testContent);
409     }
410 
getAudioContentDisplayName(ContentResolver resolver, Uri uri)411     private String getAudioContentDisplayName(ContentResolver resolver, Uri uri) {
412         String name = null;
413         try (Cursor cursor = resolver.query(
414                 uri,
415                 /* projection = */ null,
416                 /* selection = */ null,
417                 /* selectionArgs = */ null,
418                 /* sortOrder = */ null)) {
419             final int nameColumn =
420                     cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
421             if (cursor.moveToNext()) {
422                 name = cursor.getString(nameColumn);
423             }
424         }
425         return name;
426     }
427 
428     private class MockContentProvider extends ContentProvider {
429         private int mInsertCount = 0;
430 
431         @Override
delete(Uri uri, String selection, String[] selectionArgs)432         public int delete(Uri uri, String selection, String[] selectionArgs) {
433             return 0;
434         }
435 
436         @Override
getType(Uri uri)437         public String getType(Uri uri) {
438             return null;
439         }
440 
441         @Override
insert(Uri uri, ContentValues values)442         public Uri insert(Uri uri, ContentValues values) {
443             mInsertCount++;
444             return null;
445         }
446 
getInsertCount()447         public int getInsertCount() {
448             return mInsertCount;
449         }
450 
451         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)452         public Cursor query(Uri uri, String[] projection, String selection,
453                 String[] selectionArgs, String sortOrder) {
454             return null;
455         }
456 
457         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)458         public int update(Uri uri, ContentValues values, String selection,
459                 String[] selectionArgs) {
460             return 0;
461         }
462 
463         @Override
onCreate()464         public boolean onCreate() {
465             return false;
466         }
467 
468         // wrapper or override for the protected methods
setReadPermissionWrapper(String permission)469         public void setReadPermissionWrapper(String permission) {
470             super.setReadPermission(permission);
471         }
472 
setWritePermissionWrapper(String permission)473         public void setWritePermissionWrapper(String permission) {
474             super.setWritePermission(permission);
475         }
476 
477         @Override
isTemporary()478         protected boolean isTemporary() {
479             return super.isTemporary();
480         }
481 
openFileHelperWrapper(Uri uri, String mode)482         public ParcelFileDescriptor openFileHelperWrapper(Uri uri, String mode)
483                 throws FileNotFoundException {
484             return super.openFileHelper(uri, mode);
485         }
486     }
487 
488     /**
489      * This provider implements openFile/openAssetFile() using
490      * ContentProvider.openFileHelper/openPipeHelper().
491      */
492     private class OpenFilePipeContentProvider extends ContentProvider implements
493             ContentProvider.PipeDataWriter<String> {
494         private SQLiteDatabase mDb;
495 
OpenFilePipeContentProvider(String fileName, String dbName)496         OpenFilePipeContentProvider(String fileName, String dbName) {
497             // delete the database if it already exists
498             sContext.deleteDatabase(dbName);
499             mDb = sContext.openOrCreateDatabase(dbName, Context.MODE_PRIVATE, null);
500             mDb.execSQL("CREATE TABLE files ( _data TEXT );");
501             mDb.execSQL("INSERT INTO files VALUES ( \"" + fileName + "\");");
502         }
503 
504         @Override
delete(Uri uri, String selection, String[] selectionArgs)505         public int delete(Uri uri, String selection, String[] selectionArgs) {
506             throw new RuntimeException("not implemented");
507         }
508 
509         @Override
getType(Uri uri)510         public String getType(Uri uri) {
511             throw new RuntimeException("not implemented");
512         }
513 
514         @Override
insert(Uri uri, ContentValues values)515         public Uri insert(Uri uri, ContentValues values) {
516             throw new RuntimeException("not implemented");
517         }
518 
519         @Override
onCreate()520         public boolean onCreate() {
521             return true;
522         }
523 
524         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)525         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
526                 String sortOrder) {
527             return mDb.query("files", projection, selection, selectionArgs, null, null, null);
528         }
529 
530         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)531         public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
532             throw new RuntimeException("not implemented");
533         }
534 
535         @Override
openFile(Uri uri, String mode)536         public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
537             return openFileHelper(uri, mode);
538         }
539 
540         @Override
openAssetFile(Uri uri, String mode)541         public AssetFileDescriptor openAssetFile(Uri uri, String mode)
542                 throws FileNotFoundException {
543             return new AssetFileDescriptor(openPipeHelper(uri, "text/html", null,
544                     mode, this), 0,
545                     AssetFileDescriptor.UNKNOWN_LENGTH);
546         }
547 
548         @Override
writeDataToPipe(@onNull ParcelFileDescriptor output, @NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args)549         public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri,
550                 @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args) {
551             try (DataOutputStream dos = new DataOutputStream(
552                     new FileOutputStream(output.getFileDescriptor()))) {
553                 try (InputStream is = new FileInputStream(
554                         openFile(uri, args).getFileDescriptor())) {
555                     dos.writeUTF("OK");
556                 } catch (Throwable t) {
557                     dos.writeUTF(t.toString());
558                 }
559             } catch (IOException ignored) {
560 
561             }
562         }
563     }
564 }
565