Implemented testing actionbar state (#382)

* Added JSON datafiles for TV shows and music videos
   * Implemented instrumentations tests for MoviesActivity, MusicActivity,
     TVShowActivity, and AddonsActivity.
   * Moved RestoreSearchQueryViewPagerTest to music package as it uses
     the MusicActivity
   * Moved RestoreSearchQueryListFragmentTest to movies packages as it
     uses the MoviesActivity
   * Added scripts to get JSON data for music videos, addons, and TV shows
   * Added sequence diagram for BaseMediaActivity to clarify new setup
   * Refactored BaseMediaActivity to comply with diagram
   * Refactored SyncMusicVideos and SyncTVShows so we can use the same code
     for adding test data as we use for adding real data.
   * Removed unused StringBuffer and synchronize block in MockTcpServer
master
Martijn Brekhof 6 years ago committed by Synced Synapse
parent 46bb4a4bfc
commit 1cb77876be

@ -21,15 +21,23 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import org.hamcrest.Matcher;
import org.xbmc.kore.R;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
@ -37,10 +45,12 @@ import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFro
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.xbmc.kore.testhelpers.action.ViewActions.clearFocus;
public class EspressoTestUtils {
@ -164,4 +174,110 @@ public class EspressoTestUtils {
public static void checkSearchMenuCollapsed() {
onView(isAssignableFrom(AutoCompleteTextView.class)).check(doesNotExist());
}
/**
* Returns the current active activity. Use this when the originally started activity
* started a new activity and you need the reference to the new activity.
* @return reference to the current active activity
*/
public static Activity getActivity() {
final Activity[] activity = new Activity[1];
onView(allOf(withId(android.R.id.content), isDisplayed())).perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(View.class);
}
@Override
public String getDescription() {
return "getting current activity";
}
@Override
public void perform(UiController uiController, View view) {
if (view.getContext() instanceof Activity) {
activity[0] = ((Activity)view.getContext());
}
}
});
return activity[0];
}
/**
* Clicks the album tab in the music activity
*/
public static void clickAlbumsTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
onView(withText(R.string.albums)).perform(click());
}
/**
* Clicks the artists tab in the music activity
*/
public static void clickArtistsTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeRight());
onView(withText(R.string.artists)).perform(click());
}
/**
* Clicks the genres tab in the music activity
*/
public static void clickGenresTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
onView(withText(R.string.genres)).perform(click());
}
/**
* Clicks the music videos tab in the music activity
*/
public static void clickMusicVideosTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
onView(withText(R.string.videos)).perform(click());
}
/**
* Selects an item in the list, then presses back and checks the action bar title
* @param item number (0 is first item) of the item that should be pressed
* @param listResourceId Resource identifier of the AdapterView
* @param actionbarTitle title that should be displayed in the action bar after pressing back
*/
public static void selectListItemPressBackAndCheckActionbarTitle(int item,
int listResourceId,
String actionbarTitle) {
EspressoTestUtils.clickAdapterViewItem(item, listResourceId);
pressBack();
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(actionbarTitle)));
}
/**
* Selects an item in the list, then rotates the device and checks the action bar title
* @param item number (0 is first item) of the item that should be pressed
* @param listResourceId Resource identifier of the AdapterView
* @param actionbarTitle title that should be displayed in the action bar after rotating
*/
public static void selectListItemRotateDeviceAndCheckActionbarTitle(int item,
int listResourceId,
String actionbarTitle,
Activity activity) {
EspressoTestUtils.clickAdapterViewItem(item, listResourceId);
EspressoTestUtils.rotateDevice(activity);
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(actionbarTitle)));
}
/**
* Selects an item in the list and then checks the action bar title
* @param item number (0 is first item) of the item that should be pressed
* @param listResourceId Resource identifier of the AdapterView
* @param actionbarTitle title that should be displayed in the action bar after selecting item
*/
public static void selectListItemAndCheckActionbarTitle(int item,
int listResourceId,
String actionbarTitle) {
EspressoTestUtils.clickAdapterViewItem(item, listResourceId);
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(actionbarTitle)));
}
}

