Implemented unit/integration tests for the now playing panel (#420)

* Fixed issue with opening drawer and Espresso not waiting for
    when drawer was actually opened.
  * Implemented new handlers for MockTcpServer to test
    media control functions.
  * Decreased delay for sending reponses in MockTcpServer from
    1 sec to 100 ms. This was needed to prevent race conditions with
    the progress bar that increases automatically once per second.
This commit is contained in:
Martijn Brekhof 2017-07-17 20:07:11 +02:00 committed by Synced Synapse
parent 02a1d445f3
commit dbd08c07aa
21 changed files with 2160 additions and 20 deletions

View File

@ -27,8 +27,11 @@ import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.hamcrest.Matcher;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.action.ViewActions;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
@ -43,6 +46,7 @@ import static android.support.test.espresso.assertion.ViewAssertions.doesNotExis
import static android.support.test.espresso.assertion.ViewAssertions.matches;
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.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
@ -60,8 +64,8 @@ public class EspressoTestUtils {
= activity.getResources().getConfiguration().orientation;
activity.setRequestedOrientation(
(orientation == Configuration.ORIENTATION_PORTRAIT) ?
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
/**
@ -141,11 +145,11 @@ public class EspressoTestUtils {
/**
* Performs a click on an item in an adapter view, such as GridView or ListView
* @param position
* @param resourceId
* @param resourceId of adapter view holding the item that should be clicked
*/
public static void clickAdapterViewItem(int position, int resourceId) {
onData(anything()).inAdapterView(allOf(withId(resourceId), isDisplayed()))
.atPosition(position).perform(click());
.atPosition(position).perform(click());
}
/**
@ -153,7 +157,7 @@ public class EspressoTestUtils {
* @param query text that SearchView should contain
*/
public static void checkTextInSearchQuery(String query) {
onView(isAssignableFrom(AutoCompleteTextView.class)).check(matches(withText(query)));
onView(isAssignableFrom(AutoCompleteTextView.class)).check(matches(withText(query)));
}
/**
@ -280,4 +284,18 @@ public class EspressoTestUtils {
onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.default_toolbar))))
.check(matches(withText(actionbarTitle)));
}
/**
* Waits for 10 seconds till panel has given state.
*
* @param panelState desired state of panel
*/
public static void waitForPanelState(final SlidingUpPanelLayout.PanelState panelState) {
onView(isRoot()).perform(ViewActions.waitForView(R.id.now_playing_panel, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((SlidingUpPanelLayout) v).getPanelState() == panelState;
}
}, 10000));
}
}

View File

@ -24,11 +24,16 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.SeekBar;
import android.widget.TextView;
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.utils.UIUtils;
public class Matchers {
public static MenuItemTitleMatcher withMenuTitle(String title) {
@ -128,4 +133,88 @@ public class Matchers {
}
};
}
public static Matcher<View> withProgress(final int progress) {
return new BoundedMatcher<View, SeekBar>(SeekBar.class) {
@Override
protected boolean matchesSafely(SeekBar item) {
return item.getProgress() == progress;
}
@Override
public void describeTo(Description description) {
description.appendText("expected: " + progress);
}
};
}
public static Matcher<View> withProgress(final String progress) {
return new BoundedMatcher<View, TextView>(TextView.class) {
@Override
protected boolean matchesSafely(TextView item) {
return progress.contentEquals(item.getText());
}
@Override
public void describeTo(Description description) {
description.appendText("expected: " + progress);
}
};
}
public static Matcher<View> withProgressGreaterThanOrEqual(final String time) {
return new BoundedMatcher<View, SeekBar>(SeekBar.class) {
@Override
protected boolean matchesSafely(SeekBar item) {
return item.getProgress() >= UIUtils.timeToSeconds(time);
}
@Override
public void describeTo(Description description) {
description.appendText("expected progress greater than " + time);
}
};
}
public static Matcher<View> withProgressGreaterThan(final int progress) {
return new BoundedMatcher<View, SeekBar>(SeekBar.class) {
@Override
protected boolean matchesSafely(SeekBar item) {
return item.getProgress() > progress;
}
@Override
public void describeTo(Description description) {
description.appendText("expected progress greater than " + progress);
}
};
}
public static Matcher<View> withHighlightState(final boolean highlight) {
return new BoundedMatcher<View, HighlightButton>(HighlightButton.class) {
@Override
protected boolean matchesSafely(HighlightButton item) {
return item.isHighlighted();
}
@Override
public void describeTo(Description description) {
description.appendText("expected: " + highlight);
}
};
}
public static Matcher<View> withRepeatMode(final RepeatModeButton.MODE mode) {
return new BoundedMatcher<View, RepeatModeButton>(RepeatModeButton.class) {
@Override
protected boolean matchesSafely(RepeatModeButton item) {
return item.getMode() == mode;
}
@Override
public void describeTo(Description description) {
description.appendText("expected: " + mode.name());
}
};
}
}

View File

@ -19,11 +19,14 @@ package org.xbmc.kore.testhelpers;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.support.annotation.IntDef;
import android.support.test.rule.ActivityTestRule;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import org.junit.internal.runners.statements.RunAfters;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
@ -63,6 +66,11 @@ public class Utils {
drawerLayout.openDrawer(Gravity.LEFT);
}
});
DrawerLayout drawerLayout = (DrawerLayout) activityTestRule.getActivity().findViewById(R.id.drawer_layout);
while(true) {
if (drawerLayout.isDrawerOpen(Gravity.LEFT))
return;
}
}
public static void initialize(ActivityTestRule<?> activityTestRule, HostInfo info) throws Throwable {

View File

@ -17,10 +17,27 @@
package org.xbmc.kore.testhelpers.action;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.MotionEvents;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.util.HumanReadables;
import android.support.test.espresso.util.TreeIterables;
import android.view.View;
import android.widget.SeekBar;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.util.concurrent.TimeoutException;
import static android.support.test.espresso.action.ViewActions.actionWithAssertions;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
public final class ViewActions {
/**
* Returns an action that clears the focus on the view.
* <br/>
@ -33,4 +50,97 @@ public final class ViewActions {
return actionWithAssertions(new ClearFocus());
}
public interface CheckStatus {
boolean check(View v);
}
/**
* ViewAction that waits until view with viewId becomes visible
* @param viewId Resource identifier of view item that must be checked
* @param checkStatus called when viewId has been found to check its status. If return value
* is true waitForView will stop, false it will continue until timeout is exceeded
* @param millis amount of time to wait for view to become visible
* @return
*/
public static ViewAction waitForView(final int viewId, final CheckStatus checkStatus, final long millis) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "Searches for view with id: " + viewId + " and tests its status using CheckStatus, using timeout " + millis + " ms.";
}
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle();
final long endTime = System.currentTimeMillis() + millis;
do {
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
if (child.getId() == viewId) {
if (checkStatus.check(child)) {
return;
}
}
}
uiController.loopMainThreadForAtLeast(50);
} while (System.currentTimeMillis() < endTime);
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new TimeoutException())
.build();
}
};
}
public static ViewAction slideSeekBar(final int progress) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return new TypeSafeMatcher<View>() {
@Override
protected boolean matchesSafely(View item) {
return item instanceof SeekBar;
}
@Override
public void describeTo(Description description) {
description.appendText("is a SeekBar.");
}
};
}
@Override
public String getDescription() {
return "Slides seekbar to progress position " + progress;
}
@Override
public void perform(UiController uiController, View view) {
SeekBar seekBar = (SeekBar) view;
int[] seekBarPos = {0,0};
view.getLocationOnScreen(seekBarPos);
float[] startPos = {seekBarPos[0], seekBarPos[1]};
MotionEvents.DownResultHolder downResultHolder =
MotionEvents.sendDown(uiController, startPos,
Press.PINPOINT.describePrecision());
while(seekBar.getProgress() < progress) {
startPos[0]++;
MotionEvents.sendMovement(uiController, downResultHolder.down, startPos);
uiController.loopMainThreadForAtLeast(10);
}
MotionEvents.sendUp(uiController, downResultHolder.down, startPos);
}
};
}
}

