From d634d9455ee4a0339e41018bcd501999dd3e8364 Mon Sep 17 00:00:00 2001 From: Martijn Brekhof Date: Tue, 24 Jul 2018 22:04:33 +0200 Subject: [PATCH] Refactored instrumentation tests to support RecyclerViews * Fixed Espresso not waiting on ViewPager switching * Removed BaseMediaActivityTests abstract class. Unfortunately the click action on list items now requires a unique item to be able to select the item to click. Therefore the tests from BaseMediaActivityTests have been moved to the specific test classes. This made the BaseMediaActivityTests class obsolete. * Removed dependency on Context class in AddonsHandler and inlined the json data to allow the AddonsHandler to be initialized before the test activity is started. This was needed to have the AddonsActivityTests directly start the AddonsActivity, instead of first starting MoviesActivity and from there start the AddonsActivity. --- .travis.yml | 5 - app/build.gradle | 1 + .../kore/testhelpers/EspressoTestUtils.java | 66 +- .../org/xbmc/kore/testhelpers/Matchers.java | 26 +- .../xbmc/kore/tests/ui/AbstractTestClass.java | 10 +- .../kore/tests/ui/BaseMediaActivityTests.java | 127 -- .../tests/ui/addons/AddonsActivityTests.java | 121 +- .../tests/ui/movies/MoviesActivityTests.java | 101 +- .../RestoreSearchQueryListFragmentTest.java | 12 +- .../tests/ui/music/MusicActivityTests.java | 153 +- .../RestoreSearchQueryViewPagerTest.java | 6 +- .../ui/tvshows/TVShowsActivityTests.java | 122 +- .../tcpserver/handlers/AddonsHandler.java | 1257 ++++++++++++++++- .../addon/AddonListContainerFragment.java | 9 + 14 files changed, 1770 insertions(+), 246 deletions(-) delete mode 100644 app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java diff --git a/.travis.yml b/.travis.yml index 965f0e4..1d48943 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,6 @@ env: android: components: - # following added as quick fix to get support for 23.0.3 build tools - # "tools" always refers to the latest build tools version. Remove this - # when travis is able to parse "build-tools-23.0.3" correctly. - # See https://github.com/travis-ci/travis-ci/issues/5036 - # - tools # needed build tools - build-tools-27.0.3 diff --git a/app/build.gradle b/app/build.gradle index 3fae257..18e8a31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -119,6 +119,7 @@ dependencies { androidTestUtil 'com.android.support.test:orchestrator:1.0.1' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.1' androidTestImplementation "com.android.support:support-v13:${supportLibVersion}" androidTestImplementation 'junit:junit:4.12' diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/EspressoTestUtils.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/EspressoTestUtils.java index 37cd756..4838ee8 100644 --- a/app/src/androidTest/java/org/xbmc/kore/testhelpers/EspressoTestUtils.java +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/EspressoTestUtils.java @@ -23,6 +23,8 @@ 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.support.test.espresso.ViewInteraction; +import android.support.test.espresso.contrib.RecyclerViewActions; import android.view.View; import android.widget.AutoCompleteTextView; import android.widget.TextView; @@ -39,18 +41,21 @@ import static android.support.test.espresso.Espresso.openActionBarOverflowOrOpti 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.closeSoftKeyboard; 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; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isRoot; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; 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.CoreMatchers.anything; 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; @@ -108,9 +113,7 @@ public class EspressoTestUtils { EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search); onView(isAssignableFrom(AutoCompleteTextView.class)) - .perform(click(), typeText(query), clearFocus()); - - Espresso.closeSoftKeyboard(); + .perform(click(), typeText(query), clearFocus(), closeSoftKeyboard()); } /** @@ -121,9 +124,7 @@ public class EspressoTestUtils { EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search); onView(isAssignableFrom(AutoCompleteTextView.class)) - .perform(click(), clearText()); - - Espresso.closeSoftKeyboard(); + .perform(click(), clearText(), closeSoftKeyboard()); } /** @@ -150,7 +151,21 @@ public class EspressoTestUtils { .atPosition(position).perform(click()); } - /** + + public static void clickRecyclerViewItem(int position, int resourceId) { + onView(withId(resourceId)).perform(RecyclerViewActions.actionOnItemAtPosition(position, click())); + } + + public static void clickRecyclerViewItem(String text, int resourceId) { + ViewInteraction viewInteraction = onView(allOf(withId(resourceId), + hasDescendant(withText(containsString(text))), + isDisplayed())); + viewInteraction.perform(RecyclerViewActions.scrollTo(hasDescendant(withText(containsString(text))))); + viewInteraction.perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(containsString(text))), + click())); + } + + /** * Checks that SearchView contains the given text * @param query text that SearchView should contain */ @@ -165,9 +180,9 @@ public class EspressoTestUtils { */ public static void checkListMatchesSearchQuery(String query, int listSize, int resourceId) { onView(allOf(withId(resourceId), isDisplayed())) - .check(matches(Matchers.withOnlyMatchingDataItems(Matchers.withItemContent(containsString(query))))); + .check(matches(Matchers.withOnlyMatchingDataItems(hasDescendant(withText(containsString(query)))))); onView(allOf(withId(resourceId), isDisplayed())) - .check(matches(Matchers.withAdapterSize(listSize))); + .check(matches(Matchers.withRecyclerViewSize(listSize))); } /** @@ -250,39 +265,54 @@ public class EspressoTestUtils { public static void selectListItemPressBackAndCheckActionbarTitle(int item, int listResourceId, String actionbarTitle) { - EspressoTestUtils.clickAdapterViewItem(item, listResourceId); + EspressoTestUtils.clickRecyclerViewItem(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 presses back and checks the action bar title + * @param itemText the text the item that must be pressed should contain + * @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(String itemText, + int listResourceId, + String actionbarTitle) { + EspressoTestUtils.clickRecyclerViewItem(itemText, listResourceId); + pressBack(); + onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar)))) + .check(matches(withText(containsString(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 itemText the text the item that must be pressed should contain * @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, + public static void selectListItemRotateDeviceAndCheckActionbarTitle(String itemText, int listResourceId, String actionbarTitle, Activity activity) { - EspressoTestUtils.clickAdapterViewItem(item, listResourceId); + EspressoTestUtils.clickRecyclerViewItem(itemText, listResourceId); EspressoTestUtils.rotateDevice(activity); onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar)))) - .check(matches(withText(actionbarTitle))); + .check(matches(withText(containsString(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 itemText the text the item that must be pressed should contain * @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, + public static void selectListItemAndCheckActionbarTitle(String itemText, int listResourceId, String actionbarTitle) { - EspressoTestUtils.clickAdapterViewItem(item, listResourceId); + EspressoTestUtils.clickRecyclerViewItem(itemText, listResourceId); onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar)))) .check(matches(withText(actionbarTitle))); } diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Matchers.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Matchers.java index 249b38e..119be69 100644 --- a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Matchers.java +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Matchers.java @@ -19,11 +19,10 @@ package org.xbmc.kore.testhelpers; import android.database.Cursor; import android.support.test.espresso.matcher.BoundedMatcher; import android.support.test.espresso.matcher.CursorMatchers; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Adapter; -import android.widget.AdapterView; import android.widget.SeekBar; import android.widget.TextView; @@ -31,8 +30,8 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; -import org.xbmc.kore.ui.widgets.RepeatModeButton; import org.xbmc.kore.ui.widgets.HighlightButton; +import org.xbmc.kore.ui.widgets.RepeatModeButton; import org.xbmc.kore.utils.UIUtils; public class Matchers { @@ -70,33 +69,32 @@ public class Matchers { }; } - public static Matcher withAdapterSize(final int size) { + public static Matcher withRecyclerViewSize(final int size) { return new TypeSafeMatcher() { @Override protected boolean matchesSafely(View view) { - if (!(view instanceof AdapterView)) - return false; - - return ((AdapterView) view).getCount() == size; + return (view instanceof RecyclerView) && + ((RecyclerView) view).getAdapter().getItemCount() == size; } @Override public void describeTo(Description description) { - description.appendText("Adapter should have " + size + " item(s)"); + description.appendText("RecyclerView should have " + size + " item(s)"); } }; } - public static Matcher withOnlyMatchingDataItems(final Matcher dataMatcher) { + public static Matcher withOnlyMatchingDataItems(final Matcher dataMatcher) { return new TypeSafeMatcher() { @Override protected boolean matchesSafely(View view) { - if (!(view instanceof AdapterView)) + if (!(view instanceof RecyclerView)) return false; - Adapter adapter = ((AdapterView) view).getAdapter(); - for (int i = 0; i < adapter.getCount(); i++) { - if (! dataMatcher.matches(adapter.getItem(i))) { + RecyclerView recyclerView = (RecyclerView) view; + for (int i = 0; i < recyclerView.getChildCount(); i++) { + RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(i); + if (! dataMatcher.matches(viewHolder.itemView)) { return false; } } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/AbstractTestClass.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/AbstractTestClass.java index 66f09a8..4295130 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/AbstractTestClass.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/AbstractTestClass.java @@ -72,7 +72,6 @@ abstract public class AbstractTestClass { private ActivityTestRule activityTestRule; private static MockTcpServer server; private static JSONConnectionHandlerManager manager; - private AddonsHandler addonsHandler; private static PlayerHandler playerHandler; private static ApplicationHandler applicationHandler; private static InputHandler inputHandler; @@ -88,6 +87,7 @@ abstract public class AbstractTestClass { manager.addHandler(playerHandler); manager.addHandler(applicationHandler); manager.addHandler(inputHandler); + manager.addHandler(new AddonsHandler()); manager.addHandler(new JSONRPCHandler()); server = new MockTcpServer(manager); server.start(); @@ -108,13 +108,6 @@ abstract public class AbstractTestClass { //Allow each test to change the shared preferences setSharedPreferences(context); - //Note: as the activity is not yet available in @BeforeClass we need - // to add the handler here - if (addonsHandler == null) { - addonsHandler = new AddonsHandler(context); - manager.addHandler(addonsHandler); - } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean useEventServer = prefs.getBoolean(HostFragmentManualConfiguration.HOST_USE_EVENT_SERVER, false); @@ -136,6 +129,7 @@ abstract public class AbstractTestClass { Utils.switchHost(context, activityTestRule.getActivity(), hostInfo); //Relaunch the activity for the changes (Host selection, preference changes, and database fill) to take effect + activityTestRule.finishActivity(); activityTestRule.launchActivity(new Intent()); } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java deleted file mode 100644 index 7e5d58a..0000000 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.Context; -import android.support.test.espresso.Espresso; -import android.support.test.rule.ActivityTestRule; - -import org.junit.Ignore; -import org.junit.Test; -import org.xbmc.kore.R; -import org.xbmc.kore.host.HostInfo; -import org.xbmc.kore.testhelpers.EspressoTestUtils; -import org.xbmc.kore.testhelpers.Utils; -import org.xbmc.kore.ui.BaseMediaActivity; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickAdapterViewItem; -import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice; - -/** - * Contains generic tests for all activities extending BaseMediaActivity - * @param - */ -@Ignore -abstract public class BaseMediaActivityTests extends AbstractTestClass { - - @Override - protected void setSharedPreferences(Context context) { - - } - - @Override - protected void configureHostInfo(HostInfo hostInfo) { - - } - - /** - * 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() { - 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() { - 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() { - clickAdapterViewItem(0, R.id.list); - 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() { - clickAdapterViewItem(0, R.id.list); - rotateDevice(getActivity()); - Espresso.pressBack(); - - assertFalse(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow()); - - } -} diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/addons/AddonsActivityTests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/addons/AddonsActivityTests.java index 50877e4..73edf58 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/addons/AddonsActivityTests.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/addons/AddonsActivityTests.java @@ -16,29 +16,34 @@ package org.xbmc.kore.tests.ui.addons; -import android.content.Intent; -import android.os.SystemClock; +import android.content.Context; +import android.support.test.espresso.Espresso; import android.support.test.rule.ActivityTestRule; +import android.view.View; 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.host.HostInfo; import org.xbmc.kore.testhelpers.EspressoTestUtils; -import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.testhelpers.action.ViewActions; import org.xbmc.kore.tests.ui.AbstractTestClass; -import org.xbmc.kore.tests.ui.BaseMediaActivityTests; import org.xbmc.kore.ui.sections.addon.AddonsActivity; -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.isRoot; 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickRecyclerViewItem; +import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice; import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBackAndCheckActionbarTitle; /** @@ -59,24 +64,35 @@ import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBac * 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 { +public class AddonsActivityTests extends AbstractTestClass { @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>( - MoviesActivity.class); + public ActivityTestRule mActivityRule = new ActivityTestRule<>(AddonsActivity.class); @Override - protected ActivityTestRule getActivityTestRule() { + protected ActivityTestRule getActivityTestRule() { return mActivityRule; } - @Before @Override + protected void setSharedPreferences(Context context) { + + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + + } + + @Before public void setUp() throws Throwable { super.setUp(); - - Intent intent = new Intent(getActivity(), AddonsActivity.class); - getActivity().startActivity(intent); + onView(isRoot()).perform(ViewActions.waitForView(R.id.list, new ViewActions.CheckStatus() { + @Override + public boolean check(View v) { + return v.isShown(); + } + },10000)); } /** @@ -97,7 +113,7 @@ public class AddonsActivityTests extends BaseMediaActivityTests */ @Test public void setActionBarTitle() { - EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list, + EspressoTestUtils.selectListItemAndCheckActionbarTitle("Dumpert", R.id.list, "Dumpert"); } @@ -111,7 +127,7 @@ public class AddonsActivityTests extends BaseMediaActivityTests */ @Test public void restoreActionBarTitleOnConfigurationStateChanged() { - EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, + EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle("Dumpert", R.id.list, "Dumpert", getActivity()); } @@ -129,4 +145,79 @@ public class AddonsActivityTests extends BaseMediaActivityTests selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, getActivity().getString(R.string.addons)); } + + /** + * 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() { + clickRecyclerViewItem(0, R.id.list); + + assertTrue(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() { + clickRecyclerViewItem(0, R.id.list); + Espresso.pressBack(); + + assertFalse(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() { + clickRecyclerViewItem(0, R.id.list); + rotateDevice(getActivity()); + + assertTrue(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() { + clickRecyclerViewItem(0, R.id.list); + rotateDevice(getActivity()); + Espresso.pressBack(); + + assertTrue(EspressoTestUtils.getActivity() instanceof AddonsActivity); + assertFalse(((AddonsActivity) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow()); + } } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/MoviesActivityTests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/MoviesActivityTests.java index 5c1c562..b7f9614 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/MoviesActivityTests.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/MoviesActivityTests.java @@ -16,15 +16,17 @@ package org.xbmc.kore.tests.ui.movies; -import android.os.SystemClock; +import android.content.Context; +import android.support.test.espresso.Espresso; 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.host.HostInfo; import org.xbmc.kore.testhelpers.EspressoTestUtils; -import org.xbmc.kore.tests.ui.BaseMediaActivityTests; +import org.xbmc.kore.tests.ui.AbstractTestClass; import org.xbmc.kore.ui.sections.video.MoviesActivity; import static android.support.test.espresso.Espresso.onView; @@ -34,9 +36,13 @@ 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickRecyclerViewItem; +import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice; import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBackAndCheckActionbarTitle; -public class MoviesActivityTests extends BaseMediaActivityTests { +public class MoviesActivityTests extends AbstractTestClass { @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( @@ -47,6 +53,16 @@ public class MoviesActivityTests extends BaseMediaActivityTests return mActivityRule; } + @Override + protected void setSharedPreferences(Context context) { + + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + + } + /** * Test if action bar title initially displays Movies */ @@ -65,7 +81,7 @@ public class MoviesActivityTests extends BaseMediaActivityTests */ @Test public void setActionBarTitle() { - EspressoTestUtils.selectListItemAndCheckActionbarTitle(0, R.id.list, + EspressoTestUtils.selectListItemAndCheckActionbarTitle("#Rookie93 Marc Marquez: Beyond the Smile", R.id.list, "#Rookie93 Marc Marquez: Beyond the Smile"); } @@ -79,7 +95,7 @@ public class MoviesActivityTests extends BaseMediaActivityTests */ @Test public void restoreActionBarTitleOnConfigurationStateChanged() { - EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, + EspressoTestUtils.selectListItemRotateDeviceAndCheckActionbarTitle("#Rookie93 Marc Marquez: Beyond the Smile", R.id.list, "#Rookie93 Marc Marquez: Beyond the Smile", getActivity()); } @@ -97,4 +113,79 @@ public class MoviesActivityTests extends BaseMediaActivityTests selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, getActivity().getString(R.string.movies)); } + + /** + * 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() { + clickRecyclerViewItem(0, R.id.list); + + assertTrue(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() { + clickRecyclerViewItem(0, R.id.list); + Espresso.pressBack(); + + assertFalse(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() { + clickRecyclerViewItem(0, R.id.list); + rotateDevice(getActivity()); + + assertTrue(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() { + clickRecyclerViewItem(0, R.id.list); + rotateDevice(getActivity()); + Espresso.pressBack(); + + assertTrue(EspressoTestUtils.getActivity() instanceof MoviesActivity); + assertFalse(((MoviesActivity) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow()); + } } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/RestoreSearchQueryListFragmentTest.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/RestoreSearchQueryListFragmentTest.java index adcbd20..3196e9d 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/RestoreSearchQueryListFragmentTest.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/movies/RestoreSearchQueryListFragmentTest.java @@ -95,7 +95,7 @@ public class RestoreSearchQueryListFragmentTest extends AbstractTestClass { - +public class MusicActivityTests extends AbstractTestClass { @Rule public ActivityTestRule musicActivityActivityTestRule = new ActivityTestRule<>(MusicActivity.class); @@ -51,6 +57,16 @@ public class MusicActivityTests extends BaseMediaActivityTests { return musicActivityActivityTestRule; } + @Override + protected void setSharedPreferences(Context context) { + + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + + } + /** * Test if action bar title initially displays Music */ @@ -69,7 +85,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { */ @Test public void setActionBarTitleArtist() { - selectListItemAndCheckActionbarTitle(0, R.id.list, "ABC Orch"); + selectListItemAndCheckActionbarTitle(ArtistTestData.title, R.id.list, ArtistTestData.title); } /** @@ -83,7 +99,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void setActionBarTitleAlbum() { clickAlbumsTab(); - selectListItemAndCheckActionbarTitle(0, R.id.list, "1958 - The Fabulous Johnny Cash"); + selectListItemAndCheckActionbarTitle(AlbumTestData.title, R.id.list, AlbumTestData.title); } /** @@ -97,7 +113,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void setActionBarTitleGenre() { clickGenresTab(); - selectListItemAndCheckActionbarTitle(0, R.id.list, "Ambient"); + selectListItemAndCheckActionbarTitle(GenreTestData.title, R.id.list, GenreTestData.title); } /** @@ -111,7 +127,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void setActionBarTitleVideo() { clickMusicVideosTab(); - selectListItemAndCheckActionbarTitle(0, R.id.list, "(You Drive Me) Crazy"); + selectListItemAndCheckActionbarTitle(MusicVideoTestData.title, R.id.list, MusicVideoTestData.title); } /** @@ -125,8 +141,9 @@ public class MusicActivityTests extends BaseMediaActivityTests { */ @Test public void restoreActionBarTitleArtistOnConfigurationStateChanged() { - selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, - "ABC Orch", getActivity()); + SystemClock.sleep(10000); + selectListItemRotateDeviceAndCheckActionbarTitle(ArtistTestData.title, R.id.list, + ArtistTestData.title, getActivity()); } /** @@ -142,8 +159,8 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleAlbumOnConfigurationStateChanged() { clickAlbumsTab(); - selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, - "1958 - The Fabulous Johnny Cash", + selectListItemRotateDeviceAndCheckActionbarTitle(AlbumTestData.title, R.id.list, + AlbumTestData.title, getActivity()); } @@ -160,8 +177,8 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleGenreOnConfigurationStateChanged() { clickGenresTab(); - selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, - "Ambient", getActivity()); + selectListItemRotateDeviceAndCheckActionbarTitle(GenreTestData.title, R.id.list, + GenreTestData.title, getActivity()); } /** @@ -177,8 +194,8 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleMusicVideoOnConfigurationStateChanged() { clickMusicVideosTab(); - selectListItemRotateDeviceAndCheckActionbarTitle(0, R.id.list, - "(You Drive Me) Crazy", + selectListItemRotateDeviceAndCheckActionbarTitle(MusicVideoTestData.title, R.id.list, + MusicVideoTestData.title, getActivity()); } @@ -192,7 +209,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { */ @Test public void restoreActionBarTitleOnReturningFromArtist() { - selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, + selectListItemPressBackAndCheckActionbarTitle(ArtistTestData.title, R.id.list, getActivity().getString(R.string.music)); } @@ -208,9 +225,9 @@ public class MusicActivityTests extends BaseMediaActivityTests { */ @Test public void restoreActionBarTitleOnArtistOnReturningFromAlbum() { - EspressoTestUtils.clickAdapterViewItem(0, R.id.list); + EspressoTestUtils.clickRecyclerViewItem(ArtistTestData.title, R.id.list); clickAlbumsTab(); - selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, "ABC Orch"); + selectListItemPressBackAndCheckActionbarTitle(ArtistTestData.album, R.id.list, ArtistTestData.title); } /** @@ -224,7 +241,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleOnReturningFromMusicVideo() { clickMusicVideosTab(); - selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, + selectListItemPressBackAndCheckActionbarTitle(MusicVideoTestData.title, R.id.list, getActivity().getString(R.string.music)); } @@ -239,7 +256,7 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleOnReturningFromGenre() { clickGenresTab(); - selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, + selectListItemPressBackAndCheckActionbarTitle(GenreTestData.title, R.id.list, getActivity().getString(R.string.music)); } @@ -254,7 +271,101 @@ public class MusicActivityTests extends BaseMediaActivityTests { @Test public void restoreActionBarTitleOnReturningFromAlbum() { clickAlbumsTab(); - selectListItemPressBackAndCheckActionbarTitle(0, R.id.list, + selectListItemPressBackAndCheckActionbarTitle(AlbumTestData.title, R.id.list, getActivity().getString(R.string.music)); } + + /** + * 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.clickRecyclerViewItem(ArtistTestData.title, R.id.list); + + assertTrue(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.clickRecyclerViewItem(ArtistTestData.title, R.id.list); + + Espresso.pressBack(); + + assertFalse(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.clickRecyclerViewItem(ArtistTestData.title, R.id.list); + + rotateDevice(getActivity()); + + assertTrue(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.clickRecyclerViewItem(ArtistTestData.title, R.id.list); + rotateDevice(getActivity()); + Espresso.pressBack(); + + assertTrue(EspressoTestUtils.getActivity() instanceof MusicActivity); + assertFalse(((MusicActivity) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow()); + } + + private static class ArtistTestData { + static String title = "ABC Orch Conducted by Herschel Burke Gilbert"; + static String album = "Songs Of The West"; + } + + private static class AlbumTestData { + static String title = "1958 - The Fabulous Johnny Cash"; + } + + private static class GenreTestData { + static String title = "Ambient"; + } + + private static class MusicVideoTestData { + static String title = "(You Drive Me) Crazy"; + } } diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/music/RestoreSearchQueryViewPagerTest.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/music/RestoreSearchQueryViewPagerTest.java index f0273d9..bc9ecf7 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/music/RestoreSearchQueryViewPagerTest.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/music/RestoreSearchQueryViewPagerTest.java @@ -18,7 +18,6 @@ package org.xbmc.kore.tests.ui.music; import android.app.Activity; import android.content.Context; -import android.os.SystemClock; import android.support.test.espresso.Espresso; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; @@ -43,6 +42,7 @@ public class RestoreSearchQueryViewPagerTest extends AbstractTestClass { +public class TVShowsActivityTests extends AbstractTestClass { + private final String TV_SHOW_TITLE = "11.22.63"; + private final String EPISODE_TITLE = "The Rabbit Hole"; @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( @@ -47,6 +56,16 @@ public class TVShowsActivityTests extends BaseMediaActivityTests getNotifications() { @@ -65,8 +58,7 @@ public class AddonsHandler implements JSONConnectionHandlerManager.ConnectionHan switch (method) { case Addons.GetAddons.METHOD_NAME: try { - String result = FileUtils.readFile(context, "Addons.GetAddons.json"); - Addons.GetAddons getAddons = new Addons.GetAddons(methodId, result); + Addons.GetAddons getAddons = new Addons.GetAddons(methodId, jsonResult); jsonResponses.add(getAddons); } catch (IOException e) { LogUtils.LOGW(TAG, "Error creating GetAddons response: " + e.getMessage()); @@ -77,4 +69,1249 @@ public class AddonsHandler implements JSONConnectionHandlerManager.ConnectionHan } return jsonResponses; } + + private String jsonResult = "{\n" + + " \"jsonrpc\" : \"2.0\",\n" + + " \"id\" : \"libAddons\",\n" + + " \"result\" : {\n" + + " \"addons\" : [\n" + + " {\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.fanart.tv\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"Download backdrops from www.fanart.tv.com\",\n" + + " \"rating\" : -1,\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"3.1.4\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.fanart.tv%2ficon.png/\",\n" + + " \"summary\" : \"fanart.tv Scraper Library\",\n" + + " \"name\" : \"fanart.tv Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.fanart.tv\"\n" + + " },\n" + + " {\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.1.8\",\n" + + " \"type\" : \"kodi.resource.images\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"name\" : \"Weather Icons - Default\",\n" + + " \"addonid\" : \"resource.images.weathericons.default\",\n" + + " \"summary\" : \"Default Weather Icons\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fresource.images.weathericons.default%2ficon.png/\",\n" + + " \"description\" : \"Default set of Weather Icons shipped with Kodi\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/resource.images.weathericons.default\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"1.0.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"kodi.resource\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"author\" : \"Skipmode A1, Sparkline, Martijn\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"5.1.7\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"plugin.video.youtube\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"3.0.8\",\n" + + " \"addonid\" : \"script.module.beautifulsoup\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"script.module.requests\",\n" + + " \"version\" : \"2.4.3\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.14.0\"\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"For bugs, requests or general questions visit the Dumpert.nl thread on the XBMC forum.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"value\" : \"nl\",\n" + + " \"key\" : \"language\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Watch funny videos from Dumpert.nl (dutch)\",\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.dumpert\",\n" + + " \"name\" : \"Dumpert\",\n" + + " \"addonid\" : \"plugin.video.dumpert\",\n" + + " \"summary\" : \"Watch funny videos from Dumpert.nl (dutch)\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.dumpert%2ficon.png/\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.dumpert%2ffanart.jpg/\",\n" + + " \"version\" : \"1.1.4\",\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true\n" + + " },\n" + + " {\n" + + " \"name\" : \"Kodi Add-on repository\",\n" + + " \"addonid\" : \"repository.xbmc.org\",\n" + + " \"summary\" : \"Install Add-ons from Kodi.tv\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2frepository.xbmc.org%2ficon.png/\",\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"2.5.9\",\n" + + " \"type\" : \"xbmc.addon.repository\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.addon\",\n" + + " \"version\" : \"12.0.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"Team Kodi did not make all the add-ons on this repository and are not responsible for their content\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Download and install add-ons from the Official Kodi.tv add-on repository.[CR] By using the official Repository you will be able to take advantage of our extensive file mirror service to help get you faster downloads from a region close to you.[CR] All add-ons on this repository have under gone basic testing, if you find a broken or not working add-on please report it to Team Kodi so we can take any action needed.\",\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/repository.xbmc.org\"\n" + + " },\n" + + " {\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"jez500, Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"xbmc.json\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"6.0.0\"\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/usr/share/kodi/addons/webinterface.default\",\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"language\",\n" + + " \"value\" : \"en\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Browse and interact with your Music, Movies, TV Shows and more via a web browser. Stream music and videos to your browser. Edit and manage your Kodi media library.\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"summary\" : \"Default web interface\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fwebinterface.default%2ficon.png/\",\n" + + " \"addonid\" : \"webinterface.default\",\n" + + " \"name\" : \"Kodi web interface - Chorus2\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.webinterface\",\n" + + " \"broken\" : false,\n" + + " \"version\" : \"2.4.4\",\n" + + " \"fanart\" : \"\"\n" + + " },\n" + + " {\n" + + " \"description\" : \"Black is a simple screensaver that will turn your screen black.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/screensaver.xbmc.builtin.black\",\n" + + " \"dependencies\" : [],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.31\",\n" + + " \"type\" : \"xbmc.ui.screensaver\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"name\" : \"Black\",\n" + + " \"addonid\" : \"screensaver.xbmc.builtin.black\",\n" + + " \"summary\" : \"Screensaver that turns your screen black\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fscreensaver.xbmc.builtin.black%2ficon.png/\"\n" + + " },\n" + + " {\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.albums.theaudiodb.com\",\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"TheAudioDB.com is a community driven database of audio releases. It is our aim to be the most simple, easy to use and accurate source for Music metadata on the web. We also provide an API to access our repository of data so it can be used in many popular HTPC and Mobile apps to give you the best possible audio experience without the hassle.\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"3.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.fanart.tv\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.7.3\",\n" + + " \"addonid\" : \"metadata.common.theaudiodb.com\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Olympia, Team Kodi\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.albums\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.2.0\",\n" + + " \"summary\" : \"TheAudioDb Album Scraper\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.albums.theaudiodb.com%2ficon.png/\",\n" + + " \"name\" : \"TheAudioDb Album Scraper\",\n" + + " \"addonid\" : \"metadata.albums.theaudiodb.com\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"audioencoder.xbmc.builtin.wma\",\n" + + " \"name\" : \"WMA encoder\",\n" + + " \"summary\" : \"WMA Audio Encoder\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2faudioencoder.xbmc.builtin.wma%2ficon.png/\",\n" + + " \"version\" : \"1.0.0\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.audioencoder\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.audioencoder\",\n" + + " \"version\" : \"1.0.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"spiff\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"WMA Audio Encoder\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/audioencoder.xbmc.builtin.wma\"\n" + + " },\n" + + " {\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"path\" : \"/usr/share/kodi/addons/game.controller.default\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"The default media center controller is based on the Xbox 360 controller.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fgame.controller.default%2ficon.png/\",\n" + + " \"summary\" : \"Default Controller\",\n" + + " \"name\" : \"Default Controller\",\n" + + " \"addonid\" : \"game.controller.default\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"kodi.game.controller\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.3\"\n" + + " },\n" + + " {\n" + + " \"dependencies\" : [],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"The Dim screensaver is a simple screensaver that will dim (fade out) your screen to a setable value between 20 and 100% .\",\n" + + " \"rating\" : -1,\n" + + " \"path\" : \"/usr/share/kodi/addons/screensaver.xbmc.builtin.dim\",\n" + + " \"addonid\" : \"screensaver.xbmc.builtin.dim\",\n" + + " \"name\" : \"Dim\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fscreensaver.xbmc.builtin.dim%2ficon.png/\",\n" + + " \"summary\" : \"Screensaver that dims your screen\",\n" + + " \"version\" : \"1.0.38\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.ui.screensaver\",\n" + + " \"broken\" : false\n" + + " },\n" + + " {\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.module.beautifulsoup4%2ficon.png/\",\n" + + " \"summary\" : \"HTML/XML parser for quick-turnaround applications like screen-scraping\",\n" + + " \"addonid\" : \"script.module.beautifulsoup4\",\n" + + " \"name\" : \"BeautifulSoup4\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.python.module\",\n" + + " \"broken\" : false,\n" + + " \"version\" : \"4.5.3\",\n" + + " \"fanart\" : \"\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Leonard Richardson (leonardr@segfault.org)\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"version\" : \"2.25.0\"\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/script.module.beautifulsoup4\",\n" + + " \"enabled\" : true,\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Beautiful Soup parses arbitrarily invalid SGML and provides a variety of methods and Pythonic idioms for iterating and searching the parse tree.\",\n" + + " \"extrainfo\" : []\n" + + " },\n" + + " {\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.imdb.com\",\n" + + " \"version\" : \"2.7.8\"\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.themoviedb.org\",\n" + + " \"version\" : \"2.13.1\"\n" + + " },\n" + + " {\n" + + " \"optional\" : true,\n" + + " \"addonid\" : \"plugin.video.youtube\",\n" + + " \"version\" : \"4.4.10\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"\",\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"themoviedb.org is a free and open movie database. It's completely user driven by people like you. TMDb is currently used by millions of people every month and with their powerful API, it is also used by many popular media centers like Kodi to retrieve Movie Metadata, Posters and Fanart to enrich the user's experience.\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.themoviedb.org\",\n" + + " \"addonid\" : \"metadata.themoviedb.org\",\n" + + " \"name\" : \"The Movie Database\",\n" + + " \"summary\" : \"TMDB Movie Scraper\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.themoviedb.org%2ficon.png/\",\n" + + " \"version\" : \"3.9.3\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.movies\"\n" + + " },\n" + + " {\n" + + " \"disclaimer\" : \"Feel free to use this script. For information visit kodi.tv\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/service.xbmc.versioncheck\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Kodi Version Check only supports a number of platforms/distros as releases may differ between them. For more information visit the kodi.tv website.\",\n" + + " \"enabled\" : true,\n" + + " \"summary\" : \"Kodi Version Check checks if you are running latest released version.\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fservice.xbmc.versioncheck%2ficon.png/\",\n" + + " \"addonid\" : \"service.xbmc.versioncheck\",\n" + + " \"name\" : \"Version Check\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.service\",\n" + + " \"broken\" : false,\n" + + " \"version\" : \"0.3.22\",\n" + + " \"fanart\" : \"\"\n" + + " },\n" + + " {\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"language\",\n" + + " \"value\" : \"nl\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Watch videos from Gamekings.nl (dutch)\",\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.gamekings\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"1.4.5\",\n" + + " \"addonid\" : \"plugin.video.twitch\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"4.1.4\",\n" + + " \"addonid\" : \"plugin.video.vimeo\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"plugin.video.youtube\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"5.1.7\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"script.module.beautifulsoup\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"3.0.8\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.4.3\",\n" + + " \"addonid\" : \"script.module.requests\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"version\" : \"2.14.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Skipmode A1, Amelandbor\",\n" + + " \"disclaimer\" : \"For bugs, requests or general questions visit the Gamekings.nl thread on the Kodi forum.\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.gamekings%2ffanart.jpg/\",\n" + + " \"version\" : \"1.2.7\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"installed\" : true,\n" + + " \"name\" : \"GameKings\",\n" + + " \"addonid\" : \"plugin.video.gamekings\",\n" + + " \"summary\" : \"Watch videos from Gamekings.nl (dutch)\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.gamekings%2ficon.png/\"\n" + + " },\n" + + " {\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.module.beautifulsoup%2ficon.png/\",\n" + + " \"summary\" : \"HTML/XML parser for quick-turnaround applications like screen-scraping\",\n" + + " \"name\" : \"BeautifulSoup\",\n" + + " \"addonid\" : \"script.module.beautifulsoup\",\n" + + " \"type\" : \"xbmc.python.module\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"3.2.1\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Leonard Richardson (leonardr@segfault.org)\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/script.module.beautifulsoup\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"Beautiful Soup parses arbitrarily invalid SGML and provides a variety of methods and Pythonic idioms for iterating and searching the parse tree.\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1\n" + + " },\n" + + " {\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"PythonWare\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"\",\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/script.module.pil\",\n" + + " \"addonid\" : \"script.module.pil\",\n" + + " \"name\" : \"Python Image Library\",\n" + + " \"summary\" : \"\",\n" + + " \"thumbnail\" : \"\",\n" + + " \"version\" : \"1.1.7\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.python.module\",\n" + + " \"broken\" : false\n" + + " },\n" + + " {\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.southpark_unofficial%2ffanart.jpg/\",\n" + + " \"version\" : \"0.4.5\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.southpark_unofficial%2ficon.png/\",\n" + + " \"summary\" : \"South Park Unofficial Player\",\n" + + " \"name\" : \"South Park\",\n" + + " \"addonid\" : \"plugin.video.southpark_unofficial\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.southpark_unofficial\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"language\",\n" + + " \"value\" : \"en\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Watch South Park episodes. The supported countries are the one that can view videos from http://southpark.cc.com or http://www.southpark.de.\",\n" + + " \"rating\" : -1,\n" + + " \"disclaimer\" : \"Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing.\",\n" + + " \"author\" : \"Deroad\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.vimeo\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"Vimeo is a one of the biggest video-sharing websites of the world.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"provides\",\n" + + " \"value\" : \"video\"\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"version\" : \"2.14.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"bromix\",\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.vimeo%2ffanart.jpg/\",\n" + + " \"version\" : \"4.1.4\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.vimeo%2ficon.png/\",\n" + + " \"summary\" : \"Plugin for Vimeo\",\n" + + " \"name\" : \"Vimeo\",\n" + + " \"addonid\" : \"plugin.video.vimeo\"\n" + + " },\n" + + " {\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.metadata.scraper.musicvideos\",\n" + + " \"broken\" : false,\n" + + " \"version\" : \"1.3.3\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.musicvideos.theaudiodb.com%2ffanart.jpg/\",\n" + + " \"summary\" : \"theaudiodb.com Music Video Scraper\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.musicvideos.theaudiodb.com%2ficon.png/\",\n" + + " \"addonid\" : \"metadata.musicvideos.theaudiodb.com\",\n" + + " \"name\" : \"TheAudioDb.com for Music Videos\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.musicvideos.theaudiodb.com\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"This scraper downloads Music Video information from TheAudioDB.com website. Due to various search difficulties the scraper currently expects the folder/filename to be formatted as 'artist - trackname' otherwise it will not return results. It is important to note the space between the hyphen.\",\n" + + " \"enabled\" : true,\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"3.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.fanart.tv\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.7.3\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.theaudiodb.com\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"script.module.requests\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.12.4\"\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"version\" : \"2.19.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"jdf76, bromix\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.youtube\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"YouTube is one of the biggest video-sharing websites of the world.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.youtube%2ficon.png/\",\n" + + " \"summary\" : \"Plugin for YouTube\",\n" + + " \"addonid\" : \"plugin.video.youtube\",\n" + + " \"name\" : \"YouTube\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"version\" : \"5.3.12\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.youtube%2ffanart.jpg/\"\n" + + " },\n" + + " {\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.3.1\",\n" + + " \"summary\" : \"Uitzendinggemist (NPO) - Watch free videos from Uitzendinggemist (only with a dutch ip-address)\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.uzg%2ficon.png/\",\n" + + " \"name\" : \"Uitzendinggemist (NPO)\",\n" + + " \"addonid\" : \"plugin.video.uzg\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.uzg\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"language\",\n" + + " \"value\" : \"nl\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Dutch Uitzendinggemist (NPO) videos NED1 / NED2 / NED3 (only with a dutch ip-address)\",\n" + + " \"enabled\" : true,\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Bas Magré (Opvolger)\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.4.0\",\n" + + " \"addonid\" : \"script.module.xbmcswift2\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"fanart\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fskin.estouchy%2fresources%2ffanart.jpg/\",\n" + + " \"version\" : \"1.1.9\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.gui.skin\",\n" + + " \"installed\" : true,\n" + + " \"name\" : \"Estouchy\",\n" + + " \"addonid\" : \"skin.estouchy\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fskin.estouchy%2fresources%2ficon.png/\",\n" + + " \"summary\" : \"Skin for touchscreen devices\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"Skin designed to be used on touchscreen devices like tablets and smartphones\",\n" + + " \"rating\" : -1,\n" + + " \"path\" : \"/usr/share/kodi/addons/skin.estouchy\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"5.12.0\",\n" + + " \"addonid\" : \"xbmc.gui\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"script.module.requests\",\n" + + " \"name\" : \"requests\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.module.requests%2ficon.png/\",\n" + + " \"summary\" : \"Python HTTP for Humans\",\n" + + " \"version\" : \"2.12.4\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.module\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.14.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"kennethreitz, beenje\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"Packed for KODI from https://github.com/kennethreitz/requests\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/script.module.requests\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"0.9.98\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.lazytv%2ffanart.jpg/\",\n" + + " \"installed\" : true,\n" + + " \"type\" : \"xbmc.python.script\",\n" + + " \"broken\" : false,\n" + + " \"addonid\" : \"script.lazytv\",\n" + + " \"name\" : \"LazyTV\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.lazytv%2ficon.png/\",\n" + + " \"summary\" : \"LazyTV\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"value\" : \"en\",\n" + + " \"key\" : \"language\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"executable\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"You have a huge library of TV shows and you havent viewed half of it. So why does it feel like such a chore to sit down and watch something?\\nLazyTV is here to free you from your battles with indecision, instead letting you lean back and soak up content. With one click you can be channel-surfing your own library, or have what you probably want to watch pop up in a single window.\\nAfterall, you know you want to watch TV, so why do you also have to decide what specifically to watch?\\n\\nUnlike a smart playlist or skin widget, LazyTV doesnt just provide the first unwatched episode of a TV show. It provides the first unwatched episode AFTER the last watched one in your library. A small, but important, distinction.\\n\\nLazyTV offers two main functions:\\nThe first creates and launches a randomised playlist of the TV episodes. And not just any episodes, but the next episode it thinks you would want to watch. You also have the option to blend in your movies (both the watched and the unwatched) to complete the channel-surfing experience.\\nThe second main function populates a window with the next available episode for each of your TV Shows. One click and your viewing menu is there, immediately.\\n\\nCombine either of the main functions with a playlist of preselected shows to customise your experience even further.\\nSome TV shows, like cartoons or skit shows, can be viewed out of episodic order. So LazyTV gives you the ability to identify these shows and treat them differently. Those shows will be played in a random order.\\n\\nLazyTV also offers two minor functions that extend beyond the addon itself:\\nThe first is an option to be notified if you are about to watch an episode that has an unwatched episode before it. This function excludes the TV shows identified as able to be watched out of order.\\nThe second option posts a notification when you finish watching a TV episode telling you that the next show is available and asks if you want to view it now.\\n\\n\\nLazyTV contains a service that stores the next episodes' information and monitors your player to pre-empt database changes. This is my attempt to make the addon more responsive on my Raspberry Pi. The Pi still takes a while to \\\"warm-up\\\"; a full refresh of the episode data (which occurs at start-up and on a library update) takes about 30 seconds for my ~100 show library*. However, the show list window opens and the random player starts in less than 2 seconds.\\n\\n*The same update takes 2 seconds on my laptop with its i5 processor.\",\n" + + " \"rating\" : -1,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/script.lazytv\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"KodeKarnage\",\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"value\" : \"executable\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"The most powerful way to access content on Netflix and YouTube and Amazon Instant Video would be a web browser, if web browsers provided good native support for a 10-foot user interface. This add-on launches a browser and connects the arrow buttons on the remote control to the mouse pointer. This is the most user-friendly way to consume online content without needing a wireless keyboard.\",\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.program.remote.control.browser\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"4.3.2\",\n" + + " \"addonid\" : \"script.module.beautifulsoup4\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.1.7\",\n" + + " \"addonid\" : \"script.module.pil\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.24.0\",\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Chad Parry\",\n" + + " \"disclaimer\" : \"The experience will be degraded unless these external dependencies are installed: “psutil,” “pyalsaaudio,” “pylirc2,” and “Pillow.” Another helpful utility is “unclutter,” which automatically hides the mouse pointer. (On a Debian-based system, run “sudo apt-get install python-psutil python-alsaaudio python-pylirc python-pil unclutter”). Finally, a theme with a large mouse pointer will improve pointer visibility, (e.g., https://www.gnome-look.org/p/999574/).\",\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.5\",\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"name\" : \"Remote Control Browser\",\n" + + " \"addonid\" : \"plugin.program.remote.control.browser\",\n" + + " \"summary\" : \"Browse websites with a remote control\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.program.remote.control.browser%2ficon.png/\"\n" + + " },\n" + + " {\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"2.7.0\",\n" + + " \"type\" : \"xbmc.metadata.scraper.albums\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"name\" : \"Universal Album Scraper\",\n" + + " \"addonid\" : \"metadata.album.universal\",\n" + + " \"summary\" : \"Universal Scraper for Albums\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.album.universal%2ficon.png/\",\n" + + " \"description\" : \"This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information.\\n\\nThe initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.album.universal\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.allmusic.com\",\n" + + " \"version\" : \"3.1.0\"\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.fanart.tv\",\n" + + " \"version\" : \"3.1.0\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.musicbrainz.org\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.8.1\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.theaudiodb.com\"\n" + + " },\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Olympia, Team Kodi\",\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.0\",\n" + + " \"type\" : \"xbmc.audioencoder\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"name\" : \"AAC encoder\",\n" + + " \"addonid\" : \"audioencoder.xbmc.builtin.aac\",\n" + + " \"summary\" : \"AAC Audio Encoder\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2faudioencoder.xbmc.builtin.aac%2ficon.png/\",\n" + + " \"description\" : \"AAC Audio Encoder\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/audioencoder.xbmc.builtin.aac\",\n" + + " \"author\" : \"spiff\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"1.0.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.audioencoder\"\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.3.4\",\n" + + " \"summary\" : \"HTBackdrops Scraper Library\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.htbackdrops.com%2ficon.png/\",\n" + + " \"name\" : \"HTBackdrops Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.htbackdrops.com\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.htbackdrops.com\",\n" + + " \"description\" : \"Download backdrops from www.htbackdrops.com\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\"\n" + + " },\n" + + " {\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fresource.language.en_gb%2ficon.png/\",\n" + + " \"summary\" : \"English language pack\",\n" + + " \"name\" : \"English\",\n" + + " \"addonid\" : \"resource.language.en_gb\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"kodi.resource.language\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"2.0.1\",\n" + + " \"disclaimer\" : \"English is the default language for Kodi, removing it may cause issues\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"kodi.resource\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"1.0.0\"\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/usr/share/kodi/addons/resource.language.en_gb\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"English version of all texts used in Kodi.\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1\n" + + " },\n" + + " {\n" + + " \"summary\" : \"AllMusic Music Scraper Library\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.allmusic.com%2ficon.png/\",\n" + + " \"name\" : \"AllMusic Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.allmusic.com\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"3.1.1\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.allmusic.com\",\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Download Music information from www.allmusic.com\",\n" + + " \"extrainfo\" : [],\n" + + " \"enabled\" : true\n" + + " },\n" + + " {\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"TheTVDB.com is a TV Scraper. The site is a massive open database that can be modified by anybody and contains full meta data for many shows in different languages. All content and images on the site have been contributed by their users for users and have a high standard or quality. The database schema and website are open source under the GPL.\",\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.tvdb.com\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.7.8\",\n" + + " \"addonid\" : \"metadata.common.imdb.com\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\"\n" + + " }\n" + + " ],\n" + + " \"disclaimer\" : \"\",\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.8.4\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.tvshows\",\n" + + " \"installed\" : true,\n" + + " \"name\" : \"The TVDB\",\n" + + " \"addonid\" : \"metadata.tvdb.com\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.tvdb.com%2ficon.png/\",\n" + + " \"summary\" : \"Fetch TV show metadata from TheTVDB.com\"\n" + + " },\n" + + " {\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"2.1.1\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"installed\" : true,\n" + + " \"name\" : \"MusicBrainz Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.musicbrainz.org\",\n" + + " \"summary\" : \"MusicBrainz Music Scraper Library\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.musicbrainz.org%2ficon.png/\",\n" + + " \"description\" : \"Download Music information from www.musicbrainz.org\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.musicbrainz.org\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.albums\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.0\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fmetadata.local%2ficon.png/\",\n" + + " \"summary\" : \"Local Infomation only pseudo-scraper\",\n" + + " \"name\" : \"Local information only\",\n" + + " \"addonid\" : \"metadata.local\",\n" + + " \"path\" : \"/usr/share/kodi/addons/metadata.local\",\n" + + " \"enabled\" : true,\n" + + " \"description\" : \"Use local information only\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.metadata\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"summary\" : \"TMDb Scraper Library\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.themoviedb.org%2ficon.png/\",\n" + + " \"name\" : \"The Movie Database Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.themoviedb.org\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"2.14.0\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.themoviedb.org\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Download thumbs and fanarts from www.themoviedb.org\",\n" + + " \"enabled\" : true\n" + + " },\n" + + " {\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"kodi.resource\",\n" + + " \"version\" : \"1.0.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"disclaimer\" : \"\",\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"Kodi GUI sounds\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/usr/share/kodi/addons/resource.uisounds.kodi\",\n" + + " \"name\" : \"Kodi UI Sounds\",\n" + + " \"addonid\" : \"resource.uisounds.kodi\",\n" + + " \"summary\" : \"Kodi GUI sounds\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fresource.uisounds.kodi%2ficon.png/\",\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.0.0\",\n" + + " \"type\" : \"kodi.resource.uisounds\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true\n" + + " },\n" + + " {\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.theaudiodb.com\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"Download Music information from www.theaudiodb.com\",\n" + + " \"rating\" : -1,\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"broken\" : false,\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"\",\n" + + " \"version\" : \"1.9.0\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.theaudiodb.com%2ficon.png/\",\n" + + " \"summary\" : \"TheAudioDb Music Scraper Library\",\n" + + " \"name\" : \"TheAudioDb Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.theaudiodb.com\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"3.7.2\",\n" + + " \"fanart\" : \"\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.artists\",\n" + + " \"addonid\" : \"metadata.artists.universal\",\n" + + " \"name\" : \"Universal Artist Scraper\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.artists.universal%2ficon.png/\",\n" + + " \"summary\" : \"Universal Scraper for Artists\",\n" + + " \"enabled\" : true,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"This scraper collects information from the following supported sites: TheAudioDb.com, MusicBrainz, last.fm, and allmusic.com, while grabs artwork from: fanart.tv, htbackdrops.com, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information.\\n\\nThe initial search is always done on MusicBrainz. In case allmusic link is not added on the MusicBrainz site fields from allmusic.com cannot be fetched (very easy to add those missing links though).\",\n" + + " \"rating\" : -1,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.artists.universal\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"3.1.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.allmusic.com\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"3.1.0\",\n" + + " \"addonid\" : \"metadata.common.fanart.tv\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.3.2\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"metadata.common.htbackdrops.com\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"metadata.common.musicbrainz.org\",\n" + + " \"optional\" : false\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"metadata.common.theaudiodb.com\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"1.8.1\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Olympia, Team Kodi\",\n" + + " \"disclaimer\" : \"\"\n" + + " },\n" + + " {\n" + + " \"path\" : \"/home/martijn/.kodi/addons/metadata.common.imdb.com\",\n" + + " \"enabled\" : true,\n" + + " \"rating\" : -1,\n" + + " \"description\" : \"Download Movie information from www.imdb.com\",\n" + + " \"extrainfo\" : [],\n" + + " \"disclaimer\" : \"\",\n" + + " \"author\" : \"Team Kodi\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"2.1.0\",\n" + + " \"addonid\" : \"xbmc.metadata\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.metadata.scraper.library\",\n" + + " \"version\" : \"2.8.7\",\n" + + " \"fanart\" : \"\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fmetadata.common.imdb.com%2ficon.png/\",\n" + + " \"summary\" : \"IMDB Scraper Library\",\n" + + " \"addonid\" : \"metadata.common.imdb.com\",\n" + + " \"name\" : \"IMDB Scraper Library\"\n" + + " },\n" + + " {\n" + + " \"disclaimer\" : \"\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"version\" : \"2.1.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"Jonathan Beluch (jbel)\",\n" + + " \"path\" : \"/home/martijn/.kodi/addons/script.module.xbmcswift2\",\n" + + " \"description\" : \"\",\n" + + " \"extrainfo\" : [],\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"summary\" : \"\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fscript.module.xbmcswift2%2ficon.png/\",\n" + + " \"addonid\" : \"script.module.xbmcswift2\",\n" + + " \"name\" : \"xbmcswift2\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.module\",\n" + + " \"version\" : \"2.4.0\",\n" + + " \"fanart\" : \"\"\n" + + " },\n" + + " {\n" + + " \"path\" : \"/usr/share/kodi/addons/skin.estuary\",\n" + + " \"enabled\" : true,\n" + + " \"rating\" : -1,\n" + + " \"extrainfo\" : [],\n" + + " \"description\" : \"Estuary is the default skin for Kodi 17.0 and above. It attempts to be easy for first time Kodi users to understand and use.\",\n" + + " \"disclaimer\" : \"Estuary is the default skin for Kodi, removing it may cause issues\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"5.12.0\",\n" + + " \"addonid\" : \"xbmc.gui\",\n" + + " \"optional\" : false\n" + + " }\n" + + " ],\n" + + " \"author\" : \"phil65, Ichabod Fletchman\",\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.gui.skin\",\n" + + " \"installed\" : true,\n" + + " \"fanart\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fskin.estuary%2fresources%2ffanart.jpg/\",\n" + + " \"version\" : \"1.9.12\",\n" + + " \"thumbnail\" : \"image://%2fusr%2fshare%2fkodi%2faddons%2fskin.estuary%2fresources%2ficon.png/\",\n" + + " \"summary\" : \"Estuary skin by phil65. (Kodi's default skin)\",\n" + + " \"name\" : \"Estuary\",\n" + + " \"addonid\" : \"skin.estuary\"\n" + + " },\n" + + " {\n" + + " \"version\" : \"1.4.5\",\n" + + " \"fanart\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.twitch%2ffanart.jpg/\",\n" + + " \"installed\" : true,\n" + + " \"broken\" : false,\n" + + " \"type\" : \"xbmc.python.pluginsource\",\n" + + " \"addonid\" : \"plugin.video.twitch\",\n" + + " \"name\" : \"Twitch\",\n" + + " \"summary\" : \"Twitch video plugin\",\n" + + " \"thumbnail\" : \"image://%2fhome%2fmartijn%2f.kodi%2faddons%2fplugin.video.twitch%2ficon.png/\",\n" + + " \"extrainfo\" : [\n" + + " {\n" + + " \"key\" : \"language\",\n" + + " \"value\" : \"en\"\n" + + " },\n" + + " {\n" + + " \"value\" : \"video\",\n" + + " \"key\" : \"provides\"\n" + + " }\n" + + " ],\n" + + " \"description\" : \"Watch your favorite gaming streams!\",\n" + + " \"rating\" : -1,\n" + + " \"enabled\" : true,\n" + + " \"path\" : \"/home/martijn/.kodi/addons/plugin.video.twitch\",\n" + + " \"dependencies\" : [\n" + + " {\n" + + " \"version\" : \"1.2.0\",\n" + + " \"optional\" : false,\n" + + " \"addonid\" : \"script.module.xbmcswift2\"\n" + + " },\n" + + " {\n" + + " \"addonid\" : \"xbmc.python\",\n" + + " \"optional\" : false,\n" + + " \"version\" : \"2.20.0\"\n" + + " }\n" + + " ],\n" + + " \"author\" : \"A Talented Community\",\n" + + " \"disclaimer\" : \"\"\n" + + " }\n" + + " ],\n" + + " \"limits\" : {\n" + + " \"start\" : 0,\n" + + " \"end\" : 41,\n" + + " \"total\" : 41\n" + + " }\n" + + " }\n" + + "}\n"; } diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/addon/AddonListContainerFragment.java b/app/src/main/java/org/xbmc/kore/ui/sections/addon/AddonListContainerFragment.java index 2f351b1..9bab888 100644 --- a/app/src/main/java/org/xbmc/kore/ui/sections/addon/AddonListContainerFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/sections/addon/AddonListContainerFragment.java @@ -46,6 +46,15 @@ public class AddonListContainerFragment extends AbstractTabsFragment { if (arguments == null) arguments = new Bundle(); + /** + * Following check required to support testing AddonsActivity. + * The database with host info is setup after initializing AddonsActivity using + * ActivityTestRule from the test support library. This means getHostInfo() will + * return null as long as the database info has not been set. + */ + if (HostManager.getInstance(getContext()).getHostInfo() == null) + return null; + int hostId = HostManager.getInstance(getContext()).getHostInfo().getId(); TabsAdapter tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager());