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