View File

@ -17,10 +17,12 @@
package org.xbmc.kore.tests.ui;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import org.junit.After;
import org.junit.AfterClass;
@ -35,7 +37,10 @@ import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.testutils.Database;
import org.xbmc.kore.testutils.tcpserver.MockTcpServer;
import org.xbmc.kore.testutils.tcpserver.handlers.AddonsHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.ApplicationHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.JSONConnectionHandlerManager;
import org.xbmc.kore.testutils.tcpserver.handlers.JSONRPCHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
import java.io.IOException;
@ -50,10 +55,17 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
private static MockTcpServer server;
private static JSONConnectionHandlerManager manager;
private AddonsHandler addonsHandler;
private static PlayerHandler playerHandler;
private static ApplicationHandler applicationHandler;
@BeforeClass
public static void setupMockTCPServer() throws Throwable {
playerHandler = new PlayerHandler();
applicationHandler = new ApplicationHandler();
manager = new JSONConnectionHandlerManager();
manager.addHandler(playerHandler);
manager.addHandler(applicationHandler);
manager.addHandler(new JSONRPCHandler());
server = new MockTcpServer(manager);
server.start();
}
@ -79,6 +91,12 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
loaderIdlingResource = new LoaderIdlingResource(activityTestRule.getActivity().getSupportLoaderManager());
Espresso.registerIdlingResources(loaderIdlingResource);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activityTestRule.getActivity());
SharedPreferences.Editor editor = prefs.edit();
editor.clear();
editor.commit();
//Relaunch the activity for the changes (Host selections, preference reset) to take effect
activityTestRule.launchActivity(new Intent());
Utils.closeDrawer(activityTestRule);
@ -89,6 +107,9 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
if ( loaderIdlingResource != null )
Espresso.unregisterIdlingResources(loaderIdlingResource);
applicationHandler.reset();
playerHandler.reset();
Utils.cleanup();
}
@ -103,4 +124,12 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
}
return null;
}
public static PlayerHandler getPlayerHandler() {
return playerHandler;
}
public static ApplicationHandler getApplicationHandler() {
return applicationHandler;
}
}

View File

