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