• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             try {
375                 final Uri uri = getUri(path);
376 
377                 checkQueryExecutable(uri, // uri
378                         null, // projection
379                         null, // selection
380                         null, // selection args
381                         null // sort order
382                         );
383             } catch (Throwable th) {
384                 addFailure("Failed: URI=" + path[0] + " Message=" + th.getMessage(), th);
385             }
386         }
387         failIfFailed();
388     }
389 
testNoHiddenColumns()390     public void testNoHiddenColumns() {
391         for (String[] path : URIs) {
392             if (!supportsQuery(path)) continue;
393             try {
394                 final Uri uri = getUri(path);
395 
396                 for (String column : getColumns(uri)) {
397                     if (column.toLowerCase().startsWith(ContactsContract.HIDDEN_COLUMN_PREFIX)) {
398                         addFailure("Uri " + uri + " returned hidden column " + column, null);
399                     }
400                 }
401             } catch (Throwable th) {
402                 addFailure("Failed: URI=" + path[0] + " Message=" + th.getMessage(), th);
403             }
404         }
405         failIfFailed();
406     }
407 
408 // Temporarily disabled due to taking too much time.
409 //    /**
410 //     * Make sure all URLs are accessible with a projection.
411 //     */
412 //    public void testSelectWithProjection() {
413 //        for (String[] path : URIs) {
414 //            if (!supportsQuery(path)) continue;
415 //            final Uri uri = getUri(path);
416 //
417 //            for (String column : getColumns(uri)) {
418 //                // Some columns are not selectable alone due to bugs, and we don't want to fix them
419 //                // in order to avoid expanding the differences between versions, so here're some
420 //                // hacks to make it work...
421 //
422 //                String[] projection = {column};
423 //
424 //                final String u = path[0];
425 //                if ((u.startsWith("content://com.android.contacts/status_updates")
426 //                        || u.startsWith("content://com.android.contacts/profile/status_updates"))
427 //                        && ("im_handle".equals(column)
428 //                        || "im_account".equals(column)
429 //                        || "protocol".equals(column)
430 //                        || "custom_protocol".equals(column)
431 //                        || "presence_raw_contact_id".equals(column)
432 //                        )) {
433 //                    // These columns only show up when the projection contains certain columns.
434 //
435 //                    projection = new String[]{"mode", column};
436 //                } else if ((u.startsWith("content://com.android.contacts/search_suggest_query")
437 //                        || u.startsWith("content://contacts/search_suggest_query"))
438 //                        && "suggest_intent_action".equals(column)) {
439 //                    // Can't be included in the projection due to a bug in GlobalSearchSupport.
440 //                    continue;
441 //                } else if (RawContacts.BACKUP_ID.equals(column)) {
442 //                    // Some URIs don't support a projection with BAKCUP_ID only.
443 //                    projection = new String[]{RawContacts.BACKUP_ID, RawContacts.SOURCE_ID};
444 //                }
445 //
446 //                checkQueryExecutable(uri,
447 //                        projection, // projection
448 //                        null, // selection
449 //                        null, // selection args
450 //                        null // sort order
451 //                );
452 //            }
453 //        }
454 //        failIfFailed();
455 //    }
456 
457     /**
458      * Make sure all URLs are accessible with a selection.
459      */
testSelectWithSelection()460     public void testSelectWithSelection() {
461         for (String[] path : URIs) {
462             if (!supportsQuery(path)) continue;
463             final Uri uri = getUri(path);
464 
465             checkQueryExecutable(uri,
466                     null, // projection
467                     "1=?", // selection
468                     ARG1, // , // selection args
469                     null // sort order
470             );
471         }
472         failIfFailed();
473     }
474 
475 //    /**
476 //     * Make sure all URLs are accessible with a selection.
477 //     */
478 //    public void testSelectWithSelectionUsingColumns() {
479 //        for (String[] path : URIs) {
480 //            if (!supportsQuery(path)) continue;
481 //            final Uri uri = getUri(path);
482 //
483 //            for (String column : getColumns(uri)) {
484 //                checkQueryExecutable(uri,
485 //                        null, // projection
486 //                        column + "=?", // selection
487 //                        ARG1, // , //  selection args
488 //                        null // sort order
489 //                );
490 //            }
491 //        }
492 //        failIfFailed();
493 //    }
494 
495 // Temporarily disabled due to taking too much time.
496 //    /**
497 //     * Make sure all URLs are accessible with an order-by.
498 //     */
499 //    public void testSelectWithSortOrder() {
500 //        for (String[] path : URIs) {
501 //            if (!supportsQuery(path)) continue;
502 //            final Uri uri = getUri(path);
503 //
504 //            for (String column : getColumns(uri)) {
505 //                checkQueryExecutable(uri,
506 //                        null, // projection
507 //                        "1=2", // selection
508 //                        null, // , // selection args
509 //                        column // sort order
510 //                );
511 //            }
512 //        }
513 //        failIfFailed();
514 //    }
515 
516     /**
517      * Make sure all URLs are accessible with all arguments.
518      */
testSelectWithAllArgs()519     public void testSelectWithAllArgs() {
520         for (String[] path : URIs) {
521             if (!supportsQuery(path)) continue;
522             final Uri uri = getUri(path);
523 
524             final String[] projection = {getColumns(uri)[0]};
525 
526             checkQueryExecutable(uri,
527                     projection, // projection
528                     "1=?", // selection
529                     ARG1, // , // selection args
530                     getColumns(uri)[0] // sort order
531             );
532         }
533         failIfFailed();
534     }
535 
testNonSelect()536     public void testNonSelect() {
537         for (String[] path : URIs) {
538             if (supportsQuery(path)) continue;
539             final Uri uri = getUri(path);
540 
541             checkQueryNotExecutable(uri, // uri
542                     null, // projection
543                     null, // selection
544                     null, // selection args
545                     null // sort order
546             );
547         }
548         failIfFailed();
549     }
550 
supportsTimesContacted(String[] path)551     private static boolean supportsTimesContacted(String[] path) {
552         return path.length > 1 && path[1].contains("s");
553     }
554 
supportsTimesUsed(String[] path)555     private static boolean supportsTimesUsed(String[] path) {
556         return path.length > 1 && path[1].contains("t");
557     }
558 
checkColumnAccessible(Uri uri, String column)559     private void checkColumnAccessible(Uri uri, String column) {
560         try {
561             try (Cursor c = mResolver.query(
562                     uri, new String[]{column}, column + "=0", null, column
563             )) {
564                 c.moveToFirst();
565             }
566         } catch (Throwable th) {
567             addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th);
568         }
569     }
570 
571     /** Test for {@link #checkColumnAccessible} */
testCheckColumnAccessible()572     public void testCheckColumnAccessible() {
573         checkColumnAccessible(Contacts.CONTENT_URI, "x_times_contacted");
574         try {
575             failIfFailed();
576         } catch (AssertionFailedError expected) {
577             return; // expected.
578         }
579         fail("Failed to detect issue.");
580     }
581 
checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)582     private void checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection,
583             String[] selectionArgs, String sortOrder) {
584         try {
585             try (Cursor c = mResolver.query(uri, projection, selection,
586                     selectionArgs, sortOrder)) {
587                 c.moveToFirst();
588             }
589         } catch (IllegalArgumentException th) {
590             // pass.
591             return;
592         }
593         addFailure("Query on " + uri +
594                 " expected to throw IllegalArgumentException, but succeeded.", null);
595     }
596 
checkColumnNotAccessible(Uri uri, String column)597     private void checkColumnNotAccessible(Uri uri, String column) {
598         checkColumnNotAccessibleInner(uri, new String[] {column}, null, null, null);
599         checkColumnNotAccessibleInner(uri, null, column + "=1", null, null);
600         checkColumnNotAccessibleInner(uri, null, null, null, /* order by */ column);
601     }
602 
603     /** Test for {@link #checkColumnNotAccessible} */
testCheckColumnNotAccessible()604     public void testCheckColumnNotAccessible() {
605         checkColumnNotAccessible(Contacts.CONTENT_URI, "times_contacted");
606         try {
607             failIfFailed();
608         } catch (AssertionFailedError expected) {
609             return; // expected.
610         }
611         fail("Failed to detect issue.");
612     }
613 
614     /**
615      * Make sure the x_ columns are not accessible.
616      */
testProhibitedColumns()617     public void testProhibitedColumns() {
618         for (String[] path : URIs) {
619             final Uri uri = getUri(path);
620             if (supportsTimesContacted(path)) {
621                 checkColumnAccessible(uri, "times_contacted");
622                 checkColumnAccessible(uri, "last_time_contacted");
623 
624                 checkColumnNotAccessible(uri, "X_times_contacted");
625                 checkColumnNotAccessible(uri, "X_slast_time_contacted");
626             }
627             if (supportsTimesUsed(path)) {
628                 checkColumnAccessible(uri, "times_used");
629                 checkColumnAccessible(uri, "last_time_used");
630 
631                 checkColumnNotAccessible(uri, "X_times_used");
632                 checkColumnNotAccessible(uri, "X_last_time_used");
633             }
634         }
635         failIfFailed();
636     }
637 
checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r)638     private void checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r) {
639         if (shouldWork) {
640             try {
641                 r.run();
642             } catch (Exception e) {
643                 addFailure(operation + " for '" + uri + "' failed: " + e.getMessage(), e);
644             }
645         } else {
646             try {
647                 r.run();
648                 addFailure(operation + " for '" + uri + "' NOT failed.", null);
649             } catch (Exception expected) {
650             }
651         }
652     }
653 
testAllOperations()654     public void testAllOperations() {
655         final ContentValues cv = new ContentValues();
656 
657         for (String[] path : URIs) {
658             final Uri uri = getUri(path);
659 
660             try {
661                 cv.clear();
662                 if (supportsQuery(path)) {
663                     cv.put(getColumns(uri)[0], 1);
664                 } else {
665                     cv.put("_id", 1);
666                 }
667                 if (uri.toString().contains("syncstate")) {
668                     cv.put(SyncState.ACCOUNT_NAME, "abc");
669                     cv.put(SyncState.ACCOUNT_TYPE, "def");
670                 }
671 
672                 checkExecutable("insert", uri, supportsInsert(path), () -> {
673                     final Uri newUri = mResolver.insert(uri, cv);
674                     if (newUri == null) {
675                         addFailure("Insert for '" + uri + "' returned null.", null);
676                     } else {
677                         // "profile/raw_contacts/#" is missing update support.  too late to add, so
678                         // just skip.
679                         if (!newUri.toString().startsWith(
680                                 "content://com.android.contacts/profile/raw_contacts/")) {
681                             checkExecutable("insert -> update", newUri, true, () -> {
682                                 mResolver.update(newUri, cv, null, null);
683                             });
684                         }
685                         checkExecutable("insert -> delete", newUri, true, () -> {
686                             mResolver.delete(newUri, null, null);
687                         });
688                     }
689                 });
690                 checkExecutable("update", uri, supportsUpdate(path), () -> {
691                     mResolver.update(uri, cv, "1=2", null);
692                 });
693                 checkExecutable("delete", uri, supportsDelete(path), () -> {
694                     mResolver.delete(uri, "1=2", null);
695                 });
696             } catch (Throwable th) {
697                 addFailure("Failed: URI=" + uri + " Message=" + th.getMessage(), th);
698             }
699         }
700         failIfFailed();
701     }
702 
testAllFileOperations()703     public void testAllFileOperations() {
704         for (String[] path : URIs) {
705             final Uri uri = getUri(path);
706 
707             checkExecutable("openInputStream", uri, supportsRead(path), () -> {
708                 try (InputStream st = mResolver.openInputStream(uri)) {
709                 } catch (FileNotFoundException e) {
710                     // TODO This happens because we try to read nonexistent photos.  Ideally
711                     // we should actually check it's readable.
712                     if (e.getMessage().contains("Stream I/O not supported")) {
713                         throw new RuntimeException("Caught Exception: " + e.toString(), e);
714                     }
715                 } catch (Exception e) {
716                     throw new RuntimeException("Caught Exception: " + e.toString(), e);
717                 }
718             });
719             checkExecutable("openOutputStream", uri, supportsWrite(path), () -> {
720                 try (OutputStream st = mResolver.openOutputStream(uri)) {
721                 } catch (Exception e) {
722                     throw new RuntimeException("Caught Exception: " + e.toString(), e);
723                 }
724             });
725         }
726         failIfFailed();
727     }
728 }
729 
730 
731