• 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 
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