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