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