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