1 /* 2 * Copyright (C) 2016 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.contacts; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.database.Cursor; 22 import android.net.Uri; 23 import android.os.CancellationSignal; 24 import android.provider.ContactsContract; 25 import android.provider.ContactsContract.Contacts; 26 import android.provider.ContactsContract.RawContacts; 27 import android.provider.ContactsContract.SyncState; 28 import android.test.AndroidTestCase; 29 import android.test.suitebuilder.annotation.LargeTest; 30 import android.util.Log; 31 32 import junit.framework.AssertionFailedError; 33 34 import java.io.FileNotFoundException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.util.ArrayList; 38 39 @LargeTest 40 public class ContactsContract_AllUriTest extends AndroidTestCase { 41 private static final String TAG = "AllUrlTest"; 42 43 // "-" : Query not supported. 44 // "!" : Can't query because it requires the cross-user permission. 45 // The following markers are planned, but not implemented and the definition below is not all 46 // correct yet. 47 // "d" : supports delete. 48 // "u" : supports update. 49 // "i" : supports insert. 50 // "r" : supports read. 51 // "w" : supports write. 52 // "s" : has x_times_contacted and x_last_time_contacted. 53 // "t" : has x_times_used and x_last_time_used. 54 private static final String[][] URIs = { 55 {"content://com.android.contacts/contacts", "sud"}, 56 {"content://com.android.contacts/contacts/1", "sud"}, 57 {"content://com.android.contacts/contacts/1/data", "t"}, 58 {"content://com.android.contacts/contacts/1/entities", "t"}, 59 {"content://com.android.contacts/contacts/1/suggestions"}, 60 {"content://com.android.contacts/contacts/1/suggestions/XXX"}, 61 {"content://com.android.contacts/contacts/1/photo", "r"}, 62 {"content://com.android.contacts/contacts/1/display_photo", "-r"}, 63 {"content://com.android.contacts/contacts_corp/1/photo", "-r"}, 64 {"content://com.android.contacts/contacts_corp/1/display_photo", "-r"}, 65 66 {"content://com.android.contacts/contacts/filter", "s"}, 67 {"content://com.android.contacts/contacts/filter/XXX", "s"}, 68 69 {"content://com.android.contacts/contacts/lookup/nlookup", "sud"}, 70 {"content://com.android.contacts/contacts/lookup/nlookup/data", "t"}, 71 {"content://com.android.contacts/contacts/lookup/nlookup/photo", "tr"}, 72 73 {"content://com.android.contacts/contacts/lookup/nlookup/1", "sud"}, 74 {"content://com.android.contacts/contacts/lookup/nlookup/1/data"}, 75 {"content://com.android.contacts/contacts/lookup/nlookup/1/photo", "r"}, 76 {"content://com.android.contacts/contacts/lookup/nlookup/display_photo", "-r"}, 77 {"content://com.android.contacts/contacts/lookup/nlookup/1/display_photo", "-r"}, 78 {"content://com.android.contacts/contacts/lookup/nlookup/entities"}, 79 {"content://com.android.contacts/contacts/lookup/nlookup/1/entities"}, 80 81 {"content://com.android.contacts/contacts/as_vcard/nlookup", "r"}, 82 {"content://com.android.contacts/contacts/as_multi_vcard/XXX"}, 83 84 {"content://com.android.contacts/contacts/strequent/", "s"}, 85 {"content://com.android.contacts/contacts/strequent/filter/XXX", "s"}, 86 87 {"content://com.android.contacts/contacts/group/XXX"}, 88 89 {"content://com.android.contacts/contacts/frequent", "s"}, 90 {"content://com.android.contacts/contacts/delete_usage", "-d"}, 91 {"content://com.android.contacts/contacts/filter_enterprise?directory=0", "s"}, 92 {"content://com.android.contacts/contacts/filter_enterprise/XXX?directory=0", "s"}, 93 94 {"content://com.android.contacts/raw_contacts", "siud"}, 95 {"content://com.android.contacts/raw_contacts/1", "sud"}, 96 {"content://com.android.contacts/raw_contacts/1/data", "tu"}, 97 {"content://com.android.contacts/raw_contacts/1/display_photo", "-rw"}, 98 {"content://com.android.contacts/raw_contacts/1/entity"}, 99 100 {"content://com.android.contacts/raw_contact_entities"}, 101 {"content://com.android.contacts/raw_contact_entities_corp", "!"}, 102 103 {"content://com.android.contacts/data", "tud"}, 104 {"content://com.android.contacts/data/1", "tudr"}, 105 {"content://com.android.contacts/data/phones", "t"}, 106 {"content://com.android.contacts/data_enterprise/phones", "!"}, 107 {"content://com.android.contacts/data/phones/1", "tud"}, 108 {"content://com.android.contacts/data/phones/filter", "t"}, 109 {"content://com.android.contacts/data/phones/filter/XXX", "t"}, 110 111 {"content://com.android.contacts/data/phones/filter_enterprise?directory=0", "t"}, 112 {"content://com.android.contacts/data/phones/filter_enterprise/XXX?directory=0", "t"}, 113 114 {"content://com.android.contacts/data/emails", "t"}, 115 {"content://com.android.contacts/data/emails/1", "tud"}, 116 {"content://com.android.contacts/data/emails/lookup", "t"}, 117 {"content://com.android.contacts/data/emails/lookup/XXX", "t"}, 118 {"content://com.android.contacts/data/emails/filter", "t"}, 119 {"content://com.android.contacts/data/emails/filter/XXX", "t"}, 120 {"content://com.android.contacts/data/emails/filter_enterprise?directory=0", "t"}, 121 {"content://com.android.contacts/data/emails/filter_enterprise/XXX?directory=0", "t"}, 122 {"content://com.android.contacts/data/emails/lookup_enterprise", "t"}, 123 {"content://com.android.contacts/data/emails/lookup_enterprise/XXX", "t"}, 124 {"content://com.android.contacts/data/postals", "t"}, 125 {"content://com.android.contacts/data/postals/1", "tud"}, 126 {"content://com.android.contacts/data/usagefeedback/1,2,3", "-u"}, 127 {"content://com.android.contacts/data/callables/", "t"}, 128 {"content://com.android.contacts/data/callables/1", "tud"}, 129 {"content://com.android.contacts/data/callables/filter", "t"}, 130 {"content://com.android.contacts/data/callables/filter/XXX", "t"}, 131 {"content://com.android.contacts/data/callables/filter_enterprise?directory=0", "t"}, 132 {"content://com.android.contacts/data/callables/filter_enterprise/XXX?directory=0", 133 "t"}, 134 {"content://com.android.contacts/data/contactables/", "t"}, 135 {"content://com.android.contacts/data/contactables/filter", "t"}, 136 {"content://com.android.contacts/data/contactables/filter/XXX", "t"}, 137 138 {"content://com.android.contacts/groups", "iud"}, 139 {"content://com.android.contacts/groups/1", "ud"}, 140 {"content://com.android.contacts/groups_summary"}, 141 {"content://com.android.contacts/syncstate", "iud"}, 142 {"content://com.android.contacts/syncstate/1", "-ud"}, 143 {"content://com.android.contacts/profile/syncstate", "iud"}, 144 {"content://com.android.contacts/phone_lookup/XXX"}, 145 {"content://com.android.contacts/phone_lookup_enterprise/XXX"}, 146 {"content://com.android.contacts/aggregation_exceptions", "u"}, 147 {"content://com.android.contacts/settings", "ud"}, 148 {"content://com.android.contacts/status_updates", "ud"}, 149 {"content://com.android.contacts/status_updates/1"}, 150 {"content://com.android.contacts/search_suggest_query"}, 151 {"content://com.android.contacts/search_suggest_query/XXX"}, 152 {"content://com.android.contacts/search_suggest_shortcut/XXX"}, 153 {"content://com.android.contacts/provider_status"}, 154 {"content://com.android.contacts/directories", "u"}, 155 {"content://com.android.contacts/directories/1"}, 156 {"content://com.android.contacts/directories_enterprise"}, 157 {"content://com.android.contacts/directories_enterprise/1"}, 158 {"content://com.android.contacts/complete_name"}, 159 {"content://com.android.contacts/profile", "su"}, 160 {"content://com.android.contacts/profile/entities", "s"}, 161 {"content://com.android.contacts/profile/data", "tud"}, 162 {"content://com.android.contacts/profile/data/1", "td"}, 163 {"content://com.android.contacts/profile/photo", "t"}, 164 {"content://com.android.contacts/profile/display_photo", "-r"}, 165 {"content://com.android.contacts/profile/as_vcard", "r"}, 166 {"content://com.android.contacts/profile/raw_contacts", "siud"}, 167 168 // Note this should have supported update... Too late to add. 169 {"content://com.android.contacts/profile/raw_contacts/1", "sd"}, 170 {"content://com.android.contacts/profile/raw_contacts/1/data", "tu"}, 171 {"content://com.android.contacts/profile/raw_contacts/1/entity"}, 172 {"content://com.android.contacts/profile/status_updates", "ud"}, 173 {"content://com.android.contacts/profile/raw_contact_entities"}, 174 {"content://com.android.contacts/display_photo/1", "-r"}, 175 {"content://com.android.contacts/photo_dimensions"}, 176 {"content://com.android.contacts/deleted_contacts"}, 177 {"content://com.android.contacts/deleted_contacts/1"}, 178 {"content://com.android.contacts/directory_file_enterprise/XXX?directory=0", "-"}, 179 }; 180 181 private static final String[] ARG1 = {"-1"}; 182 183 private ContentResolver mResolver; 184 185 private ArrayList<String> mFailures; 186 187 @Override setUp()188 protected void setUp() throws Exception { 189 super.setUp(); 190 191 mFailures = new ArrayList<>(); 192 mResolver = getContext().getContentResolver(); 193 } 194 195 @Override tearDown()196 protected void tearDown() throws Exception { 197 if (mFailures != null) { 198 fail("mFailures is not null. Did you forget to call failIfFailed()?"); 199 } 200 201 super.tearDown(); 202 } 203 addFailure(String message, Throwable th)204 private void addFailure(String message, Throwable th) { 205 Log.e(TAG, "Failed: " + message, th); 206 207 final int MAX = 100; 208 if (mFailures.size() == MAX) { 209 mFailures.add("Too many failures."); 210 } else if (mFailures.size() > MAX) { 211 // Too many failures already... 212 } else { 213 mFailures.add(message); 214 } 215 } 216 failIfFailed()217 private void failIfFailed() { 218 if (mFailures == null) { 219 fail("mFailures is null. Maybe called failIfFailed() twice?"); 220 } 221 if (mFailures.size() > 0) { 222 StringBuilder message = new StringBuilder(); 223 224 if (mFailures.size() > 0) { 225 Log.e(TAG, "Something went wrong:"); 226 for (String s : mFailures) { 227 Log.e(TAG, s); 228 if (message.length() > 0) { 229 message.append("\n"); 230 } 231 message.append(s); 232 } 233 } 234 mFailures = null; 235 fail("Following test(s) failed:\n" + message); 236 } 237 mFailures = null; 238 } 239 getUri(String[] path)240 private static Uri getUri(String[] path) { 241 return Uri.parse(path[0]); 242 } 243 supportsQuery(String[] path)244 private static boolean supportsQuery(String[] path) { 245 if (path.length == 1) { 246 return true; // supports query by default. 247 } 248 return !(path[1].contains("-") || path[1].contains("!")); 249 } 250 supportsInsert(String[] path)251 private static boolean supportsInsert(String[] path) { 252 return (path.length) >= 2 && path[1].contains("i"); 253 } 254 supportsUpdate(String[] path)255 private static boolean supportsUpdate(String[] path) { 256 return (path.length) >= 2 && path[1].contains("u"); 257 } 258 supportsDelete(String[] path)259 private static boolean supportsDelete(String[] path) { 260 return (path.length) >= 2 && path[1].contains("d"); 261 } 262 supportsRead(String[] path)263 private static boolean supportsRead(String[] path) { 264 return (path.length) >= 2 && path[1].contains("r"); 265 } 266 supportsWrite(String[] path)267 private static boolean supportsWrite(String[] path) { 268 return (path.length) >= 2 && path[1].contains("w"); 269 } 270 getColumns(Uri uri)271 private String[] getColumns(Uri uri) { 272 try (Cursor c = mResolver.query(uri, 273 null, // projection 274 "1=2", // selection 275 null, // selection args 276 null // sort order 277 )) { 278 return c.getColumnNames(); 279 } 280 } 281 checkQueryExecutable(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)282 private void checkQueryExecutable(Uri uri, 283 String[] projection, String selection, 284 String[] selectionArgs, String sortOrder) { 285 try { 286 try (Cursor c = mResolver.query(uri, projection, selection, 287 selectionArgs, sortOrder)) { 288 c.moveToFirst(); 289 } 290 } catch (Throwable th) { 291 addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th); 292 } 293 try { 294 // With CancellationSignal. 295 try (Cursor c = mResolver.query(uri, projection, selection, 296 selectionArgs, sortOrder, new CancellationSignal())) { 297 c.moveToFirst(); 298 } 299 } catch (Throwable th) { 300 addFailure("Query with cancel failed: URI=" + uri + " Message=" + th.getMessage(), th); 301 } 302 try { 303 // With limit. 304 try (Cursor c = mResolver.query( 305 uri.buildUpon().appendQueryParameter( 306 ContactsContract.LIMIT_PARAM_KEY, "0").build(), 307 projection, selection, selectionArgs, sortOrder)) { 308 c.moveToFirst(); 309 } 310 } catch (Throwable th) { 311 addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th); 312 } 313 314 try { 315 // With account. 316 try (Cursor c = mResolver.query( 317 uri.buildUpon() 318 .appendQueryParameter(RawContacts.ACCOUNT_NAME, "a") 319 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, "b") 320 .appendQueryParameter(RawContacts.DATA_SET, "c") 321 .build(), 322 projection, selection, selectionArgs, sortOrder)) { 323 c.moveToFirst(); 324 } 325 } catch (Throwable th) { 326 addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th); 327 } 328 } 329 checkQueryNotExecutable(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)330 private void checkQueryNotExecutable(Uri uri, 331 String[] projection, String selection, 332 String[] selectionArgs, String sortOrder) { 333 try { 334 try (Cursor c = mResolver.query(uri, projection, selection, 335 selectionArgs, sortOrder)) { 336 c.moveToFirst(); 337 } 338 } catch (Throwable th) { 339 // pass. 340 return; 341 } 342 addFailure("Query on " + uri + " expected to fail, but succeeded.", null); 343 } 344 345 /** 346 * Make sure all URLs are accessible with all arguments = null. 347 */ testSelect()348 public void testSelect() { 349 for (String[] path : URIs) { 350 if (!supportsQuery(path)) continue; 351 final Uri uri = getUri(path); 352 353 checkQueryExecutable(uri, // uri 354 null, // projection 355 null, // selection 356 null, // selection args 357 null // sort order 358 ); 359 } 360 failIfFailed(); 361 } 362 testNoHiddenColumns()363 public void testNoHiddenColumns() { 364 for (String[] path : URIs) { 365 if (!supportsQuery(path)) continue; 366 final Uri uri = getUri(path); 367 368 for (String column : getColumns(uri)) { 369 if (column.toLowerCase().startsWith(ContactsContract.HIDDEN_COLUMN_PREFIX)) { 370 addFailure("Uri " + uri + " returned hidden column " + column, null); 371 } 372 } 373 } 374 failIfFailed(); 375 } 376 377 // Temporarily disabled due to taking too much time. 378 // /** 379 // * Make sure all URLs are accessible with a projection. 380 // */ 381 // public void testSelectWithProjection() { 382 // for (String[] path : URIs) { 383 // if (!supportsQuery(path)) continue; 384 // final Uri uri = getUri(path); 385 // 386 // for (String column : getColumns(uri)) { 387 // // Some columns are not selectable alone due to bugs, and we don't want to fix them 388 // // in order to avoid expanding the differences between versions, so here're some 389 // // hacks to make it work... 390 // 391 // String[] projection = {column}; 392 // 393 // final String u = path[0]; 394 // if ((u.startsWith("content://com.android.contacts/status_updates") 395 // || u.startsWith("content://com.android.contacts/profile/status_updates")) 396 // && ("im_handle".equals(column) 397 // || "im_account".equals(column) 398 // || "protocol".equals(column) 399 // || "custom_protocol".equals(column) 400 // || "presence_raw_contact_id".equals(column) 401 // )) { 402 // // These columns only show up when the projection contains certain columns. 403 // 404 // projection = new String[]{"mode", column}; 405 // } else if ((u.startsWith("content://com.android.contacts/search_suggest_query") 406 // || u.startsWith("content://contacts/search_suggest_query")) 407 // && "suggest_intent_action".equals(column)) { 408 // // Can't be included in the projection due to a bug in GlobalSearchSupport. 409 // continue; 410 // } else if (RawContacts.BACKUP_ID.equals(column)) { 411 // // Some URIs don't support a projection with BAKCUP_ID only. 412 // projection = new String[]{RawContacts.BACKUP_ID, RawContacts.SOURCE_ID}; 413 // } 414 // 415 // checkQueryExecutable(uri, 416 // projection, // projection 417 // null, // selection 418 // null, // selection args 419 // null // sort order 420 // ); 421 // } 422 // } 423 // failIfFailed(); 424 // } 425 426 /** 427 * Make sure all URLs are accessible with a selection. 428 */ testSelectWithSelection()429 public void testSelectWithSelection() { 430 for (String[] path : URIs) { 431 if (!supportsQuery(path)) continue; 432 final Uri uri = getUri(path); 433 434 checkQueryExecutable(uri, 435 null, // projection 436 "1=?", // selection 437 ARG1, // , // selection args 438 null // sort order 439 ); 440 } 441 failIfFailed(); 442 } 443 444 // /** 445 // * Make sure all URLs are accessible with a selection. 446 // */ 447 // public void testSelectWithSelectionUsingColumns() { 448 // for (String[] path : URIs) { 449 // if (!supportsQuery(path)) continue; 450 // final Uri uri = getUri(path); 451 // 452 // for (String column : getColumns(uri)) { 453 // checkQueryExecutable(uri, 454 // null, // projection 455 // column + "=?", // selection 456 // ARG1, // , // selection args 457 // null // sort order 458 // ); 459 // } 460 // } 461 // failIfFailed(); 462 // } 463 464 // Temporarily disabled due to taking too much time. 465 // /** 466 // * Make sure all URLs are accessible with an order-by. 467 // */ 468 // public void testSelectWithSortOrder() { 469 // for (String[] path : URIs) { 470 // if (!supportsQuery(path)) continue; 471 // final Uri uri = getUri(path); 472 // 473 // for (String column : getColumns(uri)) { 474 // checkQueryExecutable(uri, 475 // null, // projection 476 // "1=2", // selection 477 // null, // , // selection args 478 // column // sort order 479 // ); 480 // } 481 // } 482 // failIfFailed(); 483 // } 484 485 /** 486 * Make sure all URLs are accessible with all arguments. 487 */ testSelectWithAllArgs()488 public void testSelectWithAllArgs() { 489 for (String[] path : URIs) { 490 if (!supportsQuery(path)) continue; 491 final Uri uri = getUri(path); 492 493 final String[] projection = {getColumns(uri)[0]}; 494 495 checkQueryExecutable(uri, 496 projection, // projection 497 "1=?", // selection 498 ARG1, // , // selection args 499 getColumns(uri)[0] // sort order 500 ); 501 } 502 failIfFailed(); 503 } 504 testNonSelect()505 public void testNonSelect() { 506 for (String[] path : URIs) { 507 if (supportsQuery(path)) continue; 508 final Uri uri = getUri(path); 509 510 checkQueryNotExecutable(uri, // uri 511 null, // projection 512 null, // selection 513 null, // selection args 514 null // sort order 515 ); 516 } 517 failIfFailed(); 518 } 519 supportsTimesContacted(String[] path)520 private static boolean supportsTimesContacted(String[] path) { 521 return path.length > 1 && path[1].contains("s"); 522 } 523 supportsTimesUsed(String[] path)524 private static boolean supportsTimesUsed(String[] path) { 525 return path.length > 1 && path[1].contains("t"); 526 } 527 checkColumnAccessible(Uri uri, String column)528 private void checkColumnAccessible(Uri uri, String column) { 529 try { 530 try (Cursor c = mResolver.query( 531 uri, new String[]{column}, column + "=0", null, column 532 )) { 533 c.moveToFirst(); 534 } 535 } catch (Throwable th) { 536 addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th); 537 } 538 } 539 540 /** Test for {@link #checkColumnAccessible} */ testCheckColumnAccessible()541 public void testCheckColumnAccessible() { 542 checkColumnAccessible(Contacts.CONTENT_URI, "x_times_contacted"); 543 try { 544 failIfFailed(); 545 } catch (AssertionFailedError expected) { 546 return; // expected. 547 } 548 fail("Failed to detect issue."); 549 } 550 checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)551 private void checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection, 552 String[] selectionArgs, String sortOrder) { 553 try { 554 try (Cursor c = mResolver.query(uri, projection, selection, 555 selectionArgs, sortOrder)) { 556 c.moveToFirst(); 557 } 558 } catch (IllegalArgumentException th) { 559 // pass. 560 return; 561 } 562 addFailure("Query on " + uri + 563 " expected to throw IllegalArgumentException, but succeeded.", null); 564 } 565 checkColumnNotAccessible(Uri uri, String column)566 private void checkColumnNotAccessible(Uri uri, String column) { 567 checkColumnNotAccessibleInner(uri, new String[] {column}, null, null, null); 568 checkColumnNotAccessibleInner(uri, null, column + "=1", null, null); 569 checkColumnNotAccessibleInner(uri, null, null, null, /* order by */ column); 570 } 571 572 /** Test for {@link #checkColumnNotAccessible} */ testCheckColumnNotAccessible()573 public void testCheckColumnNotAccessible() { 574 checkColumnNotAccessible(Contacts.CONTENT_URI, "times_contacted"); 575 try { 576 failIfFailed(); 577 } catch (AssertionFailedError expected) { 578 return; // expected. 579 } 580 fail("Failed to detect issue."); 581 } 582 583 /** 584 * Make sure the x_ columns are not accessible. 585 */ testProhibitedColumns()586 public void testProhibitedColumns() { 587 for (String[] path : URIs) { 588 final Uri uri = getUri(path); 589 if (supportsTimesContacted(path)) { 590 checkColumnAccessible(uri, "times_contacted"); 591 checkColumnAccessible(uri, "last_time_contacted"); 592 593 checkColumnNotAccessible(uri, "X_times_contacted"); 594 checkColumnNotAccessible(uri, "X_slast_time_contacted"); 595 } 596 if (supportsTimesUsed(path)) { 597 checkColumnAccessible(uri, "times_used"); 598 checkColumnAccessible(uri, "last_time_used"); 599 600 checkColumnNotAccessible(uri, "X_times_used"); 601 checkColumnNotAccessible(uri, "X_last_time_used"); 602 } 603 } 604 failIfFailed(); 605 } 606 checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r)607 private void checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r) { 608 if (shouldWork) { 609 try { 610 r.run(); 611 } catch (Exception e) { 612 addFailure(operation + " for '" + uri + "' failed: " + e.getMessage(), e); 613 } 614 } else { 615 try { 616 r.run(); 617 addFailure(operation + " for '" + uri + "' NOT failed.", null); 618 } catch (Exception expected) { 619 } 620 } 621 } 622 testAllOperations()623 public void testAllOperations() { 624 final ContentValues cv = new ContentValues(); 625 626 for (String[] path : URIs) { 627 final Uri uri = getUri(path); 628 629 cv.clear(); 630 if (supportsQuery(path)) { 631 cv.put(getColumns(uri)[0], 1); 632 } else { 633 cv.put("_id", 1); 634 } 635 if (uri.toString().contains("syncstate")) { 636 cv.put(SyncState.ACCOUNT_NAME, "abc"); 637 cv.put(SyncState.ACCOUNT_TYPE, "def"); 638 } 639 640 checkExecutable("insert", uri, supportsInsert(path), () -> { 641 final Uri newUri = mResolver.insert(uri, cv); 642 if (newUri == null) { 643 addFailure("Insert for '" + uri + "' returned null.", null); 644 } else { 645 // "profile/raw_contacts/#" is missing update support. too late to add, so 646 // just skip. 647 if (!newUri.toString().startsWith( 648 "content://com.android.contacts/profile/raw_contacts/")) { 649 checkExecutable("insert -> update", newUri, true, () -> { 650 mResolver.update(newUri, cv, null, null); 651 }); 652 } 653 checkExecutable("insert -> delete", newUri, true, () -> { 654 mResolver.delete(newUri, null, null); 655 }); 656 } 657 }); 658 checkExecutable("update", uri, supportsUpdate(path), () -> { 659 mResolver.update(uri, cv, "1=2", null); 660 }); 661 checkExecutable("delete", uri, supportsDelete(path), () -> { 662 mResolver.delete(uri, "1=2", null); 663 }); 664 } 665 failIfFailed(); 666 } 667 testAllFileOperations()668 public void testAllFileOperations() { 669 for (String[] path : URIs) { 670 final Uri uri = getUri(path); 671 672 checkExecutable("openInputStream", uri, supportsRead(path), () -> { 673 try (InputStream st = mResolver.openInputStream(uri)) { 674 } catch (FileNotFoundException e) { 675 // TODO This happens because we try to read nonexistent photos. Ideally 676 // we should actually check it's readable. 677 if (e.getMessage().contains("Stream I/O not supported")) { 678 throw new RuntimeException("Caught Exception: " + e.toString(), e); 679 } 680 } catch (Exception e) { 681 throw new RuntimeException("Caught Exception: " + e.toString(), e); 682 } 683 }); 684 checkExecutable("openOutputStream", uri, supportsWrite(path), () -> { 685 try (OutputStream st = mResolver.openOutputStream(uri)) { 686 } catch (Exception e) { 687 throw new RuntimeException("Caught Exception: " + e.toString(), e); 688 } 689 }); 690 } 691 failIfFailed(); 692 } 693 } 694 695 696