@ -22,6 +22,7 @@ import android.os.IBinder;
import android.support.test.rule.ActivityTestRule;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Gravity;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
@ -54,10 +55,21 @@ public class Utils {
});
}
public static void initialize(ActivityTestRule<?> activityTestRule) throws Throwable {
public static void openDrawer(final ActivityTestRule<?> activityTestRule) throws Throwable {
activityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
DrawerLayout drawerLayout = (DrawerLayout) activityTestRule.getActivity().findViewById(R.id.drawer_layout);
drawerLayout.openDrawer(Gravity.LEFT);
}
});
}
public static void initialize(ActivityTestRule<?> activityTestRule, HostInfo info) throws Throwable {
if (isInitialized)
return;
hostInfo = info;
context = activityTestRule.getActivity();
disableAnimations();
@ -66,7 +78,7 @@ public class Utils {
mediaProvider.setContext(context);
mediaProvider.onCreate();
hostInfo = Database.fill(context, context.getContentResolver());
Database.fill(hostInfo, context, context.getContentResolver());
HostManager.getInstance(context).switchHost(hostInfo);
Utils.closeDrawer(activityTestRule);

@ -0,0 +1,106 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui;
import android.content.Intent;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.app.AppCompatActivity;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.HostConnection;
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.testutils.Database;
import org.xbmc.kore.testutils.tcpserver.MockTcpServer;
import org.xbmc.kore.testutils.tcpserver.handlers.AddonsHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.JSONConnectionHandlerManager;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@Ignore
abstract public class AbstractTestClass<T extends AppCompatActivity> {
abstract protected ActivityTestRule<T> getActivityTestRule();
private LoaderIdlingResource loaderIdlingResource;
private ActivityTestRule<T> activityTestRule;
private static MockTcpServer server;
private static JSONConnectionHandlerManager manager;
private AddonsHandler addonsHandler;
@BeforeClass
public static void setupMockTCPServer() throws Throwable {
manager = new JSONConnectionHandlerManager();
server = new MockTcpServer(manager);
server.start();
}
@Before
public void setUp() throws Throwable {
activityTestRule = getActivityTestRule();
//Note: as the activity is not yet available in @BeforeClass we need
// to add the handler here
if (addonsHandler == null) {
addonsHandler = new AddonsHandler(activityTestRule.getActivity());
manager.addHandler(addonsHandler);
}
HostInfo hostInfo = Database.addHost(activityTestRule.getActivity(), server.getHostName(),
HostConnection.PROTOCOL_TCP, HostInfo.DEFAULT_HTTP_PORT,
server.getPort());
Utils.initialize(activityTestRule, hostInfo);
loaderIdlingResource = new LoaderIdlingResource(activityTestRule.getActivity().getSupportLoaderManager());
Espresso.registerIdlingResources(loaderIdlingResource);
activityTestRule.launchActivity(new Intent());
Utils.closeDrawer(activityTestRule);
}
@After
public void tearDown() throws Exception {
if ( loaderIdlingResource != null )
Espresso.unregisterIdlingResources(loaderIdlingResource);
Utils.cleanup();
}
@AfterClass
public static void cleanup() throws IOException {
server.shutdown();
}
protected T getActivity() {
if (activityTestRule != null) {
return activityTestRule.getActivity();
}
return null;
}
}

@ -0,0 +1,113 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui;
import android.support.test.espresso.Espresso;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.ui.BaseMediaActivity;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Contains generic tests for all activities extending BaseMediaActivity
* @param <T>
*/
@Ignore
abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extends AbstractTestClass<T> {
/**
* Test if the initial state shows the hamburger icon
*/
@Test
public void showHamburgerInInitialState() {
assertFalse(getActivity().getDrawerIndicatorIsArrow());
}
/**
* Test if navigation icon is changed to an arrow when selecting a list item
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Result: navigation icon should be an arrow
*/
@Test
public void showArrowWhenSelectingListItem() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
assertTrue(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
/**
* Test if navigation icon is changed to an arrow when selecting a list item
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: navigation icon should be a hamburger
*/
@Test
public void showHamburgerWhenSelectingListItemAndReturn() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
Espresso.pressBack();
assertFalse(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
/**
* Test if navigation icon is restored to an arrow when selecting a list item
* and rotating the device
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Rotate device
* 3. Result: navigation icon should be an arrow
*/
@Test
public void restoreArrowOnConfigurationChange() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
EspressoTestUtils.rotateDevice(getActivity());
assertTrue(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
/**
* Test if navigation icon is restored to an hamburger when selecting a list item
* and rotating the device and returning to the list
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Rotate device
* 3. Press back
* 4. Result: navigation icon should be a hamburger
*/
@Test
public void restoreHamburgerOnConfigurationChangeOnReturn() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
EspressoTestUtils.rotateDevice(getActivity());
Espresso.pressBack();
assertFalse(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
}

@ -0,0 +1,129 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui.addons;
import android.support.test.rule.ActivityTestRule;
import android.widget.TextView;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.tests.ui.BaseMediaActivityTests;
import org.xbmc.kore.ui.sections.video.MoviesActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBackAndCheckActionbarTitle;
/**
* Note: we use MoviesActivity here instead of AddonsActivity. The reason is that we use @Rule
* to start the activity which is done prior to executing @Before. This results in a deadlock
* situation.
*
* Normal startup procedure would be as follows:
*
* 1. Start MockTCPServer {@link AbstractTestClass#setupMockTCPServer()}
* 2. Start activity {mActivityRule}
* 3. Espresso waits for activity to become idle before calling {@link AbstractTestClass#setUp()}
* 4. Add AddonsHandler {@link AbstractTestClass#setUp()}
*
* At step 2 the AddonsActivity displays an animated progress indicator while it waits for the
* MockTCPServer to send the list of addons.
* This is never send as the {@link org.xbmc.kore.testutils.tcpserver.handlers.AddonsHandler} is
* added in {@link super#setUp()} which is never started by Espresso as it waits for
* {@link org.xbmc.kore.ui.sections.addon.AddonsActivity} to become idle.
*/
public class AddonsActivityTests extends BaseMediaActivityTests<MoviesActivity> {
@Rule
public ActivityTestRule<MoviesActivity> mActivityRule = new ActivityTestRule<>(
MoviesActivity.class);
@Override
protected ActivityTestRule<MoviesActivity> getActivityTestRule() {
return mActivityRule;
}
@Before
@Override
public void setUp() throws Throwable {
super.setUp();
//Start the AddonsActivity from the MoviesActivity
Utils.openDrawer(getActivityTestRule());
EspressoTestUtils.clickAdapterViewItem(7, R.id.navigation_drawer);
}
/**
* Test if action bar title initially displays Addons
*/
@Test
public void setActionBarTitleMain() {
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(R.string.addons)));
}
/**
* Test if action bar title is correctly set after selecting a list item
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitle() {
EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list,
"Dumpert");
}
/**
* Test if action bar title is correctly restored after a configuration change
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Rotate device
* 3. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleOnConfigurationStateChanged() {
EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"Dumpert",
getActivity());
}
/**
* Test if action bar title is correctly restored after returning from a movie selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromMovie() {
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
getActivity().getString(R.string.addons));
}
}

@ -0,0 +1,99 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui.movies;
import android.support.test.rule.ActivityTestRule;
import android.widget.TextView;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.tests.ui.BaseMediaActivityTests;
import org.xbmc.kore.ui.sections.video.MoviesActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBackAndCheckActionbarTitle;
public class MoviesActivityTests extends BaseMediaActivityTests<MoviesActivity> {
@Rule
public ActivityTestRule<MoviesActivity> mActivityRule = new ActivityTestRule<>(
MoviesActivity.class);
@Override
protected ActivityTestRule<MoviesActivity> getActivityTestRule() {
return mActivityRule;
}
/**
* Test if action bar title initially displays Movies
*/
@Test
public void setActionBarTitleMain() {
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(R.string.movies)));
}
/**
* Test if action bar title is correctly set after selecting a list item
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitle() {
EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list,
"#Rookie93 Marc Marquez: Beyond the Smile");
}
/**
* Test if action bar title is correctly restored after a configuration change
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Rotate device
* 3. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleOnConfigurationStateChanged() {
EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"#Rookie93 Marc Marquez: Beyond the Smile",
mActivityRule.getActivity());
}
/**
* Test if action bar title is correctly restored after returning from a movie selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromMovie() {
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
mActivityRule.getActivity().getString(R.string.movies));
}
}

@ -14,55 +14,36 @@
* limitations under the License.
*/
package org.xbmc.kore.tests.ui;
package org.xbmc.kore.tests.ui.movies;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.ui.sections.video.MoviesActivity;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class RestoreSearchQueryListFragmentTest {
public class RestoreSearchQueryListFragmentTest extends AbstractTestClass<MoviesActivity> {
private final String SEARCH_QUERY = "Room";
private final int SEARCH_QUERY_LIST_SIZE = 2;
private final int COMPLETE_LIST_SIZE = 300;
private LoaderIdlingResource loaderIdlingResource;
@Rule
public ActivityTestRule<MoviesActivity> mActivityRule = new ActivityTestRule<>(
MoviesActivity.class);
@Before
public void setUp() throws Throwable {
Utils.initialize(mActivityRule);
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
Espresso.registerIdlingResources(loaderIdlingResource);
Utils.closeDrawer(mActivityRule);
}
@After
public void tearDown() throws Exception {
Espresso.unregisterIdlingResources(loaderIdlingResource);
}
@AfterClass
public static void cleanup() {
Utils.cleanup();
@Override
protected ActivityTestRule<MoviesActivity> getActivityTestRule() {
return mActivityRule;
}
/**

@ -0,0 +1,260 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui.music;
import android.support.test.rule.ActivityTestRule;
import android.widget.TextView;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.tests.ui.BaseMediaActivityTests;
import org.xbmc.kore.ui.sections.audio.MusicActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickAlbumsTab;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickGenresTab;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickMusicVideosTab;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemAndCheckActionbarTitle;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBackAndCheckActionbarTitle;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle;
public class MusicActivityTests extends BaseMediaActivityTests<MusicActivity> {
@Rule
public ActivityTestRule<MusicActivity> musicActivityActivityTestRule =
new ActivityTestRule<>(MusicActivity.class);
@Override
protected ActivityTestRule<MusicActivity> getActivityTestRule() {
return musicActivityActivityTestRule;
}
/**
* Test if action bar title initially displays Music
*/
@Test
public void setActionBarTitleMain() {
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(R.string.music)));
}
/**
* Test if action bar title is correctly set after selecting an artist
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitleArtist() {
selectListItemAndCheckActionbarTitle(0, R.id.list, "ABC Orch");
}
/**
* Test if action bar title is correctly set after selecting an album
*
* UI interaction flow tested:
* 1. Click on albums tab
* 2. Click on list item
* 3. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitleAlbum() {
clickAlbumsTab();
selectListItemAndCheckActionbarTitle(0, R.id.list, "1958 - The Fabulous Johnny Cash");
}
/**
* Test if action bar title is correctly set after selecting a genre
*
* UI interaction flow tested:
* 1. Click on genres tab
* 2. Click on list item
* 3. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitleGenre() {
clickGenresTab();
selectListItemAndCheckActionbarTitle(0, R.id.list, "Ambient");
}
/**
* Test if action bar title is correctly set after selecting a video
*
* UI interaction flow tested:
* 1. Click on videos tab
* 2. Click on list item
* 3. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitleVideo() {
clickMusicVideosTab();
selectListItemAndCheckActionbarTitle(0, R.id.list, "(You Drive Me) Crazy");
}
/**
* Test if action bar title is correctly restored after a configuration change when artist
* is selected
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Rotate device
* 3. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleArtistOnConfigurationStateChanged() {
selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"ABC Orch", getActivity());
}
/**
* Test if action bar title is correctly restored after a configuration change when album
* is selected
*
* UI interaction flow tested:
* 1. Select albums tab
* 2. Click on list item
* 3. Rotate device
* 4. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleAlbumOnConfigurationStateChanged() {
clickAlbumsTab();
selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"1958 - The Fabulous Johnny Cash",
getActivity());
}
/**
* Test if action bar title is correctly restored after a configuration change when genre
* is selected
*
* UI interaction flow tested:
* 1. Select genres tab
* 2. Click on list item
* 3. Rotate device
* 4. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleGenreOnConfigurationStateChanged() {
clickGenresTab();
selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"Ambient", getActivity());
}
/**
* Test if action bar title is correctly restored after a configuration change when music video
* is selected
*
* UI interaction flow tested:
* 1. Select music videos tab
* 2. Click on list item
* 3. Rotate device
* 4. Result: action bar title should show list item title
*/
@Test
public void restoreActionBarTitleMusicVideoOnConfigurationStateChanged() {
clickMusicVideosTab();
selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"(You Drive Me) Crazy",
getActivity());
}
/**
* Test if action bar title is correctly restored after returning from artist selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromArtist() {
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
getActivity().getString(R.string.music));
}
/**
* Test if action bar title is correctly restored after returning from an album under
* artist
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Select albums tab
* 3. Press back
* 4. Result: action bar title should show artist title
*/
@Test
public void restoreActionBarTitleOnArtistOnReturningFromAlbum() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
clickAlbumsTab();
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, "ABC Orch");
}
/**
* Test if action bar title is correctly restored after returning from music video selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromMusicVideo() {
clickMusicVideosTab();
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
getActivity().getString(R.string.music));
}
/**
* Test if action bar title is correctly restored after returning from genre selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromGenre() {
clickGenresTab();
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
getActivity().getString(R.string.music));
}
/**
* Test if action bar title is correctly restored after returning from album selection
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Press back
* 3. Result: action bar title should show main title
*/
@Test
public void restoreActionBarTitleOnReturningFromAlbum() {
clickAlbumsTab();
selectListItemPressBackAndCheckActionbarTitle(0, R.id.list,
getActivity().getString(R.string.music));
}
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.xbmc.kore.tests.ui;
package org.xbmc.kore.tests.ui.music;
import android.app.Activity;
import android.support.test.espresso.Espresso;
@ -22,28 +22,21 @@ import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.ui.sections.audio.MusicActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickAlbumsTab;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickArtistsTab;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class RestoreSearchQueryViewPagerTest {
public class RestoreSearchQueryViewPagerTest extends AbstractTestClass<MusicActivity> {
private final String ARTIST_SEARCH_QUERY = "Ben";
private final int ARTIST_SEARCH_QUERY_LIST_SIZE = 2;
@ -58,22 +51,9 @@ public class RestoreSearchQueryViewPagerTest {
public ActivityTestRule<MusicActivity> mActivityRule = new ActivityTestRule<>(
MusicActivity.class);
@Before
public void setUp() throws Throwable {
Utils.initialize(mActivityRule);
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
Espresso.registerIdlingResources(loaderIdlingResource);
Utils.closeDrawer(mActivityRule);
}
@After
public void tearDown() throws Exception {
Espresso.unregisterIdlingResources(loaderIdlingResource);
}
@AfterClass
public static void cleanup() {
Utils.cleanup();
@Override
protected ActivityTestRule<MusicActivity> getActivityTestRule() {
return mActivityRule;
}
/**
@ -223,7 +203,7 @@ public class RestoreSearchQueryViewPagerTest {
* UI interaction flow tested:
* 1. Enter search query
* 2. Switch to Albums tab
* 3. Rotated device
* 3. Rotate device
* 4. Switch to Artists tab
* 5. Result: search query entered at 1. should show in search field and list should match search query
*/
@ -325,14 +305,4 @@ public class RestoreSearchQueryViewPagerTest {
EspressoTestUtils.checkTextInSearchQuery(ALBUMS_SEARCH_QUERY);
EspressoTestUtils.checkListMatchesSearchQuery(ALBUMS_SEARCH_QUERY, ALBUM_SEARCH_QUERY_LIST_SIZE, R.id.list);
}
private void clickAlbumsTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
onView(withText(R.string.albums)).perform(click());
}
private void clickArtistsTab() {
onView(withId(R.id.pager_tab_strip)).perform(swipeRight());
onView(withText(R.string.artists)).perform(click());
}
}

@ -0,0 +1,193 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* 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 org.xbmc.kore.tests.ui.tvshows;
import android.support.test.rule.ActivityTestRule;
import android.widget.TextView;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.tests.ui.BaseMediaActivityTests;
import org.xbmc.kore.ui.sections.video.TVShowsActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
public class TVShowsActivityTests extends BaseMediaActivityTests<TVShowsActivity> {
@Rule
public ActivityTestRule<TVShowsActivity> mActivityRule = new ActivityTestRule<>(
TVShowsActivity.class);
@Override
protected ActivityTestRule<TVShowsActivity> getActivityTestRule() {
return mActivityRule;
}
/**
* Test if action bar title initially displays TV Shows
*/
@Test
public void setActionBarTitleMain() {
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(R.string.tv_shows)));
}
/**
* Test if action bar title is correctly set after selecting a list item
*
* UI interaction flow tested:
* 1. Click on list item
* 2. Result: action bar title should show list item title
*/
@Test
public void setActionBarTitle() {
EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list, "11.22.63");
}
/**
* Test if action bar title is correctly set after selecting a season
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on next episode item
* 3. Result: action bar title should show next episode title
*/
@Test
public void setActionBarTitleOnNextEpisode() {
EspressoTestUtils.clickAdapterViewItem(1, R.id.list);
onView( withId(R.id.next_episode_list)).perform( scrollTo(), click());
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText("3")));
}
/**
* Test if action bar title is correctly set after selecting a season
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on season item
* 3. Result: action bar title should show season title
*/
@Test
public void setActionBarTitleOnSeasonList() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
onView( withId(R.id.seasons_list)).perform( scrollTo(), click());
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText("Season 01")));
}
/**
* Test if action bar title is correctly set after selecting an episode from the season list
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on season item
* 3. Click on an episode
* 4. Result: action bar title should show episode title
*/
@Test
public void setActionBarTitleOnSeasonListEpisode() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
onView( withId(R.id.seasons_list)).perform( scrollTo(), click());
EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list, "11.22.63");
}
/**
* Test if action bar title is correctly restored after a configuration change
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Rotate device
* 3. Result: action bar title should show TV show item title
*/
@Test
public void restoreActionBarTitleOnConfigurationStateChanged() {
EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"11.22.63",
mActivityRule.getActivity());
}
/**
* Test if action bar title is correctly restored on season list after a configuration change
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on season item
* 3. Rotate device
* 4. Result: action bar title should show season title
*/
@Test
public void restoreActionBarTitleSeasonListOnConfigurationStateChanged() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
onView( withId(R.id.seasons_list)).perform( scrollTo(), click());
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText("Season 01")));
}
/**
* Test if action bar title is correctly restored on episode item title after a configuration change
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on season item
* 3. Click on episode item
* 4. Rotate device
* 5. Result: action bar title should TV show title
*/
@Test
public void restoreActionBarTitleSeasonListEpisodeOnConfigurationStateChanged() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
onView( withId(R.id.seasons_list)).perform( scrollTo(), click());
EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list,
"11.22.63",
mActivityRule.getActivity());
}
/**
* Test if action bar title is correctly restored on next episode item title after a configuration change
*
* UI interaction flow tested:
* 1. Click on TV Show item
* 2. Click on next episode item
* 3. Rotate device
* 4. Result: action bar title should show season title
*/
@Test
public void restoreActionBarTitleNextEpisodeOnConfigurationStateChanged() {
EspressoTestUtils.clickAdapterViewItem(1, R.id.list);
onView( withId(R.id.next_episode_list)).perform( scrollTo() );
onView( withText("You'll See the Sparkle")).perform( click() );
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText("3")));
}
}

