blob: 71bffc772b8acab55aa7fa0b1e9ce131963ab0b2 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.cts;
import static android.content.ContentResolver.NOTIFY_INSERT;
import static android.content.ContentResolver.NOTIFY_UPDATE;
import android.accounts.Account;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentResolver.MimeTypeInfo;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
import android.icu.text.Collator;
import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ContentResolverTest extends AndroidTestCase {
private static final String TAG = "ContentResolverTest";
private final static String COLUMN_ID_NAME = "_id";
private final static String COLUMN_KEY_NAME = "key";
private final static String COLUMN_VALUE_NAME = "value";
private static final String AUTHORITY = "ctstest";
private static final Uri TABLE1_URI = Uri.parse("content://" + AUTHORITY + "/testtable1/");
private static final Uri TABLE1_CROSS_URI =
Uri.parse("content://" + AUTHORITY + "/testtable1/cross");
private static final Uri TABLE2_URI = Uri.parse("content://" + AUTHORITY + "/testtable2/");
private static final Uri LEVEL1_URI = Uri.parse("content://" + AUTHORITY + "/level/");
private static final Uri LEVEL2_URI = Uri.parse("content://" + AUTHORITY + "/level/child");
private static final Uri LEVEL3_URI = Uri.parse("content://" + AUTHORITY
+ "/level/child/grandchild/");
private static final String REMOTE_AUTHORITY = "remotectstest";
private static final Uri REMOTE_TABLE1_URI = Uri.parse("content://"
+ REMOTE_AUTHORITY + "/testtable1/");
private static final Uri REMOTE_CRASH_URI = Uri.parse("content://"
+ REMOTE_AUTHORITY + "/crash/");
private static final Uri REMOTE_HANG_URI = Uri.parse("content://"
+ REMOTE_AUTHORITY + "/hang/");
private static final Account ACCOUNT = new Account("cts", "cts");
private static final String KEY1 = "key1";
private static final String KEY2 = "key2";
private static final String KEY3 = "key3";
private static final int VALUE1 = 1;
private static final int VALUE2 = 2;
private static final int VALUE3 = 3;
private static final String TEST_PACKAGE_NAME = "android.content.cts";
private Context mContext;
private ContentResolver mContentResolver;
private Cursor mCursor;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getContext();
mContentResolver = mContext.getContentResolver();
MockContentProvider.setCrashOnLaunch(mContext, false);
// add three rows to database when every test case start.
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, KEY1);
values.put(COLUMN_VALUE_NAME, VALUE1);
mContentResolver.insert(TABLE1_URI, values);
mContentResolver.insert(REMOTE_TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, KEY2);
values.put(COLUMN_VALUE_NAME, VALUE2);
mContentResolver.insert(TABLE1_URI, values);
mContentResolver.insert(REMOTE_TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, KEY3);
values.put(COLUMN_VALUE_NAME, VALUE3);
mContentResolver.insert(TABLE1_URI, values);
mContentResolver.insert(REMOTE_TABLE1_URI, values);
}
@Override
protected void tearDown() throws Exception {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
mContentResolver.delete(TABLE1_URI, null, null);
if ( null != mCursor && !mCursor.isClosed() ) {
mCursor.close();
}
mContentResolver.delete(REMOTE_TABLE1_URI, null, null);
if ( null != mCursor && !mCursor.isClosed() ) {
mCursor.close();
}
super.tearDown();
}
public void testConstructor() {
assertNotNull(mContentResolver);
}
public void testCrashOnLaunch() {
// This test is going to make sure that the platform deals correctly
// with a content provider process going away while a client is waiting
// for it to come up.
// First, we need to make sure our provider process is gone. Goodbye!
ContentProviderClient client = mContentResolver.acquireContentProviderClient(
REMOTE_AUTHORITY);
// We are going to do something wrong here... release the client first,
// so the act of killing it doesn't kill our own process.
client.release();
try {
client.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
// Now make sure the thing is actually gone.
boolean gone = true;
try {
client.getType(REMOTE_TABLE1_URI);
gone = false;
} catch (RemoteException e) {
}
if (!gone) {
fail("Content provider process is not gone!");
}
try {
MockContentProvider.setCrashOnLaunch(mContext, true);
String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
} finally {
MockContentProvider.setCrashOnLaunch(mContext, false);
}
}
public void testUnstableToStableRefs() {
// Get an unstable refrence on the remote content provider.
ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
REMOTE_AUTHORITY);
// Verify we can access it.
String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Get a stable reference on the remote content provider.
ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
REMOTE_AUTHORITY);
// Verify we can still access it.
type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Release unstable reference.
uClient.release();
// Verify we can still access it.
type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Release stable reference, removing last ref.
sClient.release();
// Kill it. Note that a bug at this point where it causes our own
// process to be killed will result in the entire test failing.
try {
Log.i("ContentResolverTest",
"Killing remote client -- if test process goes away, that is why!");
uClient.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
// Make sure the remote client is actually gone.
boolean gone = true;
try {
sClient.getType(REMOTE_TABLE1_URI);
gone = false;
} catch (RemoteException e) {
}
if (!gone) {
fail("Content provider process is not gone!");
}
}
public void testStableToUnstableRefs() {
// Get a stable reference on the remote content provider.
ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
REMOTE_AUTHORITY);
// Verify we can still access it.
String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Get an unstable refrence on the remote content provider.
ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
REMOTE_AUTHORITY);
// Verify we can access it.
type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Release stable reference, leaving only an unstable ref.
sClient.release();
// Kill it. Note that a bug at this point where it causes our own
// process to be killed will result in the entire test failing.
try {
Log.i("ContentResolverTest",
"Killing remote client -- if test process goes away, that is why!");
uClient.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
// Make sure the remote client is actually gone.
boolean gone = true;
try {
uClient.getType(REMOTE_TABLE1_URI);
gone = false;
} catch (RemoteException e) {
}
if (!gone) {
fail("Content provider process is not gone!");
}
// Release unstable reference.
uClient.release();
}
public void testGetType() {
String type1 = mContentResolver.getType(TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
String type2 = mContentResolver.getType(TABLE2_URI);
assertTrue(type2.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
Uri invalidUri = Uri.parse("abc");
assertNull(mContentResolver.getType(invalidUri));
try {
mContentResolver.getType(null);
fail("did not throw NullPointerException when Uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testUnstableGetType() {
// Get an unstable refrence on the remote content provider.
ContentProviderClient client = mContentResolver.acquireUnstableContentProviderClient(
REMOTE_AUTHORITY);
// Verify we can access it.
String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
// Kill it. Note that a bug at this point where it causes our own
// process to be killed will result in the entire test failing.
try {
Log.i("ContentResolverTest",
"Killing remote client -- if test process goes away, that is why!");
client.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
// Make sure the remote client is actually gone.
boolean gone = true;
try {
client.getType(REMOTE_TABLE1_URI);
gone = false;
} catch (RemoteException e) {
}
if (!gone) {
fail("Content provider process is not gone!");
}
// Now the remote client is gone, can we recover?
// Release our old reference.
client.release();
// Get a new reference.
client = mContentResolver.acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
// Verify we can access it.
type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
}
public void testQuery() {
mCursor = mContentResolver.query(TABLE1_URI, null, null, null);
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
assertEquals(3, mCursor.getColumnCount());
mCursor.moveToLast();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToPrevious();
assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
}
public void testQuery_WithSqlSelectionArgs() {
Bundle queryArgs = new Bundle();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, COLUMN_ID_NAME + "=?");
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[] {"1"});
mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
assertNotNull(mCursor);
assertEquals(1, mCursor.getCount());
assertEquals(3, mCursor.getColumnCount());
mCursor.moveToFirst();
assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, COLUMN_KEY_NAME + "=?");
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[] {KEY3});
mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
assertNotNull(mCursor);
assertEquals(1, mCursor.getCount());
assertEquals(3, mCursor.getColumnCount());
mCursor.moveToFirst();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
}
/*
* NOTE: this test is implicitly coupled to the implementation
* of MockContentProvider#query, specifically the facts:
*
* - it does *not* override the query w/ Bundle methods
* - it receives the auto-generated sql format arguments (supplied by the framework)
* - it is backed by sqlite and forwards the sql formatted args.
*/
public void testQuery_SqlSortingFromBundleArgs() {
mContentResolver.delete(TABLE1_URI, null, null);
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, "0");
values.put(COLUMN_VALUE_NAME, "abc");
mContentResolver.insert(TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, "1");
values.put(COLUMN_VALUE_NAME, "DEF");
mContentResolver.insert(TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, "2");
values.put(COLUMN_VALUE_NAME, "ghi");
mContentResolver.insert(TABLE1_URI, values);
String[] sortCols = new String[] { COLUMN_VALUE_NAME };
Bundle queryArgs = new Bundle();
queryArgs.putStringArray(
ContentResolver.QUERY_ARG_SORT_COLUMNS,
sortCols);
// Sort ascending...
queryArgs.putInt(
ContentResolver.QUERY_ARG_SORT_DIRECTION,
ContentResolver.QUERY_SORT_DIRECTION_ASCENDING);
mCursor = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null);
int col = mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME);
mCursor.moveToNext();
assertEquals("DEF", mCursor.getString(col));
mCursor.moveToNext();
assertEquals("abc", mCursor.getString(col));
mCursor.moveToNext();
assertEquals("ghi", mCursor.getString(col));
mCursor.close();
// Nocase collation, descending...
queryArgs.putInt(
ContentResolver.QUERY_ARG_SORT_DIRECTION,
ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
queryArgs.putInt(
ContentResolver.QUERY_ARG_SORT_COLLATION,
java.text.Collator.SECONDARY);
mCursor = mContentResolver.query(TABLE1_URI, null, queryArgs, null);
col = mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME);
mCursor.moveToNext();
assertEquals("ghi", mCursor.getString(col));
mCursor.moveToNext();
assertEquals("DEF", mCursor.getString(col));
mCursor.moveToNext();
assertEquals("abc", mCursor.getString(col));
mCursor.close();
}
public void testQuery_SqlSortingFromBundleArgs_Locale() {
mContentResolver.delete(TABLE1_URI, null, null);
final List<String> data = Arrays.asList(
"ABC", "abc", "pinyin", "가나다", "바사", "테스트", "马",
"嘛", "妈", "骂", "吗", "码", "玛", "麻", "中", "梵", "苹果", "久了", "伺候");
for (String s : data) {
final ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, s.hashCode());
values.put(COLUMN_VALUE_NAME, s);
mContentResolver.insert(TABLE1_URI, values);
}
String[] sortCols = new String[] { COLUMN_VALUE_NAME };
Bundle queryArgs = new Bundle();
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, sortCols);
for (String locale : new String[] {
"zh",
"zh@collation=pinyin",
"zh@collation=stroke",
"zh@collation=zhuyin",
}) {
// Assert that sorting is identical between SQLite and ICU4J
queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
try (Cursor c = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null)) {
data.sort(Collator.getInstance(new ULocale(locale)));
assertEquals(data, collect(c));
}
}
}
private static List<String> collect(Cursor c) {
List<String> res = new ArrayList<>();
while (c.moveToNext()) {
res.add(c.getString(0));
}
return res;
}
/**
* Verifies that paging information is correctly relayed, and that
* honored arguments from a supporting client are returned correctly.
*/
public void testQuery_PagedResults() {
Bundle queryArgs = new Bundle();
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 10);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 3);
queryArgs.putInt(TestPagingContentProvider.RECORD_COUNT, 100);
mCursor = mContentResolver.query(
TestPagingContentProvider.PAGED_DATA_URI, null, queryArgs, null);
Bundle extras = mCursor.getExtras();
extras = extras != null ? extras : Bundle.EMPTY;
assertEquals(3, mCursor.getCount());
assertTrue(extras.containsKey(ContentResolver.EXTRA_TOTAL_COUNT));
assertEquals(100, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
assertNotNull(honoredArgs);
assertTrue(ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
assertTrue(ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
int col = mCursor.getColumnIndexOrThrow(TestPagingContentProvider.COLUMN_POS);
mCursor.moveToNext();
assertEquals(10, mCursor.getInt(col));
mCursor.moveToNext();
assertEquals(11, mCursor.getInt(col));
mCursor.moveToNext();
assertEquals(12, mCursor.getInt(col));
assertFalse(mCursor.moveToNext());
mCursor.close();
}
public void testQuery_NullUriThrows() {
try {
mContentResolver.query(null, null, null, null, null);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testCrashingQuery() {
try {
MockContentProvider.setCrashOnLaunch(mContext, true);
mCursor = mContentResolver.query(REMOTE_CRASH_URI, null, null, null, null);
assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
} finally {
MockContentProvider.setCrashOnLaunch(mContext, false);
}
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
assertEquals(3, mCursor.getColumnCount());
mCursor.moveToLast();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToPrevious();
assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
}
public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
CancellationSignal cancellationSignal = new CancellationSignal();
Cursor cursor = mContentResolver.query(TABLE1_URI, null, null, null, null,
cancellationSignal);
assertEquals(3, cursor.getCount());
cursor.close();
}
public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.cancel();
try {
mContentResolver.query(TABLE1_URI, null, null, null, null, cancellationSignal);
fail("Expected OperationCanceledException");
} catch (OperationCanceledException ex) {
// expected
}
}
public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
// Populate a table with a bunch of integers.
mContentResolver.delete(TABLE1_URI, null, null);
ContentValues values = new ContentValues();
for (int i = 0; i < 100; i++) {
values.put(COLUMN_KEY_NAME, i);
values.put(COLUMN_VALUE_NAME, i);
mContentResolver.insert(TABLE1_URI, values);
}
for (int i = 0; i < 5; i++) {
final CancellationSignal cancellationSignal = new CancellationSignal();
Thread cancellationThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException ex) {
}
cancellationSignal.cancel();
}
};
try {
// Build an unsatisfiable 5-way cross-product query over 100 values but
// produces no output. This should force SQLite to loop for a long time
// as it tests 10^10 combinations.
cancellationThread.start();
final long startTime = System.nanoTime();
try {
mContentResolver.query(TABLE1_CROSS_URI, null,
"a.value + b.value + c.value + d.value + e.value > 1000000",
null, null, cancellationSignal);
fail("Expected OperationCanceledException");
} catch (OperationCanceledException ex) {
// expected
}
// We want to confirm that the query really was running and then got
// canceled midway.
final long waitTime = System.nanoTime() - startTime;
if (waitTime > 150 * 1000000L && waitTime < 600 * 1000000L) {
return; // success!
}
} finally {
try {
cancellationThread.join();
} catch (InterruptedException e) {
}
}
}
// Occasionally we might miss the timing deadline due to factors in the
// environment, but if after several trials we still couldn't demonstrate
// that the query was canceled, then the test must be broken.
fail("Could not prove that the query actually canceled midway during execution.");
}
public void testOpenInputStream() throws IOException {
final Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + TEST_PACKAGE_NAME + "/" + R.drawable.pass);
InputStream is = mContentResolver.openInputStream(uri);
assertNotNull(is);
is.close();
final Uri invalidUri = Uri.parse("abc");
try {
mContentResolver.openInputStream(invalidUri);
fail("did not throw FileNotFoundException when uri is invalid.");
} catch (FileNotFoundException e) {
//expected.
}
}
public void testOpenOutputStream() throws IOException {
Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
getContext().getCacheDir().getAbsolutePath() +
"/temp.jpg");
OutputStream os = mContentResolver.openOutputStream(uri);
assertNotNull(os);
os.close();
os = mContentResolver.openOutputStream(uri, "wa");
assertNotNull(os);
os.close();
uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
try {
mContentResolver.openOutputStream(uri);
fail("did not throw FileNotFoundException when scheme is not accepted.");
} catch (FileNotFoundException e) {
//expected.
}
try {
mContentResolver.openOutputStream(uri, "w");
fail("did not throw FileNotFoundException when scheme is not accepted.");
} catch (FileNotFoundException e) {
//expected.
}
Uri invalidUri = Uri.parse("abc");
try {
mContentResolver.openOutputStream(invalidUri);
fail("did not throw FileNotFoundException when uri is invalid.");
} catch (FileNotFoundException e) {
//expected.
}
try {
mContentResolver.openOutputStream(invalidUri, "w");
fail("did not throw FileNotFoundException when uri is invalid.");
} catch (FileNotFoundException e) {
//expected.
}
}
public void testOpenAssetFileDescriptor() throws IOException {
Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(uri, "r");
assertNotNull(afd);
afd.close();
try {
mContentResolver.openAssetFileDescriptor(uri, "d");
fail("did not throw FileNotFoundException when mode is unknown.");
} catch (FileNotFoundException e) {
//expected.
}
Uri invalidUri = Uri.parse("abc");
try {
mContentResolver.openAssetFileDescriptor(invalidUri, "r");
fail("did not throw FileNotFoundException when uri is invalid.");
} catch (FileNotFoundException e) {
//expected.
}
}
private String consumeAssetFileDescriptor(AssetFileDescriptor afd)
throws IOException {
FileInputStream stream = null;
try {
stream = afd.createInputStream();
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
// Got it... copy the stream into a local string and return it.
StringBuilder builder = new StringBuilder(128);
char[] buffer = new char[8192];
int len;
while ((len=reader.read(buffer)) > 0) {
builder.append(buffer, 0, len);
}
return builder.toString();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
}
public void testCrashingOpenAssetFileDescriptor() throws IOException {
AssetFileDescriptor afd = null;
try {
MockContentProvider.setCrashOnLaunch(mContext, true);
afd = mContentResolver.openAssetFileDescriptor(REMOTE_CRASH_URI, "rw");
assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
assertNotNull(afd);
String str = consumeAssetFileDescriptor(afd);
afd = null;
assertEquals(str, "This is the openAssetFile test data!");
} finally {
MockContentProvider.setCrashOnLaunch(mContext, false);
if (afd != null) {
afd.close();
}
}
// Make sure a content provider crash at this point won't hurt us.
ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
REMOTE_AUTHORITY);
// Kill it. Note that a bug at this point where it causes our own
// process to be killed will result in the entire test failing.
try {
Log.i("ContentResolverTest",
"Killing remote client -- if test process goes away, that is why!");
uClient.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
uClient.release();
}
public void testCrashingOpenTypedAssetFileDescriptor() throws IOException {
AssetFileDescriptor afd = null;
try {
MockContentProvider.setCrashOnLaunch(mContext, true);
afd = mContentResolver.openTypedAssetFileDescriptor(
REMOTE_CRASH_URI, "text/plain", null);
assertFalse(MockContentProvider.getCrashOnLaunch(mContext));
assertNotNull(afd);
String str = consumeAssetFileDescriptor(afd);
afd = null;
assertEquals(str, "This is the openTypedAssetFile test data!");
} finally {
MockContentProvider.setCrashOnLaunch(mContext, false);
if (afd != null) {
afd.close();
}
}
// Make sure a content provider crash at this point won't hurt us.
ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
REMOTE_AUTHORITY);
// Kill it. Note that a bug at this point where it causes our own
// process to be killed will result in the entire test failing.
try {
Log.i("ContentResolverTest",
"Killing remote client -- if test process goes away, that is why!");
uClient.delete(REMOTE_CRASH_URI, null, null);
} catch (RemoteException e) {
}
uClient.release();
}
public void testOpenFileDescriptor() throws IOException {
Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
getContext().getCacheDir().getAbsolutePath() +
"/temp.jpg");
ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(uri, "w");
assertNotNull(pfd);
pfd.close();
try {
mContentResolver.openFileDescriptor(uri, "d");
fail("did not throw IllegalArgumentException when mode is unknown.");
} catch (IllegalArgumentException e) {
//expected.
}
Uri invalidUri = Uri.parse("abc");
try {
mContentResolver.openFileDescriptor(invalidUri, "w");
fail("did not throw FileNotFoundException when uri is invalid.");
} catch (FileNotFoundException e) {
//expected.
}
uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + TEST_PACKAGE_NAME + "/" + R.raw.testimage);
try {
mContentResolver.openFileDescriptor(uri, "w");
fail("did not throw FileNotFoundException when scheme is not accepted.");
} catch (FileNotFoundException e) {
//expected.
}
}
public void testInsert() {
String key4 = "key4";
String key5 = "key5";
int value4 = 4;
int value5 = 5;
String key4Selection = COLUMN_KEY_NAME + "=\"" + key4 + "\"";
mCursor = mContentResolver.query(TABLE1_URI, null, key4Selection, null, null);
assertEquals(0, mCursor.getCount());
mCursor.close();
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, key4);
values.put(COLUMN_VALUE_NAME, value4);
Uri uri = mContentResolver.insert(TABLE1_URI, values);
assertNotNull(uri);
mCursor = mContentResolver.query(TABLE1_URI, null, key4Selection, null, null);
assertNotNull(mCursor);
assertEquals(1, mCursor.getCount());
mCursor.moveToFirst();
assertEquals(4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key4, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
values.put(COLUMN_KEY_NAME, key5);
values.put(COLUMN_VALUE_NAME, value5);
uri = mContentResolver.insert(TABLE1_URI, values);
assertNotNull(uri);
// check returned uri
mCursor = mContentResolver.query(uri, null, null, null, null);
assertNotNull(mCursor);
assertEquals(1, mCursor.getCount());
mCursor.moveToLast();
assertEquals(5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key5, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
try {
mContentResolver.insert(null, values);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testBulkInsert() {
String key4 = "key4";
String key5 = "key5";
int value4 = 4;
int value5 = 5;
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
mCursor.close();
ContentValues[] cvs = new ContentValues[2];
cvs[0] = new ContentValues();
cvs[0].put(COLUMN_KEY_NAME, key4);
cvs[0].put(COLUMN_VALUE_NAME, value4);
cvs[1] = new ContentValues();
cvs[1].put(COLUMN_KEY_NAME, key5);
cvs[1].put(COLUMN_VALUE_NAME, value5);
assertEquals(2, mContentResolver.bulkInsert(TABLE1_URI, cvs));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(5, mCursor.getCount());
mCursor.moveToLast();
assertEquals(5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key5, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value5, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToPrevious();
assertEquals(4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key4, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value4, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
try {
mContentResolver.bulkInsert(null, cvs);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testDelete() {
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
mCursor.close();
assertEquals(3, mContentResolver.delete(TABLE1_URI, null, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(0, mCursor.getCount());
mCursor.close();
// add three rows to database.
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, KEY1);
values.put(COLUMN_VALUE_NAME, VALUE1);
mContentResolver.insert(TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, KEY2);
values.put(COLUMN_VALUE_NAME, VALUE2);
mContentResolver.insert(TABLE1_URI, values);
values.put(COLUMN_KEY_NAME, KEY3);
values.put(COLUMN_VALUE_NAME, VALUE3);
mContentResolver.insert(TABLE1_URI, values);
// test delete row using selection
String selection = COLUMN_ID_NAME + "=2";
assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(2, mCursor.getCount());
mCursor.moveToFirst();
assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToNext();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
selection = COLUMN_VALUE_NAME + "=3";
assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(1, mCursor.getCount());
mCursor.moveToFirst();
assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(KEY1, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(VALUE1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
selection = COLUMN_KEY_NAME + "=\"" + KEY1 + "\"";
assertEquals(1, mContentResolver.delete(TABLE1_URI, selection, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(0, mCursor.getCount());
mCursor.close();
try {
mContentResolver.delete(null, null, null);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testUpdate() {
ContentValues values = new ContentValues();
String key10 = "key10";
String key20 = "key20";
int value10 = 10;
int value20 = 20;
values.put(COLUMN_KEY_NAME, key10);
values.put(COLUMN_VALUE_NAME, value10);
// test update all the rows.
assertEquals(3, mContentResolver.update(TABLE1_URI, values, null, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
mCursor.moveToFirst();
assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToNext();
assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToLast();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
// test update one row using selection.
String selection = COLUMN_ID_NAME + "=1";
values.put(COLUMN_KEY_NAME, key20);
values.put(COLUMN_VALUE_NAME, value20);
assertEquals(1, mContentResolver.update(TABLE1_URI, values, selection, null));
mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
assertNotNull(mCursor);
assertEquals(3, mCursor.getCount());
mCursor.moveToFirst();
assertEquals(1, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key20, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value20, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToNext();
assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.moveToLast();
assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
assertEquals(key10, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
assertEquals(value10, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
mCursor.close();
try {
mContentResolver.update(null, values, null, null);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
// javadoc says it will throw NullPointerException when values are null,
// but actually, it throws IllegalArgumentException here.
try {
mContentResolver.update(TABLE1_URI, null, null, null);
fail("did not throw IllegalArgumentException when values are null.");
} catch (IllegalArgumentException e) {
//expected.
}
}
public void testRefresh_DefaultImplReturnsFalse() {
boolean refreshed = mContentResolver.refresh(TABLE1_URI, null, null);
assertFalse(refreshed);
MockContentProvider.assertRefreshed(TABLE1_URI);
}
public void testRefresh_ReturnsProviderValue() {
try {
MockContentProvider.setRefreshReturnValue(true);
boolean refreshed = mContentResolver.refresh(TABLE1_URI, null, null);
assertTrue(refreshed);
MockContentProvider.assertRefreshed(TABLE1_URI);
} finally {
MockContentProvider.setRefreshReturnValue(false);
}
}
public void testRefresh_NullUriThrowsImmediately() {
try {
mContentResolver.refresh(null, null, null);
fail("did not throw NullPointerException when uri is null.");
} catch (NullPointerException e) {
//expected.
}
}
public void testRefresh_CancellableThrowsImmediately() {
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.cancel();
try {
mContentResolver.refresh(TABLE1_URI, null, cancellationSignal);
fail("Expected OperationCanceledException");
} catch (OperationCanceledException ex) {
// expected
}
}
public void testCheckUriPermission() {
assertEquals(PackageManager.PERMISSION_GRANTED, mContentResolver.checkUriPermission(
TABLE1_URI, android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION));
assertEquals(PackageManager.PERMISSION_DENIED, mContentResolver.checkUriPermission(
TABLE1_URI, android.os.Process.myUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
}
public void testRegisterContentObserver() {
final MockContentObserver mco = new MockContentObserver();
mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
assertFalse(mco.hadOnChanged());
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, "key10");
values.put(COLUMN_VALUE_NAME, 10);
mContentResolver.update(TABLE1_URI, values, null, null);
new PollingCheck() {
@Override
protected boolean check() {
return mco.hadOnChanged();
}
}.run();
mco.reset();
mContentResolver.unregisterContentObserver(mco);
assertFalse(mco.hadOnChanged());
mContentResolver.update(TABLE1_URI, values, null, null);
assertFalse(mco.hadOnChanged());
try {
mContentResolver.registerContentObserver(null, false, mco);
fail("did not throw NullPointerException or IllegalArgumentException when uri is null.");
} catch (NullPointerException e) {
//expected.
} catch (IllegalArgumentException e) {
// also expected
}
try {
mContentResolver.registerContentObserver(TABLE1_URI, false, null);
fail("did not throw NullPointerException when register null content observer.");
} catch (NullPointerException e) {
//expected.
}
try {
mContentResolver.unregisterContentObserver(null);
fail("did not throw NullPointerException when unregister null content observer.");
} catch (NullPointerException e) {
//expected.
}
}
// Tests registerContentObserverForAllUsers without INTERACT_ACROSS_USERS_FULL: verify
// SecurityException.
public void testRegisterContentObserverForAllUsersWithoutPermission() {
final MockContentObserver mco = new MockContentObserver();
try {
mContentResolver.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL);
fail("testRegisterContentObserverForAllUsers: "
+ "SecurityException expected on testRegisterContentObserverForAllUsers");
} catch (SecurityException se) {
// expected
}
}
public void testRegisterContentObserverAsUser() {
final MockContentObserver mco = new MockContentObserver();
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, mContext.getUser())
);
assertFalse(mco.hadOnChanged());
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, "key10");
values.put(COLUMN_VALUE_NAME, 10);
mContentResolver.update(TABLE1_URI, values, null, null);
new PollingCheck() {
@Override
protected boolean check() {
return mco.hadOnChanged();
}
}.run();
mco.reset();
mContentResolver.unregisterContentObserver(mco);
assertFalse(mco.hadOnChanged());
mContentResolver.update(TABLE1_URI, values, null, null);
assertFalse(mco.hadOnChanged());
}
public void testRegisterContentObserverForAllUsers() {
final MockContentObserver mco = new MockContentObserver();
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL)
);
assertFalse(mco.hadOnChanged());
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, "key10");
values.put(COLUMN_VALUE_NAME, 10);
mContentResolver.update(TABLE1_URI, values, null, null);
new PollingCheck() {
@Override
protected boolean check() {
return mco.hadOnChanged();
}
}.run();
mco.reset();
mContentResolver.unregisterContentObserver(mco);
assertFalse(mco.hadOnChanged());
mContentResolver.update(TABLE1_URI, values, null, null);
assertFalse(mco.hadOnChanged());
try {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(null, false, mco, UserHandle.ALL)
);
fail("did not throw NullPointerException or IllegalArgumentException when uri is null"
+ ".");
} catch (NullPointerException e) {
//expected.
} catch (IllegalArgumentException e) {
// also expected
}
try {
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(TABLE1_URI, false, null,
UserHandle.ALL)
);
fail("did not throw NullPointerException when register null content observer.");
} catch (NullPointerException e) {
//expected.
}
try {
mContentResolver.unregisterContentObserver(null);
fail("did not throw NullPointerException when unregister null content observer.");
} catch (NullPointerException e) {
//expected.
}
}
public void testRegisterContentObserverDescendantBehavior() throws Exception {
final MockContentObserver mco1 = new MockContentObserver();
final MockContentObserver mco2 = new MockContentObserver();
// Register one content observer with notifyDescendants set to false, and
// another with true.
mContentResolver.registerContentObserver(LEVEL2_URI, false, mco1);
mContentResolver.registerContentObserver(LEVEL2_URI, true, mco2);
// Initially nothing has happened.
assertFalse(mco1.hadOnChanged());
assertFalse(mco2.hadOnChanged());
// Fire a change with the exact URI.
// Should signal both observers due to exact match, notifyDescendants doesn't matter.
mContentResolver.notifyChange(LEVEL2_URI, null);
Thread.sleep(200);
assertTrue(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco1.reset();
mco2.reset();
// Fire a change with a descendant URI.
// Should only signal observer with notifyDescendants set to true.
mContentResolver.notifyChange(LEVEL3_URI, null);
Thread.sleep(200);
assertFalse(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco2.reset();
// Fire a change with an ancestor URI.
// Should signal both observers due to ancestry, notifyDescendants doesn't matter.
mContentResolver.notifyChange(LEVEL1_URI, null);
Thread.sleep(200);
assertTrue(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco1.reset();
mco2.reset();
// Fire a change with an unrelated URI.
// Should signal neither observer.
mContentResolver.notifyChange(TABLE1_URI, null);
Thread.sleep(200);
assertFalse(mco1.hadOnChanged());
assertFalse(mco2.hadOnChanged());
}
public void testRegisterContentObserverForAllUsersDescendantBehavior() throws Exception {
final MockContentObserver mco1 = new MockContentObserver();
final MockContentObserver mco2 = new MockContentObserver();
// Register one content observer with notifyDescendants set to false, and
// another with true.
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, false, mco1, UserHandle.ALL)
);
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
mContentResolver,
(cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, true, mco2, UserHandle.ALL)
);
// Initially nothing has happened.
assertFalse(mco1.hadOnChanged());
assertFalse(mco2.hadOnChanged());
// Fire a change with the exact URI.
// Should signal both observers due to exact match, notifyDescendants doesn't matter.
mContentResolver.notifyChange(LEVEL2_URI, null);
Thread.sleep(200);
assertTrue(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco1.reset();
mco2.reset();
// Fire a change with a descendant URI.
// Should only signal observer with notifyDescendants set to true.
mContentResolver.notifyChange(LEVEL3_URI, null);
Thread.sleep(200);
assertFalse(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco2.reset();
// Fire a change with an ancestor URI.
// Should signal both observers due to ancestry, notifyDescendants doesn't matter.
mContentResolver.notifyChange(LEVEL1_URI, null);
Thread.sleep(200);
assertTrue(mco1.hadOnChanged());
assertTrue(mco2.hadOnChanged());
mco1.reset();
mco2.reset();
// Fire a change with an unrelated URI.
// Should signal neither observer.
mContentResolver.notifyChange(TABLE1_URI, null);
Thread.sleep(200);
assertFalse(mco1.hadOnChanged());
assertFalse(mco2.hadOnChanged());
}
public void testNotifyChange1() {
final MockContentObserver mco = new MockContentObserver();
mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
assertFalse(mco.hadOnChanged());
mContentResolver.notifyChange(TABLE1_URI, mco);
new PollingCheck() {
@Override
protected boolean check() {
return mco.hadOnChanged();
}
}.run();
mContentResolver.unregisterContentObserver(mco);
}
public void testNotifyChange2() {
final MockContentObserver mco = new MockContentObserver();
mContentResolver.registerContentObserver(TABLE1_URI, true, mco);
assertFalse(mco.hadOnChanged());
mContentResolver.notifyChange(TABLE1_URI, mco, false);
new PollingCheck() {
@Override
protected boolean check() {
return mco.hadOnChanged();
}
}.run();
mContentResolver.unregisterContentObserver(mco);
}
/**
* Verify that callers using the {@link Iterable} version of
* {@link ContentResolver#notifyChange} are correctly split and delivered to
* disjoint listeners.
*/
public void testNotifyChange_MultipleSplit() {
final MockContentObserver observer1 = new MockContentObserver();
final MockContentObserver observer2 = new MockContentObserver();
mContentResolver.registerContentObserver(TABLE1_URI, true, observer1);
mContentResolver.registerContentObserver(TABLE2_URI, true, observer2);
assertFalse(observer1.hadOnChanged());
assertFalse(observer2.hadOnChanged());
final ArrayList<Uri> list = new ArrayList<>();
list.add(TABLE1_URI);
list.add(TABLE2_URI);
mContentResolver.notifyChange(list, null, 0);
new PollingCheck() {
@Override
protected boolean check() {
return observer1.hadOnChanged() && observer2.hadOnChanged();
}
}.run();
mContentResolver.unregisterContentObserver(observer1);
mContentResolver.unregisterContentObserver(observer2);
}
/**
* Verify that callers using the {@link Iterable} version of
* {@link ContentResolver#notifyChange} are correctly grouped and delivered
* to overlapping listeners, including untouched flags.
*/
public void testNotifyChange_MultipleFlags() {
final MockContentObserver observer1 = new MockContentObserver();
final MockContentObserver observer2 = new MockContentObserver();
mContentResolver.registerContentObserver(LEVEL1_URI, false, observer1);
mContentResolver.registerContentObserver(LEVEL2_URI, false, observer2);
mContentResolver.notifyChange(
Arrays.asList(LEVEL1_URI), null, 0);
mContentResolver.notifyChange(
Arrays.asList(LEVEL1_URI, LEVEL2_URI), null, NOTIFY_INSERT);
mContentResolver.notifyChange(
Arrays.asList(LEVEL2_URI), null, NOTIFY_UPDATE);
final List<Change> expected1 = Arrays.asList(
new Change(false, Arrays.asList(LEVEL1_URI), 0),
new Change(false, Arrays.asList(LEVEL1_URI), NOTIFY_INSERT));
final List<Change> expected2 = Arrays.asList(
new Change(false, Arrays.asList(LEVEL1_URI), 0),
new Change(false, Arrays.asList(LEVEL1_URI, LEVEL2_URI), NOTIFY_INSERT),
new Change(false, Arrays.asList(LEVEL2_URI), NOTIFY_UPDATE));
new PollingCheck() {
@Override
protected boolean check() {
return observer1.hadChanges(expected1)
&& observer2.hadChanges(expected2);
}
}.run();
mContentResolver.unregisterContentObserver(observer1);
mContentResolver.unregisterContentObserver(observer2);
}
public void testStartCancelSync() {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
ContentResolver.requestSync(ACCOUNT, AUTHORITY, extras);
//FIXME: how to get the result to assert.
ContentResolver.cancelSync(ACCOUNT, AUTHORITY);
//FIXME: how to assert.
}
public void testStartSyncFailure() {
try {
ContentResolver.requestSync(null, null, null);
fail("did not throw IllegalArgumentException when extras is null.");
} catch (IllegalArgumentException e) {
//expected.
}
}
public void testValidateSyncExtrasBundle() {
Bundle extras = new Bundle();
extras.putInt("Integer", 20);
extras.putLong("Long", 10l);
extras.putBoolean("Boolean", true);
extras.putFloat("Float", 5.5f);
extras.putDouble("Double", 2.5);
extras.putString("String", "cts");
extras.putCharSequence("CharSequence", null);
ContentResolver.validateSyncExtrasBundle(extras);
extras.putChar("Char", 'a'); // type Char is invalid
try {
ContentResolver.validateSyncExtrasBundle(extras);
fail("did not throw IllegalArgumentException when extras is invalide.");
} catch (IllegalArgumentException e) {
//expected.
}
}
@AppModeFull
public void testHangRecover() throws Exception {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(android.Manifest.permission.REMOVE_TASKS);
final CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
final ContentProviderClient client = mContentResolver
.acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
client.setDetectNotResponding(2_000);
try {
client.query(REMOTE_HANG_URI, null, null, null);
fail("Funky, we somehow returned?");
} catch (RemoteException e) {
latch.countDown();
}
}).start();
// The remote process should have been killed after the ANR was detected
// above, causing our pending call to return and release our latch above
// within 10 seconds; if our Binder thread hasn't been freed, then we
// fail with a timeout.
latch.await(10, TimeUnit.SECONDS);
}
public void testGetTypeInfo() throws Exception {
for (String mimeType : new String[] {
"image/png",
"IMage/PnG",
"image/x-custom",
"application/x-flac",
"application/rdf+xml",
"x-custom/x-custom",
}) {
final MimeTypeInfo ti = mContentResolver.getTypeInfo(mimeType);
assertNotNull(ti);
assertNotNull(ti.getLabel());
assertNotNull(ti.getContentDescription());
assertNotNull(ti.getIcon());
}
}
public void testGetTypeInfo_Invalid() throws Exception {
try {
mContentResolver.getTypeInfo(null);
fail("Expected exception for null");
} catch (NullPointerException expected) {
}
}
public void testWrapContentProvider() throws Exception {
try (ContentProviderClient local = getContext().getContentResolver()
.acquireContentProviderClient(AUTHORITY)) {
final ContentResolver resolver = ContentResolver.wrap(local.getLocalContentProvider());
assertNotNull(resolver.getType(TABLE1_URI));
try {
resolver.getType(REMOTE_TABLE1_URI);
fail();
} catch (SecurityException | IllegalArgumentException expected) {
}
}
}
public void testWrapContentProviderClient() throws Exception {
try (ContentProviderClient remote = getContext().getContentResolver()
.acquireContentProviderClient(REMOTE_AUTHORITY)) {
final ContentResolver resolver = ContentResolver.wrap(remote);
assertNotNull(resolver.getType(REMOTE_TABLE1_URI));
try {
resolver.getType(TABLE1_URI);
fail();
} catch (SecurityException | IllegalArgumentException expected) {
}
}
}
@AppModeFull
public void testContentResolverCaching() throws Exception {
InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
android.Manifest.permission.CACHE_CONTENT,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
Bundle cached = new Bundle();
cached.putString("key", "value");
mContentResolver.putCache(TABLE1_URI, cached);
Bundle response = mContentResolver.getCache(TABLE1_URI);
assertEquals("value", response.getString("key"));
ContentValues values = new ContentValues();
values.put(COLUMN_KEY_NAME, "key10");
values.put(COLUMN_VALUE_NAME, 10);
mContentResolver.update(TABLE1_URI, values, null, null);
response = mContentResolver.getCache(TABLE1_URI);
assertNull(response);
}
public void testEncodeDecode() {
final Uri expected = Uri.parse("content://com.example/item/23");
final File file = ContentResolver.encodeToFile(expected);
assertNotNull(file);
final Uri actual = ContentResolver.decodeFromFile(file);
assertNotNull(actual);
assertEquals(expected, actual);
}
public static class Change {
public final boolean selfChange;
public final Iterable<Uri> uris;
public final int flags;
@UserIdInt
public final int userId;
public Change(boolean selfChange, Iterable<Uri> uris, int flags) {
this.selfChange = selfChange;
this.uris = uris;
this.flags = flags;
this.userId = -1;
}
public Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId) {
this.selfChange = selfChange;
this.uris = uris;
this.flags = flags;
this.userId = userId;
}
@Override
public String toString() {
return String.format("onChange(%b, %s, %d, %d)",
selfChange, asSet(uris).toString(), flags, userId);
}
@Override
public boolean equals(Object other) {
if (other instanceof Change) {
final Change change = (Change) other;
return change.selfChange == selfChange &&
Objects.equals(asSet(change.uris), asSet(uris)) &&
change.flags == flags && change.userId == userId;
} else {
return false;
}
}
private static Set<Uri> asSet(Iterable<Uri> uris) {
final Set<Uri> asSet = new HashSet<>();
uris.forEach(asSet::add);
return asSet;
}
}
private static class MockContentObserver extends ContentObserver {
private boolean mHadOnChanged = false;
private List<Change> mChanges = new ArrayList<>();
public MockContentObserver() {
super(null);
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public synchronized void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
doOnChangeLocked(selfChange, uris, flags, /*userId=*/ -1);
}
@Override
public synchronized void onChange(boolean selfChange, @NonNull Collection<Uri> uris,
@ContentResolver.NotifyFlags int flags, UserHandle user) {
doOnChangeLocked(selfChange, uris, flags, user.getIdentifier());
}
public synchronized boolean hadOnChanged() {
return mHadOnChanged;
}
public synchronized void reset() {
mHadOnChanged = false;
}
public synchronized boolean hadChanges(Collection<Change> changes) {
return mChanges.containsAll(changes);
}
@GuardedBy("this")
private void doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris,
@ContentResolver.NotifyFlags int flags, @UserIdInt int userId) {
final Change change = new Change(selfChange, uris, flags, userId);
Log.v(TAG, change.toString());
mHadOnChanged = true;
mChanges.add(change);
}
}
}