1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.contentpager.content; 18 19import static androidx.contentpager.content.ContentPager.createArgs; 20import static androidx.contentpager.content.TestContentProvider.PAGED_URI; 21import static androidx.contentpager.content.TestContentProvider.PAGED_WINDOWED_URI; 22import static androidx.contentpager.content.TestContentProvider.UNPAGED_URI; 23 24import static org.junit.Assert.assertEquals; 25import static org.junit.Assert.assertFalse; 26import static org.junit.Assert.assertNotNull; 27import static org.junit.Assert.assertTrue; 28 29import android.app.Activity; 30import android.content.ContentResolver; 31import android.database.Cursor; 32import android.net.Uri; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.Looper; 36import android.support.test.filters.MediumTest; 37import android.support.test.rule.ActivityTestRule; 38import android.support.test.runner.AndroidJUnit4; 39 40import androidx.annotation.Nullable; 41import androidx.contentpager.content.ContentPager.ContentCallback; 42 43import org.junit.Before; 44import org.junit.Rule; 45import org.junit.Test; 46import org.junit.runner.RunWith; 47 48import java.util.Arrays; 49import java.util.HashMap; 50import java.util.List; 51import java.util.Map; 52import java.util.concurrent.TimeUnit; 53 54@MediumTest 55@RunWith(AndroidJUnit4.class) 56public class ContentPagerTest { 57 58 private ContentResolver mResolver; 59 private TestQueryRunner mRunner; 60 private TestContentCallback mCallback; 61 private ContentPager mPager; 62 63 @Rule 64 public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule(TestActivity.class); 65 66 @Before 67 public void setUp() { 68 mRunner = new TestQueryRunner(); 69 mResolver = mActivityRule.getActivity().getContentResolver(); 70 mCallback = new TestContentCallback(); 71 mPager = new ContentPager(mResolver, mRunner); 72 } 73 74 @Test 75 public void testRelaysProviderPagedResults() throws Throwable { 76 int offset = 0; 77 int limit = 10; 78 79 // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that 80 // accepts a Bundle. That means on older platforms we either have to cook up 81 // a special way of paging that doesn't use the bundle (that's what we do here) 82 // or we simply skip testing how we deal with provider paged results. 83 Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec(PAGED_URI, offset, limit); 84 85 Query query = mPager.query( 86 uriWithTestPagingData, 87 null, 88 createArgs(offset, limit), 89 null, 90 mCallback); 91 92 mCallback.assertNumPagesLoaded(1); 93 mCallback.assertPageLoaded(query); 94 Cursor cursor = mCallback.getCursor(query); 95 Bundle extras = cursor.getExtras(); 96 97 assertExpectedRecords(cursor, query.getOffset()); 98 99 assertEquals( 100 ContentPager.CURSOR_DISPOSITION_PAGED, 101 extras.getInt(ContentPager.CURSOR_DISPOSITION, -1)); 102 103 assertEquals( 104 TestContentProvider.DEFAULT_RECORD_COUNT, 105 extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); 106 107 assertHasHonoredArgs( 108 extras, 109 ContentResolver.QUERY_ARG_LIMIT, 110 ContentResolver.QUERY_ARG_OFFSET); 111 112 assertEquals( 113 1, 114 extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED)); 115 assertEquals( 116 1, 117 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 118 assertEquals( 119 1, 120 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 121 } 122 123 @Test 124 public void testLimitsPagedResultsToWindowSize() throws Throwable { 125 int offset = 0; 126 int limit = 10; 127 128 // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that 129 // accepts a Bundle. That means on older platforms we either have to cook up 130 // a special way of paging that doesn't use the bundle (that's what we do here) 131 // or we simply skip testing how we deal with provider paged results. 132 Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec( 133 PAGED_WINDOWED_URI, offset, limit); 134 135 Query query = mPager.query( 136 uriWithTestPagingData, 137 null, 138 createArgs(offset, limit), 139 null, 140 mCallback); 141 142 mCallback.assertNumPagesLoaded(1); 143 mCallback.assertPageLoaded(query); 144 Cursor cursor = mCallback.getCursor(query); 145 Bundle extras = cursor.getExtras(); 146 147 assertExpectedRecords(cursor, query.getOffset()); 148 149 assertEquals( 150 ContentPager.CURSOR_DISPOSITION_REPAGED, 151 extras.getInt(ContentPager.CURSOR_DISPOSITION, -1)); 152 153 assertEquals( 154 TestContentProvider.DEFAULT_RECORD_COUNT, 155 extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); 156 157 158 assertEquals(limit, extras.getInt(ContentPager.EXTRA_REQUESTED_LIMIT)); 159 160 assertEquals(7, extras.getInt(ContentPager.EXTRA_SUGGESTED_LIMIT)); 161 162 assertHasHonoredArgs( 163 extras, 164 ContentResolver.QUERY_ARG_LIMIT, 165 ContentResolver.QUERY_ARG_OFFSET); 166 167 assertEquals( 168 1, 169 extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED)); 170 assertEquals( 171 1, 172 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 173 assertEquals( 174 1, 175 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 176 } 177 178 @Test 179 public void testAdaptsUnpagedToPaged() throws Throwable { 180 Query query = mPager.query( 181 UNPAGED_URI, 182 null, 183 createArgs(0, 10), 184 null, 185 mCallback); 186 187 mCallback.assertNumPagesLoaded(1); 188 mCallback.assertPageLoaded(query); 189 Cursor cursor = mCallback.getCursor(query); 190 Bundle extras = cursor.getExtras(); 191 192 assertExpectedRecords(cursor, query.getOffset()); 193 194 assertEquals( 195 ContentPager.CURSOR_DISPOSITION_COPIED, 196 extras.getInt(ContentPager.CURSOR_DISPOSITION)); 197 198 assertEquals( 199 TestContentProvider.DEFAULT_RECORD_COUNT, 200 extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); 201 202 assertHasHonoredArgs( 203 extras, 204 ContentResolver.QUERY_ARG_LIMIT, 205 ContentResolver.QUERY_ARG_OFFSET); 206 207 assertEquals( 208 1, 209 extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); 210 assertEquals( 211 1, 212 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 213 assertEquals( 214 1, 215 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 216 } 217 218 @Test 219 public void testCachesUnpagedCursor() throws Throwable { 220 mPager.query( 221 UNPAGED_URI, 222 null, 223 createArgs(0, 10), 224 null, 225 mCallback); 226 227 mPager.query( 228 UNPAGED_URI, 229 null, 230 createArgs(10, 10), 231 null, 232 mCallback); 233 234 // Rerun the same query as the first...extra exercise to ensure we can return 235 // to previously loaded results. 236 Query query = mPager.query( 237 UNPAGED_URI, 238 null, 239 createArgs(0, 10), 240 null, 241 mCallback); 242 243 mCallback.assertNumPagesLoaded(3); 244 Cursor cursor = mCallback.getCursor(query); 245 Bundle extras = cursor.getExtras(); 246 247 assertEquals( 248 3, 249 extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); 250 assertEquals( 251 1, 252 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 253 assertEquals( 254 3, 255 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 256 } 257 258 @Test 259 public void testWrapsCursorsThatJustHappenToFitInPageRange() throws Throwable { 260 261 // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that 262 // accepts a Bundle. That means on older platforms we either have to cook up 263 // a special way of paging that doesn't use the bundle (that's what we do here) 264 // or we simply skip testing how we deal with provider paged results. 265 Uri uri = TestContentProvider.forceRecordCount(UNPAGED_URI, 22); 266 267 Query query = mPager.query( 268 uri, 269 null, 270 createArgs(0, 44), 271 null, 272 mCallback); 273 274 mCallback.assertNumPagesLoaded(1); 275 // mCallback.assertPageLoaded(pageId); 276 mCallback.assertPageLoaded(query); 277 Cursor cursor = mCallback.getCursor(query); 278 Bundle extras = cursor.getExtras(); 279 280 assertExpectedRecords(cursor, query.getOffset()); 281 282 assertEquals( 283 ContentPager.CURSOR_DISPOSITION_WRAPPED, 284 extras.getInt(ContentPager.CURSOR_DISPOSITION)); 285 286 assertEquals( 287 22, 288 extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); 289 290 assertHasHonoredArgs( 291 extras, 292 ContentResolver.QUERY_ARG_LIMIT, 293 ContentResolver.QUERY_ARG_OFFSET); 294 295 assertEquals( 296 1, 297 extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); 298 assertEquals( 299 1, 300 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 301 assertEquals( 302 1, 303 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 304 } 305 306 @Test 307 public void testCorrectlyCopiesRecords_EndOfResults() throws Throwable { 308 // finally, check the last page. 309 int limit = 100; 310 // This will be the size of the last page. Should be 67. 311 int leftOvers = TestContentProvider.DEFAULT_RECORD_COUNT % limit; 312 int offset = TestContentProvider.DEFAULT_RECORD_COUNT - leftOvers; 313 314 Query query = mPager.query( 315 UNPAGED_URI, 316 null, 317 createArgs(offset, limit), 318 null, 319 mCallback); 320 321 mCallback.assertNumPagesLoaded(1); 322 mCallback.assertPageLoaded(query); 323 Cursor cursor = mCallback.getCursor(query); 324 assertEquals(leftOvers, cursor.getCount()); 325 Bundle extras = cursor.getExtras(); 326 327 assertExpectedRecords(cursor, query.getOffset()); 328 329 assertEquals( 330 ContentPager.CURSOR_DISPOSITION_COPIED, 331 extras.getInt(ContentPager.CURSOR_DISPOSITION)); 332 333 assertHasHonoredArgs( 334 extras, 335 ContentResolver.QUERY_ARG_LIMIT, 336 ContentResolver.QUERY_ARG_OFFSET); 337 338 assertEquals( 339 1, 340 extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); 341 assertEquals( 342 1, 343 extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); 344 assertEquals( 345 1, 346 extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); 347 } 348 349 @Test 350 public void testCancelsRunningQueriesOnReset() throws Throwable { 351 mRunner.runQuery = false; 352 Query query = mPager.query( 353 UNPAGED_URI, 354 null, 355 createArgs(0, 10), 356 null, 357 mCallback); 358 359 assertTrue(mRunner.isRunning(query)); 360 361 mPager.reset(); 362 363 assertFalse(mRunner.isRunning(query)); 364 } 365 366 @Test 367 public void testRelaysContentChangeNotificationsOnPagedCursors() throws Throwable { 368 369 TestContentObserver observer = new TestContentObserver( 370 new Handler(Looper.getMainLooper())); 371 observer.expectNotifications(1); 372 373 Query query = mPager.query( 374 UNPAGED_URI, 375 null, 376 createArgs(10, 99), 377 null, 378 mCallback); 379 380 Cursor cursor = mCallback.getCursor(query); 381 cursor.registerContentObserver(observer); 382 383 mResolver.notifyChange(UNPAGED_URI, null); 384 385 assertTrue(observer.mNotifiedLatch.await(1000, TimeUnit.MILLISECONDS)); 386 } 387 388 private void assertExpectedRecords(Cursor cursor, int offset) { 389 for (int row = 0; row < cursor.getCount(); row++) { 390 assertTrue(cursor.moveToPosition(row)); 391 int unpagedRow = offset + row; 392 for (int column = 0; column < cursor.getColumnCount(); column++) { 393 TestContentProvider.assertExpectedCellValue(cursor, unpagedRow, column); 394 } 395 } 396 } 397 398 private static void assertHasHonoredArgs(Bundle extras, String... expectedArgs) { 399 List<String> honored = Arrays.asList( 400 extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS)); 401 402 for (String arg : expectedArgs) { 403 assertTrue(honored.contains(arg)); 404 } 405 } 406 407 private static final class TestContentCallback implements ContentCallback { 408 409 private int mPagesLoaded; 410 private Map<Query, Cursor> mCursors = new HashMap<>(); 411 412 @Override 413 public void onCursorReady(Query query, Cursor cursor) { 414 mPagesLoaded++; 415 mCursors.put(query, cursor); 416 } 417 418 private void assertPageLoaded(Query query) { 419 assertTrue(mCursors.containsKey(query)); 420 assertNotNull(mCursors.get(query)); 421 } 422 423 private void assertNumPagesLoaded(int expected) { 424 assertEquals(expected, mPagesLoaded); 425 } 426 427 private @Nullable Cursor getCursor(Query query) { 428 return mCursors.get(query); 429 } 430 } 431} 432