Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package android.content.cts;
     18 
     19 
     20 import android.content.AsyncQueryHandler;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 import android.os.Handler;
     26 import android.os.HandlerThread;
     27 import android.os.Looper;
     28 import android.test.InstrumentationTestCase;
     29 import android.test.UiThreadTest;
     30 
     31 /**
     32  * Test {@link AsyncQueryHandler} and {@link WorkerHandler}}.
     33  *
     34  * @see DummyProvider
     35  */
     36 public class AsyncQueryHandlerTest extends InstrumentationTestCase {
     37     private static final long TEST_TIME_OUT = DummyProvider.MOCK_OPERATION_SLEEP_TIME + 5000;
     38 
     39     private static final int INSERT_TOKEN_1    = 100;
     40     private static final int INSERT_TOKEN_2    = 101;
     41     private static final int QUERY_TOKEN_1     = 200;
     42     private static final int QUERY_TOKEN_2     = 201;
     43     private static final int MOCK_QUERY_TOKEN  = 202;
     44     private static final int DELETE_TOKEN_1    = 300;
     45     private static final int DELETE_TOKEN_2    = 301;
     46     private static final int UPDATE_TOKEN_1    = 400;
     47     private static final int UPDATE_TOKEN_2    = 401;
     48 
     49     private static final Object INSERT_COOKIE = "insert cookie";
     50     private static final Object QUERY_COOKIE  = "query cookie";
     51     private static final Object DELETE_COOKIE = "delete cookie";
     52     private static final Object UPDATE_COOKIE = "update cookie";
     53 
     54     private static final String NAME0 = "name0";
     55     private static final String NAME1 = "name1";
     56     private static final String NAME2 = "name2";
     57     private static final String NAME3 = "name3";
     58     private static final String NAME4 = "name4";
     59 
     60     private static final int NAME_COLUMN_INDEX = 1;
     61 
     62     private static final boolean CANCELABLE = true;
     63     private static final boolean NO_CANCEL = false;
     64 
     65     private static final String[] PROJECTIONS = new String[] {
     66             DummyProvider._ID, DummyProvider.NAME};
     67 
     68     private static final String ORDER_BY = "_id ASC";
     69     private static final int ORIGINAL_ROW_COUNT = 3;
     70 
     71     private MockAsyncQueryHandler mAsyncHandler;
     72     private ContentResolver mResolver;
     73 
     74     @Override
     75     protected void setUp() throws Exception {
     76         super.setUp();
     77 
     78         mResolver = getInstrumentation().getTargetContext().getContentResolver();
     79 
     80         ContentValues values0 = new ContentValues();
     81         values0.put(DummyProvider.NAME, NAME0);
     82         mResolver.insert(DummyProvider.CONTENT_URI, values0);
     83 
     84         ContentValues values1 = new ContentValues();
     85         values1.put(DummyProvider.NAME, NAME1);
     86         mResolver.insert(DummyProvider.CONTENT_URI, values1);
     87 
     88         ContentValues values2 = new ContentValues();
     89         values2.put(DummyProvider.NAME, NAME2);
     90         mResolver.insert(DummyProvider.CONTENT_URI, values2);
     91     }
     92 
     93     @Override
     94     protected void tearDown() throws Exception {
     95         mResolver.delete(DummyProvider.CONTENT_URI, null, null);
     96 
     97         super.tearDown();
     98     }
     99 
    100     @UiThreadTest
    101     public void testConstructor() {
    102         new AsyncQueryHandler(mResolver) {};
    103         new AsyncQueryHandler(null) {};
    104     }
    105 
    106     public void testStartInsert() throws InterruptedException {
    107         final ContentValues values1 = new ContentValues();
    108         values1.put(DummyProvider.NAME, NAME3);
    109         final ContentValues values2 = new ContentValues();
    110         values2.put(DummyProvider.NAME, NAME4);
    111 
    112         mAsyncHandler = createAsyncQueryHandlerSync();
    113         assertFalse(mAsyncHandler.hadInserted(0));
    114 
    115         // insert without cancelling.
    116         startInsert(INSERT_TOKEN_1, INSERT_COOKIE, DummyProvider.CONTENT_URI, values1, NO_CANCEL);
    117 
    118         assertTrue(mAsyncHandler.hadInserted(TEST_TIME_OUT));
    119         assertEquals(INSERT_TOKEN_1, mAsyncHandler.getToken());
    120         assertEquals(INSERT_COOKIE, mAsyncHandler.getCookie());
    121         assertEquals(DummyProvider.CONTENT_URI, (Uri) mAsyncHandler.getResult());
    122 
    123         mAsyncHandler.reset();
    124         // insert will be cancelled.
    125         startInsert(INSERT_TOKEN_2, INSERT_COOKIE, DummyProvider.CONTENT_URI, values2, CANCELABLE);
    126         mAsyncHandler.cancelOperation(INSERT_TOKEN_2);
    127         assertFalse(mAsyncHandler.hadInserted(TEST_TIME_OUT));
    128 
    129         // only value1 has been inserted.
    130         Cursor cursor = null;
    131         try {
    132             cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
    133             assertEquals(ORIGINAL_ROW_COUNT + 1, cursor.getCount());
    134             cursor.moveToLast();
    135             assertEquals(NAME3, cursor.getString(NAME_COLUMN_INDEX));
    136         } finally {
    137             if (cursor != null) {
    138                 cursor.close();
    139             }
    140         }
    141     }
    142 
    143     public void testStartQuery() throws InterruptedException {
    144         mAsyncHandler = createAsyncQueryHandlerSync();
    145         assertFalse(mAsyncHandler.hadQueried(0));
    146 
    147         // query without cancelling.
    148         startQuery(QUERY_TOKEN_1, QUERY_COOKIE, DummyProvider.CONTENT_URI,
    149                 PROJECTIONS, null, null, ORDER_BY, NO_CANCEL);
    150 
    151         assertTrue(mAsyncHandler.hadQueried(TEST_TIME_OUT));
    152         assertEquals(QUERY_TOKEN_1, mAsyncHandler.getToken());
    153         assertEquals(QUERY_COOKIE, mAsyncHandler.getCookie());
    154         Cursor cursor = (Cursor) mAsyncHandler.getResult();
    155         assertNotNull(cursor);
    156         try {
    157             assertEquals(ORIGINAL_ROW_COUNT, cursor.getCount());
    158             assertEquals(2, cursor.getColumnCount());
    159             cursor.moveToFirst();
    160             assertEquals(NAME0, cursor.getString(NAME_COLUMN_INDEX));
    161 
    162             cursor.moveToNext();
    163             assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
    164 
    165             cursor.moveToNext();
    166             assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
    167         } finally {
    168             cursor.close();
    169         }
    170 
    171         // query will be cancelled.
    172         mAsyncHandler.reset();
    173         startQuery(QUERY_TOKEN_2, QUERY_COOKIE, DummyProvider.CONTENT_URI,
    174                 PROJECTIONS, null, null, ORDER_BY, CANCELABLE);
    175         mAsyncHandler.cancelOperation(QUERY_TOKEN_2);
    176         assertFalse(mAsyncHandler.hadQueried(TEST_TIME_OUT));
    177     }
    178 
    179     public void testStartUpdate() throws InterruptedException {
    180         final ContentValues values1 = new ContentValues();
    181         values1.put(DummyProvider.NAME, NAME3);
    182         final ContentValues values2 = new ContentValues();
    183         values2.put(DummyProvider.NAME, NAME4);
    184 
    185         mAsyncHandler = createAsyncQueryHandlerSync();
    186         assertFalse(mAsyncHandler.hadUpdated(0));
    187 
    188         // update without cancelling.
    189         startUpdate(UPDATE_TOKEN_1, UPDATE_COOKIE, DummyProvider.CONTENT_URI, values1,
    190                 DummyProvider.NAME + "=?", new String[] { NAME0 }, NO_CANCEL);
    191 
    192         assertTrue(mAsyncHandler.hadUpdated(TEST_TIME_OUT));
    193         assertEquals(UPDATE_TOKEN_1, mAsyncHandler.getToken());
    194         assertEquals(UPDATE_COOKIE, mAsyncHandler.getCookie());
    195         assertEquals(1, ((Integer) mAsyncHandler.getResult()).intValue());
    196 
    197         mAsyncHandler.reset();
    198         // update will be cancelled.
    199         startUpdate(UPDATE_TOKEN_2, UPDATE_COOKIE, DummyProvider.CONTENT_URI, values2,
    200                 DummyProvider.NAME + "=?", new String[] { NAME1 }, CANCELABLE);
    201         mAsyncHandler.cancelOperation(UPDATE_TOKEN_2);
    202         assertFalse(mAsyncHandler.hadUpdated(TEST_TIME_OUT));
    203 
    204         // only values has been updated.
    205         Cursor cursor = null;
    206         try {
    207             cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
    208             assertEquals(ORIGINAL_ROW_COUNT, cursor.getCount());
    209             cursor.moveToFirst();
    210             assertEquals(NAME3, cursor.getString(NAME_COLUMN_INDEX));
    211 
    212             cursor.moveToNext();
    213             assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
    214 
    215             cursor.moveToNext();
    216             assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
    217         } finally {
    218             if (cursor != null) {
    219                 cursor.close();
    220             }
    221         }
    222     }
    223 
    224     public void testStartDelete() throws InterruptedException {
    225         mAsyncHandler = createAsyncQueryHandlerSync();
    226         assertFalse(mAsyncHandler.hadDeleted(0));
    227 
    228         // delete without cancelling.
    229         startDelete(DELETE_TOKEN_1, DELETE_COOKIE, DummyProvider.CONTENT_URI,
    230                 DummyProvider.NAME + "=?", new String[] { NAME0 }, NO_CANCEL);
    231 
    232         assertTrue(mAsyncHandler.hadDeleted(TEST_TIME_OUT));
    233         assertEquals(DELETE_TOKEN_1, mAsyncHandler.getToken());
    234         assertEquals(DELETE_COOKIE, mAsyncHandler.getCookie());
    235         assertEquals(1, ((Integer) mAsyncHandler.getResult()).intValue());
    236 
    237         mAsyncHandler.reset();
    238         // delete will be cancelled
    239         startDelete(DELETE_TOKEN_2, DELETE_COOKIE, DummyProvider.CONTENT_URI,
    240                 DummyProvider.NAME + "=?", new String[] { NAME1 }, CANCELABLE);
    241         mAsyncHandler.cancelOperation(DELETE_TOKEN_2);
    242         assertFalse(mAsyncHandler.hadDeleted(TEST_TIME_OUT));
    243 
    244         // only NAME0 has been deleted.
    245         Cursor cursor = null;
    246         try {
    247             cursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, ORDER_BY);
    248             assertEquals(ORIGINAL_ROW_COUNT - 1, cursor.getCount());
    249             cursor.moveToFirst();
    250             assertEquals(NAME1, cursor.getString(NAME_COLUMN_INDEX));
    251 
    252             cursor.moveToNext();
    253             assertEquals(NAME2, cursor.getString(NAME_COLUMN_INDEX));
    254         } finally {
    255             if (cursor != null) {
    256                 cursor.close();
    257             }
    258         }
    259     }
    260 
    261     @UiThreadTest
    262     public void testCreateHandler() {
    263         MockAsyncQueryHandler wrapper = new MockAsyncQueryHandler(mResolver);
    264         Handler result = wrapper.createHandler(Looper.getMainLooper());
    265         assertNotNull(result);
    266         assertSame(Looper.myLooper(), result.getLooper());
    267 
    268         try {
    269             wrapper.createHandler(null);
    270             fail("Should throw NullPointerException if param looper is null");
    271         } catch (NullPointerException e) {
    272         }
    273     }
    274 
    275     private void startQuery(int token, Object cookie, Uri uri, String[] projection,
    276             String selection, String[] selectionArgs, String orderBy, boolean cancelable) {
    277         if (cancelable) {
    278             mAsyncHandler.injectMockQuery();
    279         }
    280         mAsyncHandler.startQuery(token, cookie, uri, projection,
    281                 selection, selectionArgs, orderBy);
    282     }
    283 
    284     private void startInsert(int token, Object cookie, Uri uri,
    285             ContentValues initialValues, boolean cancelable) {
    286         if (cancelable) {
    287             mAsyncHandler.injectMockQuery();
    288         }
    289         mAsyncHandler.startInsert(token, cookie, uri, initialValues);
    290     }
    291 
    292     private void startUpdate(int token, Object cookie, Uri uri, ContentValues values,
    293             String selection, String[] selectionArgs, boolean cancelable) {
    294         if (cancelable) {
    295             mAsyncHandler.injectMockQuery();
    296         }
    297         mAsyncHandler.startUpdate(token, cookie, uri, values, selection, selectionArgs);
    298     }
    299 
    300     private void startDelete(int token, Object cookie, Uri uri, String selection,
    301             String[] selectionArgs, boolean cancelable) {
    302         if (cancelable) {
    303             mAsyncHandler.injectMockQuery();
    304         }
    305         mAsyncHandler.startDelete(token, cookie, uri, selection, selectionArgs);
    306     }
    307 
    308     private static class MockAsyncQueryHandler extends AsyncQueryHandler {
    309         private boolean mHadInserted;
    310         private boolean mHadDeleted;
    311         private boolean mHadQueried;
    312         private boolean mHadUpdated;
    313 
    314         private int mToken;
    315         private Object mCookie;
    316         private Object mResult;
    317 
    318         public MockAsyncQueryHandler(ContentResolver cr) {
    319             super(cr);
    320         }
    321 
    322         @Override
    323         protected Handler createHandler(Looper looper) {
    324             return super.createHandler(looper);
    325         }
    326 
    327         /**
    328          * Injects a mock query operation which does nothing except make queue busy so that
    329          * following operations should be cancelled successfully.
    330          */
    331         private void injectMockQuery() {
    332             // we have to call super.startQuery here.
    333             super.startQuery(MOCK_QUERY_TOKEN, QUERY_COOKIE,
    334                     DummyProvider.CONTENT_URI_MOCK_OPERATION, PROJECTIONS, null, null, ORDER_BY);
    335         }
    336 
    337         @Override
    338         protected void onDeleteComplete(int token, Object cookie, int result) {
    339             super.onDeleteComplete(token, cookie, result);
    340             mToken = token;
    341             mCookie = cookie;
    342             mResult = Integer.valueOf(result);
    343             synchronized (this) {
    344                 mHadDeleted = true;
    345                 notify();
    346             }
    347         }
    348 
    349         @Override
    350         protected void onInsertComplete(int token, Object cookie, Uri uri) {
    351             super.onInsertComplete(token, cookie, uri);
    352             mToken = token;
    353             mCookie = cookie;
    354             mResult = uri;
    355             synchronized (this) {
    356                 mHadInserted = true;
    357                 notify();
    358             }
    359         }
    360 
    361         @Override
    362         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    363             super.onQueryComplete(token, cookie, cursor);
    364             if (token == MOCK_QUERY_TOKEN) {
    365                 // ignore mock query operation
    366                 return;
    367             }
    368             mToken = token;
    369             mCookie = cookie;
    370             mResult = cursor;
    371             synchronized (this) {
    372                 mHadQueried = true;
    373                 notify();
    374             }
    375         }
    376 
    377         @Override
    378         protected void onUpdateComplete(int token, Object cookie, int result) {
    379             super.onUpdateComplete(token, cookie, result);
    380             mToken = token;
    381             mCookie = cookie;
    382             mResult = Integer.valueOf(result);
    383             synchronized (this) {
    384                 mHadUpdated = true;
    385                 notify();
    386             }
    387         }
    388 
    389         public synchronized boolean hadInserted(long timeout) throws InterruptedException {
    390             synchronized (this) {
    391                 // do not wait if timeout is 0
    392                 if (timeout > 0 && !mHadInserted) {
    393                     wait(timeout);
    394                 }
    395             }
    396             return mHadInserted;
    397         }
    398 
    399         public synchronized boolean hadUpdated(long timeout) throws InterruptedException {
    400             // do not wait if timeout is 0
    401             if (timeout > 0 && !mHadUpdated) {
    402                 wait(timeout);
    403             }
    404             return mHadUpdated;
    405         }
    406 
    407         public synchronized boolean hadDeleted(long timeout) throws InterruptedException {
    408             // do not wait if timeout is 0
    409             if (timeout > 0 && !mHadDeleted) {
    410                 wait(timeout);
    411             }
    412             return mHadDeleted;
    413         }
    414 
    415         public synchronized boolean hadQueried(long timeout) throws InterruptedException {
    416             // do not wait if timeout is 0
    417             if (timeout > 0 && !mHadQueried) {
    418                 wait(timeout);
    419             }
    420             return mHadQueried;
    421         }
    422 
    423         public int getToken() {
    424             return mToken;
    425         }
    426 
    427         public Object getCookie() {
    428             return mCookie;
    429         }
    430 
    431         public Object getResult() {
    432             return mResult;
    433         }
    434 
    435         public void reset() {
    436             mHadInserted = false;
    437             mHadDeleted = false;
    438             mHadQueried = false;
    439             mHadUpdated = false;
    440             mToken = 0;
    441             mCookie = null;
    442             mResult = null;
    443         }
    444     }
    445 
    446     private MockAsyncQueryHandler createAsyncQueryHandlerSync() throws InterruptedException {
    447         SyncRunnable sr = new SyncRunnable(new Runnable() {
    448             public void run() {
    449                 mAsyncHandler = new MockAsyncQueryHandler(mResolver);
    450             }
    451         });
    452         TestHandlerThread t = new TestHandlerThread(sr);
    453         t.start();
    454         sr.waitForComplete();
    455         return mAsyncHandler;
    456     }
    457 
    458     /**
    459      * The Class TestHandlerThread. Convenience for created AsyncQueryHandler
    460      * object on the handler thread.
    461      */
    462     private static class TestHandlerThread extends HandlerThread {
    463         private Runnable mTarget;
    464 
    465         public TestHandlerThread(Runnable target) {
    466             super("AsyncQueryHandlerTestHandlerThread");
    467             mTarget = target;
    468         }
    469 
    470         @Override
    471         protected void onLooperPrepared() {
    472             super.onLooperPrepared();
    473             if (mTarget != null) {
    474                 mTarget.run();
    475             }
    476         }
    477     }
    478 
    479     /**
    480      * The Class SyncRunnable. The object of this class make the thread wait
    481      * until the target finished
    482      */
    483     private static final class SyncRunnable implements Runnable {
    484         /** The target. */
    485         private final Runnable mTarget;
    486 
    487         /** true if the target is completed. */
    488         private boolean mHadCompleted;
    489 
    490         public SyncRunnable(Runnable target) {
    491             mTarget = target;
    492         }
    493 
    494         public void run() {
    495             if (mTarget != null) {
    496                 mTarget.run();
    497             }
    498 
    499             synchronized (this) {
    500                 mHadCompleted = true;
    501                 notifyAll();
    502             }
    503         }
    504 
    505         public synchronized void waitForComplete() throws InterruptedException {
    506             if (!mHadCompleted) {
    507                 wait(TEST_TIME_OUT);
    508             }
    509         }
    510     }
    511 }
    512