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