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