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