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