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