@ -17,17 +17,18 @@
package org.xbmc.kore.tests.ui;
import android.support.test.espresso.Espresso;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.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
@ -53,7 +54,7 @@ abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extend
*/
@Test
public void showArrowWhenSelectingListItem() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
clickAdapterViewItem(0, R.id.list);
assertTrue(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
@ -68,7 +69,7 @@ abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extend
*/
@Test
public void showHamburgerWhenSelectingListItemAndReturn() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
clickAdapterViewItem(0, R.id.list);
Espresso.pressBack();
assertFalse(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
@ -85,8 +86,8 @@ abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extend
*/
@Test
public void restoreArrowOnConfigurationChange() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
EspressoTestUtils.rotateDevice(getActivity());
clickAdapterViewItem(0, R.id.list);
rotateDevice(getActivity());
assertTrue(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());
}
@ -103,8 +104,8 @@ abstract public class BaseMediaActivityTests<T extends BaseMediaActivity> extend
*/
@Test
public void restoreHamburgerOnConfigurationChangeOnReturn() {
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
EspressoTestUtils.rotateDevice(getActivity());
clickAdapterViewItem(0, R.id.list);
rotateDevice(getActivity());
Espresso.pressBack();
assertFalse(((T) EspressoTestUtils.getActivity()).getDrawerIndicatorIsArrow());

View File

@ -0,0 +1,629 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.music;
import android.content.SharedPreferences;
import android.os.SystemClock;
import android.support.test.rule.ActivityTestRule;
import android.support.v7.preference.PreferenceManager;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.testhelpers.Utils;
import org.xbmc.kore.testhelpers.action.ViewActions;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
import org.xbmc.kore.ui.sections.audio.MusicActivity;
import org.xbmc.kore.ui.widgets.HighlightButton;
import org.xbmc.kore.ui.widgets.RepeatModeButton;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
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.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.not;
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;
import static org.xbmc.kore.testhelpers.EspressoTestUtils.waitForPanelState;
import static org.xbmc.kore.testhelpers.Matchers.withHighlightState;
import static org.xbmc.kore.testhelpers.Matchers.withProgress;
public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
@Rule
public ActivityTestRule<MusicActivity> musicActivityActivityTestRule =
new ActivityTestRule<>(MusicActivity.class);
@Override
protected ActivityTestRule<MusicActivity> getActivityTestRule() {
return musicActivityActivityTestRule;
}
@Override
public void setUp() throws Throwable {
super.setUp();
getPlayerHandler().reset();
getPlayerHandler().startPlay();
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
/**
* Test if panel title is correctly set
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Result: panel title should show current playing media item
*/
@Test
public void panelTitleTest() {
Player.GetItem item = getPlayerHandler().getMediaItem();
onView(withId(R.id.npp_title)).check(matches(withText(item.getTitle())));
}
/**
* Test if panel buttons are correctly set for music items
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Result: panel should show next, play, and previous buttons
*/
@Test
public void panelButtonsMusicTest() {
onView(withId(R.id.npp_next)).check(matches(isDisplayed()));
onView(withId(R.id.npp_previous)).check(matches(isDisplayed()));
onView(withId(R.id.npp_play)).check(matches(isDisplayed()));
}
/**
* Test if panel buttons are correctly set for movie items
*
* UI interaction flow tested:
* 1. Start playing a movie item
* 2. Result: panel should show play button
*/
@Test
public void panelButtonsMoviesTest() {
getPlayerHandler().setMediaType(PlayerHandler.TYPE.MOVIE);
getPlayerHandler().startPlay();
Player.GetItem item = getPlayerHandler().getMediaItem();
final String title = item.getTitle();
onView(isRoot()).perform(ViewActions.waitForView(
R.id.npp_title, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return title.contentEquals(((TextView) v).getText());
}
}, 10000));
onView(withId(R.id.npp_next)).check(matches(not(isDisplayed())));
onView(withId(R.id.npp_previous)).check(matches(not(isDisplayed())));
onView(withId(R.id.npp_play)).check(matches(isDisplayed()));
}
/**
* Test if panel buttons are correctly set for music video items
*
* UI interaction flow tested:
* 1. Start playing a music video item
* 2. Result: panel should show next, play, and previous buttons
*/
@Test
public void panelButtonsMusicVideoTest() {
getPlayerHandler().setMediaType(PlayerHandler.TYPE.MUSICVIDEO);
getPlayerHandler().startPlay();
Player.GetItem item = getPlayerHandler().getMediaItem();
final String title = item.getTitle();
onView(isRoot()).perform(ViewActions.waitForView(
R.id.npp_title, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return title.contentEquals(((TextView) v).getText());
}
}, 10000));
onView(withId(R.id.npp_next)).check(matches(isDisplayed()));
onView(withId(R.id.npp_previous)).check(matches(isDisplayed()));
onView(withId(R.id.npp_play)).check(matches(isDisplayed()));
}
/**
* Test if shuffle button state is correctly set
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Click on shuffle button
* 4. Result: shuffle button should be highlighted
*/
@Test
public void panelButtonsShuffleTest() {
expandPanel();
onView(withId(R.id.npp_shuffle)).perform(click());
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_shuffle, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((HighlightButton) v).isHighlighted();
}
}, 10000));
}
/**
* Test if repeat button state is correctly set
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Click on repeat button
* 4. Result: repeat button should be highlighted and show single item repeat mode
* 5. Click on repeat button
* 6. Result: repeat button should be highlighted and show repeat playlist mode
* 7. Click on repeat button
* 8. Result: repeat button should not be highlighted
*/
@Test
public void panelButtonsRepeatModes() {
expandPanel();
//Initial state should be OFF
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_repeat, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((RepeatModeButton) v).getMode() == RepeatModeButton.MODE.OFF;
}
}, 10000));
// Test if repeat mode is set to ONE after first click
onView(withId(R.id.npp_repeat)).perform(click());
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_repeat, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((RepeatModeButton) v).getMode() == RepeatModeButton.MODE.ONE;
}
}, 10000));
// Test if repeat mode is set to ALL after second click
onView(withId(R.id.npp_repeat)).perform(click());
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_repeat, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((RepeatModeButton) v).getMode() == RepeatModeButton.MODE.ALL;
}
}, 10000));
// Test if repeat mode is set to OFF after third click
onView(withId(R.id.npp_repeat)).perform(click());
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_repeat, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((RepeatModeButton) v).getMode() == RepeatModeButton.MODE.OFF;
}
}, 10000));
}
/**
* Test if panel collapsed state is restored on configuration changes
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Rotate device
* 3. Result: panel state should be collapsed
*/
@Test
public void keepCollapsedOnRotate() {
rotateDevice(getActivity());
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
/**
* Test if panel expanded state is restored on configuration changes
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Rotate device
* 4. Result: panel state should be expanded
*/
@Test
public void keepExpandedOnRotate() {
expandPanel();
rotateDevice(getActivity());
waitForPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
}
/**
* Test if repeat button state is restored on configuration changes
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Click on repeat button
* 4. Rotate device
* 5. Result: repeat button state should be restored to state in step 2
*/
@Test
public void restoreRepeatButtonStateOnRotate() {
expandPanel();
onView(withId(R.id.npp_repeat)).perform(click());
rotateDevice(getActivity());
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_repeat, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((RepeatModeButton) v).getMode() == RepeatModeButton.MODE.ONE;
}
}, 10000));
}
/**
* Test if shuffle button state is correctly set
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Click on shuffle button
* 4. Result: shuffle button state should be set to shuffle
*/
@Test
public void setShuffleButtonState() {
expandPanel();
onView(withId(R.id.npp_shuffle)).perform(click()); //Set state to shuffled
onView(withId(R.id.npp_shuffle)).check(matches(withHighlightState(true)));
}
/**
* Test if shuffle button state is restored on configuration changes
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Click on shuffle button
* 4. Rotate device
* 5. Result: shuffle button state should be restored to state in step 2
*/
@Test
public void restoreShuffleButtonStateOnRotate() {
expandPanel();
onView(withId(R.id.npp_shuffle)).perform(click()); //Set state to shuffled
rotateDevice(getActivityTestRule().getActivity());
//Using waitForView as we need to wait for the rotate to finish
onView(isRoot()).perform(ViewActions.waitForView(R.id.npp_shuffle, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((HighlightButton) v).isHighlighted();
}
}, 10000));
}
/**
* Test if volume is correctly set at start
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Set volume at server
* 3. Expand panel
* 4. Result: Volume indicator should show the same volume level as set at the server
*/
@Test
public void setVolume() {
final int volume = 16;
getApplicationHandler().setVolume(volume, true);
assertTrue(getApplicationHandler().getVolume() == volume);
expandPanel();
onView(withId(R.id.vli_seek_bar)).check(matches(withProgress(volume)));
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
}
/**
* Test if changing volume through the volume slider, updates the volume indicator correctly
* and sends the volume change to the server
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Set volume using slider
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
*/
@Test
public void changeVolume() {
final int volume = 16;
expandPanel();
onView(withId(R.id.vli_seek_bar)).perform(ViewActions.slideSeekBar(volume));
onView(withId(R.id.vli_seek_bar)).check(matches(withProgress(volume)));
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
assertTrue(getApplicationHandler().getVolume() == volume);
}
/**
* Test if changing volume through the volume slider, updates the volume indicator correctly
* and sends the volume change to the server
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Set volume using slider
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
*/
@Test
public void restoreVolumeIndicatorOnRotate() {
final int volume = 16;
expandPanel();
onView(withId(R.id.vli_seek_bar)).perform(ViewActions.slideSeekBar(volume));
rotateDevice(getActivity());
onView(withId(R.id.vli_seek_bar)).check(matches(withProgress(volume)));
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
assertTrue(getApplicationHandler().getVolume() == volume);
}
/**
* Test if setting progression correctly updates the media progress indicator
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Pause playback
* 3. Expand panel
* 4. Set progression
* 5. Result: Media progression indicator should be correctly updated and progression change
* should be sent to the server.
*/
@Test
public void setProgression() {
final int progress = 16;
final String progressText = "0:16";
expandPanel();
onView(withId(R.id.npp_play)).perform(click()); //Pause playback
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
onView(withId(R.id.mpi_progress)).check(matches(withText(progressText)));
assertTrue(getPlayerHandler().getPosition() == progress);
}
/**
* Test if progression is correctly restored after device configuration change
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Pause playback
* 3. Expand panel
* 4. Set progression
* 5. Rotate device
* 6. Result: Progression should be correctly same as before rotating the device.
*/
@Test
public void restoreProgressOnRotate() {
final int progress = 16;
final String progressText = "0:16";
expandPanel();
onView(withId(R.id.npp_play)).perform(click()); //Pause playback
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
rotateDevice(getActivity());
assertTrue(getPlayerHandler().getPosition() == progress);
onView(withId(R.id.mpi_progress)).check(matches(withProgress(progressText)));
onView(withId(R.id.mpi_seek_bar)).check(matches(withProgress(progress)));
}
/**
* Kodi resumes playback when progression changes.
* Test if changing progression when player is paused caused
* progression to start updating again
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Pause playback
* 4. Set progression
* 5. Start playback at server (that's what Kodi does)
* 6. Result: Playback should start at paused position
*/
@Test
public void pauseSetProgressionPlay() {
expandPanel();
onView(withId(R.id.npp_play)).perform(click()); //Pause playback
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(16));
getPlayerHandler().startPlay();
SeekBar seekBar = (SeekBar) getActivity().findViewById(R.id.mpi_seek_bar);
int progress = seekBar.getProgress();
SystemClock.sleep(1000); //wait one second to check if progression has indeed progressed
assertTrue(seekBar.getProgress() > progress);
}
/**
* Test if panel's progressionbar progresses when playing media
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Result: Progression should be progressing
*/
@Test
public void progressionUpdaterStartedAfterPlay() {
expandPanel();
SeekBar seekBar = (SeekBar) getActivity().findViewById(R.id.mpi_seek_bar);
int progress = seekBar.getProgress();
SystemClock.sleep(1000); //wait one second to check if progression has indeed progressed
assertTrue(seekBar.getProgress() > progress);
}
/**
* Test if panel's progression is maintained when starting a new activity
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Expand panel
* 3. Set progression
* 4. Switch to movies (new activity)
* 5. Result: Progression should continue from step 3
*/
@Test
public void continueProgressionAfterSwitchingActivity() throws Throwable {
final int progress = 24;
expandPanel();
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
Utils.openDrawer(getActivityTestRule());
clickAdapterViewItem(2, R.id.navigation_drawer); //select movie activity
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
expandPanel();
onView(isRoot()).perform(ViewActions.waitForView(R.id.mpi_seek_bar, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
int seekBarProgress = ((SeekBar) v).getProgress();
return (seekBarProgress > progress) && (seekBarProgress < (progress + 4));
}
}, 10000));
}
/**
* Test if pause button pauses playback
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Pause playback
* 3. Result: Server should stop playing and progressbar should pause
*/
@Test
public void pausePlayback() {
onView(withId(R.id.npp_play)).perform(click());
assertFalse(getPlayerHandler().isPlaying());
expandPanel();
final int progress = ((SeekBar) getActivity().findViewById(R.id.mpi_seek_bar)).getProgress();
SystemClock.sleep(1000); //wait one second to check if progression has indeed paused
onView(isRoot()).perform(ViewActions.waitForView(R.id.mpi_seek_bar, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
int seekBarProgress = ((SeekBar) v).getProgress();
return seekBarProgress == progress;
}
}, 10000));
}
/**
* Test if panel is not displayed when user disables the panel
* through the preference screen
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Disable showing panel in settings
* 3. Result: Panel should not show
*/
@Test
public void disableShowingPanelInPreferences() throws Throwable {
Utils.openDrawer(getActivityTestRule());
clickAdapterViewItem(10, R.id.navigation_drawer); //Show preference screen
SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(getActivity()).edit();
edit.putBoolean(Settings.KEY_PREF_SHOW_NOW_PLAYING_PANEL, false);
edit.apply();
pressBack();
waitForPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
/**
* Test if panel is displayed when user enables the panel
* through the preference screen
*
* UI interaction flow tested:
* 1. Start playing a music item
* 2. Disable showing panel in settings
* 3. Show Music screen
* 4. Enable showing panel in settings
* 4. Return to Music screen
* 5. Result: Panel should show
*/
@Test
public void showPanelWhenUserEnablesPanel() throws Throwable {
Utils.openDrawer(getActivityTestRule());
clickAdapterViewItem(10, R.id.navigation_drawer); //Show preference screen
SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(getActivity()).edit();
edit.putBoolean(Settings.KEY_PREF_SHOW_NOW_PLAYING_PANEL, false);
edit.apply();
pressBack();
Utils.openDrawer(getActivityTestRule());
clickAdapterViewItem(10, R.id.navigation_drawer); //Show preference screen
edit.putBoolean(Settings.KEY_PREF_SHOW_NOW_PLAYING_PANEL, true);
edit.apply();
pressBack();
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
private void expandPanel() {
int tries = 10;
while (tries-- > 0) {
try {
onView(withId(R.id.npp_title)).perform(click());
onView(isRoot()).perform(ViewActions.waitForView(R.id.now_playing_panel, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((SlidingUpPanelLayout) v).getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED;
}
}, 1000));
return;
} catch (Exception e) {
//Either the click event did not work or the panel did not expand.
//Let's try again.
}
}
}
}

