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