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