blob: 3d4ca8108b6fbf04702a5528edf1067c1165b468 [file] [log] [blame]
/*
* Copyright (C) 2009 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 android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
/**
* Test {@link AsyncQueryHandler} and {@link WorkerHandler}}.
*
* @see DummyProvider
*/
public class AsyncQueryHandlerTest extends InstrumentationTestCase {
private static final long TEST_TIME_OUT = DummyProvider.MOCK_OPERATION_SLEEP_TIME + 5000;
private static final int INSERT_TOKEN_1 = 100;
private static final int INSERT_TOKEN_2 = 101;
private static final int QUERY_TOKEN_1 = 200;
private static final int QUERY_TOKEN_2 = 201;
private static final int MOCK_QUERY_TOKEN = 202;
private static final int DELETE_TOKEN_1 = 300;
private static final int DELETE_TOKEN_2 = 301;
private static final int UPDATE_TOKEN_1 = 400;
private static final int UPDATE_TOKEN_2 = 401;
private static final Object INSERT_COOKIE = "insert cookie";
private static final Object QUERY_COOKIE = "query cookie";
private static final Object DELETE_COOKIE = "delete cookie";
private static final Object UPDATE_COOKIE = "update cookie";
private static final String NAME0 = "name0";
private static final String NAME1 = "name1";
private static final String NAME2 = "name2";
private static final String NAME3 = "name3";
private static final String NAME4 = "name4";
private static final int NAME_COLUMN_INDEX = 1;
private static final boolean CANCELABLE = true;
private static final boolean NO_CANCEL = false;
private static final String[] PROJECTIONS = new String[] {
DummyProvider._ID, DummyProvider.NAME};
private static final String ORDER_BY = "_id ASC";
private static final int ORIGINAL_ROW_COUNT = 3;
private MockAsyncQueryHandler mAsyncHandler;
private ContentResolver mResolver;
@Override
protected void setUp() throws Exception {
super.setUp();
mResolver = getInstrumentation().getTargetContext().getContentResolver();
ContentValues values0 = new ContentValues();
values0.put(DummyProvider.NAME, NAME0);
mResolver.insert(DummyProvider.CONTENT_URI, values0);
ContentValues values1 = new ContentValues();
values1.put(DummyProvider.NAME, NAME1);
mResolver.insert(DummyProvider.CONTENT_URI, values1);
ContentValues values2 = new ContentValues();
values2.put(DummyProvider.NAME, NAME2);
mResolver.insert(DummyProvider.CONTENT_URI, values2);
}
@Override
protected void tearDown() throws Exception {
mResolver.delete(DummyProvider.CONTENT_URI, null, null);
super.tearDown();
}
@UiThreadTest
public void testConstructor() {
new AsyncQueryHandler(mResolver) {};
new AsyncQueryHandler(null) {};
}
public void testStartInsert() throws InterruptedException {
final ContentValues values1 = new ContentValues();
values1.put(DummyProvider.NAME, NAME3);
final ContentValues values2 = new ContentValues();
values2.put(DummyProvider.NAME, NAME4);
mAsyncHandler = createAsyncQueryHandlerSync();
assertFalse(mAsyncHandler.hadInserted(0));
// insert without cancelling.
startInsert(INSERT_TOKEN_1, INSERT_COOKIE, DummyProvider.CONTENT_URI, values1, NO_CANCEL);
assertTrue(mAsyncHandler.hadInserted(TEST_TIME_OUT));
assertEquals(INSERT_TOKEN_1, mAsyncHandler.getToken());
assertEquals(INSERT_COOKIE, mAsyncHandler.getCookie());
assertEquals(DummyProvider.CONTENT_URI, (Uri) mAsyncHandler.getResult());
mAsyncHandler.reset();
// insert will be cancelled.
startInsert(INSERT_TOKEN_2, INSERT_COOKIE, DummyProvider.CONTENT_URI, values2, CANCELABLE);
mAsyncHandler.cancelOperation(INSERT_TOKEN_2);
assertFalse(mAsyncHandler.hadInserted(TEST_TIME_OUT));
// only value1 has been inserted.
Cursor cursor = null;
try {
cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
assertEquals(ORIGINAL_ROW_COUNT + 1, cursor.getCount());
cursor.moveToLast();
assertEquals(NAME3, cursor.getString(NAME_COLUMN_INDEX));
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public void testStartQuery() throws InterruptedException {
mAsyncHandler = createAsyncQueryHandlerSync();
assertFalse(mAsyncHandler.hadQueried(0));
// query without cancelling.
startQuery(QUERY_TOKEN_1, QUERY_COOKIE, DummyProvider.CONTENT_URI,
PROJECTIONS, null, null, ORDER_BY, NO_CANCEL);
assertTrue(mAsyncHandler.hadQueried(TEST_TIME_OUT));
assertEquals(QUERY_TOKEN_1, mAsyncHandler.getToken());
assertEquals(QUERY_COOKIE, mAsyncHandler.getCookie());
Cursor cursor = (Cursor) mAsyncHandler.getResult();
assertNotNull(cursor);
try {
assertEquals(ORIGINAL_ROW_COUNT, cursor.getCount());
assertEquals(2, cursor.getColumnCount());
cursor.moveToFirst();
assertEquals(NAME0, cursor.getString(NAME_COLUMN_INDEX));
cursor.moveToNext();
assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
cursor.moveToNext();
assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
} finally {
cursor.close();
}
// query will be cancelled.
mAsyncHandler.reset();
startQuery(QUERY_TOKEN_2, QUERY_COOKIE, DummyProvider.CONTENT_URI,
PROJECTIONS, null, null, ORDER_BY, CANCELABLE);
mAsyncHandler.cancelOperation(QUERY_TOKEN_2);
assertFalse(mAsyncHandler.hadQueried(TEST_TIME_OUT));
}
public void testStartUpdate() throws InterruptedException {
final ContentValues values1 = new ContentValues();
values1.put(DummyProvider.NAME, NAME3);
final ContentValues values2 = new ContentValues();
values2.put(DummyProvider.NAME, NAME4);
mAsyncHandler = createAsyncQueryHandlerSync();
assertFalse(mAsyncHandler.hadUpdated(0));
// update without cancelling.
startUpdate(UPDATE_TOKEN_1, UPDATE_COOKIE, DummyProvider.CONTENT_URI, values1,
DummyProvider.NAME + "=?", new String[] { NAME0 }, NO_CANCEL);
assertTrue(mAsyncHandler.hadUpdated(TEST_TIME_OUT));
assertEquals(UPDATE_TOKEN_1, mAsyncHandler.getToken());
assertEquals(UPDATE_COOKIE, mAsyncHandler.getCookie());
assertEquals(1, ((Integer) mAsyncHandler.getResult()).intValue());
mAsyncHandler.reset();
// update will be cancelled.
startUpdate(UPDATE_TOKEN_2, UPDATE_COOKIE, DummyProvider.CONTENT_URI, values2,
DummyProvider.NAME + "=?", new String[] { NAME1 }, CANCELABLE);
mAsyncHandler.cancelOperation(UPDATE_TOKEN_2);
assertFalse(mAsyncHandler.hadUpdated(TEST_TIME_OUT));
// only values has been updated.
Cursor cursor = null;
try {
cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
assertEquals(ORIGINAL_ROW_COUNT, cursor.getCount());
cursor.moveToFirst();
assertEquals(NAME3, cursor.getString(NAME_COLUMN_INDEX));
cursor.moveToNext();
assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
cursor.moveToNext();
assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public void testStartDelete() throws InterruptedException {
mAsyncHandler = createAsyncQueryHandlerSync();
assertFalse(mAsyncHandler.hadDeleted(0));
// delete without cancelling.
startDelete(DELETE_TOKEN_1, DELETE_COOKIE, DummyProvider.CONTENT_URI,
DummyProvider.NAME + "=?", new String[] { NAME0 }, NO_CANCEL);
assertTrue(mAsyncHandler.hadDeleted(TEST_TIME_OUT));
assertEquals(DELETE_TOKEN_1, mAsyncHandler.getToken());
assertEquals(DELETE_COOKIE, mAsyncHandler.getCookie());
assertEquals(1, ((Integer) mAsyncHandler.getResult()).intValue());
mAsyncHandler.reset();
// delete will be cancelled
startDelete(DELETE_TOKEN_2, DELETE_COOKIE, DummyProvider.CONTENT_URI,
DummyProvider.NAME + "=?", new String[] { NAME1 }, CANCELABLE);
mAsyncHandler.cancelOperation(DELETE_TOKEN_2);
assertFalse(mAsyncHandler.hadDeleted(TEST_TIME_OUT));
// only NAME0 has been deleted.
Cursor cursor = null;
try {
cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
assertEquals(ORIGINAL_ROW_COUNT - 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
cursor.moveToNext();
assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@UiThreadTest
public void testCreateHandler() {
MockAsyncQueryHandler wrapper = new MockAsyncQueryHandler(mResolver);
Handler result = wrapper.createHandler(Looper.getMainLooper());
assertNotNull(result);
assertSame(Looper.myLooper(), result.getLooper());
try {
wrapper.createHandler(null);
fail("Should throw NullPointerException if param looper is null");
} catch (NullPointerException e) {
}
}
private void startQuery(int token, Object cookie, Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy, boolean cancelable) {
if (cancelable) {
mAsyncHandler.injectMockQuery();
}
mAsyncHandler.startQuery(token, cookie, uri, projection,
selection, selectionArgs, orderBy);
}
private void startInsert(int token, Object cookie, Uri uri,
ContentValues initialValues, boolean cancelable) {
if (cancelable) {
mAsyncHandler.injectMockQuery();
}
mAsyncHandler.startInsert(token, cookie, uri, initialValues);
}
private void startUpdate(int token, Object cookie, Uri uri, ContentValues values,
String selection, String[] selectionArgs, boolean cancelable) {
if (cancelable) {
mAsyncHandler.injectMockQuery();
}
mAsyncHandler.startUpdate(token, cookie, uri, values, selection, selectionArgs);
}
private void startDelete(int token, Object cookie, Uri uri, String selection,
String[] selectionArgs, boolean cancelable) {
if (cancelable) {
mAsyncHandler.injectMockQuery();
}
mAsyncHandler.startDelete(token, cookie, uri, selection, selectionArgs);
}
private static class MockAsyncQueryHandler extends AsyncQueryHandler {
private boolean mHadInserted;
private boolean mHadDeleted;
private boolean mHadQueried;
private boolean mHadUpdated;
private int mToken;
private Object mCookie;
private Object mResult;
public MockAsyncQueryHandler(ContentResolver cr) {
super(cr);
}
@Override
protected Handler createHandler(Looper looper) {
return super.createHandler(looper);
}
/**
* Injects a mock query operation which does nothing except make queue busy so that
* following operations should be cancelled successfully.
*/
private void injectMockQuery() {
// we have to call super.startQuery here.
super.startQuery(MOCK_QUERY_TOKEN, QUERY_COOKIE,
DummyProvider.CONTENT_URI_MOCK_OPERATION, PROJECTIONS, null, null, ORDER_BY);
}
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
super.onDeleteComplete(token, cookie, result);
mToken = token;
mCookie = cookie;
mResult = Integer.valueOf(result);
synchronized (this) {
mHadDeleted = true;
notify();
}
}
@Override
protected void onInsertComplete(int token, Object cookie, Uri uri) {
super.onInsertComplete(token, cookie, uri);
mToken = token;
mCookie = cookie;
mResult = uri;
synchronized (this) {
mHadInserted = true;
notify();
}
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
if (token == MOCK_QUERY_TOKEN) {
// ignore mock query operation
return;
}
mToken = token;
mCookie = cookie;
mResult = cursor;
synchronized (this) {
mHadQueried = true;
notify();
}
}
@Override
protected void onUpdateComplete(int token, Object cookie, int result) {
super.onUpdateComplete(token, cookie, result);
mToken = token;
mCookie = cookie;
mResult = Integer.valueOf(result);
synchronized (this) {
mHadUpdated = true;
notify();
}
}
public synchronized boolean hadInserted(long timeout) throws InterruptedException {
synchronized (this) {
// do not wait if timeout is 0
if (timeout > 0 && !mHadInserted) {
wait(timeout);
}
}
return mHadInserted;
}
public synchronized boolean hadUpdated(long timeout) throws InterruptedException {
// do not wait if timeout is 0
if (timeout > 0 && !mHadUpdated) {
wait(timeout);
}
return mHadUpdated;
}
public synchronized boolean hadDeleted(long timeout) throws InterruptedException {
// do not wait if timeout is 0
if (timeout > 0 && !mHadDeleted) {
wait(timeout);
}
return mHadDeleted;
}
public synchronized boolean hadQueried(long timeout) throws InterruptedException {
// do not wait if timeout is 0
if (timeout > 0 && !mHadQueried) {
wait(timeout);
}
return mHadQueried;
}
public int getToken() {
return mToken;
}
public Object getCookie() {
return mCookie;
}
public Object getResult() {
return mResult;
}
public void reset() {
mHadInserted = false;
mHadDeleted = false;
mHadQueried = false;
mHadUpdated = false;
mToken = 0;
mCookie = null;
mResult = null;
}
}
private MockAsyncQueryHandler createAsyncQueryHandlerSync() throws InterruptedException {
SyncRunnable sr = new SyncRunnable(new Runnable() {
public void run() {
mAsyncHandler = new MockAsyncQueryHandler(mResolver);
}
});
TestHandlerThread t = new TestHandlerThread(sr);
t.start();
sr.waitForComplete();
return mAsyncHandler;
}
/**
* The Class TestHandlerThread. Convenience for created AsyncQueryHandler
* object on the handler thread.
*/
private static class TestHandlerThread extends HandlerThread {
private Runnable mTarget;
public TestHandlerThread(Runnable target) {
super("AsyncQueryHandlerTestHandlerThread");
mTarget = target;
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
if (mTarget != null) {
mTarget.run();
}
}
}
/**
* The Class SyncRunnable. The object of this class make the thread wait
* until the target finished
*/
private static final class SyncRunnable implements Runnable {
/** The target. */
private final Runnable mTarget;
/** true if the target is completed. */
private boolean mHadCompleted;
public SyncRunnable(Runnable target) {
mTarget = target;
}
public void run() {
if (mTarget != null) {
mTarget.run();
}
synchronized (this) {
mHadCompleted = true;
notifyAll();
}
}
public synchronized void waitForComplete() throws InterruptedException {
if (!mHadCompleted) {
wait(TEST_TIME_OUT);
}
}
}
}