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