@ -86,7 +86,8 @@ public class SyncMusicVideos extends SyncItem {
@Override
public void onSuccess(List<VideoType.DetailsMusicVideo> result) {
deleteMusicVideos(contentResolver, hostId);
insertMusicVideos(orchestrator, contentResolver, result);
insertMusicVideos(result, contentResolver);
orchestrator.syncItemFinished();
}
@Override
@ -104,9 +105,7 @@ public class SyncMusicVideos extends SyncItem {
where, new String[]{String.valueOf(hostId)});
}
private void insertMusicVideos(final SyncOrchestrator orchestrator,
final ContentResolver contentResolver,
final List<VideoType.DetailsMusicVideo> musicVideos) {
public void insertMusicVideos(List<VideoType.DetailsMusicVideo> musicVideos, ContentResolver contentResolver) {
ContentValues musicVideosValuesBatch[] = new ContentValues[musicVideos.size()];
// Iterate on each music video
@ -117,6 +116,5 @@ public class SyncMusicVideos extends SyncItem {
// Insert the movies
contentResolver.bulkInsert(MediaContract.MusicVideos.CONTENT_URI, musicVideosValuesBatch);
orchestrator.syncItemFinished();
}
}

@ -115,8 +115,9 @@ public class SyncTVShows extends SyncItem {
deleteTVShows(contentResolver, hostId, tvshowId);
List<VideoType.DetailsTVShow> tvShows = new ArrayList<>(1);
tvShows.add(result);
insertTVShowsAndGetDetails(orchestrator, hostConnection, callbackHandler,
contentResolver, tvShows);
insertTVShows(tvShows, contentResolver);
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
contentResolver, tvShows, 0);
// insertTVShows calls syncItemFinished
}
@ -157,8 +158,10 @@ public class SyncTVShows extends SyncItem {
// Ok, we have all the shows, insert them
LogUtils.LOGD(TAG, "syncAllTVShows: Got all tv shows. Total: " + allResults.size());
deleteTVShows(contentResolver, hostId, -1);
insertTVShowsAndGetDetails(orchestrator, hostConnection, callbackHandler,
contentResolver, allResults);
insertTVShows(allResults, contentResolver);
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
contentResolver, allResults, 0);
}
}
@ -197,42 +200,6 @@ public class SyncTVShows extends SyncItem {
}
}
private void insertTVShowsAndGetDetails(final SyncOrchestrator orchestrator,
final HostConnection hostConnection,
final Handler callbackHandler,
final ContentResolver contentResolver,
List<VideoType.DetailsTVShow> tvShows) {
ContentValues tvshowsValuesBatch[] = new ContentValues[tvShows.size()];
int castCount = 0;
// Iterate on each show
for (int i = 0; i < tvShows.size(); i++) {
VideoType.DetailsTVShow tvshow = tvShows.get(i);
tvshowsValuesBatch[i] = SyncUtils.contentValuesFromTVShow(hostId, tvshow);
castCount += tvshow.cast.size();
}
// Insert the tvshows
contentResolver.bulkInsert(MediaContract.TVShows.CONTENT_URI, tvshowsValuesBatch);
LogUtils.LOGD(TAG, "Inserted " + tvShows.size() + " tv shows.");
ContentValues tvshowsCastValuesBatch[] = new ContentValues[castCount];
int count = 0;
// Iterate on each show/cast
for (VideoType.DetailsTVShow tvshow : tvShows) {
for (VideoType.Cast cast : tvshow.cast) {
tvshowsCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
tvshowsCastValuesBatch[count].put(MediaContract.TVShowCastColumns.TVSHOWID, tvshow.tvshowid);
count++;
}
}
// Insert the cast list for this movie
contentResolver.bulkInsert(MediaContract.TVShowCast.CONTENT_URI, tvshowsCastValuesBatch);
// Start the sequential syncing of seasons
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
contentResolver, tvShows, 0);
}
private final static String seasonsProperties[] = {
VideoType.FieldsSeason.SEASON, VideoType.FieldsSeason.SHOWTITLE,
//VideoType.FieldsSeason.PLAYCOUNT,
@ -270,27 +237,8 @@ public class SyncTVShows extends SyncItem {
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsSeason>>() {
@Override
public void onSuccess(List<VideoType.DetailsSeason> result) {
ContentValues seasonsValuesBatch[] = new ContentValues[result.size()];
int totalWatchedEpisodes = 0;
for (int i = 0; i < result.size(); i++) {
VideoType.DetailsSeason season = result.get(i);
seasonsValuesBatch[i] = SyncUtils.contentValuesFromSeason(hostId, season);
totalWatchedEpisodes += season.watchedepisodes;
}
// Insert the seasons
contentResolver.bulkInsert(MediaContract.Seasons.CONTENT_URI, seasonsValuesBatch);
if (getSyncType().equals(LibrarySyncService.SYNC_SINGLE_TVSHOW)) {
// HACK: Update watched episodes count for the tvshow with the sum
// of watched episodes from seasons, given that the value that we
// got from XBMC from the call to GetTVShowDetails is wrong (note
// that the value returned from GetTVShows is correct).
Uri uri = MediaContract.TVShows.buildTVShowUri(hostId, tvShow.tvshowid);
ContentValues tvshowUpdate = new ContentValues(1);
tvshowUpdate.put(MediaContract.TVShowsColumns.WATCHEDEPISODES, totalWatchedEpisodes);
contentResolver.update(uri, tvshowUpdate, null, null);
}
insertSeason(tvShow.tvshowid, result, contentResolver);
// Sync the next tv show
chainSyncSeasons(orchestrator, hostConnection, callbackHandler,
@ -358,13 +306,8 @@ public class SyncTVShows extends SyncItem {
action.execute(hostConnection, new ApiCallback<List<VideoType.DetailsEpisode>>() {
@Override
public void onSuccess(List<VideoType.DetailsEpisode> result) {
ContentValues episodesValuesBatch[] = new ContentValues[result.size()];
for (int i = 0; i < result.size(); i++) {
VideoType.DetailsEpisode episode = result.get(i);
episodesValuesBatch[i] = SyncUtils.contentValuesFromEpisode(hostId, episode);
}
// Insert the episodes
contentResolver.bulkInsert(MediaContract.Episodes.CONTENT_URI, episodesValuesBatch);
insertEpisodes(result, contentResolver);
chainSyncEpisodes(orchestrator, hostConnection, callbackHandler,
contentResolver, tvShows, position + 1);
@ -382,4 +325,67 @@ public class SyncTVShows extends SyncItem {
orchestrator.syncItemFinished();
}
}
public void insertTVShows(List<VideoType.DetailsTVShow> tvShows, ContentResolver contentResolver) {
ContentValues tvshowsValuesBatch[] = new ContentValues[tvShows.size()];
int castCount = 0;
// Iterate on each show
for (int i = 0; i < tvShows.size(); i++) {
VideoType.DetailsTVShow tvshow = tvShows.get(i);
tvshowsValuesBatch[i] = SyncUtils.contentValuesFromTVShow(hostId, tvshow);
castCount += tvshow.cast.size();
}
// Insert the tvshows
contentResolver.bulkInsert(MediaContract.TVShows.CONTENT_URI, tvshowsValuesBatch);
LogUtils.LOGD(TAG, "Inserted " + tvShows.size() + " tv shows.");
ContentValues tvshowsCastValuesBatch[] = new ContentValues[castCount];
int count = 0;
// Iterate on each show/cast
for (VideoType.DetailsTVShow tvshow : tvShows) {
for (VideoType.Cast cast : tvshow.cast) {
tvshowsCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
tvshowsCastValuesBatch[count].put(MediaContract.TVShowCastColumns.TVSHOWID, tvshow.tvshowid);
count++;
}
}
</