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