diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java new file mode 100644 index 0000000..7944479 --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/TestUtils.java @@ -0,0 +1,46 @@ +/* + * 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.testhelpers; + + +import org.xbmc.kore.testutils.tcpserver.handlers.InputHandler; + +import static junit.framework.Assert.assertTrue; +import static org.xbmc.kore.tests.ui.AbstractTestClass.getInputHandler; + +public class TestUtils { + /** + * Tests if the event received at the server matches the given + * method name and action + * @param methodName name of the method that should be received serverside. + * @param executeAction name of the action that should be received serverside. May be null if the input does not specify an action. + */ + public static void testHTTPEvent(String methodName, String executeAction) { + InputHandler inputHandler = getInputHandler(); + assertTrue(inputHandler != null); + + String methodNameReceived = inputHandler.getMethodName(); + assertTrue(methodNameReceived != null); + assertTrue(methodNameReceived.contentEquals(methodName)); + + if (executeAction != null) { + String actionReceived = inputHandler.getAction(); + assertTrue(actionReceived != null); + assertTrue(actionReceived.contentEquals(executeAction)); + } + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java index 766df0c..df49507 100644 --- a/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java +++ b/app/src/androidTest/java/org/xbmc/kore/testhelpers/Utils.java @@ -31,6 +31,7 @@ import org.xbmc.kore.R; import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.provider.MediaProvider; +import org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration; import org.xbmc.kore.utils.LogUtils; import java.lang.reflect.Method; @@ -88,7 +89,14 @@ public class Utils { public static void setLearnedAboutDrawerPreference(Context context, boolean learned) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREF_USER_LEARNED_DRAWER, true); + editor.putBoolean(PREF_USER_LEARNED_DRAWER, learned); + editor.commit(); + } + + public static void setUseEventServerPreference(Context context, boolean use) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(HostFragmentManualConfiguration.HOST_USE_EVENT_SERVER, use); editor.commit(); } 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 c5cf67f..236f425 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 @@ -19,7 +19,6 @@ package org.xbmc.kore.tests.ui; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; import android.support.test.espresso.Espresso; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; @@ -33,27 +32,42 @@ import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.runner.RunWith; import org.xbmc.kore.host.HostInfo; -import org.xbmc.kore.host.HostManager; import org.xbmc.kore.jsonrpc.HostConnection; -import org.xbmc.kore.provider.MediaProvider; import org.xbmc.kore.testhelpers.LoaderIdlingResource; import org.xbmc.kore.testhelpers.Utils; import org.xbmc.kore.testutils.Database; import org.xbmc.kore.testutils.tcpserver.MockTcpServer; import org.xbmc.kore.testutils.tcpserver.handlers.AddonsHandler; import org.xbmc.kore.testutils.tcpserver.handlers.ApplicationHandler; +import org.xbmc.kore.testutils.tcpserver.handlers.InputHandler; 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 org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration; +import org.xbmc.kore.utils.LogUtils; import java.io.IOException; @RunWith(AndroidJUnit4.class) @Ignore abstract public class AbstractTestClass { + private static final String TAG = LogUtils.makeLogTag(AbstractTestClass.class); abstract protected ActivityTestRule getActivityTestRule(); + /** + * Method that can be used to change the shared preferences. + * This will be called before each test after clearing the settings + * in {@link #setUp()} + */ + abstract protected void setSharedPreferences(Context context); + + /** + * Called from {@link #setUp()} right after HostInfo has been created. + * @param hostInfo created HostInfo used by the activity under test + */ + abstract protected void configureHostInfo(HostInfo hostInfo); + private LoaderIdlingResource loaderIdlingResource; private ActivityTestRule activityTestRule; private static MockTcpServer server; @@ -61,6 +75,7 @@ abstract public class AbstractTestClass { private AddonsHandler addonsHandler; private static PlayerHandler playerHandler; private static ApplicationHandler applicationHandler; + private static InputHandler inputHandler; private HostInfo hostInfo; @@ -68,9 +83,11 @@ abstract public class AbstractTestClass { public static void setupMockTCPServer() throws Throwable { playerHandler = new PlayerHandler(); applicationHandler = new ApplicationHandler(); + inputHandler = new InputHandler(); manager = new JSONConnectionHandlerManager(); manager.addHandler(playerHandler); manager.addHandler(applicationHandler); + manager.addHandler(inputHandler); manager.addHandler(new JSONRPCHandler()); server = new MockTcpServer(manager); server.start(); @@ -81,8 +98,15 @@ abstract public class AbstractTestClass { activityTestRule = getActivityTestRule(); -// final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final Context context = activityTestRule.getActivity(); + if (context == null) + throw new RuntimeException("Could not get context. Maybe activity failed to start?"); + + Utils.clearSharedPreferences(context); + //Prevent drawer from opening when we start a new activity + Utils.setLearnedAboutDrawerPreference(context, true); + //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 @@ -91,14 +115,14 @@ abstract public class AbstractTestClass { manager.addHandler(addonsHandler); } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean useEventServer = prefs.getBoolean(HostFragmentManualConfiguration.HOST_USE_EVENT_SERVER, false); + hostInfo = Database.addHost(context, server.getHostName(), HostConnection.PROTOCOL_TCP, HostInfo.DEFAULT_HTTP_PORT, - server.getPort()); - - Utils.clearSharedPreferences(context); - - //Prevent drawer from opening when we start a new activity - Utils.setLearnedAboutDrawerPreference(context, true); + server.getPort(), useEventServer); + //Allow each test to change the host info + configureHostInfo(hostInfo); loaderIdlingResource = new LoaderIdlingResource(activityTestRule.getActivity().getSupportLoaderManager()); Espresso.registerIdlingResources(loaderIdlingResource); @@ -111,7 +135,7 @@ abstract public class AbstractTestClass { Utils.switchHost(context, activityTestRule.getActivity(), hostInfo); - //Relaunch the activity for the changes (Host selection and database fill) to take effect + //Relaunch the activity for the changes (Host selection, preference changes, and database fill) to take effect activityTestRule.launchActivity(new Intent()); } @@ -147,4 +171,8 @@ abstract public class AbstractTestClass { public static ApplicationHandler getApplicationHandler() { return applicationHandler; } + + public static InputHandler getInputHandler() { + return inputHandler; + } } 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 index 1e731e3..7e5d58a 100644 --- a/app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/BaseMediaActivityTests.java @@ -16,11 +16,14 @@ 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; @@ -37,6 +40,16 @@ import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice; @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 */ 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 58888ab..adcbd20 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 @@ -16,6 +16,7 @@ package org.xbmc.kore.tests.ui.movies; +import android.content.Context; import android.support.test.espresso.Espresso; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; @@ -25,6 +26,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xbmc.kore.R; +import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.testhelpers.EspressoTestUtils; import org.xbmc.kore.tests.ui.AbstractTestClass; import org.xbmc.kore.ui.sections.video.MoviesActivity; @@ -46,6 +48,16 @@ public class RestoreSearchQueryListFragmentTest extends AbstractTestClass { return musicActivityActivityTestRule; } + @Override + protected void setSharedPreferences(Context context) { + + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + + } + @Override public void setUp() throws Throwable { super.setUp(); diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/ButtonTests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/ButtonTests.java new file mode 100644 index 0000000..2b1813a --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/ButtonTests.java @@ -0,0 +1,168 @@ +/* + * 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.remote.controlpad.eventserver; + +import android.content.Context; +import android.support.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.xbmc.kore.R; +import org.xbmc.kore.eventclient.ButtonCodes; +import org.xbmc.kore.host.HostInfo; +import org.xbmc.kore.host.HostManager; +import org.xbmc.kore.jsonrpc.method.Input; +import org.xbmc.kore.testhelpers.TestUtils; +import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.tests.ui.AbstractTestClass; +import org.xbmc.kore.testutils.eventserver.EventPacket; +import org.xbmc.kore.testutils.eventserver.EventPacketBUTTON; +import org.xbmc.kore.testutils.eventserver.MockEventServer; +import org.xbmc.kore.ui.sections.remote.RemoteActivity; + +import java.io.IOException; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static junit.framework.Assert.assertTrue; + +public class ButtonTests extends AbstractTestClass { + private static MockEventServer mockEventServer; + + @Rule + public ActivityTestRule remoteActivityActivityTestRule = + new ActivityTestRule<>(RemoteActivity.class); + + @Override + protected ActivityTestRule getActivityTestRule() { + return remoteActivityActivityTestRule; + } + + @Override + protected void setSharedPreferences(Context context) { + Utils.setUseEventServerPreference(context, true); + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + hostInfo.setKodiVersionMajor(17); + } + + @BeforeClass + public static void setupEventServer() throws Throwable { + mockEventServer = new MockEventServer(); + mockEventServer.setListenPort(HostInfo.DEFAULT_EVENT_SERVER_PORT); + mockEventServer.start(); + } + + @After + public void resetState() { + mockEventServer.reset(); + } + + @AfterClass + public static void cleanup() throws IOException { + mockEventServer.shutdown(); + } + + @Test + public void leftControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.left)).perform(click()); + + testRemoteButton(ButtonCodes.REMOTE_LEFT); + } + + @Test + public void rightControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.right)).perform(click()); + + testRemoteButton(ButtonCodes.REMOTE_RIGHT); + } + + @Test + public void upControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.up)).perform(click()); + + testRemoteButton(ButtonCodes.REMOTE_UP); + } + + @Test + public void downControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.down)).perform(click()); + + testRemoteButton(ButtonCodes.REMOTE_DOWN); + } + + @Test + public void selectPadButtonTest() throws InterruptedException { + onView(withId(R.id.select)).perform(click()); + + testRemoteButton(ButtonCodes.REMOTE_SELECT); + } + + //The following tests do not use the event server. They're included here + //to make sure they still work when the event server is enabled. + @Test + public void contextControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.context)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU); + } + + @Test + public void infoControlPadButtonTest() throws InterruptedException { + HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17); + + onView(withId(R.id.info)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.INFO); + } + + @Test + public void infoControlPadButtonLongClickTest() throws InterruptedException { + onView(withId(R.id.info)).perform(longClick()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO); + } + + @Test + public void osdControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.osd)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD); + } + + @Test + public void backControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.back)).perform(click()); + + TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null); + } + + private void testRemoteButton(String buttonName) { + EventPacket packet = mockEventServer.getEventPacket(); + assertTrue(packet != null); + assertTrue(packet.getPacketType() == EventPacket.PT_BUTTON); + assertTrue(((EventPacketBUTTON) packet).getButtonName().contentEquals(buttonName)); + assertTrue(((EventPacketBUTTON) packet).getMapName().contentEquals(ButtonCodes.MAP_REMOTE)); + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/KodiPreV17Tests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/KodiPreV17Tests.java new file mode 100644 index 0000000..33e2d7b --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/eventserver/KodiPreV17Tests.java @@ -0,0 +1,92 @@ +/* + * 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.remote.controlpad.eventserver; + +import android.content.Context; +import android.support.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.xbmc.kore.R; +import org.xbmc.kore.host.HostInfo; +import org.xbmc.kore.host.HostManager; +import org.xbmc.kore.jsonrpc.method.Input; +import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.tests.ui.AbstractTestClass; +import org.xbmc.kore.testutils.eventserver.MockEventServer; +import org.xbmc.kore.ui.sections.remote.RemoteActivity; + +import java.io.IOException; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static junit.framework.Assert.assertTrue; + +public class KodiPreV17Tests extends AbstractTestClass { + private static MockEventServer mockEventServer; + + @Rule + public ActivityTestRule remoteActivityActivityTestRule = + new ActivityTestRule<>(RemoteActivity.class); + + @Override + protected ActivityTestRule getActivityTestRule() { + return remoteActivityActivityTestRule; + } + + @Override + protected void setSharedPreferences(Context context) { + Utils.setUseEventServerPreference(context, true); + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + hostInfo.setKodiVersionMajor(16); + } + + @BeforeClass + public static void setupEventServer() throws Throwable { + mockEventServer = new MockEventServer(); + mockEventServer.setListenPort(HostInfo.DEFAULT_EVENT_SERVER_PORT); + mockEventServer.start(); + } + + @After + public void resetState() { + mockEventServer.reset(); + } + + @AfterClass + public static void cleanup() throws IOException { + mockEventServer.shutdown(); + } + + @Test + public void infoControlPadButtonLongClickTest() throws InterruptedException { + HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(16); + + onView(withId(R.id.info)).perform(longClick()); + + String actionReceived = getInputHandler().getAction(); + assertTrue(actionReceived != null); + assertTrue(actionReceived.contentEquals(Input.ExecuteAction.CODECINFO)); + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/ButtonTests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/ButtonTests.java new file mode 100644 index 0000000..b7d0e6b --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/ButtonTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Martijn Brekhof. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xbmc.kore.tests.ui.remote.controlpad.http; + +import android.content.Context; +import android.support.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.xbmc.kore.R; +import org.xbmc.kore.host.HostInfo; +import org.xbmc.kore.host.HostManager; +import org.xbmc.kore.jsonrpc.method.Input; +import org.xbmc.kore.testhelpers.TestUtils; +import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.tests.ui.AbstractTestClass; +import org.xbmc.kore.ui.sections.remote.RemoteActivity; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +public class ButtonTests extends AbstractTestClass { + @Rule + public ActivityTestRule remoteActivityActivityTestRule = + new ActivityTestRule<>(RemoteActivity.class); + + @Override + protected ActivityTestRule getActivityTestRule() { + return remoteActivityActivityTestRule; + } + + @Override + protected void setSharedPreferences(Context context) { + Utils.setUseEventServerPreference(context, false); + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + hostInfo.setKodiVersionMajor(17); + } + + @Test + public void leftControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.left)).perform(click()); + + TestUtils.testHTTPEvent(Input.Left.METHOD_NAME, null); + } + + @Test + public void rightControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.right)).perform(click()); + + TestUtils.testHTTPEvent(Input.Right.METHOD_NAME, null); + } + + @Test + public void upControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.up)).perform(click()); + + TestUtils.testHTTPEvent(Input.Up.METHOD_NAME, null); + } + + @Test + public void downControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.down)).perform(click()); + + TestUtils.testHTTPEvent(Input.Down.METHOD_NAME, null); + } + + @Test + public void selectPadButtonTest() throws InterruptedException { + onView(withId(R.id.select)).perform(click()); + + TestUtils.testHTTPEvent(Input.Select.METHOD_NAME, null); + } + + @Test + public void contextControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.context)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU); + } + + @Test + public void infoControlPadButtonTest() throws InterruptedException { + HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17); + + onView(withId(R.id.info)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.INFO); + } + + @Test + public void infoControlPadButtonLongClickTest() throws InterruptedException { + onView(withId(R.id.info)).perform(longClick()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO); + } + + @Test + public void osdControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.osd)).perform(click()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD); + } + + @Test + public void backControlPadButtonTest() throws InterruptedException { + onView(withId(R.id.back)).perform(click()); + + TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null); + } +} diff --git a/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/KodiPreV17Tests.java b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/KodiPreV17Tests.java new file mode 100644 index 0000000..4c00032 --- /dev/null +++ b/app/src/androidTest/java/org/xbmc/kore/tests/ui/remote/controlpad/http/KodiPreV17Tests.java @@ -0,0 +1,62 @@ +/* + * 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.remote.controlpad.http; + +import android.content.Context; +import android.support.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.xbmc.kore.R; +import org.xbmc.kore.host.HostInfo; +import org.xbmc.kore.jsonrpc.method.Input; +import org.xbmc.kore.testhelpers.TestUtils; +import org.xbmc.kore.testhelpers.Utils; +import org.xbmc.kore.tests.ui.AbstractTestClass; +import org.xbmc.kore.ui.sections.remote.RemoteActivity; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +public class KodiPreV17Tests extends AbstractTestClass { + @Rule + public ActivityTestRule remoteActivityActivityTestRule = + new ActivityTestRule<>(RemoteActivity.class); + + @Override + protected ActivityTestRule getActivityTestRule() { + return remoteActivityActivityTestRule; + } + + @Override + protected void setSharedPreferences(Context context) { + Utils.setUseEventServerPreference(context, false); + } + + @Override + protected void configureHostInfo(HostInfo hostInfo) { + hostInfo.setKodiVersionMajor(16); + } + + @Test + public void infoControlPadButtonLongClickTest() throws InterruptedException { + onView(withId(R.id.info)).perform(longClick()); + + TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CODECINFO); + } +} diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/Database.java b/app/src/testUtils/java/org/xbmc/kore/testutils/Database.java index 2ae1dda..5053b63 100644 --- a/app/src/testUtils/java/org/xbmc/kore/testutils/Database.java +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/Database.java @@ -66,14 +66,15 @@ public class Database { public static HostInfo addHost(Context context) { return addHost(context, "127.0.0.1", HostConnection.PROTOCOL_TCP, - HostInfo.DEFAULT_HTTP_PORT, HostInfo.DEFAULT_TCP_PORT); + HostInfo.DEFAULT_HTTP_PORT, HostInfo.DEFAULT_TCP_PORT, false); } - public static HostInfo addHost(Context context, String hostname, int protocol, int httpPort, int tcpPort) { + public static HostInfo addHost(Context context, String hostname, int protocol, int httpPort, + int tcpPort, boolean useEventServer) { return HostManager.getInstance(context).addHost("TestHost", hostname, protocol, httpPort, tcpPort, null, null, "52:54:00:12:35:02", 9, - false, HostInfo.DEFAULT_EVENT_SERVER_PORT, + useEventServer, HostInfo.DEFAULT_EVENT_SERVER_PORT, HostInfo.DEFAULT_KODI_VERSION_MAJOR, HostInfo.DEFAULT_KODI_VERSION_MINOR, HostInfo.DEFAULT_KODI_VERSION_REVISION, diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacket.java b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacket.java new file mode 100644 index 0000000..25cc38c --- /dev/null +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacket.java @@ -0,0 +1,128 @@ +/* + * 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.testutils.eventserver; + +import org.xbmc.kore.eventclient.Packet; +import org.xbmc.kore.utils.LogUtils; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * Class that implements a single event packet. + *
+ *   -----------------------------
+ *   | -H1 Signature ("XBMC")    | - 4  x CHAR                4B
+ *   | -H2 Version (eg. 2.0)     | - 2  x UNSIGNED CHAR       2B
+ *   | -H3 PacketType            | - 1  x UNSIGNED SHORT      2B
+ *   | -H4 Sequence number       | - 1  x UNSIGNED LONG       4B
+ *   | -H5 No. of packets in msg | - 1  x UNSIGNED LONG       4B
+ *   | -H6 Payloadsize of packet | - 1  x UNSIGNED SHORT      2B
+ *   | -H7 Client's unique token | - 1  x UNSIGNED LONG       4B
+ *   | -H8 Reserved              | - 10 x UNSIGNED CHAR      10B
+ *   |---------------------------|
+ *   | -P1 payload               | -
+ *   -----------------------------
+ * 
+ */ +abstract public class EventPacket { + + private static final String TAG = LogUtils.makeLogTag(EventPacket.class); + + //Package types + public final static byte PT_BUTTON = 0x03; + + private String signature; + private String version; + private int packetType; + private long sequenceNumber; + private long numberOfPackets; + private int payloadSize; + private long token; + + private byte[] payload; + + private EventPacket() {} + + EventPacket(byte[] packet) { + signature = new String(new byte[] {packet[0], packet[1], packet[2], packet[3]}); + version = ((int) packet[4]) + "." + ((int) packet[5]); + packetType = ByteBuffer.wrap(packet, 6, 2).getChar(); + sequenceNumber = ByteBuffer.wrap(packet, 8, 4).getInt(); + numberOfPackets = ByteBuffer.wrap(packet, 12, 4).getInt(); + payloadSize = ByteBuffer.wrap(packet, 16, 2).getChar(); + token = ByteBuffer.wrap(packet, 18, 4).getInt(); + //Reserved 22 - 32 + payload = new byte[payloadSize]; + ByteBuffer.wrap(packet, 32, payloadSize).get(payload); + } + + @Override + public String toString() { + return signature + ":" + + version + ":" + + packetType + ":" + + sequenceNumber + ":" + + numberOfPackets + ":" + + payloadSize + ":" + + token+ ":" + + payload; + } + + public int getPacketType() { + return packetType; + } + + public byte[] getPayload() { + return payload; + } + + /** + * Returns the packet type from a {@link Packet} as a single byte. + *
+ * Note that, although the specification specifies two bytes, + * we only use a single byte in {@link Packet} for the packet types. + * @param packet + * @return second byte of packet type + */ + static public byte getPacketType(byte[] packet) { + return packet[7]; + } + + /** + * Gets the string from payload terminated by 0x00. + * @param payload byte array holding the characters + * @param offset starting offset of string + * @return string from payload or null if not found + */ + String getStringFromPayload(byte[] payload, int offset) { + int strTerminatorIndex = offset; + for (; strTerminatorIndex < payload.length; strTerminatorIndex++) { + if (payload[strTerminatorIndex] == 0x00) + break; + } + + if (strTerminatorIndex == payload.length) + return null; + + int stringLength = strTerminatorIndex - offset; + byte[] bytes = new byte[stringLength]; + System.arraycopy(payload, offset, bytes, 0, stringLength); + + return new String(bytes); + } +} diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacketBUTTON.java b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacketBUTTON.java new file mode 100644 index 0000000..8795798 --- /dev/null +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/EventPacketBUTTON.java @@ -0,0 +1,64 @@ +/* + * 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.testutils.eventserver; + +import java.nio.ByteBuffer; + +public class EventPacketBUTTON extends EventPacket { + + private short code; + private String mapName; + private String buttonName; + private boolean repeat; + private boolean down; + private boolean queue; + private short amount; + private byte axis; + private short flags; + + public EventPacketBUTTON(byte[] packet) { + super(packet); + + byte[] payload = getPayload(); + code = ByteBuffer.wrap(payload, 0, 2).getShort(); + flags = ByteBuffer.wrap(payload, 2, 2).getShort(); + amount = ByteBuffer.wrap(payload, 4, 2).getShort(); + + mapName = getStringFromPayload(payload, 6); + + int nextStringPosition = 6 + mapName.getBytes().length + 1; + buttonName = getStringFromPayload(payload, nextStringPosition); + } + + public String getButtonName() { + return buttonName; + } + + public String getMapName() { + return mapName; + } + + @Override + public String toString() { + return super.toString() + + ", code: " + code + + ", flags: " + flags + + ", amount: " + amount + + ", mapName: " + mapName + + ", buttonName: " + buttonName; + } +} diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/MockEventServer.java b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/MockEventServer.java new file mode 100644 index 0000000..ae6d776 --- /dev/null +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/eventserver/MockEventServer.java @@ -0,0 +1,84 @@ +/* + * 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.testutils.eventserver; + +import org.xbmc.kore.utils.LogUtils; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; + +public class MockEventServer extends Thread { + private static final String TAG = LogUtils.makeLogTag(MockEventServer.class); + + private int listenPort = 9997; + private boolean keepRunning; + private EventPacket packet; + private DatagramSocket datagramSocket; + + public MockEventServer() { + } + + public void setListenPort(int portNumber) { + this.listenPort = portNumber; + } + + public void run() { + try { + datagramSocket = new DatagramSocket(this.listenPort); + } catch (SocketException e) { + System.out.println("MockEventServer: Failed to open socket: " + e.getMessage()); + return; + } + + keepRunning = true; + while(keepRunning) { + byte[] buf = new byte[1024]; + DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); + try { + datagramSocket.receive(datagramPacket); + packet = new EventPacketBUTTON(datagramPacket.getData()); + } catch (IOException e) { + System.out.println("MockEventServer: error receiving packet: " + e.getMessage()); + } + } + } + + /** + * Returns the last received packet + * @return + */ + public EventPacket getEventPacket() { + return packet; + } + + /** + * Stops the server from listening for new packets + */ + public void shutdown() { + keepRunning = false; + datagramSocket.close(); + } + + /** + * Resets the state of the event server + */ + public void reset() { + packet = null; + } +} diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/InputHandler.java b/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/InputHandler.java new file mode 100644 index 0000000..c2976d8 --- /dev/null +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/InputHandler.java @@ -0,0 +1,98 @@ +/* + * 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.testutils.tcpserver.handlers; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.xbmc.kore.jsonrpc.method.Input; +import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse; +import org.xbmc.kore.utils.LogUtils; + +import java.util.ArrayList; + +/** + * Simulates Input JSON-RPC API + */ +public class InputHandler implements JSONConnectionHandlerManager.ConnectionHandler { + private static final String TAG = LogUtils.makeLogTag(InputHandler.class); + + private static final String ACTION = "action"; + private static final String PARAMS_NODE = "params"; + + private String action; + private String methodName; + + @Override + public ArrayList getNotifications() { + return null; + } + + @Override + public void reset() { + } + + @Override + public String[] getType() { + return new String[]{Input.ExecuteAction.METHOD_NAME, + Input.Back.METHOD_NAME, + Input.Up.METHOD_NAME, + Input.Down.METHOD_NAME, + Input.Left.METHOD_NAME, + Input.Right.METHOD_NAME, + Input.Select.METHOD_NAME, + }; + } + + @Override + public ArrayList getResponse(String method, ObjectNode jsonRequest) { + ArrayList jsonResponses = new ArrayList<>(); + + methodName = method; + + switch (method) { + case Input.ExecuteAction.METHOD_NAME: + action = jsonRequest.get(PARAMS_NODE).get(ACTION).asText(); + break; + case Input.Left.METHOD_NAME: + case Input.Right.METHOD_NAME: + case Input.Up.METHOD_NAME: + case Input.Down.METHOD_NAME: + case Input.Select.METHOD_NAME: + // These inputs do not have an action + break; + default: + LogUtils.LOGD(TAG, "method: " + method + ", not implemented"); + } + return jsonResponses; + } + + /** + * Returns the last received action + * @return + */ + public String getAction() { + return action; + } + + /** + * Returns the last received method name + * @return + */ + public String getMethodName() { + return methodName; + } +} diff --git a/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/JSONConnectionHandlerManager.java b/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/JSONConnectionHandlerManager.java index ff354f0..e910bbc 100644 --- a/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/JSONConnectionHandlerManager.java +++ b/app/src/testUtils/java/org/xbmc/kore/testutils/tcpserver/handlers/JSONConnectionHandlerManager.java @@ -65,7 +65,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 + * @return {@link JsonResponse} that should be sent to the client or null if there are no notifications */ ArrayList getNotifications();