1 /* 2 * Copyright (C) 2015 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 17 package com.android.tv.data; 18 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 import static androidx.test.InstrumentationRegistry.getTargetContext; 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import android.content.ContentProvider; 25 import android.content.ContentUris; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.res.AssetFileDescriptor; 29 import android.database.ContentObserver; 30 import android.database.Cursor; 31 import android.media.tv.TvContract; 32 import android.media.tv.TvContract.Channels; 33 import android.net.Uri; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.test.MoreAsserts; 37 import android.test.mock.MockContentProvider; 38 import android.test.mock.MockContentResolver; 39 import android.test.mock.MockCursor; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.util.SparseArray; 43 import androidx.test.filters.SmallTest; 44 import androidx.test.runner.AndroidJUnit4; 45 import com.android.tv.data.api.Channel; 46 import com.android.tv.testing.constants.Constants; 47 import com.android.tv.testing.data.ChannelInfo; 48 import com.android.tv.util.TvInputManagerHelper; 49 import java.io.FileNotFoundException; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.List; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.concurrent.TimeUnit; 55 import org.junit.After; 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.ArgumentMatchers; 60 import org.mockito.Mockito; 61 62 /** 63 * Test for {@link ChannelDataManager} 64 * 65 * <p>A test method may include tests for multiple methods to minimize the DB access. Note that all 66 * the methods of {@link ChannelDataManager} should be called from the UI thread. 67 */ 68 @SmallTest 69 @RunWith(AndroidJUnit4.class) 70 public class ChannelDataManagerTest { 71 private static final boolean DEBUG = false; 72 private static final String TAG = "ChannelDataManagerTest"; 73 74 // Wait time for expected success. 75 private static final long WAIT_TIME_OUT_MS = 1000L; 76 private static final String DUMMY_INPUT_ID = "dummy"; 77 private static final String COLUMN_BROWSABLE = "browsable"; 78 private static final String COLUMN_LOCKED = "locked"; 79 80 private ChannelDataManager mChannelDataManager; 81 private TestChannelDataManagerListener mListener; 82 private FakeContentResolver mContentResolver; 83 private FakeContentProvider mContentProvider; 84 85 @Before setUp()86 public void setUp() { 87 assertWithMessage("More than 2 channels to test") 88 .that(Constants.UNIT_TEST_CHANNEL_COUNT > 2) 89 .isTrue(); 90 91 mContentProvider = new FakeContentProvider(getTargetContext()); 92 mContentResolver = new FakeContentResolver(); 93 mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); 94 mListener = new TestChannelDataManagerListener(); 95 getInstrumentation() 96 .runOnMainSync( 97 new Runnable() { 98 @Override 99 public void run() { 100 TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class); 101 Mockito.when(mockHelper.hasTvInputInfo(ArgumentMatchers.anyString())) 102 .thenReturn(true); 103 Context mockContext = Mockito.mock(Context.class); 104 Mockito.when(mockContext.getContentResolver()).thenReturn(mContentResolver); 105 Mockito.when(mockContext.checkSelfPermission(ArgumentMatchers.anyString())) 106 .thenAnswer( 107 invocation -> { 108 Object[] args = invocation.getArguments(); 109 return getTargetContext().checkSelfPermission(((String) args[0])); 110 }); 111 112 mChannelDataManager = 113 new ChannelDataManager( 114 mockContext, mockHelper, AsyncTask.SERIAL_EXECUTOR, mContentResolver); 115 mChannelDataManager.addListener(mListener); 116 } 117 }); 118 } 119 120 @After tearDown()121 public void tearDown() { 122 getInstrumentation() 123 .runOnMainSync( 124 new Runnable() { 125 @Override 126 public void run() { 127 mChannelDataManager.stop(); 128 } 129 }); 130 } 131 startAndWaitForComplete()132 private void startAndWaitForComplete() throws InterruptedException { 133 getInstrumentation() 134 .runOnMainSync( 135 new Runnable() { 136 @Override 137 public void run() { 138 mChannelDataManager.start(); 139 } 140 }); 141 assertThat(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 142 .isTrue(); 143 } 144 restart()145 private void restart() throws InterruptedException { 146 getInstrumentation() 147 .runOnMainSync( 148 new Runnable() { 149 @Override 150 public void run() { 151 mChannelDataManager.stop(); 152 mListener.reset(); 153 } 154 }); 155 startAndWaitForComplete(); 156 } 157 158 @Test testIsDbLoadFinished()159 public void testIsDbLoadFinished() throws InterruptedException { 160 startAndWaitForComplete(); 161 assertThat(mChannelDataManager.isDbLoadFinished()).isTrue(); 162 } 163 164 /** 165 * Test for following methods - {@link ChannelDataManager#getChannelCount} - {@link 166 * ChannelDataManager#getChannelList} - {@link ChannelDataManager#getChannel} 167 */ 168 @Test testGetChannels()169 public void testGetChannels() throws InterruptedException { 170 startAndWaitForComplete(); 171 172 // Test {@link ChannelDataManager#getChannelCount} 173 assertThat(mChannelDataManager.getChannelCount()) 174 .isEqualTo(Constants.UNIT_TEST_CHANNEL_COUNT); 175 176 // Test {@link ChannelDataManager#getChannelList} 177 List<ChannelInfo> channelInfoList = new ArrayList<>(); 178 for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { 179 channelInfoList.add(ChannelInfo.create(getTargetContext(), i)); 180 } 181 List<Channel> channelList = mChannelDataManager.getChannelList(); 182 for (Channel channel : channelList) { 183 boolean found = false; 184 for (ChannelInfo channelInfo : channelInfoList) { 185 if (TextUtils.equals(channelInfo.name, channel.getDisplayName())) { 186 found = true; 187 channelInfoList.remove(channelInfo); 188 break; 189 } 190 } 191 assertWithMessage("Cannot find (" + channel + ")").that(found).isTrue(); 192 } 193 194 // Test {@link ChannelDataManager#getChannelIndex()} 195 for (Channel channel : channelList) { 196 assertThat(mChannelDataManager.getChannel(channel.getId())).isEqualTo(channel); 197 } 198 } 199 200 /** Test for {@link ChannelDataManager#getChannelCount} when no channel is available. */ 201 @Test testGetChannels_noChannels()202 public void testGetChannels_noChannels() throws InterruptedException { 203 mContentProvider.clear(); 204 startAndWaitForComplete(); 205 assertThat(mChannelDataManager.getChannelCount()).isEqualTo(0); 206 } 207 208 /** 209 * Test for following methods and channel listener with notifying change. - {@link 210 * ChannelDataManager#updateBrowsable} - {@link ChannelDataManager#applyUpdatedValuesToDb} 211 */ 212 @Test testBrowsable()213 public void testBrowsable() throws InterruptedException { 214 startAndWaitForComplete(); 215 216 // Test if all channels are browsable 217 List<Channel> channelList = mChannelDataManager.getChannelList(); 218 List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList(); 219 for (Channel browsableChannel : browsableChannelList) { 220 boolean found = channelList.remove(browsableChannel); 221 assertWithMessage("Cannot find (" + browsableChannel + ")").that(found).isTrue(); 222 } 223 assertThat(channelList).isEmpty(); 224 225 // Prepare for next tests. 226 channelList = mChannelDataManager.getChannelList(); 227 TestChannelDataManagerChannelListener channelListener = 228 new TestChannelDataManagerChannelListener(); 229 Channel channel1 = channelList.get(0); 230 mChannelDataManager.addChannelListener(channel1.getId(), channelListener); 231 232 // Test {@link ChannelDataManager#updateBrowsable} & notification. 233 mChannelDataManager.updateBrowsable(channel1.getId(), false, false); 234 assertThat(mListener.channelBrowsableChangedCalled).isTrue(); 235 assertThat(mChannelDataManager.getBrowsableChannelList()).doesNotContain(channel1); 236 MoreAsserts.assertContentsInAnyOrder(channelListener.updatedChannels, channel1); 237 channelListener.reset(); 238 239 // Test {@link ChannelDataManager#applyUpdatedValuesToDb} 240 // Disable the update notification to avoid the unwanted call of "onLoadFinished". 241 mContentResolver.mNotifyDisabled = true; 242 mChannelDataManager.applyUpdatedValuesToDb(); 243 restart(); 244 browsableChannelList = mChannelDataManager.getBrowsableChannelList(); 245 assertThat(browsableChannelList).hasSize(Constants.UNIT_TEST_CHANNEL_COUNT - 1); 246 assertThat(browsableChannelList).doesNotContain(channel1); 247 } 248 249 /** 250 * Test for following methods and channel listener without notifying change. - {@link 251 * ChannelDataManager#updateBrowsable} - {@link ChannelDataManager#applyUpdatedValuesToDb} 252 */ 253 @Test testBrowsable_skipNotification()254 public void testBrowsable_skipNotification() throws InterruptedException { 255 startAndWaitForComplete(); 256 257 List<Channel> channels = mChannelDataManager.getChannelList(); 258 // Prepare for next tests. 259 TestChannelDataManagerChannelListener channelListener = 260 new TestChannelDataManagerChannelListener(); 261 Channel channel1 = channels.get(0); 262 Channel channel2 = channels.get(1); 263 mChannelDataManager.addChannelListener(channel1.getId(), channelListener); 264 mChannelDataManager.addChannelListener(channel2.getId(), channelListener); 265 266 // Test {@link ChannelDataManager#updateBrowsable} & skip notification. 267 mChannelDataManager.updateBrowsable(channel1.getId(), false, true); 268 mChannelDataManager.updateBrowsable(channel2.getId(), false, true); 269 mChannelDataManager.updateBrowsable(channel1.getId(), true, true); 270 assertThat(mListener.channelBrowsableChangedCalled).isFalse(); 271 List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList(); 272 assertThat(browsableChannelList).contains(channel1); 273 assertThat(browsableChannelList).doesNotContain(channel2); 274 275 // Test {@link ChannelDataManager#applyUpdatedValuesToDb} 276 // Disable the update notification to avoid the unwanted call of "onLoadFinished". 277 mContentResolver.mNotifyDisabled = true; 278 mChannelDataManager.applyUpdatedValuesToDb(); 279 restart(); 280 browsableChannelList = mChannelDataManager.getBrowsableChannelList(); 281 assertThat(browsableChannelList).hasSize(Constants.UNIT_TEST_CHANNEL_COUNT - 1); 282 assertThat(browsableChannelList).doesNotContain(channel2); 283 } 284 285 /** 286 * Test for following methods and channel listener. - {@link ChannelDataManager#updateLocked} - 287 * {@link ChannelDataManager#applyUpdatedValuesToDb} 288 */ 289 @Test testLocked()290 public void testLocked() throws InterruptedException { 291 startAndWaitForComplete(); 292 293 // Test if all channels aren't locked at the first time. 294 List<Channel> channelList = mChannelDataManager.getChannelList(); 295 for (Channel channel : channelList) { 296 assertWithMessage(channel + " is locked").that(channel.isLocked()).isFalse(); 297 } 298 299 // Prepare for next tests. 300 Channel channel = mChannelDataManager.getChannelList().get(0); 301 302 // Test {@link ChannelDataManager#updateLocked} 303 mChannelDataManager.updateLocked(channel.getId(), true); 304 assertThat(mChannelDataManager.getChannel(channel.getId()).isLocked()).isTrue(); 305 306 // Test {@link ChannelDataManager#applyUpdatedValuesToDb}. 307 // Disable the update notification to avoid the unwanted call of "onLoadFinished". 308 mContentResolver.mNotifyDisabled = true; 309 mChannelDataManager.applyUpdatedValuesToDb(); 310 restart(); 311 assertThat(mChannelDataManager.getChannel(channel.getId()).isLocked()).isTrue(); 312 313 // Cleanup 314 mChannelDataManager.updateLocked(channel.getId(), false); 315 } 316 317 /** Test ChannelDataManager when channels in TvContract are updated, removed, or added. */ 318 @Test testChannelListChanged()319 public void testChannelListChanged() throws InterruptedException { 320 startAndWaitForComplete(); 321 322 // Test channel add. 323 mListener.reset(); 324 long testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; 325 ChannelInfo testChannelInfo = ChannelInfo.create(getTargetContext(), (int) testChannelId); 326 testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; 327 mContentProvider.simulateInsert(testChannelInfo); 328 assertThat(mListener.channelListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 329 .isTrue(); 330 assertThat(mChannelDataManager.getChannelCount()) 331 .isEqualTo(Constants.UNIT_TEST_CHANNEL_COUNT + 1); 332 333 // Test channel update 334 mListener.reset(); 335 TestChannelDataManagerChannelListener channelListener = 336 new TestChannelDataManagerChannelListener(); 337 mChannelDataManager.addChannelListener(testChannelId, channelListener); 338 String newName = testChannelInfo.name + "_test"; 339 mContentProvider.simulateUpdate(testChannelId, newName); 340 assertThat(mListener.channelListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 341 .isTrue(); 342 assertThat( 343 channelListener.channelChangedLatch.await( 344 WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 345 .isTrue(); 346 assertThat(channelListener.removedChannels).isEmpty(); 347 assertThat(channelListener.updatedChannels).hasSize(1); 348 Channel updatedChannel = channelListener.updatedChannels.get(0); 349 assertThat(updatedChannel.getId()).isEqualTo(testChannelId); 350 assertThat(updatedChannel.getDisplayNumber()).isEqualTo(testChannelInfo.number); 351 assertThat(updatedChannel.getDisplayName()).isEqualTo(newName); 352 assertThat(mChannelDataManager.getChannelCount()) 353 .isEqualTo(Constants.UNIT_TEST_CHANNEL_COUNT + 1); 354 355 // Test channel remove. 356 mListener.reset(); 357 channelListener.reset(); 358 mContentProvider.simulateDelete(testChannelId); 359 assertThat(mListener.channelListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 360 .isTrue(); 361 assertThat( 362 channelListener.channelChangedLatch.await( 363 WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 364 .isTrue(); 365 assertThat(channelListener.removedChannels).hasSize(1); 366 assertThat(channelListener.updatedChannels).isEmpty(); 367 Channel removedChannel = channelListener.removedChannels.get(0); 368 assertThat(removedChannel.getDisplayName()).isEqualTo(newName); 369 assertThat(removedChannel.getDisplayNumber()).isEqualTo(testChannelInfo.number); 370 assertThat(mChannelDataManager.getChannelCount()) 371 .isEqualTo(Constants.UNIT_TEST_CHANNEL_COUNT); 372 } 373 374 private static class ChannelInfoWrapper { 375 public ChannelInfo channelInfo; 376 public boolean browsable; 377 public boolean locked; 378 ChannelInfoWrapper(ChannelInfo channelInfo)379 public ChannelInfoWrapper(ChannelInfo channelInfo) { 380 this.channelInfo = channelInfo; 381 browsable = true; 382 locked = false; 383 } 384 } 385 386 private class FakeContentResolver extends MockContentResolver { 387 boolean mNotifyDisabled; 388 389 @Override notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)390 public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { 391 super.notifyChange(uri, observer, syncToNetwork); 392 if (DEBUG) { 393 Log.d( 394 TAG, 395 "onChanged(uri=" 396 + uri 397 + ", observer=" 398 + observer 399 + ") - Notification " 400 + (mNotifyDisabled ? "disabled" : "enabled")); 401 } 402 if (mNotifyDisabled) { 403 return; 404 } 405 // Do not call {@link ContentObserver#onChange} directly to run it on the correct 406 // thread. 407 if (observer != null) { 408 observer.dispatchChange(false, uri); 409 } else { 410 mChannelDataManager.getContentObserver().dispatchChange(false, uri); 411 } 412 } 413 } 414 415 // This implements the minimal methods in content resolver 416 // and detailed assumptions are written in each method. 417 private class FakeContentProvider extends MockContentProvider { 418 private final SparseArray<ChannelInfoWrapper> mChannelInfoList = new SparseArray<>(); 419 FakeContentProvider(Context context)420 public FakeContentProvider(Context context) { 421 super(context); 422 for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { 423 mChannelInfoList.put( 424 i, new ChannelInfoWrapper(ChannelInfo.create(getTargetContext(), i))); 425 } 426 } 427 428 @Override openTypedAssetFile(Uri url, String mimeType, Bundle opts)429 public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) { 430 try { 431 return getTargetContext().getContentResolver().openAssetFileDescriptor(url, "r"); 432 } catch (FileNotFoundException e) { 433 return null; 434 } 435 } 436 437 /** 438 * Implementation of {@link ContentProvider#query}. This assumes that {@link 439 * ChannelDataManager} queries channels with empty {@code selection}. (i.e. channels are 440 * always queries for all) 441 */ 442 @Override query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)443 public Cursor query( 444 Uri uri, 445 String[] projection, 446 String selection, 447 String[] selectionArgs, 448 String sortOrder) { 449 if (DEBUG) { 450 Log.d(TAG, "dump query"); 451 Log.d(TAG, " uri=" + uri); 452 Log.d(TAG, " projection=" + Arrays.toString(projection)); 453 Log.d(TAG, " selection=" + selection); 454 } 455 assertChannelUri(uri); 456 return new FakeCursor(projection); 457 } 458 459 /** 460 * Implementation of {@link ContentProvider#update}. This assumes that {@link 461 * ChannelDataManager} update channels only for changing browsable and locked. 462 */ 463 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)464 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 465 if (DEBUG) Log.d(TAG, "update(uri=" + uri + ", selection=" + selection); 466 assertChannelUri(uri); 467 List<Long> channelIds = new ArrayList<>(); 468 try { 469 long channelId = ContentUris.parseId(uri); 470 channelIds.add(channelId); 471 } catch (NumberFormatException e) { 472 // Update for multiple channels. 473 if (TextUtils.isEmpty(selection)) { 474 for (int i = 0; i < mChannelInfoList.size(); i++) { 475 channelIds.add((long) mChannelInfoList.keyAt(i)); 476 } 477 } else { 478 // See {@link Utils#buildSelectionForIds} for the syntax. 479 String selectionForId = 480 selection.substring( 481 selection.indexOf("(") + 1, selection.lastIndexOf(")")); 482 String[] ids = selectionForId.split(", "); 483 if (ids != null) { 484 for (String id : ids) { 485 channelIds.add(Long.parseLong(id)); 486 } 487 } 488 } 489 } 490 int updateCount = 0; 491 for (long channelId : channelIds) { 492 boolean updated = false; 493 ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId); 494 if (channel == null) { 495 return 0; 496 } 497 if (values.containsKey(COLUMN_BROWSABLE)) { 498 updated = true; 499 channel.browsable = (values.getAsInteger(COLUMN_BROWSABLE) == 1); 500 } 501 if (values.containsKey(COLUMN_LOCKED)) { 502 updated = true; 503 channel.locked = (values.getAsInteger(COLUMN_LOCKED) == 1); 504 } 505 updateCount += updated ? 1 : 0; 506 } 507 if (updateCount > 0) { 508 if (channelIds.size() == 1) { 509 mContentResolver.notifyChange(uri, null); 510 } else { 511 mContentResolver.notifyChange(Channels.CONTENT_URI, null); 512 } 513 } else { 514 if (DEBUG) { 515 Log.d(TAG, "Update to channel(uri=" + uri + ") is ignored for " + values); 516 } 517 } 518 return updateCount; 519 } 520 521 /** 522 * Simulates channel data insert. This assigns original network ID (the same with channel 523 * number) to channel ID. 524 */ simulateInsert(ChannelInfo testChannelInfo)525 public void simulateInsert(ChannelInfo testChannelInfo) { 526 long channelId = testChannelInfo.originalNetworkId; 527 mChannelInfoList.put( 528 (int) channelId, 529 new ChannelInfoWrapper( 530 ChannelInfo.create(getTargetContext(), (int) channelId))); 531 mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); 532 } 533 534 /** Simulates channel data delete. */ simulateDelete(long channelId)535 public void simulateDelete(long channelId) { 536 mChannelInfoList.remove((int) channelId); 537 mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); 538 } 539 540 /** Simulates channel data update. */ simulateUpdate(long channelId, String newName)541 public void simulateUpdate(long channelId, String newName) { 542 ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId); 543 ChannelInfo.Builder builder = new ChannelInfo.Builder(channel.channelInfo); 544 builder.setName(newName); 545 channel.channelInfo = builder.build(); 546 mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); 547 } 548 assertChannelUri(Uri uri)549 private void assertChannelUri(Uri uri) { 550 assertWithMessage("Uri(" + uri + ") isn't channel uri") 551 .that(uri.toString().startsWith(Channels.CONTENT_URI.toString())) 552 .isTrue(); 553 } 554 clear()555 public void clear() { 556 mChannelInfoList.clear(); 557 } 558 get(int position)559 public ChannelInfoWrapper get(int position) { 560 return mChannelInfoList.get(mChannelInfoList.keyAt(position)); 561 } 562 getCount()563 public int getCount() { 564 return mChannelInfoList.size(); 565 } 566 keyAt(int position)567 public long keyAt(int position) { 568 return mChannelInfoList.keyAt(position); 569 } 570 } 571 572 private class FakeCursor extends MockCursor { 573 private final String[] allColumns = { 574 Channels._ID, 575 Channels.COLUMN_DISPLAY_NAME, 576 Channels.COLUMN_DISPLAY_NUMBER, 577 Channels.COLUMN_INPUT_ID, 578 Channels.COLUMN_VIDEO_FORMAT, 579 Channels.COLUMN_ORIGINAL_NETWORK_ID, 580 COLUMN_BROWSABLE, 581 COLUMN_LOCKED 582 }; 583 private final String[] mColumns; 584 private int mPosition; 585 FakeCursor(String[] columns)586 public FakeCursor(String[] columns) { 587 mColumns = (columns == null) ? allColumns : columns; 588 mPosition = -1; 589 } 590 591 @Override getColumnName(int columnIndex)592 public String getColumnName(int columnIndex) { 593 return mColumns[columnIndex]; 594 } 595 596 @Override getColumnIndex(String columnName)597 public int getColumnIndex(String columnName) { 598 for (int i = 0; i < mColumns.length; i++) { 599 if (mColumns[i].equalsIgnoreCase(columnName)) { 600 return i; 601 } 602 } 603 return -1; 604 } 605 606 @Override getLong(int columnIndex)607 public long getLong(int columnIndex) { 608 String columnName = getColumnName(columnIndex); 609 switch (columnName) { 610 case Channels._ID: 611 return mContentProvider.keyAt(mPosition); 612 default: // fall out 613 } 614 if (DEBUG) { 615 Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()"); 616 } 617 return 0; 618 } 619 620 @Override getString(int columnIndex)621 public String getString(int columnIndex) { 622 String columnName = getColumnName(columnIndex); 623 ChannelInfoWrapper channel = mContentProvider.get(mPosition); 624 switch (columnName) { 625 case Channels.COLUMN_DISPLAY_NAME: 626 return channel.channelInfo.name; 627 case Channels.COLUMN_DISPLAY_NUMBER: 628 return channel.channelInfo.number; 629 case Channels.COLUMN_INPUT_ID: 630 return DUMMY_INPUT_ID; 631 case Channels.COLUMN_VIDEO_FORMAT: 632 return channel.channelInfo.getVideoFormat(); 633 default: // fall out 634 } 635 if (DEBUG) { 636 Log.d(TAG, "Column (" + columnName + ") is ignored in getString()"); 637 } 638 return null; 639 } 640 641 @Override getInt(int columnIndex)642 public int getInt(int columnIndex) { 643 String columnName = getColumnName(columnIndex); 644 ChannelInfoWrapper channel = mContentProvider.get(mPosition); 645 switch (columnName) { 646 case Channels.COLUMN_ORIGINAL_NETWORK_ID: 647 return channel.channelInfo.originalNetworkId; 648 case COLUMN_BROWSABLE: 649 return channel.browsable ? 1 : 0; 650 case COLUMN_LOCKED: 651 return channel.locked ? 1 : 0; 652 default: // fall out 653 } 654 if (DEBUG) { 655 Log.d(TAG, "Column (" + columnName + ") is ignored in getInt()"); 656 } 657 return 0; 658 } 659 660 @Override getCount()661 public int getCount() { 662 return mContentProvider.getCount(); 663 } 664 665 @Override moveToNext()666 public boolean moveToNext() { 667 return ++mPosition < mContentProvider.getCount(); 668 } 669 670 @Override close()671 public void close() { 672 // No-op. 673 } 674 } 675 676 private static class TestChannelDataManagerListener implements ChannelDataManager.Listener { 677 public CountDownLatch loadFinishedLatch = new CountDownLatch(1); 678 public CountDownLatch channelListUpdatedLatch = new CountDownLatch(1); 679 public boolean channelBrowsableChangedCalled; 680 681 @Override onLoadFinished()682 public void onLoadFinished() { 683 loadFinishedLatch.countDown(); 684 } 685 686 @Override onChannelListUpdated()687 public void onChannelListUpdated() { 688 channelListUpdatedLatch.countDown(); 689 } 690 691 @Override onChannelBrowsableChanged()692 public void onChannelBrowsableChanged() { 693 channelBrowsableChangedCalled = true; 694 } 695 reset()696 public void reset() { 697 loadFinishedLatch = new CountDownLatch(1); 698 channelListUpdatedLatch = new CountDownLatch(1); 699 channelBrowsableChangedCalled = false; 700 } 701 } 702 703 private static class TestChannelDataManagerChannelListener 704 implements ChannelDataManager.ChannelListener { 705 public CountDownLatch channelChangedLatch = new CountDownLatch(1); 706 public final List<Channel> removedChannels = new ArrayList<>(); 707 public final List<Channel> updatedChannels = new ArrayList<>(); 708 709 @Override onChannelRemoved(Channel channel)710 public void onChannelRemoved(Channel channel) { 711 removedChannels.add(channel); 712 channelChangedLatch.countDown(); 713 } 714 715 @Override onChannelUpdated(Channel channel)716 public void onChannelUpdated(Channel channel) { 717 updatedChannels.add(channel); 718 channelChangedLatch.countDown(); 719 } 720 reset()721 public void reset() { 722 channelChangedLatch = new CountDownLatch(1); 723 removedChannels.clear(); 724 updatedChannels.clear(); 725 } 726 } 727 } 728