diff options
Diffstat (limited to 'tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java')
-rw-r--r-- | tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java | 646 |
1 files changed, 646 insertions, 0 deletions
diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java new file mode 100644 index 00000000..38ccdfb6 --- /dev/null +++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.data; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.media.tv.TvContract; +import android.media.tv.TvContract.Channels; +import android.net.Uri; +import android.os.HandlerThread; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.test.mock.MockContentProvider; +import android.test.mock.MockContentResolver; +import android.test.mock.MockCursor; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.Constants; +import com.android.tv.util.TvInputManagerHelper; + +import org.mockito.Matchers; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test for {@link com.android.tv.data.ChannelDataManager} + * + * A test method may include tests for multiple methods to minimize the DB access. + */ +@SmallTest +public class ChannelDataManagerTest extends AndroidTestCase { + private static final boolean DEBUG = false; + private static final String TAG = "ChannelDataManagerTest"; + + // Wait time for expected success. + private static final long WAIT_TIME_OUT_MS = 1000L; + private static final String DUMMY_INPUT_ID = "dummy"; + // TODO: Use Channels.COLUMN_BROWSABLE and Channels.COLUMN_LOCKED instead. + private static final String COLUMN_BROWSABLE = "browsable"; + private static final String COLUMN_LOCKED = "locked"; + + private ChannelDataManager mChannelDataManager; + private HandlerThread mHandlerThread; + private TestChannelDataManagerListener mListener; + private FakeContentResolver mContentResolver; + private FakeContentProvider mContentProvider; + + @Override + protected void setUp() throws Exception { + super.setUp(); + assertTrue("More than 2 channels to test", Constants.UNIT_TEST_CHANNEL_COUNT > 2); + TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class); + Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())).thenReturn(true); + + mContentProvider = new FakeContentProvider(getContext()); + mContentResolver = new FakeContentResolver(); + mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider); + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mChannelDataManager = new ChannelDataManager( + getContext(), mockHelper, mContentResolver, mHandlerThread.getLooper()); + mListener = new TestChannelDataManagerListener(); + mChannelDataManager.addListener(mListener); + + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mHandlerThread.quitSafely(); + mChannelDataManager.stop(); + } + + private void startAndWaitForComplete() throws Exception { + mChannelDataManager.start(); + try { + assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + throw e; + } + } + + private void restart() throws Exception { + mChannelDataManager.stop(); + mListener.reset(); + startAndWaitForComplete(); + } + + public void testIsDbLoadFinished() throws Exception { + startAndWaitForComplete(); + assertTrue(mChannelDataManager.isDbLoadFinished()); + } + + /** + * Test for following methods + * - {@link ChannelDataManager#getChannelCount} + * - {@link ChannelDataManager#getChannelList} + * - {@link ChannelDataManager#getChannel} + */ + public void testGetChannels() throws Exception { + startAndWaitForComplete(); + + // Test {@link ChannelDataManager#getChannelCount} + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT, mChannelDataManager.getChannelCount()); + + // Test {@link ChannelDataManager#getChannelList} + List<ChannelInfo> channelInfoList = new ArrayList<>(); + for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { + channelInfoList.add(ChannelInfo.create(getContext(), i)); + } + List<Channel> channelList = mChannelDataManager.getChannelList(); + for (Channel channel : channelList) { + boolean found = false; + for (ChannelInfo channelInfo : channelInfoList) { + if (TextUtils.equals(channelInfo.name, channel.getDisplayName()) + && TextUtils.equals(channelInfo.name, channel.getDisplayName())) { + found = true; + channelInfoList.remove(channelInfo); + break; + } + } + assertTrue("Cannot find (" + channel + ")", found); + } + + // Test {@link ChannelDataManager#getChannelIndex()} + for (Channel channel : channelList) { + assertEquals(channel, mChannelDataManager.getChannel(channel.getId())); + } + } + + /** + * Test for {@link ChannelDataManager#getChannelCount} when no channel is available. + */ + public void testGetChannels_noChannels() throws Exception { + mContentProvider.clear(); + startAndWaitForComplete(); + assertEquals(0, mChannelDataManager.getChannelCount()); + } + + /** + * Test for following methods and channel listener with notifying change. + * - {@link ChannelDataManager#updateBrowsable} + * - {@link ChannelDataManager#applyUpdatedValuesToDb} + */ + public void testBrowsable() throws Exception { + startAndWaitForComplete(); + + // Test if all channels are browable + List<Channel> channelList = new ArrayList<>(mChannelDataManager.getChannelList()); + List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList(); + for (Channel browsableChannel : browsableChannelList) { + boolean found = channelList.remove(browsableChannel); + assertTrue("Cannot find (" + browsableChannel + ")", found); + } + assertEquals(0, channelList.size()); + + // Prepare for next tests. + TestChannelDataManagerChannelListener channelListener = + new TestChannelDataManagerChannelListener(); + Channel channel1 = mChannelDataManager.getChannelList().get(0); + mChannelDataManager.addChannelListener(channel1.getId(), channelListener); + + // Test {@link ChannelDataManager#updateBrowsable} & notification. + mChannelDataManager.updateBrowsable(channel1.getId(), false, false); + assertTrue(mListener.channelBrowsableChangedCalled); + assertFalse(mChannelDataManager.getBrowsableChannelList().contains(channel1)); + MoreAsserts.assertContentsInAnyOrder(channelListener.updatedChannels, channel1); + channelListener.reset(); + + // Test {@link ChannelDataManager#applyUpdatedValuesToDb} + mChannelDataManager.applyUpdatedValuesToDb(); + restart(); + browsableChannelList = mChannelDataManager.getBrowsableChannelList(); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT - 1, browsableChannelList.size()); + assertFalse(browsableChannelList.contains(channel1)); + } + + /** + * Test for following methods and channel listener without notifying change. + * - {@link ChannelDataManager#updateBrowsable} + * - {@link ChannelDataManager#applyUpdatedValuesToDb} + */ + public void testBrowsable_skipNotification() throws Exception { + startAndWaitForComplete(); + + // Prepare for next tests. + TestChannelDataManagerChannelListener channelListener = + new TestChannelDataManagerChannelListener(); + Channel channel1 = mChannelDataManager.getChannelList().get(0); + Channel channel2 = mChannelDataManager.getChannelList().get(1); + mChannelDataManager.addChannelListener(channel1.getId(), channelListener); + mChannelDataManager.addChannelListener(channel2.getId(), channelListener); + + // Test {@link ChannelDataManager#updateBrowsable} & skip notification. + mChannelDataManager.updateBrowsable(channel1.getId(), false, true); + mChannelDataManager.updateBrowsable(channel2.getId(), false, true); + mChannelDataManager.updateBrowsable(channel1.getId(), true, true); + assertFalse(mListener.channelBrowsableChangedCalled); + List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList(); + assertTrue(browsableChannelList.contains(channel1)); + assertFalse(browsableChannelList.contains(channel2)); + + // Test {@link ChannelDataManager#applyUpdatedValuesToDb} + mChannelDataManager.applyUpdatedValuesToDb(); + restart(); + browsableChannelList = mChannelDataManager.getBrowsableChannelList(); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT - 1, browsableChannelList.size()); + assertFalse(browsableChannelList.contains(channel2)); + } + + /** + * Test for following methods and channel listener. + * - {@link ChannelDataManager#updateLocked} + * - {@link ChannelDataManager#applyUpdatedValuesToDb} + */ + public void testLocked() throws Exception { + startAndWaitForComplete(); + + // Test if all channels aren't locked at the first time. + List<Channel> channelList = mChannelDataManager.getChannelList(); + for (Channel channel : channelList) { + assertFalse(channel + " is locked", channel.isLocked()); + } + + // Prepare for next tests. + Channel channel = mChannelDataManager.getChannelList().get(0); + + // Test {@link ChannelDataManager#updateLocked} + mChannelDataManager.updateLocked(channel.getId(), true); + assertTrue(mChannelDataManager.getChannel(channel.getId()).isLocked()); + + // Test {@link ChannelDataManager#applyUpdatedValuesToDb}. + mChannelDataManager.applyUpdatedValuesToDb(); + restart(); + assertTrue(mChannelDataManager.getChannel(channel.getId()).isLocked()); + + // Cleanup + mChannelDataManager.updateLocked(channel.getId(), false); + } + + /** + * Test ChannelDataManager when channels in TvContract are updated, removed, or added. + */ + public void testChannelListChanged() throws Exception { + startAndWaitForComplete(); + + // Test channel add. + mListener.reset(); + long testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; + ChannelInfo testChannelInfo = ChannelInfo.create(getContext(), (int) testChannelId); + testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1; + mContentProvider.simulateInsert(testChannelInfo); + assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1, mChannelDataManager.getChannelCount()); + + // Test channel update + mListener.reset(); + TestChannelDataManagerChannelListener channelListener = + new TestChannelDataManagerChannelListener(); + mChannelDataManager.addChannelListener(testChannelId, channelListener); + String newName = testChannelInfo.name + "_test"; + mContentProvider.simulateUpdate(testChannelId, newName); + assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(channelListener.channelChangedLatch.await( + WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertEquals(0, channelListener.removedChannels.size()); + assertEquals(1, channelListener.updatedChannels.size()); + Channel updatedChannel = channelListener.updatedChannels.get(0); + assertEquals(testChannelId, updatedChannel.getId()); + assertEquals(testChannelInfo.number, updatedChannel.getDisplayNumber()); + assertEquals(newName, updatedChannel.getDisplayName()); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1, + mChannelDataManager.getChannelCount()); + + // Test channel remove. + mListener.reset(); + channelListener.reset(); + mContentProvider.simulateDelete(testChannelId); + assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(channelListener.channelChangedLatch.await( + WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)); + assertEquals(1, channelListener.removedChannels.size()); + assertEquals(0, channelListener.updatedChannels.size()); + Channel removedChannel = channelListener.removedChannels.get(0); + assertEquals(newName, removedChannel.getDisplayName()); + assertEquals(testChannelInfo.number, removedChannel.getDisplayNumber()); + assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT, mChannelDataManager.getChannelCount()); + } + + private class ChannelInfoWrapper { + public ChannelInfo channelInfo; + public boolean browsable; + public boolean locked; + public ChannelInfoWrapper(ChannelInfo channelInfo) { + this.channelInfo = channelInfo; + browsable = true; + locked = false; + } + } + + private class FakeContentResolver extends MockContentResolver { + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + super.notifyChange(uri, observer, syncToNetwork); + if (DEBUG) { + Log.d(TAG, "onChanged(uri=" + uri + ", observer=" + observer + ")"); + } + // Do not call {@link ContentObserver#onChange} directly + // to run it on the {@link #mHandlerThread}. + if (observer != null) { + observer.dispatchChange(false, uri); + } else { + mChannelDataManager.getContentObserver().dispatchChange(false, uri); + } + } + } + + // This implements the minimal methods in content resolver + // and detailed assumptions are written in each method. + private class FakeContentProvider extends MockContentProvider { + private SparseArray<ChannelInfoWrapper> mChannelInfoList = new SparseArray<>(); + + public FakeContentProvider(Context context) { + super(context); + for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) { + mChannelInfoList.put(i, + new ChannelInfoWrapper(ChannelInfo.create(getContext(), i))); + } + } + + /** + * Implementation of {@link ContentProvider#query}. + * This assumes that {@link ChannelDataManager} queries channels + * with empty {@code selection}. (i.e. channels are always queries for all) + */ + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] + selectionArgs, String sortOrder) { + if (DEBUG) { + Log.d(TAG, "dump query"); + Log.d(TAG, " uri=" + uri); + if (projection == null || projection.length == 0) { + Log.d(TAG, " projection=" + projection); + } else { + for (int i = 0; i < projection.length; i++) { + Log.d(TAG, " projection=" + projection[i]); + } + } + Log.d(TAG," selection=" + selection); + } + assertChannelUri(uri); + return new FakeCursor(projection); + } + + /** + * Implementation of {@link ContentProvider#update}. + * This assumes that {@link ChannelDataManager} update channels + * only for changing browsable and locked. + */ + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + if (DEBUG) Log.d(TAG, "update(uri=" + uri + ", selection=" + selection); + assertChannelUri(uri); + List<Long> channelIds = new ArrayList<>(); + try { + long channelId = ContentUris.parseId(uri); + channelIds.add(channelId); + } catch (NumberFormatException e) { + // Update for multiple channels. + if (TextUtils.isEmpty(selection)) { + for (int i = 0; i < mChannelInfoList.size(); i++) { + channelIds.add((long) mChannelInfoList.keyAt(i)); + } + } else { + // See {@link Utils#buildSelectionForIds} for the syntax. + String selectionForId = selection.substring( + selection.indexOf("(") + 1, selection.lastIndexOf(")")); + String[] ids = selectionForId.split(", "); + if (ids != null) { + for (String id : ids) { + channelIds.add(Long.parseLong(id)); + } + } + } + } + int updateCount = 0; + for (long channelId : channelIds) { + boolean updated = false; + ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId); + if (channel == null) { + return 0; + } + if (values.containsKey(COLUMN_BROWSABLE)) { + updated = true; + channel.browsable = (values.getAsInteger(COLUMN_BROWSABLE) == 1); + } + if (values.containsKey(COLUMN_LOCKED)) { + updated = true; + channel.locked = (values.getAsInteger(COLUMN_LOCKED) == 1); + } + updateCount += updated ? 1 : 0; + } + if (updateCount > 0) { + if (channelIds.size() == 1) { + mContentResolver.notifyChange(uri, null); + } else { + mContentResolver.notifyChange(Channels.CONTENT_URI, null); + } + } else { + if (DEBUG) { + Log.d(TAG, "Update to channel(uri=" + uri + ") is ignored for " + values); + } + } + return updateCount; + } + + /** + * Simulates channel data insert. + * This assigns original network ID (the same with channel number) to channel ID. + */ + public void simulateInsert(ChannelInfo testChannelInfo) { + long channelId = testChannelInfo.originalNetworkId; + mChannelInfoList.put((int) channelId, + new ChannelInfoWrapper(ChannelInfo.create(getContext(), (int) channelId))); + mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); + } + + /** + * Simulates channel data delete. + */ + public void simulateDelete(long channelId) { + mChannelInfoList.remove((int) channelId); + mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); + } + + /** + * Simulates channel data update. + */ + public void simulateUpdate(long channelId, String newName) { + ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId); + ChannelInfo.Builder builder = new ChannelInfo.Builder(channel.channelInfo); + builder.setName(newName); + channel.channelInfo = builder.build(); + mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null); + } + + private void assertChannelUri(Uri uri) { + assertTrue("Uri(" + uri + ") isn't channel uri", + uri.toString().startsWith(Channels.CONTENT_URI.toString())); + } + + public void clear() { + mChannelInfoList.clear(); + } + + public ChannelInfoWrapper get(int position) { + return mChannelInfoList.get(mChannelInfoList.keyAt(position)); + } + + public int getCount() { + return mChannelInfoList.size(); + } + + public long keyAt(int position) { + return mChannelInfoList.keyAt(position); + } + } + + private class FakeCursor extends MockCursor { + private String[] ALL_COLUMNS = { + Channels._ID, + Channels.COLUMN_DISPLAY_NAME, + Channels.COLUMN_DISPLAY_NUMBER, + Channels.COLUMN_INPUT_ID, + Channels.COLUMN_VIDEO_FORMAT, + Channels.COLUMN_ORIGINAL_NETWORK_ID, + COLUMN_BROWSABLE, + COLUMN_LOCKED}; + private String[] mColumns; + private int mPosition; + + public FakeCursor(String[] columns) { + mColumns = (columns == null) ? ALL_COLUMNS : columns; + mPosition = -1; + } + + @Override + public String getColumnName(int columnIndex) { + return mColumns[columnIndex]; + } + + @Override + public int getColumnIndex(String columnName) { + for (int i = 0; i < mColumns.length; i++) { + if (mColumns[i].equalsIgnoreCase(columnName)) { + return i; + } + } + return -1; + } + + @Override + public long getLong(int columnIndex) { + String columnName = getColumnName(columnIndex); + switch (columnName) { + case Channels._ID: + return mContentProvider.keyAt(mPosition); + } + if (DEBUG) { + Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()"); + } + return 0; + } + + @Override + public String getString(int columnIndex) { + String columnName = getColumnName(columnIndex); + ChannelInfoWrapper channel = mContentProvider.get(mPosition); + switch (columnName) { + case Channels.COLUMN_DISPLAY_NAME: + return channel.channelInfo.name; + case Channels.COLUMN_DISPLAY_NUMBER: + return channel.channelInfo.number; + case Channels.COLUMN_INPUT_ID: + return DUMMY_INPUT_ID; + case Channels.COLUMN_VIDEO_FORMAT: + return channel.channelInfo.getVideoFormat(); + } + if (DEBUG) { + Log.d(TAG, "Column (" + columnName + ") is ignored in getString()"); + } + return null; + } + + @Override + public int getInt(int columnIndex) { + String columnName = getColumnName(columnIndex); + ChannelInfoWrapper channel = mContentProvider.get(mPosition); + switch (columnName) { + case Channels.COLUMN_ORIGINAL_NETWORK_ID: + return channel.channelInfo.originalNetworkId; + case COLUMN_BROWSABLE: + return channel.browsable ? 1 : 0; + case COLUMN_LOCKED: + return channel.locked ? 1 : 0; + } + if (DEBUG) { + Log.d(TAG, "Column (" + columnName + ") is ignored in getInt()"); + } + return 0; + } + + @Override + public int getCount() { + return mContentProvider.getCount(); + } + + @Override + public boolean moveToNext() { + return ++mPosition < mContentProvider.getCount(); + } + + @Override + public void close() { + // No-op. + } + } + + private class TestChannelDataManagerListener implements ChannelDataManager.Listener { + public CountDownLatch loadFinishedLatch = new CountDownLatch(1); + public CountDownLatch channeListUpdatedLatch = new CountDownLatch(1); + public boolean channelBrowsableChangedCalled; + + @Override + public void onLoadFinished() { + loadFinishedLatch.countDown(); + } + + @Override + public void onChannelListUpdated() { + channeListUpdatedLatch.countDown(); + } + + @Override + public void onChannelBrowsableChanged() { + channelBrowsableChangedCalled = true; + } + + public void reset() { + loadFinishedLatch = new CountDownLatch(1); + channeListUpdatedLatch = new CountDownLatch(1); + channelBrowsableChangedCalled = false; + } + } + + private class TestChannelDataManagerChannelListener + implements ChannelDataManager.ChannelListener { + public CountDownLatch channelChangedLatch = new CountDownLatch(1); + public List<Channel> removedChannels = new ArrayList<>(); + public List<Channel> updatedChannels = new ArrayList<>(); + + @Override + public void onChannelRemoved(Channel channel) { + removedChannels.add(channel); + channelChangedLatch.countDown(); + } + + @Override + public void onChannelUpdated(Channel channel) { + updatedChannels.add(channel); + channelChangedLatch.countDown(); + } + + public void reset() { + channelChangedLatch = new CountDownLatch(1); + removedChannels.clear(); + updatedChannels.clear(); + } + } +} |