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