1 /*
2  * Copyright (C) 2008 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.content.cts;
18 
19 import static android.content.ContentResolver.NOTIFY_INSERT;
20 import static android.content.ContentResolver.NOTIFY_UPDATE;
21 
22 import android.accounts.Account;
23 import android.annotation.NonNull;
24 import android.annotation.UserIdInt;
25 import android.content.ContentProviderClient;
26 import android.content.ContentResolver;
27 import android.content.ContentResolver.MimeTypeInfo;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.content.res.AssetFileDescriptor;
33 import android.database.ContentObserver;
34 import android.database.Cursor;
35 import android.icu.text.Collator;
36 import android.icu.util.ULocale;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.CancellationSignal;
40 import android.os.OperationCanceledException;
41 import android.os.ParcelFileDescriptor;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.os.UserHandle;
45 import android.platform.test.annotations.AppModeFull;
46 import android.platform.test.annotations.AppModeSdkSandbox;
47 import android.test.AndroidTestCase;
48 import android.util.Log;
49 
50 import androidx.test.InstrumentationRegistry;
51 
52 import com.android.compatibility.common.util.PollingCheck;
53 import com.android.compatibility.common.util.ShellIdentityUtils;
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.util.ArrayUtils;
56 
57 import java.io.File;
58 import java.io.FileInputStream;
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.io.InputStreamReader;
63 import java.io.OutputStream;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Objects;
70 import java.util.Set;
71 import java.util.concurrent.CountDownLatch;
72 import java.util.concurrent.TimeUnit;
73 
74 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
75 public class ContentResolverTest extends AndroidTestCase {
76     private static final String TAG = "ContentResolverTest";
77 
78     private final static String COLUMN_ID_NAME = "_id";
79     private final static String COLUMN_KEY_NAME = "key";
80     private final static String COLUMN_VALUE_NAME = "value";
81 
82     private static final String AUTHORITY = "ctstest";
83     private static final Uri TABLE1_URI = Uri.parse("content://" + AUTHORITY + "/testtable1/");
84     private static final Uri TABLE1_CROSS_URI =
85             Uri.parse("content://" + AUTHORITY + "/testtable1/cross");
86     private static final Uri TABLE2_URI = Uri.parse("content://" + AUTHORITY + "/testtable2/");
87 
88     private static final Uri LEVEL1_URI = Uri.parse("content://" + AUTHORITY + "/level/");
89     private static final Uri LEVEL2_URI = Uri.parse("content://" + AUTHORITY + "/level/child");
90     private static final Uri LEVEL3_URI = Uri.parse("content://" + AUTHORITY
91             + "/level/child/grandchild/");
92 
93     private static final String REMOTE_AUTHORITY = "remotectstest";
94     private static final Uri REMOTE_TABLE1_URI = Uri.parse("content://"
95                 + REMOTE_AUTHORITY + "/testtable1/");
96     private static final Uri REMOTE_CRASH_URI = Uri.parse("content://"
97             + REMOTE_AUTHORITY + "/crash/");
98     private static final Uri REMOTE_HANG_URI = Uri.parse("content://"
99             + REMOTE_AUTHORITY + "/hang/");
100 
101     private static final String RESTRICTED_AUTHORITY = "restrictedctstest";
102     private static final Uri RESTRICTED_TABLE1_URI =
103             Uri.parse("content://" + RESTRICTED_AUTHORITY + "/testtable1/");
104     private static final Uri RESTRICTED_TABLE1_ITEM_URI =
105             Uri.parse("content://" + RESTRICTED_AUTHORITY + "/testtable1/1");
106 
107     private static final Account ACCOUNT = new Account("cts", "cts");
108 
109     private static final String KEY1 = "key1";
110     private static final String KEY2 = "key2";
111     private static final String KEY3 = "key3";
112     private static final int VALUE1 = 1;
113     private static final int VALUE2 = 2;
114     private static final int VALUE3 = 3;
115 
116     private static final String TEST_PACKAGE_NAME = "android.content.cts";
117 
118     private Context mContext;
119     private ContentResolver mContentResolver;
120     private Cursor mCursor;
121 
122     @Override
setUp()123     protected void setUp() throws Exception {
124         super.setUp();
125 
126         mContext = getContext();
127         mContentResolver = mContext.getContentResolver();
128 
129         MockContentProvider.setCrashOnLaunch(mContext, false);
130 
131         // add three rows to database when every test case start.
132         ContentValues values = new ContentValues();
133 
134         values.put(COLUMN_KEY_NAME, KEY1);
135         values.put(COLUMN_VALUE_NAME, VALUE1);
136         mContentResolver.insert(TABLE1_URI, values);
137         mContentResolver.insert(REMOTE_TABLE1_URI, values);
138 
139         values.put(COLUMN_KEY_NAME, KEY2);
140         values.put(COLUMN_VALUE_NAME, VALUE2);
141         mContentResolver.insert(TABLE1_URI, values);
142         mContentResolver.insert(REMOTE_TABLE1_URI, values);
143 
144         values.put(COLUMN_KEY_NAME, KEY3);
145         values.put(COLUMN_VALUE_NAME, VALUE3);
146         mContentResolver.insert(TABLE1_URI, values);
147         mContentResolver.insert(REMOTE_TABLE1_URI, values);
148     }
149 
150     @Override
tearDown()151     protected void tearDown() throws Exception {
152         InstrumentationRegistry.getInstrumentation().getUiAutomation()
153                 .dropShellPermissionIdentity();
154 
155         mContentResolver.delete(TABLE1_URI, null, null);
156         if ( null != mCursor && !mCursor.isClosed() ) {
157             mCursor.close();
158         }
159         mContentResolver.delete(REMOTE_TABLE1_URI, null, null);
160         if ( null != mCursor && !mCursor.isClosed() ) {
161             mCursor.close();
162         }
163         super.tearDown();
164     }
165 
testConstructor()166     public void testConstructor() {
167         assertNotNull(mContentResolver);
168     }
169 
testCrashOnLaunch()170     public void testCrashOnLaunch() {
171         // This test is going to make sure that the platform deals correctly
172         // with a content provider process going away while a client is waiting
173         // for it to come up.
174         // First, we need to make sure our provider process is gone.  Goodbye!
175         ContentProviderClient client = mContentResolver.acquireContentProviderClient(
176                 REMOTE_AUTHORITY);
177         // We are going to do something wrong here...  release the client first,
178         // so the act of killing it doesn't kill our own process.
179         client.release();
180         try {
181             client.delete(REMOTE_CRASH_URI, null, null);
182         } catch (RemoteException e) {
183         }
184         // Now make sure the thing is actually gone.
185         boolean gone = true;
186         try {
187             client.getType(REMOTE_TABLE1_URI);
188             gone = false;
189         } catch (RemoteException e) {
190         }
191         if (!gone) {
192             fail("Content provider process is not gone!");
193         }
194         try {
195             MockContentProvider.setCrashOnLaunch(mContext, true);
196             String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
197             assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
198             assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
199         } finally {
200             MockContentProvider.setCrashOnLaunch(mContext, false);
201         }
202     }
203 
testUnstableToStableRefs()204     public void testUnstableToStableRefs() {
205         // Get an unstable refrence on the remote content provider.
206         ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
207                 REMOTE_AUTHORITY);
208         // Verify we can access it.
209         String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
210         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
211 
212         // Get a stable reference on the remote content provider.
213         ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
214                 REMOTE_AUTHORITY);
215         // Verify we can still access it.
216         type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
217         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
218 
219         // Release unstable reference.
220         uClient.release();
221         // Verify we can still access it.
222         type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
223         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
224 
225         // Release stable reference, removing last ref.
226         sClient.release();
227         // Kill it.  Note that a bug at this point where it causes our own
228         // process to be killed will result in the entire test failing.
229         try {
230             Log.i("ContentResolverTest",
231                     "Killing remote client -- if test process goes away, that is why!");
232             uClient.delete(REMOTE_CRASH_URI, null, null);
233         } catch (RemoteException e) {
234         }
235         // Make sure the remote client is actually gone.
236         boolean gone = true;
237         try {
238             sClient.getType(REMOTE_TABLE1_URI);
239             gone = false;
240         } catch (RemoteException e) {
241         }
242         if (!gone) {
243             fail("Content provider process is not gone!");
244         }
245     }
246 
testStableToUnstableRefs()247     public void testStableToUnstableRefs() {
248         // Get a stable reference on the remote content provider.
249         ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
250                 REMOTE_AUTHORITY);
251         // Verify we can still access it.
252         String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
253         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
254 
255         // Get an unstable refrence on the remote content provider.
256         ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
257                 REMOTE_AUTHORITY);
258         // Verify we can access it.
259         type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
260         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
261 
262         // Release stable reference, leaving only an unstable ref.
263         sClient.release();
264 
265         // Kill it.  Note that a bug at this point where it causes our own
266         // process to be killed will result in the entire test failing.
267         try {
268             Log.i("ContentResolverTest",
269                     "Killing remote client -- if test process goes away, that is why!");
270             uClient.delete(REMOTE_CRASH_URI, null, null);
271         } catch (RemoteException e) {
272         }
273         // Make sure the remote client is actually gone.
274         boolean gone = true;
275         try {
276             uClient.getType(REMOTE_TABLE1_URI);
277             gone = false;
278         } catch (RemoteException e) {
279         }
280         if (!gone) {
281             fail("Content provider process is not gone!");
282         }
283 
284         // Release unstable reference.
285         uClient.release();
286     }
287 
testGetType()288     public void testGetType() {
289         String type1 = mContentResolver.getType(TABLE1_URI);
290         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
291 
292         String type2 = mContentResolver.getType(TABLE2_URI);
293         assertTrue(type2.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
294 
295         Uri invalidUri = Uri.parse("abc");
296         assertNull(mContentResolver.getType(invalidUri));
297 
298         try {
299             mContentResolver.getType(null);
300             fail("did not throw NullPointerException when Uri is null.");
301         } catch (NullPointerException e) {
302             //expected.
303         }
304     }
305 
306     @AppModeFull
testGetTypeAnonymous()307     public void testGetTypeAnonymous() {
308         String type1 = mContentResolver.getType(RESTRICTED_TABLE1_URI);
309         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
310         String type2 = mContentResolver.getType(RESTRICTED_TABLE1_ITEM_URI);
311         assertNull(type2);
312     }
313 
testUnstableGetType()314     public void testUnstableGetType() {
315         // Get an unstable refrence on the remote content provider.
316         ContentProviderClient client = mContentResolver.acquireUnstableContentProviderClient(
317                 REMOTE_AUTHORITY);
318         // Verify we can access it.
319         String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
320         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
321 
322         // Kill it.  Note that a bug at this point where it causes our own
323         // process to be killed will result in the entire test failing.
324         try {
325             Log.i("ContentResolverTest",
326                     "Killing remote client -- if test process goes away, that is why!");
327             client.delete(REMOTE_CRASH_URI, null, null);
328         } catch (RemoteException e) {
329         }
330         // Make sure the remote client is actually gone.
331         boolean gone = true;
332         try {
333             client.getType(REMOTE_TABLE1_URI);
334             gone = false;
335         } catch (RemoteException e) {
336         }
337         if (!gone) {
338             fail("Content provider process is not gone!");
339         }
340 
341         // Now the remote client is gone, can we recover?
342         // Release our old reference.
343         client.release();
344         // Get a new reference.
345         client = mContentResolver.acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
346         // Verify we can access it.
347         type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
348         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
349     }
350 
testQuery()351     public void testQuery() {
352         mCursor = mContentResolver.query(TABLE1_URI, null, null, null);
353 
354         assertNotNull(mCursor);
355         assertEquals(3, mCursor.getCount());
356         assertEquals(3, mCursor.getColumnCount());
357 
358         mCursor.moveToLast();
359         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
360         assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
361         assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
362 
363         mCursor.moveToPrevious();
364         assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
365         assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
366         assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
367         mCursor.close();
368     }
369 
testQuery_WithSqlSelectionArgs()370     public void testQuery_WithSqlSelectionArgs() {
371         Bundle queryArgs = new Bundle();
372         queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, COLUMN_ID_NAME + "=?");
373         queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[] {"1"});
374 
375         mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
376         assertNotNull(mCursor);
377         assertEquals(1, mCursor.getCount());
378         assertEquals(3, mCursor.getColumnCount());
379 
380         mCursor.moveToFirst();
381         assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
382         assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
383         assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
384         mCursor.close();
385 
386         queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, COLUMN_KEY_NAME + "=?");
387         queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[] {KEY3});
388         mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
389         assertNotNull(mCursor);
390         assertEquals(1, mCursor.getCount());
391         assertEquals(3, mCursor.getColumnCount());
392 
393         mCursor.moveToFirst();
394         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
395         assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
396         assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
397         mCursor.close();
398     }
399 
400     /*
401      * NOTE: this test is implicitly coupled to the implementation
402      * of MockContentProvider#query, specifically the facts:
403      *
404      * - it does *not* override the query w/ Bundle methods
405      * - it receives the auto-generated sql format arguments (supplied by the framework)
406      * - it is backed by sqlite and forwards the sql formatted args.
407      */
testQuery_SqlSortingFromBundleArgs()408     public void testQuery_SqlSortingFromBundleArgs() {
409 
410         mContentResolver.delete(TABLE1_URI, null, null);
411         ContentValues values = new ContentValues();
412 
413         values.put(COLUMN_KEY_NAME, "0");
414         values.put(COLUMN_VALUE_NAME, "abc");
415         mContentResolver.insert(TABLE1_URI, values);
416 
417         values.put(COLUMN_KEY_NAME, "1");
418         values.put(COLUMN_VALUE_NAME, "DEF");
419         mContentResolver.insert(TABLE1_URI, values);
420 
421         values.put(COLUMN_KEY_NAME, "2");
422         values.put(COLUMN_VALUE_NAME, "ghi");
423         mContentResolver.insert(TABLE1_URI, values);
424 
425         String[] sortCols = new String[] { COLUMN_VALUE_NAME };
426         Bundle queryArgs = new Bundle();
427         queryArgs.putStringArray(
428                 ContentResolver.QUERY_ARG_SORT_COLUMNS,
429                 sortCols);
430 
431         // Sort ascending...
432         queryArgs.putInt(
433                 ContentResolver.QUERY_ARG_SORT_DIRECTION,
434                 ContentResolver.QUERY_SORT_DIRECTION_ASCENDING);
435 
436         mCursor = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null);
437         int col = mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME);
438 
439         mCursor.moveToNext();
440         assertEquals("DEF", mCursor.getString(col));
441         mCursor.moveToNext();
442         assertEquals("abc", mCursor.getString(col));
443         mCursor.moveToNext();
444         assertEquals("ghi", mCursor.getString(col));
445 
446         mCursor.close();
447 
448         // Nocase collation, descending...
449         queryArgs.putInt(
450                 ContentResolver.QUERY_ARG_SORT_DIRECTION,
451                 ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
452         queryArgs.putInt(
453                 ContentResolver.QUERY_ARG_SORT_COLLATION,
454                 java.text.Collator.SECONDARY);
455 
456         mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
457         col = mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME);
458 
459         mCursor.moveToNext();
460         assertEquals("ghi", mCursor.getString(col));
461         mCursor.moveToNext();
462         assertEquals("DEF", mCursor.getString(col));
463         mCursor.moveToNext();
464         assertEquals("abc", mCursor.getString(col));
465 
466         mCursor.close();
467     }
468 
testQuery_SqlSortingFromBundleArgs_Locale()469     public void testQuery_SqlSortingFromBundleArgs_Locale() {
470         mContentResolver.delete(TABLE1_URI, null, null);
471 
472         final List<String> data = Arrays.asList(
473                 "ABC", "abc", "pinyin", "가나다", "바사", "테스트", "马",
474                 "嘛", "妈", "骂", "吗", "码", "玛", "麻", "中", "梵", "苹果", "久了", "伺候");
475 
476         for (String s : data) {
477             final ContentValues values = new ContentValues();
478             values.put(COLUMN_KEY_NAME, s.hashCode());
479             values.put(COLUMN_VALUE_NAME, s);
480             mContentResolver.insert(TABLE1_URI, values);
481         }
482 
483         String[] sortCols = new String[] { COLUMN_VALUE_NAME };
484         Bundle queryArgs = new Bundle();
485         queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, sortCols);
486 
487         for (String locale : new String[] {
488                 "zh",
489                 "zh@collation=pinyin",
490                 "zh@collation=stroke",
491                 "zh@collation=zhuyin",
492         }) {
493             // Assert that sorting is identical between SQLite and ICU4J
494             queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
495             try (Cursor c = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null)) {
496                 data.sort(Collator.getInstance(new ULocale(locale)));
497                 assertEquals(data, collect(c));
498             }
499         }
500     }
501 
collect(Cursor c)502     private static List<String> collect(Cursor c) {
503         List<String> res = new ArrayList<>();
504         while (c.moveToNext()) {
505             res.add(c.getString(0));
506         }
507         return res;
508     }
509 
510     /**
511      * Verifies that paging information is correctly relayed, and that
512      * honored arguments from a supporting client are returned correctly.
513      */
testQuery_PagedResults()514     public void testQuery_PagedResults() {
515 
516         Bundle queryArgs = new Bundle();
517         queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 10);
518         queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 3);
519         queryArgs.putInt(TestPagingContentProvider.RECORD_COUNT, 100);
520 
521         mCursor = mContentResolver.query(
522                 TestPagingContentProvider.PAGED_DATA_URI, null, queryArgs, null);
523 
524         Bundle extras = mCursor.getExtras();
525         extras = extras != null ? extras : Bundle.EMPTY;
526 
527         assertEquals(3, mCursor.getCount());
528         assertTrue(extras.containsKey(ContentResolver.EXTRA_TOTAL_COUNT));
529         assertEquals(100, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
530 
531         String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
532         assertNotNull(honoredArgs);
533         assertTrue(ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
534         assertTrue(ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
535 
536         int col = mCursor.getColumnIndexOrThrow(TestPagingContentProvider.COLUMN_POS);
537 
538         mCursor.moveToNext();
539         assertEquals(10, mCursor.getInt(col));
540         mCursor.moveToNext();
541         assertEquals(11, mCursor.getInt(col));
542         mCursor.moveToNext();
543         assertEquals(12, mCursor.getInt(col));
544 
545         assertFalse(mCursor.moveToNext());
546 
547         mCursor.close();
548     }
549 
testQuery_NullUriThrows()550     public void testQuery_NullUriThrows() {
551         try {
552             mContentResolver.query(null, null, null, null, null);
553             fail("did not throw NullPointerException when uri is null.");
554         } catch (NullPointerException e) {
555             //expected.
556         }
557     }
558 
testCrashingQuery()559     public void testCrashingQuery() {
560         try {
561             MockContentProvider.setCrashOnLaunch(mContext, true);
562             mCursor = mContentResolver.query(REMOTE_CRASH_URI, null, null, null, null);
563             assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
564         } finally {
565             MockContentProvider.setCrashOnLaunch(mContext, false);
566         }
567 
568         assertNotNull(mCursor);
569         assertEquals(3, mCursor.getCount());
570         assertEquals(3, mCursor.getColumnCount());
571 
572         mCursor.moveToLast();
573         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
574         assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
575         assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
576 
577         mCursor.moveToPrevious();
578         assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
579         assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
580         assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
581         mCursor.close();
582     }
583 
testCancelableQuery_WhenNotCanceled_ReturnsResultSet()584     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
585         CancellationSignal cancellationSignal = new CancellationSignal();
586 
587         Cursor cursor = mContentResolver.query(TABLE1_URI, null, null, null, null,
588                 cancellationSignal);
589         assertEquals(3, cursor.getCount());
590         cursor.close();
591     }
592 
testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately()593     public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
594         CancellationSignal cancellationSignal = new CancellationSignal();
595         cancellationSignal.cancel();
596 
597         try {
598             mContentResolver.query(TABLE1_URI, null, null, null, null, cancellationSignal);
599             fail("Expected OperationCanceledException");
600         } catch (OperationCanceledException ex) {
601             // expected
602         }
603     }
604 
testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows()605     public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
606         // Populate a table with a bunch of integers.
607         mContentResolver.delete(TABLE1_URI, null, null);
608         ContentValues values = new ContentValues();
609         for (int i = 0; i < 100; i++) {
610             values.put(COLUMN_KEY_NAME, i);
611             values.put(COLUMN_VALUE_NAME, i);
612             mContentResolver.insert(TABLE1_URI, values);
613         }
614 
615         for (int i = 0; i < 5; i++) {
616             final CancellationSignal cancellationSignal = new CancellationSignal();
617             Thread cancellationThread = new Thread() {
618                 @Override
619                 public void run() {
620                     try {
621                         Thread.sleep(300);
622                     } catch (InterruptedException ex) {
623                     }
624                     cancellationSignal.cancel();
625                 }
626             };
627             try {
628                 // Build an unsatisfiable 5-way cross-product query over 100 values but
629                 // produces no output.  This should force SQLite to loop for a long time
630                 // as it tests 10^10 combinations.
631                 cancellationThread.start();
632 
633                 final long startTime = System.nanoTime();
634                 try {
635                     mContentResolver.query(TABLE1_CROSS_URI, null,
636                             "a.value + b.value + c.value + d.value + e.value > 1000000",
637                             null, null, cancellationSignal);
638                     fail("Expected OperationCanceledException");
639                 } catch (OperationCanceledException ex) {
640                     // expected
641                 }
642 
643                 // We want to confirm that the query really was running and then got
644                 // canceled midway.
645                 final long waitTime = System.nanoTime() - startTime;
646                 if (waitTime > 150 * 1000000L && waitTime < 600 * 1000000L) {
647                     return; // success!
648                 }
649             } finally {
650                 try {
651                     cancellationThread.join();
652                 } catch (InterruptedException e) {
653                 }
654             }
655         }
656 
657         // Occasionally we might miss the timing deadline due to factors in the
658         // environment, but if after several trials we still couldn't demonstrate
659         // that the query was canceled, then the test must be broken.
660         fail("Could not prove that the query actually canceled midway during execution.");
661     }
662 
testOpenInputStream()663     public void testOpenInputStream() throws IOException {
664         final Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
665                 "://" + TEST_PACKAGE_NAME + "/" + R.drawable.pass);
666 
667         InputStream is = mContentResolver.openInputStream(uri);
668         assertNotNull(is);
669         is.close();
670 
671         final Uri invalidUri = Uri.parse("abc");
672         try {
673             mContentResolver.openInputStream(invalidUri);
674             fail("did not throw FileNotFoundException when uri is invalid.");
675         } catch (FileNotFoundException e) {
676             //expected.
677         }
678     }
679 
testOpenOutputStream()680     public void testOpenOutputStream() throws IOException {
681         Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
682                 getContext().getCacheDir().getAbsolutePath() +
683                 "/temp.jpg");
684         OutputStream os = mContentResolver.openOutputStream(uri);
685         assertNotNull(os);
686         os.close();
687 
688         os = mContentResolver.openOutputStream(uri, "wa");
689         assertNotNull(os);
690         os.close();
691 
692         uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
693                 "://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
694         try {
695             mContentResolver.openOutputStream(uri);
696             fail("did not throw FileNotFoundException when scheme is not accepted.");
697         } catch (FileNotFoundException e) {
698             //expected.
699         }
700 
701         try {
702             mContentResolver.openOutputStream(uri, "w");
703             fail("did not throw FileNotFoundException when scheme is not accepted.");
704         } catch (FileNotFoundException e) {
705             //expected.
706         }
707 
708         Uri invalidUri = Uri.parse("abc");
709         try {
710             mContentResolver.openOutputStream(invalidUri);
711             fail("did not throw FileNotFoundException when uri is invalid.");
712         } catch (FileNotFoundException e) {
713             //expected.
714         }
715 
716         try {
717             mContentResolver.openOutputStream(invalidUri, "w");
718             fail("did not throw FileNotFoundException when uri is invalid.");
719         } catch (FileNotFoundException e) {
720             //expected.
721         }
722     }
723 
testOpenAssetFileDescriptor()724     public void testOpenAssetFileDescriptor() throws IOException {
725         Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
726                 "://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
727 
728         AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(uri, "r");
729         assertNotNull(afd);
730         afd.close();
731 
732         try {
733             mContentResolver.openAssetFileDescriptor(uri, "d");
734             fail("did not throw FileNotFoundException when mode is unknown.");
735         } catch (FileNotFoundException e) {
736             //expected.
737         }
738 
739         Uri invalidUri = Uri.parse("abc");
740         try {
741             mContentResolver.openAssetFileDescriptor(invalidUri, "r");
742             fail("did not throw FileNotFoundException when uri is invalid.");
743         } catch (FileNotFoundException e) {
744             //expected.
745         }
746     }
747 
consumeAssetFileDescriptor(AssetFileDescriptor afd)748     private String consumeAssetFileDescriptor(AssetFileDescriptor afd)
749             throws IOException {
750         FileInputStream stream = null;
751         try {
752             stream = afd.createInputStream();
753             InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
754 
755             // Got it...  copy the stream into a local string and return it.
756             StringBuilder builder = new StringBuilder(128);
757             char[] buffer = new char[8192];
758             int len;
759             while ((len=reader.read(buffer)) > 0) {
760                 builder.append(buffer, 0, len);
761             }
762             return builder.toString();
763 
764         } finally {
765             if (stream != null) {
766                 try {
767                     stream.close();
768                 } catch (IOException e) {
769                 }
770             }
771         }
772 
773     }
774 
testCrashingOpenAssetFileDescriptor()775     public void testCrashingOpenAssetFileDescriptor() throws IOException {
776         AssetFileDescriptor afd = null;
777         try {
778             MockContentProvider.setCrashOnLaunch(mContext, true);
779             afd = mContentResolver.openAssetFileDescriptor(REMOTE_CRASH_URI, "rw");
780             assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
781             assertNotNull(afd);
782             String str = consumeAssetFileDescriptor(afd);
783             afd = null;
784             assertEquals(str, "This is the openAssetFile test data!");
785         } finally {
786             MockContentProvider.setCrashOnLaunch(mContext, false);
787             if (afd != null) {
788                 afd.close();
789             }
790         }
791 
792         // Make sure a content provider crash at this point won't hurt us.
793         ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
794                 REMOTE_AUTHORITY);
795         // Kill it.  Note that a bug at this point where it causes our own
796         // process to be killed will result in the entire test failing.
797         try {
798             Log.i("ContentResolverTest",
799                     "Killing remote client -- if test process goes away, that is why!");
800             uClient.delete(REMOTE_CRASH_URI, null, null);
801         } catch (RemoteException e) {
802         }
803         uClient.release();
804     }
805 
testCrashingOpenTypedAssetFileDescriptor()806     public void testCrashingOpenTypedAssetFileDescriptor() throws IOException {
807         AssetFileDescriptor afd = null;
808         try {
809             MockContentProvider.setCrashOnLaunch(mContext, true);
810             afd = mContentResolver.openTypedAssetFileDescriptor(
811                     REMOTE_CRASH_URI, "text/plain", null);
812             assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
813             assertNotNull(afd);
814             String str = consumeAssetFileDescriptor(afd);
815             afd = null;
816             assertEquals(str, "This is the openTypedAssetFile test data!");
817         } finally {
818             MockContentProvider.setCrashOnLaunch(mContext, false);
819             if (afd != null) {
820                 afd.close();
821             }
822         }
823 
824         // Make sure a content provider crash at this point won't hurt us.
825         ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
826                 REMOTE_AUTHORITY);
827         // Kill it.  Note that a bug at this point where it causes our own
828         // process to be killed will result in the entire test failing.
829         try {
830             Log.i("ContentResolverTest",
831                     "Killing remote client -- if test process goes away, that is why!");
832             uClient.delete(REMOTE_CRASH_URI, null, null);
833         } catch (RemoteException e) {
834         }
835         uClient.release();
836     }
837 
testOpenFileDescriptor()838     public void testOpenFileDescriptor() throws IOException {
839         Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
840                 getContext().getCacheDir().getAbsolutePath() +
841                 "/temp.jpg");
842         ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(uri, "w");
843         assertNotNull(pfd);
844         pfd.close();
845 
846         try {
847             mContentResolver.openFileDescriptor(uri, "d");
848             fail("did not throw IllegalArgumentException when mode is unknown.");
849         } catch (IllegalArgumentException e) {
850             //expected.
851         }
852 
853         Uri invalidUri = Uri.parse("abc");
854         try {
855             mContentResolver.openFileDescriptor(invalidUri, "w");
856             fail("did not throw FileNotFoundException when uri is invalid.");
857         } catch (FileNotFoundException e) {
858             //expected.
859         }
860 
861         uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
862                 "://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
863         try {
864             mContentResolver.openFileDescriptor(uri, "w");
865             fail("did not throw FileNotFoundException when scheme is not accepted.");
866         } catch (FileNotFoundException e) {
867             //expected.
868         }
869     }
870 
testInsert()871     public void testInsert() {
872         String key4 = "key4";
873         String key5 = "key5";
874         int value4 = 4;
875         int value5 = 5;
876         String key4Selection = COLUMN_KEY_NAME + "=\"" + key4 + "\"";
877 
878         mCursor = mContentResolver.query(TABLE1_URI, null, key4Selection, null, null);
879         assertEquals(0, mCursor.getCount());
880         mCursor.close();
881 
882         ContentValues values = new ContentValues();
883         values.put(COLUMN_KEY_NAME, key4);
884         values.put(COLUMN_VALUE_NAME, value4);
885         Uri uri = mContentResolver.insert(TABLE1_URI, values);
886         assertNotNull(uri);
887 
888         mCursor = mContentResolver.query(TABLE1_URI, null, key4Selection, null, null);
889         assertNotNull(mCursor);
890         assertEquals(1, mCursor.getCount());
891 
892         mCursor.moveToFirst();
893         assertEquals(4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
894         assertEquals(key4, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
895         assertEquals(value4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
896         mCursor.close();
897 
898         values.put(COLUMN_KEY_NAME, key5);
899         values.put(COLUMN_VALUE_NAME, value5);
900         uri = mContentResolver.insert(TABLE1_URI, values);
901         assertNotNull(uri);
902 
903         // check returned uri
904         mCursor = mContentResolver.query(uri, null, null, null, null);
905         assertNotNull(mCursor);
906         assertEquals(1, mCursor.getCount());
907 
908         mCursor.moveToLast();
909         assertEquals(5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
910         assertEquals(key5, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
911         assertEquals(value5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
912         mCursor.close();
913 
914         try {
915             mContentResolver.insert(null, values);
916             fail("did not throw NullPointerException when uri is null.");
917         } catch (NullPointerException e) {
918             //expected.
919         }
920     }
921 
testBulkInsert()922     public void testBulkInsert() {
923         String key4 = "key4";
924         String key5 = "key5";
925         int value4 = 4;
926         int value5 = 5;
927 
928         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
929         assertNotNull(mCursor);
930         assertEquals(3, mCursor.getCount());
931         mCursor.close();
932 
933         ContentValues[] cvs = new ContentValues[2];
934         cvs[0] = new ContentValues();
935         cvs[0].put(COLUMN_KEY_NAME, key4);
936         cvs[0].put(COLUMN_VALUE_NAME, value4);
937 
938         cvs[1] = new ContentValues();
939         cvs[1].put(COLUMN_KEY_NAME, key5);
940         cvs[1].put(COLUMN_VALUE_NAME, value5);
941 
942         assertEquals(2, mContentResolver.bulkInsert(TABLE1_URI, cvs));
943         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
944         assertNotNull(mCursor);
945         assertEquals(5, mCursor.getCount());
946 
947         mCursor.moveToLast();
948         assertEquals(5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
949         assertEquals(key5, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
950         assertEquals(value5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
951 
952         mCursor.moveToPrevious();
953         assertEquals(4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
954         assertEquals(key4, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
955         assertEquals(value4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
956         mCursor.close();
957 
958         try {
959             mContentResolver.bulkInsert(null, cvs);
960             fail("did not throw NullPointerException when uri is null.");
961         } catch (NullPointerException e) {
962             //expected.
963         }
964     }
965 
testDelete()966     public void testDelete() {
967         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
968         assertNotNull(mCursor);
969         assertEquals(3, mCursor.getCount());
970         mCursor.close();
971 
972         assertEquals(3, mContentResolver.delete(TABLE1_URI, null, null));
973         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
974         assertNotNull(mCursor);
975         assertEquals(0, mCursor.getCount());
976         mCursor.close();
977 
978         // add three rows to database.
979         ContentValues values = new ContentValues();
980         values.put(COLUMN_KEY_NAME, KEY1);
981         values.put(COLUMN_VALUE_NAME, VALUE1);
982         mContentResolver.insert(TABLE1_URI, values);
983 
984         values.put(COLUMN_KEY_NAME, KEY2);
985         values.put(COLUMN_VALUE_NAME, VALUE2);
986         mContentResolver.insert(TABLE1_URI, values);
987 
988         values.put(COLUMN_KEY_NAME, KEY3);
989         values.put(COLUMN_VALUE_NAME, VALUE3);
990         mContentResolver.insert(TABLE1_URI, values);
991 
992         // test delete row using selection
993         String selection = COLUMN_ID_NAME + "=2";
994         assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
995 
996         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
997         assertNotNull(mCursor);
998         assertEquals(2, mCursor.getCount());
999 
1000         mCursor.moveToFirst();
1001         assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1002         assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1003         assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1004 
1005         mCursor.moveToNext();
1006         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1007         assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1008         assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1009         mCursor.close();
1010 
1011         selection = COLUMN_VALUE_NAME + "=3";
1012         assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
1013 
1014         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
1015         assertNotNull(mCursor);
1016         assertEquals(1, mCursor.getCount());
1017 
1018         mCursor.moveToFirst();
1019         assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1020         assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1021         assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1022         mCursor.close();
1023 
1024         selection = COLUMN_KEY_NAME + "=\"" + KEY1 + "\"";
1025         assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
1026 
1027         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
1028         assertNotNull(mCursor);
1029         assertEquals(0, mCursor.getCount());
1030         mCursor.close();
1031 
1032         try {
1033             mContentResolver.delete(null, null, null);
1034             fail("did not throw NullPointerException when uri is null.");
1035         } catch (NullPointerException e) {
1036             //expected.
1037         }
1038     }
1039 
testUpdate()1040     public void testUpdate() {
1041         ContentValues values = new ContentValues();
1042         String key10 = "key10";
1043         String key20 = "key20";
1044         int value10 = 10;
1045         int value20 = 20;
1046 
1047         values.put(COLUMN_KEY_NAME, key10);
1048         values.put(COLUMN_VALUE_NAME, value10);
1049 
1050         // test update all the rows.
1051         assertEquals(3, mContentResolver.update(TABLE1_URI, values, null, null));
1052         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
1053         assertNotNull(mCursor);
1054         assertEquals(3, mCursor.getCount());
1055 
1056         mCursor.moveToFirst();
1057         assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1058         assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1059         assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1060 
1061         mCursor.moveToNext();
1062         assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1063         assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1064         assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1065 
1066         mCursor.moveToLast();
1067         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1068         assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1069         assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1070         mCursor.close();
1071 
1072         // test update one row using selection.
1073         String selection = COLUMN_ID_NAME + "=1";
1074         values.put(COLUMN_KEY_NAME, key20);
1075         values.put(COLUMN_VALUE_NAME, value20);
1076 
1077         assertEquals(1, mContentResolver.update(TABLE1_URI, values, selection, null));
1078         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
1079         assertNotNull(mCursor);
1080         assertEquals(3, mCursor.getCount());
1081 
1082         mCursor.moveToFirst();
1083         assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1084         assertEquals(key20, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1085         assertEquals(value20, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1086 
1087         mCursor.moveToNext();
1088         assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1089         assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1090         assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1091 
1092         mCursor.moveToLast();
1093         assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
1094         assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
1095         assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
1096         mCursor.close();
1097 
1098         try {
1099             mContentResolver.update(null, values, null, null);
1100             fail("did not throw NullPointerException when uri is null.");
1101         } catch (NullPointerException e) {
1102             //expected.
1103         }
1104 
1105         try {
1106             mContentResolver.update(TABLE1_URI, null, null, null);
1107             fail("did not throw required exception when values are null.");
1108         } catch (Exception e) {
1109             // If this test is running in an SDK sandbox instead of a regular app, the
1110             // ContentProvider runs in the app process. When the SDK sandbox interacts with the
1111             // ContentProvider, and values are null, an NPE is thrown in ContentProviderNative. In
1112             // apps however, since there is no IPC, this does not happen and an
1113             // IllegalArgumentException is thrown instead when values are null.
1114             Class<?> expectedErrorType =
1115                     Process.isSdkSandbox()
1116                             ? NullPointerException.class
1117                             : IllegalArgumentException.class;
1118             assertEquals(e.getClass(), expectedErrorType);
1119         }
1120     }
1121 
testRefresh_DefaultImplReturnsFalse()1122     public void testRefresh_DefaultImplReturnsFalse() {
1123         boolean refreshed = mContentResolver.refresh(TABLE1_URI, null, null);
1124         assertFalse(refreshed);
1125         MockContentProvider.assertRefreshed(TABLE1_URI);
1126     }
1127 
testRefresh_ReturnsProviderValue()1128     public void testRefresh_ReturnsProviderValue() {
1129         try {
1130             MockContentProvider.setRefreshReturnValue(true);
1131             boolean refreshed = mContentResolver.refresh(TABLE1_URI, null, null);
1132             assertTrue(refreshed);
1133             MockContentProvider.assertRefreshed(TABLE1_URI);
1134         } finally {
1135             MockContentProvider.setRefreshReturnValue(false);
1136         }
1137     }
1138 
testRefresh_NullUriThrowsImmediately()1139     public void testRefresh_NullUriThrowsImmediately() {
1140         try {
1141             mContentResolver.refresh(null, null, null);
1142             fail("did not throw NullPointerException when uri is null.");
1143         } catch (NullPointerException e) {
1144             //expected.
1145         }
1146     }
1147 
testRefresh_CancellableThrowsImmediately()1148     public void testRefresh_CancellableThrowsImmediately() {
1149         CancellationSignal cancellationSignal = new CancellationSignal();
1150         cancellationSignal.cancel();
1151 
1152         try {
1153             mContentResolver.refresh(TABLE1_URI, null, cancellationSignal);
1154             fail("Expected OperationCanceledException");
1155         } catch (OperationCanceledException ex) {
1156             // expected
1157         }
1158     }
1159 
testCheckUriPermission()1160     public void testCheckUriPermission() {
1161         assertEquals(PackageManager.PERMISSION_GRANTED, mContentResolver.checkUriPermission(
1162                 TABLE1_URI, android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION));
1163         assertEquals(PackageManager.PERMISSION_DENIED, mContentResolver.checkUriPermission(
1164                 TABLE1_URI, android.os.Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
1165     }
1166 
testRegisterContentObserver()1167     public void testRegisterContentObserver() {
1168         final MockContentObserver mco = new MockContentObserver();
1169 
1170         mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
1171         assertFalse(mco.hadOnChanged());
1172 
1173         ContentValues values = new ContentValues();
1174         values.put(COLUMN_KEY_NAME, "key10");
1175         values.put(COLUMN_VALUE_NAME, 10);
1176         mContentResolver.update(TABLE1_URI, values, null, null);
1177         new PollingCheck() {
1178             @Override
1179             protected boolean check() {
1180                 return mco.hadOnChanged();
1181             }
1182         }.run();
1183 
1184         mco.reset();
1185         mContentResolver.unregisterContentObserver(mco);
1186         assertFalse(mco.hadOnChanged());
1187         mContentResolver.update(TABLE1_URI, values, null, null);
1188 
1189         assertFalse(mco.hadOnChanged());
1190 
1191         try {
1192             mContentResolver.registerContentObserver(null, false, mco);
1193             fail("did not throw NullPointerException or IllegalArgumentException when uri is null.");
1194         } catch (NullPointerException e) {
1195             //expected.
1196         } catch (IllegalArgumentException e) {
1197             // also expected
1198         }
1199 
1200         try {
1201             mContentResolver.registerContentObserver(TABLE1_URI, false, null);
1202             fail("did not throw NullPointerException when register null content observer.");
1203         } catch (NullPointerException e) {
1204             //expected.
1205         }
1206 
1207         try {
1208             mContentResolver.unregisterContentObserver(null);
1209             fail("did not throw NullPointerException when unregister null content observer.");
1210         } catch (NullPointerException e) {
1211             //expected.
1212         }
1213     }
1214     // Tests registerContentObserverForAllUsers without INTERACT_ACROSS_USERS_FULL: verify
1215     // SecurityException.
testRegisterContentObserverForAllUsersWithoutPermission()1216     public void testRegisterContentObserverForAllUsersWithoutPermission() {
1217         final MockContentObserver mco = new MockContentObserver();
1218         try {
1219             mContentResolver.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL);
1220             fail("testRegisterContentObserverForAllUsers: "
1221                     + "SecurityException expected on testRegisterContentObserverForAllUsers");
1222         } catch (SecurityException se) {
1223             // expected
1224         }
1225     }
1226 
testRegisterContentObserverAsUser()1227     public void testRegisterContentObserverAsUser() {
1228         final MockContentObserver mco = new MockContentObserver();
1229 
1230         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1231                 mContentResolver,
1232                 (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, mContext.getUser())
1233         );
1234         assertFalse(mco.hadOnChanged());
1235 
1236         ContentValues values = new ContentValues();
1237         values.put(COLUMN_KEY_NAME, "key10");
1238         values.put(COLUMN_VALUE_NAME, 10);
1239         mContentResolver.update(TABLE1_URI, values, null, null);
1240         new PollingCheck() {
1241             @Override
1242             protected boolean check() {
1243                 return mco.hadOnChanged();
1244             }
1245         }.run();
1246 
1247         mco.reset();
1248         mContentResolver.unregisterContentObserver(mco);
1249         assertFalse(mco.hadOnChanged());
1250         mContentResolver.update(TABLE1_URI, values, null, null);
1251 
1252         assertFalse(mco.hadOnChanged());
1253     }
1254 
testRegisterContentObserverForAllUsers()1255     public void testRegisterContentObserverForAllUsers() {
1256         final MockContentObserver mco = new MockContentObserver();
1257 
1258         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1259                 mContentResolver,
1260                 (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL)
1261         );
1262         assertFalse(mco.hadOnChanged());
1263 
1264         ContentValues values = new ContentValues();
1265         values.put(COLUMN_KEY_NAME, "key10");
1266         values.put(COLUMN_VALUE_NAME, 10);
1267         mContentResolver.update(TABLE1_URI, values, null, null);
1268         new PollingCheck() {
1269             @Override
1270             protected boolean check() {
1271                 return mco.hadOnChanged();
1272             }
1273         }.run();
1274 
1275         mco.reset();
1276         mContentResolver.unregisterContentObserver(mco);
1277         assertFalse(mco.hadOnChanged());
1278         mContentResolver.update(TABLE1_URI, values, null, null);
1279 
1280         assertFalse(mco.hadOnChanged());
1281 
1282         try {
1283             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1284                     mContentResolver,
1285                     (cr) -> cr.registerContentObserverAsUser(null, false, mco, UserHandle.ALL)
1286             );
1287             fail("did not throw NullPointerException or IllegalArgumentException when uri is null"
1288                     + ".");
1289         } catch (NullPointerException e) {
1290             //expected.
1291         } catch (IllegalArgumentException e) {
1292             // also expected
1293         }
1294 
1295         try {
1296             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1297                     mContentResolver,
1298                     (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, false, null,
1299                             UserHandle.ALL)
1300             );
1301             fail("did not throw NullPointerException when register null content observer.");
1302         } catch (NullPointerException e) {
1303             //expected.
1304         }
1305 
1306         try {
1307             mContentResolver.unregisterContentObserver(null);
1308             fail("did not throw NullPointerException when unregister null content observer.");
1309         } catch (NullPointerException e) {
1310             //expected.
1311         }
1312     }
1313 
testRegisterContentObserverDescendantBehavior()1314     public void testRegisterContentObserverDescendantBehavior() throws Exception {
1315         final MockContentObserver mco1 = new MockContentObserver();
1316         final MockContentObserver mco2 = new MockContentObserver();
1317 
1318         // Register one content observer with notifyDescendants set to false, and
1319         // another with true.
1320         mContentResolver.registerContentObserver(LEVEL2_URI, false, mco1);
1321         mContentResolver.registerContentObserver(LEVEL2_URI, true, mco2);
1322 
1323         // Initially nothing has happened.
1324         assertFalse(mco1.hadOnChanged());
1325         assertFalse(mco2.hadOnChanged());
1326 
1327         // Fire a change with the exact URI.
1328         // Should signal both observers due to exact match, notifyDescendants doesn't matter.
1329         mContentResolver.notifyChange(LEVEL2_URI, null);
1330         Thread.sleep(200);
1331         assertTrue(mco1.hadOnChanged());
1332         assertTrue(mco2.hadOnChanged());
1333         mco1.reset();
1334         mco2.reset();
1335 
1336         // Fire a change with a descendant URI.
1337         // Should only signal observer with notifyDescendants set to true.
1338         mContentResolver.notifyChange(LEVEL3_URI, null);
1339         Thread.sleep(200);
1340         assertFalse(mco1.hadOnChanged());
1341         assertTrue(mco2.hadOnChanged());
1342         mco2.reset();
1343 
1344         // Fire a change with an ancestor URI.
1345         // Should signal both observers due to ancestry, notifyDescendants doesn't matter.
1346         mContentResolver.notifyChange(LEVEL1_URI, null);
1347         Thread.sleep(200);
1348         assertTrue(mco1.hadOnChanged());
1349         assertTrue(mco2.hadOnChanged());
1350         mco1.reset();
1351         mco2.reset();
1352 
1353         // Fire a change with an unrelated URI.
1354         // Should signal neither observer.
1355         mContentResolver.notifyChange(TABLE1_URI, null);
1356         Thread.sleep(200);
1357         assertFalse(mco1.hadOnChanged());
1358         assertFalse(mco2.hadOnChanged());
1359     }
1360 
testRegisterContentObserverForAllUsersDescendantBehavior()1361     public void testRegisterContentObserverForAllUsersDescendantBehavior() throws Exception {
1362         final MockContentObserver mco1 = new MockContentObserver();
1363         final MockContentObserver mco2 = new MockContentObserver();
1364 
1365         // Register one content observer with notifyDescendants set to false, and
1366         // another with true.
1367         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1368                 mContentResolver,
1369                 (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, false, mco1, UserHandle.ALL)
1370         );
1371         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
1372                 mContentResolver,
1373                 (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, true, mco2, UserHandle.ALL)
1374         );
1375 
1376         // Initially nothing has happened.
1377         assertFalse(mco1.hadOnChanged());
1378         assertFalse(mco2.hadOnChanged());
1379 
1380         // Fire a change with the exact URI.
1381         // Should signal both observers due to exact match, notifyDescendants doesn't matter.
1382         mContentResolver.notifyChange(LEVEL2_URI, null);
1383         Thread.sleep(200);
1384         assertTrue(mco1.hadOnChanged());
1385         assertTrue(mco2.hadOnChanged());
1386         mco1.reset();
1387         mco2.reset();
1388 
1389         // Fire a change with a descendant URI.
1390         // Should only signal observer with notifyDescendants set to true.
1391         mContentResolver.notifyChange(LEVEL3_URI, null);
1392         Thread.sleep(200);
1393         assertFalse(mco1.hadOnChanged());
1394         assertTrue(mco2.hadOnChanged());
1395         mco2.reset();
1396 
1397         // Fire a change with an ancestor URI.
1398         // Should signal both observers due to ancestry, notifyDescendants doesn't matter.
1399         mContentResolver.notifyChange(LEVEL1_URI, null);
1400         Thread.sleep(200);
1401         assertTrue(mco1.hadOnChanged());
1402         assertTrue(mco2.hadOnChanged());
1403         mco1.reset();
1404         mco2.reset();
1405 
1406         // Fire a change with an unrelated URI.
1407         // Should signal neither observer.
1408         mContentResolver.notifyChange(TABLE1_URI, null);
1409         Thread.sleep(200);
1410         assertFalse(mco1.hadOnChanged());
1411         assertFalse(mco2.hadOnChanged());
1412     }
1413 
testNotifyChange1()1414     public void testNotifyChange1() {
1415         final MockContentObserver mco = new MockContentObserver();
1416 
1417         mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
1418         assertFalse(mco.hadOnChanged());
1419 
1420         mContentResolver.notifyChange(TABLE1_URI, mco);
1421         new PollingCheck() {
1422             @Override
1423             protected boolean check() {
1424                 return mco.hadOnChanged();
1425             }
1426         }.run();
1427 
1428         mContentResolver.unregisterContentObserver(mco);
1429     }
1430 
testNotifyChange2()1431     public void testNotifyChange2() {
1432         final MockContentObserver mco = new MockContentObserver();
1433 
1434         mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
1435         assertFalse(mco.hadOnChanged());
1436 
1437         mContentResolver.notifyChange(TABLE1_URI, mco, false);
1438         new PollingCheck() {
1439             @Override
1440             protected boolean check() {
1441                 return mco.hadOnChanged();
1442             }
1443         }.run();
1444 
1445         mContentResolver.unregisterContentObserver(mco);
1446     }
1447 
1448     /**
1449      * Verify that callers using the {@link Iterable} version of
1450      * {@link ContentResolver#notifyChange} are correctly split and delivered to
1451      * disjoint listeners.
1452      */
testNotifyChange_MultipleSplit()1453     public void testNotifyChange_MultipleSplit() {
1454         final MockContentObserver observer1 = new MockContentObserver();
1455         final MockContentObserver observer2 = new MockContentObserver();
1456 
1457         mContentResolver.registerContentObserver(TABLE1_URI, true, observer1);
1458         mContentResolver.registerContentObserver(TABLE2_URI, true, observer2);
1459 
1460         assertFalse(observer1.hadOnChanged());
1461         assertFalse(observer2.hadOnChanged());
1462 
1463         final ArrayList<Uri> list = new ArrayList<>();
1464         list.add(TABLE1_URI);
1465         list.add(TABLE2_URI);
1466         mContentResolver.notifyChange(list, null, 0);
1467 
1468         new PollingCheck() {
1469             @Override
1470             protected boolean check() {
1471                 return observer1.hadOnChanged() && observer2.hadOnChanged();
1472             }
1473         }.run();
1474 
1475         mContentResolver.unregisterContentObserver(observer1);
1476         mContentResolver.unregisterContentObserver(observer2);
1477     }
1478 
1479     /**
1480      * Verify that callers using the {@link Iterable} version of
1481      * {@link ContentResolver#notifyChange} are correctly grouped and delivered
1482      * to overlapping listeners, including untouched flags.
1483      */
testNotifyChange_MultipleFlags()1484     public void testNotifyChange_MultipleFlags() {
1485         final MockContentObserver observer1 = new MockContentObserver();
1486         final MockContentObserver observer2 = new MockContentObserver();
1487 
1488         mContentResolver.registerContentObserver(LEVEL1_URI, false, observer1);
1489         mContentResolver.registerContentObserver(LEVEL2_URI, false, observer2);
1490 
1491         mContentResolver.notifyChange(
1492                 Arrays.asList(LEVEL1_URI), null, 0);
1493         mContentResolver.notifyChange(
1494                 Arrays.asList(LEVEL1_URI, LEVEL2_URI), null, NOTIFY_INSERT);
1495         mContentResolver.notifyChange(
1496                 Arrays.asList(LEVEL2_URI), null, NOTIFY_UPDATE);
1497 
1498         final List<Change> expected1 = Arrays.asList(
1499                 new Change(false, Arrays.asList(LEVEL1_URI), 0),
1500                 new Change(false, Arrays.asList(LEVEL1_URI), NOTIFY_INSERT));
1501 
1502         final List<Change> expected2 = Arrays.asList(
1503                 new Change(false, Arrays.asList(LEVEL1_URI), 0),
1504                 new Change(false, Arrays.asList(LEVEL1_URI, LEVEL2_URI), NOTIFY_INSERT),
1505                 new Change(false, Arrays.asList(LEVEL2_URI), NOTIFY_UPDATE));
1506 
1507         new PollingCheck() {
1508             @Override
1509             protected boolean check() {
1510                 return observer1.hadChanges(expected1)
1511                         && observer2.hadChanges(expected2);
1512             }
1513         }.run();
1514 
1515         mContentResolver.unregisterContentObserver(observer1);
1516         mContentResolver.unregisterContentObserver(observer2);
1517     }
1518 
testStartCancelSync()1519     public void testStartCancelSync() {
1520         Bundle extras = new Bundle();
1521 
1522         extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1523 
1524         ContentResolver.requestSync(ACCOUNT, AUTHORITY, extras);
1525         //FIXME: how to get the result to assert.
1526 
1527         ContentResolver.cancelSync(ACCOUNT, AUTHORITY);
1528         //FIXME: how to assert.
1529     }
1530 
testStartSyncFailure()1531     public void testStartSyncFailure() {
1532         try {
1533             ContentResolver.requestSync(null, null, null);
1534             fail("did not throw IllegalArgumentException when extras is null.");
1535         } catch (IllegalArgumentException e) {
1536             //expected.
1537         }
1538     }
1539 
testValidateSyncExtrasBundle()1540     public void testValidateSyncExtrasBundle() {
1541         Bundle extras = new Bundle();
1542         extras.putInt("Integer", 20);
1543         extras.putLong("Long", 10l);
1544         extras.putBoolean("Boolean", true);
1545         extras.putFloat("Float", 5.5f);
1546         extras.putDouble("Double", 2.5);
1547         extras.putString("String", "cts");
1548         extras.putCharSequence("CharSequence", null);
1549 
1550         ContentResolver.validateSyncExtrasBundle(extras);
1551 
1552         extras.putChar("Char", 'a'); // type Char is invalid
1553         try {
1554             ContentResolver.validateSyncExtrasBundle(extras);
1555             fail("did not throw IllegalArgumentException when extras is invalide.");
1556         } catch (IllegalArgumentException e) {
1557             //expected.
1558         }
1559     }
1560 
1561     @AppModeFull
testHangRecover()1562     public void testHangRecover() throws Exception {
1563         InstrumentationRegistry.getInstrumentation().getUiAutomation()
1564                 .adoptShellPermissionIdentity(android.Manifest.permission.REMOVE_TASKS);
1565 
1566         final CountDownLatch latch = new CountDownLatch(1);
1567         new Thread(() -> {
1568             final ContentProviderClient client = mContentResolver
1569                     .acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
1570             client.setDetectNotResponding(2_000);
1571             try {
1572                 client.query(REMOTE_HANG_URI, null, null, null);
1573                 fail("Funky, we somehow returned?");
1574             } catch (RemoteException e) {
1575                 latch.countDown();
1576             }
1577         }).start();
1578 
1579         // The remote process should have been killed after the ANR was detected
1580         // above, causing our pending call to return and release our latch above
1581         // within 10 seconds; if our Binder thread hasn't been freed, then we
1582         // fail with a timeout.
1583         latch.await(10, TimeUnit.SECONDS);
1584     }
1585 
testGetTypeInfo()1586     public void testGetTypeInfo() throws Exception {
1587         for (String mimeType : new String[] {
1588                 "image/png",
1589                 "IMage/PnG",
1590                 "image/x-custom",
1591                 "application/x-flac",
1592                 "application/rdf+xml",
1593                 "x-custom/x-custom",
1594         }) {
1595             final MimeTypeInfo ti = mContentResolver.getTypeInfo(mimeType);
1596             assertNotNull(ti);
1597             assertNotNull(ti.getLabel());
1598             assertNotNull(ti.getContentDescription());
1599             assertNotNull(ti.getIcon());
1600         }
1601     }
1602 
testGetTypeInfo_Invalid()1603     public void testGetTypeInfo_Invalid() throws Exception {
1604         try {
1605             mContentResolver.getTypeInfo(null);
1606             fail("Expected exception for null");
1607         } catch (NullPointerException expected) {
1608         }
1609     }
1610 
testWrapContentProvider()1611     public void testWrapContentProvider() throws Exception {
1612         try (ContentProviderClient local = getContext().getContentResolver()
1613                 .acquireContentProviderClient(AUTHORITY)) {
1614             final ContentResolver resolver = ContentResolver.wrap(local.getLocalContentProvider());
1615             assertNotNull(resolver.getType(TABLE1_URI));
1616             try {
1617                 resolver.getType(REMOTE_TABLE1_URI);
1618                 fail();
1619             } catch (SecurityException | IllegalArgumentException expected) {
1620             }
1621         }
1622     }
1623 
testWrapContentProviderClient()1624     public void testWrapContentProviderClient() throws Exception {
1625         try (ContentProviderClient remote = getContext().getContentResolver()
1626                 .acquireContentProviderClient(REMOTE_AUTHORITY)) {
1627             final ContentResolver resolver = ContentResolver.wrap(remote);
1628             assertNotNull(resolver.getType(REMOTE_TABLE1_URI));
1629             try {
1630                 resolver.getType(TABLE1_URI);
1631                 fail();
1632             } catch (SecurityException | IllegalArgumentException expected) {
1633             }
1634         }
1635     }
1636 
1637     @AppModeFull
testContentResolverCaching()1638     public void testContentResolverCaching() throws Exception {
1639         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
1640                 android.Manifest.permission.CACHE_CONTENT,
1641                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
1642 
1643         Bundle cached = new Bundle();
1644         cached.putString("key", "value");
1645         mContentResolver.putCache(TABLE1_URI, cached);
1646 
1647         Bundle response = mContentResolver.getCache(TABLE1_URI);
1648         assertEquals("value", response.getString("key"));
1649 
1650         ContentValues values = new ContentValues();
1651         values.put(COLUMN_KEY_NAME, "key10");
1652         values.put(COLUMN_VALUE_NAME, 10);
1653         mContentResolver.update(TABLE1_URI, values, null, null);
1654 
1655         response = mContentResolver.getCache(TABLE1_URI);
1656         assertNull(response);
1657     }
1658 
testEncodeDecode()1659     public void testEncodeDecode() {
1660         final Uri expected = Uri.parse("content://com.example/item/23");
1661         final File file = ContentResolver.encodeToFile(expected);
1662         assertNotNull(file);
1663 
1664         final Uri actual = ContentResolver.decodeFromFile(file);
1665         assertNotNull(actual);
1666         assertEquals(expected, actual);
1667     }
1668 
1669     public static class Change {
1670         public final boolean selfChange;
1671         public final Iterable<Uri> uris;
1672         public final int flags;
1673         @UserIdInt
1674         public final int userId;
1675 
Change(boolean selfChange, Iterable<Uri> uris, int flags)1676         public Change(boolean selfChange, Iterable<Uri> uris, int flags) {
1677             this.selfChange = selfChange;
1678             this.uris = uris;
1679             this.flags = flags;
1680             this.userId = -1;
1681         }
1682 
Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId)1683         public Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId) {
1684             this.selfChange = selfChange;
1685             this.uris = uris;
1686             this.flags = flags;
1687             this.userId = userId;
1688         }
1689 
1690         @Override
toString()1691         public String toString() {
1692             return String.format("onChange(%b, %s, %d, %d)",
1693                     selfChange, asSet(uris).toString(), flags, userId);
1694         }
1695 
1696         @Override
equals(Object other)1697         public boolean equals(Object other) {
1698             if (other instanceof Change) {
1699                 final Change change = (Change) other;
1700                 return change.selfChange == selfChange &&
1701                         Objects.equals(asSet(change.uris), asSet(uris)) &&
1702                         change.flags == flags && change.userId == userId;
1703             } else {
1704                 return false;
1705             }
1706         }
1707 
asSet(Iterable<Uri> uris)1708         private static Set<Uri> asSet(Iterable<Uri> uris) {
1709             final Set<Uri> asSet = new HashSet<>();
1710             uris.forEach(asSet::add);
1711             return asSet;
1712         }
1713     }
1714 
1715     private static class MockContentObserver extends ContentObserver {
1716         private boolean mHadOnChanged = false;
1717         private List<Change> mChanges = new ArrayList<>();
1718 
MockContentObserver()1719         public MockContentObserver() {
1720             super(null);
1721         }
1722 
1723         @Override
deliverSelfNotifications()1724         public boolean deliverSelfNotifications() {
1725             return true;
1726         }
1727 
1728         @Override
onChange(boolean selfChange, Collection<Uri> uris, int flags)1729         public synchronized void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
1730             doOnChangeLocked(selfChange, uris, flags, /*userId=*/ -1);
1731         }
1732 
1733         @Override
onChange(boolean selfChange, @NonNull Collection<Uri> uris, @ContentResolver.NotifyFlags int flags, UserHandle user)1734         public synchronized void onChange(boolean selfChange, @NonNull Collection<Uri> uris,
1735                 @ContentResolver.NotifyFlags int flags, UserHandle user) {
1736             doOnChangeLocked(selfChange, uris, flags, user.getIdentifier());
1737         }
1738 
hadOnChanged()1739         public synchronized boolean hadOnChanged() {
1740             return mHadOnChanged;
1741         }
1742 
reset()1743         public synchronized void reset() {
1744             mHadOnChanged = false;
1745         }
1746 
hadChanges(Collection<Change> changes)1747         public synchronized boolean hadChanges(Collection<Change> changes) {
1748             return mChanges.containsAll(changes);
1749         }
1750 
1751         @GuardedBy("this")
doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris, @ContentResolver.NotifyFlags int flags, @UserIdInt int userId)1752         private void doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris,
1753                 @ContentResolver.NotifyFlags int flags, @UserIdInt int userId) {
1754             final Change change = new Change(selfChange, uris, flags, userId);
1755             Log.v(TAG, change.toString());
1756 
1757             mHadOnChanged = true;
1758             mChanges.add(change);
1759         }
1760     }
1761 }
1762