View File

@ -203,7 +203,7 @@ public class MockTcpServer {
try {
while (true) {
sendResponse();
Thread.sleep(1000);
Thread.sleep(100);
if ( serverSocket.isClosed() )
return;
}

View File

@ -43,7 +43,7 @@ public class AddonsHandler implements JSONConnectionHandlerManager.ConnectionHan
}
@Override
public ArrayList<JsonResponse> getNotification() {
public ArrayList<JsonResponse> getNotifications() {
return null;
}

View File

@ -66,8 +66,12 @@ public class ApplicationHandler implements JSONConnectionHandlerManager.Connecti
jsonNotifications.add(new OnVolumeChanged(muted, volume));
}
public int getVolume() {
return volume;
}
@Override
public ArrayList<JsonResponse> getNotification() {
public ArrayList<JsonResponse> getNotifications() {
ArrayList<JsonResponse> jsonResponses = new ArrayList<>(jsonNotifications);
jsonNotifications.clear();
return jsonResponses;

View File

@ -67,7 +67,7 @@ public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConn
* Used to get any notifications from the handler.
* @return {@link JsonResponse} that should be sent to the client
*/
ArrayList<JsonResponse> getNotification();
ArrayList<JsonResponse> getNotifications();
/**
* Should set the state of the handler to its initial state
@ -132,7 +132,7 @@ public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConn
//Handle notifications
for(ConnectionHandler handler : handlers) {
ArrayList<JsonResponse> jsonNotifications = handler.getNotification();
ArrayList<JsonResponse> jsonNotifications = handler.getNotifications();
if (jsonNotifications != null) {
for (JsonResponse jsonResponse : jsonNotifications) {
stringBuffer.append(jsonResponse.toJsonString() +"\n");
@ -140,7 +140,11 @@ public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConn
}
}
responseCount++;
return stringBuffer.toString();
if (stringBuffer.length() > 0) {
return stringBuffer.toString();
} else {
return null;
}
}
/**

View File

@ -0,0 +1,53 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.method.JSONRPC;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.JSONRPC.Ping;
import java.util.ArrayList;
/**
* Simulates JSON RPC JSON-RPC API
*/
public class JSONRPCHandler implements JSONConnectionHandlerManager.ConnectionHandler {
@Override
public String[] getType() {
return new String[] {JSONRPC.Ping.METHOD_NAME};
}
@Override
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
jsonResponses.add(new Ping(jsonRequest.get("id").asInt()));
return jsonResponses;
}
@Override
public ArrayList<JsonResponse> getNotifications() {
return null;
}
@Override
public void reset() {
}
}

View File

@ -0,0 +1,372 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPause;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPlay;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPropertyChanged;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSeek;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSpeedChanged;
import org.xbmc.kore.utils.LogUtils;
import java.util.ArrayList;
import static org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler.TYPE.MUSIC;
/**
* Simulates Player JSON-RPC API
*/
public class PlayerHandler implements JSONConnectionHandlerManager.ConnectionHandler {
private static final String TAG = LogUtils.makeLogTag(PlayerHandler.class);
public enum TYPE {
MUSIC,
MOVIE,
EPISODE,
MUSICVIDEO,
UNKNOWN,
PICTURE,
CHANNEL
}
public static String[] repeatModes = {
"off",
"one",
"all"
};
private int currentRepeatMode;
private boolean shuffled;
private boolean playing;
private int position;
private long totalTimeSec = 240; // default value
private TYPE mediaType = MUSIC;
private Player.GetItem mediaItem = createSongItem();
private String playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
private ArrayList<JsonResponse> notifications = new ArrayList<>();
@Override
public ArrayList<JsonResponse> getNotifications() {
ArrayList<JsonResponse> list = new ArrayList<>(notifications);
notifications.clear();
return list;
}
@Override
public void reset() {
this.shuffled = false;
this.currentRepeatMode = 0;
this.position = 0;
this.playing = false;
setMediaType(MUSIC);
}
@Override
public String[] getType() {
return new String[] {Player.GetActivePlayers.METHOD_NAME,
Player.GetProperties.METHOD_NAME,
Player.GetItem.METHOD_NAME,
Player.SetRepeat.METHOD_NAME,
Player.SetShuffle.METHOD_NAME,
Player.Seek.METHOD_NAME,
Player.PlayPause.METHOD_NAME};
}
@Override
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
LogUtils.LOGD(TAG, "getResponse: method="+method);
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
JsonNode node = jsonRequest.get("id");
JsonResponse response = null;
int playerId;
switch (method) {
case Player.GetActivePlayers.METHOD_NAME:
response = new Player.GetActivePlayers(node.asInt(), 0, playerType);
break;
case Player.GetProperties.METHOD_NAME:
response = updatePlayerProperties(createPlayerProperties(node.asInt()));
break;
case Player.GetItem.METHOD_NAME:
mediaItem.setMethodId(node.asInt());
response = mediaItem;
break;
case Player.SetRepeat.METHOD_NAME:
response = new Player.SetRepeat(node.asInt(), "OK");
playerId = jsonRequest.get("params").get("playerid").asInt();
currentRepeatMode = ++currentRepeatMode % 3;
notifications.add(new OnPropertyChanged(repeatModes[currentRepeatMode], null, playerId));
break;
case Player.SetShuffle.METHOD_NAME:
response = new Player.SetShuffle(node.asInt(), "OK");
playerId = jsonRequest.get("params").get("playerid").asInt();
shuffled = !shuffled;
notifications.add(new OnPropertyChanged(null, shuffled, playerId));
break;
case Player.PlayPause.METHOD_NAME:
playing = !playing;
int speed = playing ? 1 : 0;
response = new Player.PlayPause(node.asInt(), speed);
playerId = jsonRequest.get("params").get("playerid").asInt();
if (playing)
notifications.add(new OnPlay(1580, getMediaItemType(), playerId, speed));
else
notifications.add(new OnPause(1580, getMediaItemType(), playerId, speed));
notifications.add(new OnSpeedChanged(1580, getMediaItemType(), playerId, speed));
break;
case Player.Seek.METHOD_NAME:
position = new GlobalType.Time(jsonRequest.get("params").get("value")).ToSeconds();
response = new Player.Seek(node.asInt(), (100 * position) / (double) totalTimeSec, position,
totalTimeSec);
playerId = jsonRequest.get("params").get("playerid").asInt();
notifications.add(new OnSeek(node.asInt(), getMediaItemType(), playerId,
playing ? 1 : 0, 0, position));
break;
}
jsonResponses.add(response);
return jsonResponses;
}
/**
* Sets the returned media type
* @param mediaType
*/
public void setMediaType(TYPE mediaType) {
switch (mediaType) {
case MOVIE:
mediaItem = createMovieItem();
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
break;
case MUSIC:
mediaItem = createSongItem();
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
break;
case UNKNOWN:
mediaItem = createUnknownItem();
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
break;
case MUSICVIDEO:
mediaItem = createMusicVideoItem();
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
break;
case PICTURE:
mediaItem = createPictureItem();
playerType = PlayerType.GetActivePlayersReturnType.PICTURE;
break;
case CHANNEL:
mediaItem = createChannelItem();
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
break;
}
}
public void startPlay() {
OnPlay onPlay = new OnPlay(1580, getMediaItemType(), 0, 1);
notifications.add(onPlay);
playing = true;
}
/**
* Returns the current media item for the media type set through {@link #setMediaType(TYPE)}
* @return
*/
public Player.GetItem getMediaItem() {
return mediaItem;
}
/**
* Returns the play position of the current media item
* @return the time elapsed in seconds
*/
public long getPosition() {
return position;
}
public boolean isPlaying() {
return playing;
}
public void setTotalTimeSec(long totalTimeSec) {
this.totalTimeSec = totalTimeSec;
}
private String getMediaItemType() {
switch (mediaType) {
case MOVIE:
return OnPlay.TYPE_MOVIE;
case MUSIC:
return OnPlay.TYPE_SONG;
case UNKNOWN:
return OnPlay.TYPE_UNKNOWN;
case MUSICVIDEO:
return OnPlay.TYPE_MUSICVIDEO;
case PICTURE:
return OnPlay.TYPE_PICTURE;
case CHANNEL:
return OnPlay.TYPE_MOVIE;
default:
return OnPlay.TYPE_SONG;
}
}
private Player.GetProperties updatePlayerProperties(Player.GetProperties playerProperties) {
if (playing)
position++;
if ( ( position > totalTimeSec ) && currentRepeatMode != 0 )
position = 0;
playerProperties.addPosition(position);
playerProperties.addPercentage((int) ((position * 100 ) / totalTimeSec));
playerProperties.addTime(0, 0, position, 767);
playerProperties.addShuffled(shuffled);
playerProperties.addRepeat(repeatModes[currentRepeatMode]);
return playerProperties;
}
private Player.GetProperties createPlayerProperties(int id) {
Player.GetProperties properties = new Player.GetProperties(id);
properties.addPlaylistId(0);
properties.addRepeat(repeatModes[currentRepeatMode]);
properties.addShuffled(false);
properties.addSpeed(playing ? 1 : 0);
properties.addTotaltime(0,0,240,41);
return properties;
}
private Player.GetItem createSongItem() {
Player.GetItem item = new Player.GetItem();
item.addAlbum("My Time Is The Right Time");
item.addAlbumArtist("Alton Ellis");
item.addArtist("Alton Ellis");
item.addDisplayartist("Alton Ellis");
item.addDuration(240);
item.addFile("/Users/martijn/Projects/dummymediafiles/media/music/Alton Ellis/My Time Is The Right Time/11-I Can't Stand It.mp3");
item.addGenre("Reggae");
item.addLabel("I Can't Stand It");
item.addRating(0);
item.addTitle("I Can't Stand It");
item.addTrack(11);
item.addType(Player.GetItem.TYPE.SONG);
item.addYear(2000);
return item;
}
private Player.GetItem createMovieItem() {
Player.GetItem item = new Player.GetItem();
item.addTitle("Elephants Dream");
item.addCast("", "Cas Jansen", "Emo");
item.addCast("", "Tygo Gernandt", "Proog");
item.addDuration(660);
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
item.addGenre("Animation");
item.addRating(0);
item.addType(Player.GetItem.TYPE.MOVIE);
item.addYear(2006);
return item;
}
private Player.GetItem createEpisodeItem() {
Player.GetItem item = new Player.GetItem();
item.addShowtitle("According to Jim");
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41995.jpg/", "James Belushi", "Jim");
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41994.jpg/", "Courtney Thorne-Smith", "Cheryl");
item.addDuration(1800);
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
item.addGenre("Comedy");
item.addRating(7);
item.addType(Player.GetItem.TYPE.EPISODE);
item.addFirstaired("2001-10-03");
item.addEpisode(1);
item.addSeason(1);
item.addDirector("Andy Cadiff");
item.addTitle("Pilot");
return item;
}
private Player.GetItem createMusicVideoItem() {
Player.GetItem item = new Player.GetItem();
item.addType(Player.GetItem.TYPE.MUSICVIDEO);
item.addAlbum("...Baby One More Time");
item.addDirector("Nigel Dick");
item.addThumbnail("image://http%3a%2f%2fwww.theaudiodb.com%2fimages%2fmedia%2falbum%2fthumb%2fbaby-one-more-time-4dcff7453745a.jpg/");
item.addYear(1999);
item.addTitle("(You Drive Me) Crazy");
item.addLabel("(You Drive Me) Crazy");
item.addRuntime(12);
item.addGenre("Pop");
item.addPremiered("1999-01-01");
return item;
}
private Player.GetItem createChannelItem() {
Player.GetItem item = new Player.GetItem();
item.addShowtitle("According to Jim");
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41995.jpg/", "James Belushi", "Jim");
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41994.jpg/", "Courtney Thorne-Smith", "Cheryl");
item.addDuration(1800);
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
item.addGenre("Comedy");
item.addRating(7);
item.addType(Player.GetItem.TYPE.EPISODE);
item.addFirstaired("2001-10-03");
item.addEpisode(1);
item.addSeason(1);
item.addDirector("Andy Cadiff");
item.addTitle("Pilot");
item.addType(Player.GetItem.TYPE.CHANNEL);
return item;
}
private Player.GetItem createUnknownItem() {
Player.GetItem item = new Player.GetItem();
item.addTitle("Dumpert");
item.addCast("", "Martijn Kaiser", "himself");
item.addCast("", "", "Skipmode A1");
item.addCast("", "", "Sparkline");
item.addGenre("Addon");
item.addType(Player.GetItem.TYPE.UNKNOWN);
return item;
}
private Player.GetItem createPictureItem() {
Player.GetItem item = new Player.GetItem();
item.addTitle("Kore Artwork");
item.addFile("/Users/martijn/Projects/Kore/art/screenshots/Kore_Artwork_Concept_2.png");
item.addType(Player.GetItem.TYPE.PICTURE);
return item;
}
}

View File

@ -36,7 +36,7 @@ public abstract class JsonResponse {
private static final String PARAMS_NODE = "params";
private static final String METHOD_NODE = "method";
private static final String DATA_NODE = "data";
private static final String ID_NODE = "id";
protected static final String ID_NODE = "id";
private static final String JSONRPC_NODE = "jsonrpc";
public enum TYPE {
@ -167,6 +167,14 @@ public abstract class JsonResponse {
}
}
/**
* Adds the value to the array in node with the given key.
* If the array does not exist it will be created
* and added.
* @param node ObjectNode that should contain an entry with key with an array as value
* @param key the key of the item in ObjectNode that should hold the array
* @param value the value to be added to the array
*/
protected void addToArrayNode(ObjectNode node, String key, ObjectNode value) {
JsonNode jsonNode = node.get(key);
if (jsonNode == null) {
@ -189,6 +197,10 @@ public abstract class JsonResponse {
getDataNode().put(parameter, value);
}
protected void addDataToResponse(String parameter, ObjectNode node) {
getDataNode().set(parameter, node);
}
protected void addParameterToResponse(String parameter, String value) {
getParametersNode().put(parameter, value);
}

View File

@ -0,0 +1,39 @@
package org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.utils.LogUtils;
public class JsonUtils {
/**
* Fills objectNode with time values
* @param objectNode
* @param timeSec
* @return objectNode for chaining
*/
public static ObjectNode createTimeNode(ObjectNode objectNode, long timeSec) {
int hours = (int) timeSec / 3600;
int minutes = (int) ( timeSec / 60 ) % 60;
int seconds = (int) timeSec % 60 ;
return createTimeNode(objectNode, hours, minutes, seconds, 0);
}
/**
* Fills objectNode with time values
* @param objectNode
* @param hours
* @param minutes
* @param seconds
* @param milliseconds
* @return objectNode for chaining
*/
public static ObjectNode createTimeNode(ObjectNode objectNode, int hours, int minutes, int seconds, int milliseconds) {
objectNode.put(GlobalType.Time.HOURS, hours);
objectNode.put(GlobalType.Time.MINUTES, minutes);
objectNode.put(GlobalType.Time.SECONDS, seconds);
objectNode.put(GlobalType.Time.MILLISECONDS, milliseconds);
return objectNode;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.nodes;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
public class AudioDetailsNode extends JsonResponse {
private AudioDetailsNode() {};
public AudioDetailsNode(int channels, String codec, String language) {
ObjectNode node = (ObjectNode) getResultNode(TYPE.OBJECT);
node.put("channels", channels);
node.put("codec", codec);
node.put("language", language);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.nodes;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
public class SubtitleDetailsNode extends JsonResponse {
private SubtitleDetailsNode() {};
public SubtitleDetailsNode(String language) {
ObjectNode node = (ObjectNode) getResultNode(TYPE.OBJECT);
node.put("language", language);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.nodes;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
public class VideoDetailsNode extends JsonResponse {
private VideoDetailsNode() {};
public VideoDetailsNode(int width, int height, float aspect, String code, int duration) {
ObjectNode node = (ObjectNode) getResultNode(TYPE.OBJECT);
node.put("width", width);
node.put("height", height);
node.put("aspect", aspect);
node.put("code", code);
node.put("duration", duration);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.response.methods;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
/**
* Serverside JSON RPC responses in Application.*
*/
public class JSONRPC {
public static class Ping extends JsonResponse {
public final static String METHOD_NAME = "JSONRPC.Ping";
public Ping(int id) {
super(id);
setResultToResponse("pong");
}
}
}

View File

@ -0,0 +1,482 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.response.methods;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonUtils;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.AudioDetailsNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.SubtitleDetailsNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.VideoDetailsNode;
import org.xbmc.kore.utils.LogUtils;
/**
* Serverside JSON RPC responses in Methods.Player.*
*/
public class Player {
/**
* JSON response for Player.Seek request
*
* Example:
* Query: {"jsonrpc":"2.0","method":"Player.Seek","id":41,"params":{"playerid":0,"value":{"hours":0,"milliseconds":0,"minutes":0,"seconds":2}}}
* Answer: {"id":41,"jsonrpc":"2.0","result":{"percentage":16.570009231567382812,"time":{"hours":0,"milliseconds":0,"minutes":0,"seconds":2},"totaltime":{"hours":0,"milliseconds":70,"minutes":0,"seconds":12}}}
*
* @return JSON string
*/
public static class Seek extends JsonResponse {
public final static String METHOD_NAME = "Player.Seek";
public Seek(int methodId, double percentage, long timeSec, long totalTime) {
super(methodId);
ObjectNode resultNode = (ObjectNode) getResultNode(TYPE.OBJECT);
resultNode.put("percentage", percentage);
resultNode.set("time", JsonUtils.createTimeNode(createObjectNode(), timeSec));
resultNode.set("totalTime", JsonUtils.createTimeNode(createObjectNode(), totalTime));
}
}
public static class SetShuffle extends JsonResponse {
public final static String METHOD_NAME = "Player.SetShuffle";
public SetShuffle(int methodId, String result) {
super(methodId);
setResultToResponse(result);
}
}
public static class SetRepeat extends JsonResponse {
public final static String METHOD_NAME = "Player.SetRepeat";
public SetRepeat(int methodId, String result) {
super(methodId);
setResultToResponse(result);
}
}
public static class PlayPause extends JsonResponse {
public final static String METHOD_NAME = "Player.PlayPause";
public PlayPause(int methodId, int speed) {
super(methodId);
((ObjectNode) getResultNode(TYPE.OBJECT)).put("speed", speed);
}
}
public static class GetActivePlayers extends JsonResponse {
public final static String METHOD_NAME = "Player.GetActivePlayers";
public GetActivePlayers(int methodId, int playerId, String type) {
super(methodId);
ObjectNode objectNode = createObjectNode();
objectNode.put("playerid", playerId);
objectNode.put("type", type);
((ArrayNode) getResultNode(TYPE.ARRAY)).add(objectNode);
}
}
public static class GetProperties extends JsonResponse {
public final static String METHOD_NAME = "Player.GetProperties";
final static String SPEED = PlayerType.PropertyName.SPEED;
final static String PERCENTAGE = PlayerType.PropertyName.PERCENTAGE;
final static String POSITION = PlayerType.PropertyName.POSITION;
final static String TIME = PlayerType.PropertyName.TIME;
final static String TOTALTIME = PlayerType.PropertyName.TOTALTIME;
final static String REPEAT = PlayerType.PropertyName.REPEAT;
final static String SHUFFLED = PlayerType.PropertyName.SHUFFLED;
final static String CURRENTAUDIOSTREAM = PlayerType.PropertyName.CURRENTAUDIOSTREAM;
final static String CURRENTSUBTITLE = PlayerType.PropertyName.CURRENTSUBTITLE;
final static String AUDIOSTREAMS = PlayerType.PropertyName.AUDIOSTREAMS;
final static String SUBTITLES = PlayerType.PropertyName.SUBTITLES;
final static String PLAYLISTID = PlayerType.PropertyName.PLAYLISTID;
public GetProperties(int methodId) {
super(methodId);
}
public void addSpeed(int value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(SPEED, value);
}
public void addPercentage(int value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(PERCENTAGE, value);
}
public void addPosition(int value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(POSITION, value);
}
public void addTime(int hours, int minutes, int seconds, int milliseconds) {
ObjectNode timeNode = JsonUtils.createTimeNode(createObjectNode(), hours, minutes, seconds, milliseconds);
((ObjectNode) getResultNode(TYPE.OBJECT)).putObject(TIME).setAll(timeNode);
}
public void addTotaltime(int hours, int minutes, int seconds, int milliseconds) {
ObjectNode timeNode = JsonUtils.createTimeNode(createObjectNode(), hours, minutes, seconds, milliseconds);
((ObjectNode) getResultNode(TYPE.OBJECT)).putObject(TOTALTIME).setAll(timeNode);
}
public void addRepeat(String value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(REPEAT, value);
}
public void addShuffled(boolean value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(SHUFFLED, value);
}
public void addCurrentAudioStream(int channels, String codec, int bitrate) {
ObjectNode objectNode = createAudioStreamNode(channels, codec, bitrate);
((ObjectNode) getResultNode(TYPE.OBJECT)).putObject(CURRENTAUDIOSTREAM).setAll(objectNode);
}
public void addCurrentSubtitle(int index, String language, String name) {
ObjectNode objectNode = createSubtitleNode(index, language, name);
((ObjectNode) getResultNode(TYPE.OBJECT)).putObject(CURRENTSUBTITLE).setAll(objectNode);
}
public void addAudioStream(int channels, String codec, int bitrate) {
ObjectNode objectNode = createAudioStreamNode(channels, codec, bitrate);
addObjectToArray(AUDIOSTREAMS, objectNode);
}
public void addSubtitle(int index, String language, String name) {
ObjectNode objectNode = createSubtitleNode(index, language, name);
addObjectToArray(SUBTITLES, objectNode);
}
public void addPlaylistId(int value) {
((ObjectNode) getResultNode(TYPE.OBJECT)).put(PLAYLISTID, value);
}
private ObjectNode createAudioStreamNode(int channels, String codec, int bitrate) {
ObjectNode audioNode = createObjectNode();
audioNode.put("channels", channels);
audioNode.put("codec", codec);
audioNode.put("bitrate", bitrate);
return audioNode;
}
private ObjectNode createSubtitleNode(int index, String language, String name) {
ObjectNode subtitleNode = createObjectNode();
subtitleNode.put("index", index);
subtitleNode.put("language", language);
subtitleNode.put("name", name);
return subtitleNode;
}
private void addObjectToArray(String key, ObjectNode objectNode) {
ObjectNode resultNode = (ObjectNode) getResultNode(TYPE.OBJECT);
JsonNode jsonNode = resultNode.get(key);
if(jsonNode == null) {
ArrayNode arrayNode = createArrayNode().add(objectNode);
resultNode.set(key, arrayNode);
} else if(jsonNode.isArray()) {
((ArrayNode) jsonNode).add(objectNode);
} else {
LogUtils.LOGW("Player", "JsonNode at " + key + " is not of type ArrayNode");
}
}
}
public static class GetItem extends JsonResponse {
public final static String METHOD_NAME = "Player.GetItem";
final static String ITEM = "item";
final static String TYPE = "type";
final static String ART = "art";
final static String ARTIST = "artist";
final static String ALBUMARTIST = "albumartist";
final static String ALBUM = "album";
final static String CAST = "cast";
final static String DIRECTOR = "director";
final static String DISPLAYARTIST = "displayartist";
final static String DURATION = "duration";
final static String EPISODE = "episode";
final static String FANART = "fanart";
final static String FILE = "file";
final static String FIRSTAIRED = "firstaired";
final static String GENRE = "genre";
final static String IMDBNUMBER = "imdbnumber";
final static String PLOT = "plot";
final static String PREMIERED = "premiered";
final static String RATING = "rating";
final static String RESUME = "resume";
final static String RUNTIME = "runtime";
final static String SEASON = "season";
final static String SHOWTITLE = "showtitle";
final static String STREAMDETAILS = "streamdetails";
final static String STUDIO = "studio";
final static String TAGLINE = "tagline";
final static String THUMBNAIL = "thumbnail";
final static String TITLE = "title";
final static String TOP250 = "top250";
final static String TRACK = "track";
final static String VOTES = "votes";
final static String WRITER = "writer";
final static String YEAR = "year";
final static String DESCRIPTION = "description";
final static String LABEL = "label";
public enum TYPE { UNKNOWN,
MOVIE,
EPISODE,
MUSICVIDEO,
SONG,
PICTURE,
CHANNEL
}
private ObjectNode itemNode;
public GetItem() {
super();
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
itemNode = createObjectNode();
resultNode.set(ITEM, itemNode);
}
public GetItem(int methodId) {
super(methodId);
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
itemNode = createObjectNode();
resultNode.set(ITEM, itemNode);
}
public void setMethodId(int methodId) {
getResponseNode().put(ID_NODE, methodId);
}
public void addType(TYPE type) {
String strType;
switch (type) {
case MOVIE:
strType = "movie";
break;
case EPISODE:
strType = "episode";
break;
case MUSICVIDEO:
strType = "musicvideo";
break;
case SONG:
strType = "song";
break;
case PICTURE:
strType = "picture";
break;
case CHANNEL:
strType = "channel";
break;
case UNKNOWN:
default:
strType = "unknown";
break;
}
itemNode.put(TYPE, strType);
}
public void addArt(String banner, String poster, String fanart, String thumbnail) {
ObjectNode objectNode = createArtNode(banner, poster, fanart, thumbnail);
itemNode.putObject(ART).setAll(objectNode);
}
public void addArtist(String artist) {
addToArrayNode(itemNode, ARTIST, artist);
}
public void addAlbumArtist(String artist) {
addToArrayNode(itemNode, ALBUMARTIST, artist);
}
public void addAlbum(String album) {
itemNode.put(ALBUM, album);
}
public void addCast(String thumbnail, String name, String role) {
addToArrayNode(itemNode, CAST, createCastNode(thumbnail, name, role));
}
public void addDirector(String director) {
addToArrayNode(itemNode, DIRECTOR, director);
}
public void addDisplayartist(String displayartist) {
itemNode.put(DISPLAYARTIST, displayartist);
}
public void addDuration(int duration) {
itemNode.put(DURATION, duration);
}
public void addEpisode(int episode) {
itemNode.put(EPISODE, episode);
}
public void addFanart(String fanart) {
itemNode.put(FANART, fanart);
}
public void addFile(String file) {
itemNode.put(FILE, file);
}
public void addFirstaired(String firstaired) {
itemNode.put(FIRSTAIRED, firstaired);
}
public void addGenre(String genre) {
itemNode.put(GENRE, genre);
}
public void addImdbnumber(String imdbnumber) {
itemNode.put(IMDBNUMBER, imdbnumber);
}
public void addPlot(String plot) {
itemNode.put(PLOT, plot);
}
public void addPremiered(String premiered) {
itemNode.put(PREMIERED, premiered);
}
public void addRating(int rating) {
itemNode.put(RATING, rating);
}
public void addResume(int position, int total) {
itemNode.putObject(RESUME).setAll(createResumeNode(position, total));
}
public void addRuntime(int runtime) {
itemNode.put(RUNTIME, runtime);
}
public void addSeason(int season) {
itemNode.put(SEASON, season);
}
public void addShowtitle(String showtitle) {
itemNode.put(SHOWTITLE, showtitle);
}
public void addStreamdetails(AudioDetailsNode audioDetailsNode,
VideoDetailsNode videoDetailsNode,
SubtitleDetailsNode subtitleDetailsNode) {
ObjectNode objectNode = createObjectNode();
objectNode.putObject("audio").setAll(audioDetailsNode.getResponseNode());
objectNode.putObject("video").setAll(videoDetailsNode.getResponseNode());
objectNode.putObject("subtitle").setAll(subtitleDetailsNode.getResponseNode());
itemNode.set(STREAMDETAILS, objectNode);
}
public void addStudio(String studio) {
addToArrayNode(itemNode, STUDIO, studio);
}
public void addTagline(String tagline) {
itemNode.put(TAGLINE, tagline);
}
public void addThumbnail(String thumbnail) {
itemNode.put(THUMBNAIL, thumbnail);
}
public void addTitle(String title) {
itemNode.put(TITLE, title);
}
public String getTitle() {
JsonNode jsonNode = itemNode.get(TITLE);
if (jsonNode != null)
return jsonNode.asText();
else
return null;
}
public void addTop250(int top250) {
itemNode.put(TOP250, top250);
}
public void addTrack(int track) {
itemNode.put(TRACK, track);
}
public void addVotes(String votes) {
itemNode.put(VOTES, votes);
}
public void addWriter(String writer) {
addToArrayNode(itemNode, WRITER, writer);
}
public void addYear(int year) {
itemNode.put(YEAR, year);
}
public void addDescription(String description) {
itemNode.put(DESCRIPTION, description);
}
public void addLabel(String label) {
itemNode.put(LABEL, label);
}
private ObjectNode createArtNode(String banner,
String poster,
String fanart,
String thumbnail) {
ObjectNode objectNode = createObjectNode();
objectNode.put("poster", poster);
objectNode.put("fanart", fanart);
objectNode.put("thumbnail", thumbnail);
objectNode.put("banner", banner);
return objectNode;
}
private ObjectNode createArtworkNode(String banner, String poster, String fanart, String thumbnail) {
ObjectNode objectNode = createObjectNode();
objectNode.put("poster", poster);
objectNode.put("fanart", fanart);
objectNode.put("thumbnail", thumbnail);
return objectNode;
}
private ObjectNode createCastNode(String thumbnail, String name, String role) {
ObjectNode objectNode = createObjectNode();
objectNode.put("thumbnail", thumbnail);
objectNode.put("name", name);
objectNode.put("role", role);
return objectNode;
}
private ObjectNode createResumeNode(int position, int total) {
ObjectNode objectNode = createObjectNode();
objectNode.put("position", position);
objectNode.put("total", total);
return objectNode;
}
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2016 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.testutils.tcpserver.handlers.jsonrpc.response.notifications;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonUtils;
public class Player {
abstract public static class PlayPause extends JsonResponse {
public static String TYPE_SONG = "song";
public static String TYPE_EPISODE = "episode";
public static String TYPE_MOVIE = "movie";
public static String TYPE_MUSICVIDEO = "musicvideo";
public static String TYPE_VIDEO = "video";
public static String TYPE_UNKNOWN = "unknown";
public static String TYPE_PICTURE = "picture";
public static String TYPE_CHANNEL = "channel";
private PlayPause(String methodName, int itemId, String itemType, int playerId, int speed) {
addMethodToResponse(methodName);
ObjectNode itemNode = createObjectNode();
itemNode.put("id", itemId);
if (itemType != null)
itemNode.put("type", itemType);
addDataToResponse("item", itemNode);
itemNode = createObjectNode();
itemNode.put("playerid", playerId);
itemNode.put("speed", speed);
addDataToResponse("player", itemNode);
addParameterToResponse("sender", "xbmc");
}
}
/**
* JSON response for Player.OnSpeedChanged notification
*
* Example:
* Answer: {"jsonrpc":"2.0","method":"Player.OnSpeedChanged","params":{"data":{"item":{"id":94,"type":"song"},"player":{"playerid":0,"speed":0}},"sender":"xbmc"}}
*/
public static class OnSpeedChanged extends PlayPause {
public final static String METHOD_NAME = "Player.OnSpeedChanged";
public OnSpeedChanged(int itemId, String itemType, int playerId, int speed) {
super(METHOD_NAME, itemId, itemType, playerId, speed);
}
}
/**
* JSON response for Player.OnPause notification
*
* Example:
* Answer: {"jsonrpc":"2.0","method":"Player.OnPause","params":{"data":{"item":{"id":94,"type":"song"},"player":{"playerid":0,"speed":0}},"sender":"xbmc"}}
*/
public static class OnPause extends PlayPause {
public final static String METHOD_NAME = "Player.OnPause";
public OnPause(int itemId, String itemType, int playerId, int speed) {
super(METHOD_NAME, itemId, itemType, playerId, speed);
}
}
/**
* JSON response for Player.OnPlay notification
*
* Example:
* Answer: {"jsonrpc":"2.0","method":"Player.OnPlay","params":{"data":{"item":{"id":1580,"type":"song"},"player":{"playerid":0,"speed":1}},"sender":"xbmc"}}
*/
public static class OnPlay extends PlayPause {
public final static String METHOD_NAME = "Player.OnPlay";
public OnPlay(int itemId, String itemType, int playerId, int speed) {
super(METHOD_NAME, itemId, itemType, playerId, speed);
}
}
/**
* JSON response for Player.OnPropertyChanged notification
*
* Example:
* {"jsonrpc":"2.0","method":"Player.OnPropertyChanged","params":{"data":{"player":{"playerid":0},"property":{"repeat":"all"}},"sender":"xbmc"}}
*/
public static class OnPropertyChanged extends JsonResponse {
public final static String METHOD_NAME = "Player.OnPropertyChanged";
public OnPropertyChanged(String repeatType, Boolean shuffled, int playerId) {
super();
addMethodToResponse(METHOD_NAME);
ObjectNode playerIdNode = createObjectNode();
playerIdNode.put("playerid", playerId);
addDataToResponse("player", playerIdNode);
if (repeatType != null) {
ObjectNode repeatNode = createObjectNode();
repeatNode.put("repeat", repeatType);
addDataToResponse("property", repeatNode);
}
if (shuffled != null) {
ObjectNode repeatNode = createObjectNode();
repeatNode.put("shuffled", shuffled);
addDataToResponse("property", repeatNode);
}
addParameterToResponse("sender", "xbmc");
}
}
/**
* JSON response for Player.OnPropertyChanged notification
*
* Example:
* {"jsonrpc":"2.0","method":"Player.OnSeek", "params":{ "data":{"item":{ "id":127,"type":"episode" },"player":{ "playerid":1,"seekoffset":{ "hours":0,"milliseconds":0, "minutes":0,"seconds":-14 },"speed":0, "time":{"hours":0, "milliseconds":0,"minutes":0, "seconds":2} }},"sender":"xbmc" }}
*/
public static class OnSeek extends JsonResponse {
public final static String METHOD_NAME = "Player.OnSeek";
public OnSeek(int itemId, String type, int playerId, int speed, long seekOffsetSecs, long timeSecs) {
super();
addMethodToResponse(METHOD_NAME);
ObjectNode itemNode = createObjectNode();
itemNode.put("id", itemId);
itemNode.put("type", type);
addDataToResponse("item", itemNode);
ObjectNode playerNode = createObjectNode();
playerNode.put("playerid", playerId);
playerNode.set("seekoffset", JsonUtils.createTimeNode(createObjectNode(), seekOffsetSecs));
playerNode.set("time", JsonUtils.createTimeNode(createObjectNode(), timeSecs));
playerNode.put("speed", speed);
addDataToResponse("player", playerNode);
addParameterToResponse("sender", "xbmc");
}
}
}