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