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