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