diff options
author | BrettyWhite <geekman3454@protonmail.com> | 2019-06-04 10:27:25 -0400 |
---|---|---|
committer | BrettyWhite <geekman3454@protonmail.com> | 2019-06-04 10:27:25 -0400 |
commit | 031dea755899a2b06ff65422baa88a0b2aad1ade (patch) | |
tree | 06c8907a1c6da94c702cea2373404bff9b1eea8f | |
parent | 6287c6a82a167ed2059b65eb636fce958285dd07 (diff) | |
parent | 5601d6132e14f6f52ac94d96c82f8653fba4d0f8 (diff) | |
download | sdl_android-031dea755899a2b06ff65422baa88a0b2aad1ade.tar.gz |
Merge branch 'develop' into feature/choice_set_manager
# Conflicts:
# base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java
33 files changed, 4193 insertions, 138 deletions
diff --git a/.travis.yml b/.travis.yml index 6c25dd665..96bbd4a27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ android: - ndk-bundle # The BuildTools version used by your project - - build-tools-27.0.3 + - build-tools-28.0.3 # The SDK version used to compile your project - android-28 @@ -52,7 +52,7 @@ script: - ./javaEE/gradlew -p ./javaEE test before_install: - - echo yes | sdkmanager "build-tools;27.0.3" + - echo yes | sdkmanager "build-tools;28.0.3" after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/android/build.gradle b/android/build.gradle index 800d3be0e..f94362a81 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.3.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index edbfc86a9..567cd660e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Apr 8 12:14:33 EDT 2018 +#Sun Jan 20 22:30:48 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
\ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
\ No newline at end of file diff --git a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java index b180c851b..e4f1919df 100755 --- a/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java +++ b/android/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java @@ -15,24 +15,29 @@ import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.SdlManagerListener; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.screen.menu.MenuCell; +import com.smartdevicelink.managers.screen.menu.MenuSelectionListener; +import com.smartdevicelink.managers.screen.menu.VoiceCommand; +import com.smartdevicelink.managers.screen.menu.VoiceCommandSelectionListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCNotification; import com.smartdevicelink.proxy.TTSChunkFactory; -import com.smartdevicelink.proxy.rpc.AddCommand; -import com.smartdevicelink.proxy.rpc.MenuParams; -import com.smartdevicelink.proxy.rpc.OnCommand; +import com.smartdevicelink.proxy.rpc.Alert; import com.smartdevicelink.proxy.rpc.OnHMIStatus; import com.smartdevicelink.proxy.rpc.Speak; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.transport.MultiplexTransportConfig; import com.smartdevicelink.transport.TCPTransportConfig; import com.smartdevicelink.util.DebugTool; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Vector; public class SdlService extends Service { @@ -49,7 +54,6 @@ public class SdlService extends Service { private static final String WELCOME_SPEAK = "Welcome to Hello S D L"; private static final String TEST_COMMAND_NAME = "Test Command"; - private static final int TEST_COMMAND_ID = 1; private static final int FOREGROUND_SERVICE_ID = 111; @@ -159,28 +163,13 @@ public class SdlService extends Service { public void onNotified(RPCNotification notification) { OnHMIStatus status = (OnHMIStatus) notification; if (status.getHmiLevel() == HMILevel.HMI_FULL && ((OnHMIStatus) notification).getFirstRun()) { - sendCommands(); + setVoiceCommands(); + sendMenus(); performWelcomeSpeak(); performWelcomeShow(); } } }); - - // Menu Selected Listener - sdlManager.addOnRPCNotificationListener(FunctionID.ON_COMMAND, new OnRPCNotificationListener() { - @Override - public void onNotified(RPCNotification notification) { - OnCommand command = (OnCommand) notification; - Integer id = command.getCmdID(); - if(id != null){ - switch(id){ - case TEST_COMMAND_ID: - showTest(); - break; - } - } - } - }); } @Override @@ -207,16 +196,87 @@ public class SdlService extends Service { } /** - * Add commands for the app on SDL. + * Send some voice commands */ - private void sendCommands(){ - AddCommand command = new AddCommand(); - MenuParams params = new MenuParams(); - params.setMenuName(TEST_COMMAND_NAME); - command.setCmdID(TEST_COMMAND_ID); - command.setMenuParams(params); - command.setVrCommands(Collections.singletonList(TEST_COMMAND_NAME)); - sdlManager.sendRPC(command); + private void setVoiceCommands(){ + + List<String> list1 = Collections.singletonList("Command One"); + List<String> list2 = Collections.singletonList("Command two"); + + VoiceCommand voiceCommand1 = new VoiceCommand(list1, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 1 triggered"); + } + }); + + VoiceCommand voiceCommand2 = new VoiceCommand(list2, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 2 triggered"); + } + }); + + sdlManager.getScreenManager().setVoiceCommands(Arrays.asList(voiceCommand1,voiceCommand2)); + } + + /** + * Add menus for the app on SDL. + */ + private void sendMenus(){ + + // some arts + SdlArtwork livio = new SdlArtwork("livio", FileType.GRAPHIC_PNG, R.drawable.sdl, false); + + // some voice commands + List<String> voice2 = Collections.singletonList("Cell two"); + + MenuCell mainCell1 = new MenuCell("Test Cell 1 (speak)", livio, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 1 triggered. Source: "+ trigger.toString()); + showTest(); + } + }); + + MenuCell mainCell2 = new MenuCell("Test Cell 2", null, voice2, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // SUB MENU + + MenuCell subCell1 = new MenuCell("SubCell 1",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 1 triggered. Source: "+ trigger.toString()); + } + }); + + MenuCell subCell2 = new MenuCell("SubCell 2",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // sub menu parent cell + MenuCell mainCell3 = new MenuCell("Test Cell 3 (sub menu)", null, Arrays.asList(subCell1,subCell2)); + + MenuCell mainCell4 = new MenuCell("Clear the menu",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Clearing Menu. Source: "+ trigger.toString()); + // Clear this thing + sdlManager.getScreenManager().setMenu(Collections.<MenuCell>emptyList()); + showAlert("Menu Cleared"); + } + }); + + // Send the entire menu off to be created + sdlManager.getScreenManager().setMenu(Arrays.asList(mainCell1, mainCell2, mainCell3, mainCell4)); } /** @@ -251,12 +311,19 @@ public class SdlService extends Service { */ private void showTest(){ sdlManager.getScreenManager().beginTransaction(); - sdlManager.getScreenManager().setTextField1("Command has been selected"); + sdlManager.getScreenManager().setTextField1("Test Cell 1 has been selected"); sdlManager.getScreenManager().setTextField2(""); sdlManager.getScreenManager().commit(null); sdlManager.sendRPC(new Speak(TTSChunkFactory.createSimpleTTSChunks(TEST_COMMAND_NAME))); } + private void showAlert(String text){ + Alert alert = new Alert(); + alert.setAlertText1(text); + alert.setDuration(5000); + sdlManager.sendRPC(alert); + } + } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java index 872e09b81..23c60e295 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java @@ -4,10 +4,12 @@ import com.smartdevicelink.AndroidTestCase2; import com.smartdevicelink.managers.BaseSubManager; import com.smartdevicelink.managers.file.FileManager; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdatesMode; import com.smartdevicelink.proxy.interfaces.ISdl; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.MetadataType; import com.smartdevicelink.proxy.rpc.enums.TextAlignment; +import com.smartdevicelink.test.Test; import java.util.Arrays; import java.util.List; @@ -53,9 +55,12 @@ public class ScreenManagerTests extends AndroidTestCase2 { assertNull(screenManager.getTextField2Type()); assertNull(screenManager.getTextField3Type()); assertNull(screenManager.getTextField4Type()); + assertNull(screenManager.getMenu()); + assertNull(screenManager.getVoiceCommands()); assertTrue(screenManager.getSoftButtonObjects().isEmpty()); assertNull(screenManager.getSoftButtonObjectByName("test")); assertNull(screenManager.getSoftButtonObjectById(1)); + assertEquals(screenManager.getDynamicMenuUpdatesMode(), DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE); assertEquals(screenManager.getState(), BaseSubManager.READY); } @@ -114,6 +119,19 @@ public class ScreenManagerTests extends AndroidTestCase2 { assertEquals(screenManager.getTextField4Type(), MetadataType.MEDIA_GENRE); } + public void testSetMenuManagerFields(){ + screenManager.setDynamicMenuUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + screenManager.setMenu(Test.GENERAL_MENUCELL_LIST); + + assertEquals(screenManager.getMenu(), Test.GENERAL_MENUCELL_LIST); + assertEquals(screenManager.getDynamicMenuUpdatesMode(), DynamicMenuUpdatesMode.FORCE_ON); + } + + public void testSetVoiceCommands(){ + screenManager.setVoiceCommands(Test.GENERAL_VOICE_COMMAND_LIST); + assertEquals(screenManager.getVoiceCommands(), Test.GENERAL_VOICE_COMMAND_LIST); + } + public void testSetSoftButtonObjects(){ // Create softButtonObject1 SoftButtonState softButtonState1 = new SoftButtonState("object1-state1", "it is", testArtwork); diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesModeTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesModeTests.java new file mode 100644 index 000000000..2d2a8853c --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesModeTests.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class DynamicMenuUpdatesModeTests extends TestCase { + + /** + * Verifies that the enum values are not null upon valid assignment. + */ + public void testValidEnums() { + String example = "FORCE_ON"; + DynamicMenuUpdatesMode forceOn = DynamicMenuUpdatesMode.valueForString(example); + example = "FORCE_OFF"; + DynamicMenuUpdatesMode forceOff = DynamicMenuUpdatesMode.valueForString(example); + example = "ON_WITH_COMPAT_MODE"; + DynamicMenuUpdatesMode onWithCompatMode = DynamicMenuUpdatesMode.valueForString(example); + + assertNotNull("FORCE_ON returned null", forceOn); + assertNotNull("FORCE_OFF returned null", forceOff); + assertNotNull("ON_WITH_COMPAT_MODE returned null", onWithCompatMode); + } + + /** + * Verifies that an invalid assignment is null. + */ + public void testInvalidEnum() { + String example = "deFaUlt"; + try { + DynamicMenuUpdatesMode temp = DynamicMenuUpdatesMode.valueForString(example); + assertNull("Result of valueForString should be null.", temp); + } catch (IllegalArgumentException exception) { + fail("Invalid enum throws IllegalArgumentException."); + } + } + + /** + * Verifies that a null assignment is invalid. + */ + public void testNullEnum() { + String example = null; + try { + DynamicMenuUpdatesMode temp = DynamicMenuUpdatesMode.valueForString(example); + assertNull("Result of valueForString should be null.", temp); + } catch (NullPointerException exception) { + fail("Null string throws NullPointerException."); + } + } + + /** + * Verifies the possible enum values of DynamicMenuUpdatesMode. + */ + public void testListEnum() { + List<DynamicMenuUpdatesMode> enumValueList = Arrays.asList(DynamicMenuUpdatesMode.values()); + + List<DynamicMenuUpdatesMode> enumTestList = new ArrayList<>(); + enumTestList.add(DynamicMenuUpdatesMode.FORCE_ON); + enumTestList.add(DynamicMenuUpdatesMode.FORCE_OFF); + enumTestList.add(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE); + + assertTrue("Enum value list does not match enum class list", + enumValueList.containsAll(enumTestList) && enumTestList.containsAll(enumValueList)); + } +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuCellTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuCellTests.java new file mode 100644 index 000000000..9699a3899 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuCellTests.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; +import com.smartdevicelink.test.Test; + + +public class MenuCellTests extends AndroidTestCase2 { + + private MenuSelectionListener menuSelectionListener = new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + // stuff + } + }; + + @Override + public void setUp() throws Exception{ + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testSettersAndGetters(){ + + // set everything + MenuCell menuCell = new MenuCell(Test.GENERAL_STRING, null, null, menuSelectionListener); + menuCell.setIcon(Test.GENERAL_ARTWORK); + menuCell.setVoiceCommands(Test.GENERAL_STRING_LIST); + menuCell.setMenuSelectionListener(menuSelectionListener); + + // use getters and assert equality + assertEquals(menuCell.getTitle(), Test.GENERAL_STRING); + assertEquals(menuCell.getIcon(), Test.GENERAL_ARTWORK); + assertEquals(menuCell.getVoiceCommands(), Test.GENERAL_STRING_LIST); + assertEquals(menuCell.getMenuSelectionListener(), menuSelectionListener); + assertEquals(menuCell.getCellId(), Test.GENERAL_MENU_MAX_ID); + assertEquals(menuCell.getParentCellId(), Test.GENERAL_MENU_MAX_ID); + } + + public void testConstructors(){ + + // first constructor was tested in previous method, use the last two here + + MenuCell menuCell3 =new MenuCell(Test.GENERAL_STRING, Test.GENERAL_ARTWORK, Test.GENERAL_STRING_LIST, menuSelectionListener); + assertEquals(menuCell3.getTitle(), Test.GENERAL_STRING); + assertEquals(menuCell3.getIcon(), Test.GENERAL_ARTWORK); + assertEquals(menuCell3.getVoiceCommands(), Test.GENERAL_STRING_LIST); + assertEquals(menuCell3.getMenuSelectionListener(), menuSelectionListener); + + MenuCell menuCell4 =new MenuCell(Test.GENERAL_STRING,null, null, menuSelectionListener); + assertEquals(menuCell4.getTitle(), Test.GENERAL_STRING); + assertEquals(menuCell4.getMenuSelectionListener(), menuSelectionListener); + } + + public void testEquality(){ + + //We should use assertTrue (or assertFalse) because we want to use the overridden equals() method + + MenuCell menuCell = new MenuCell(Test.GENERAL_STRING, Test.GENERAL_ARTWORK, Test.GENERAL_STRING_LIST, menuSelectionListener); + MenuCell menuCell2 = new MenuCell(Test.GENERAL_STRING, Test.GENERAL_ARTWORK, Test.GENERAL_STRING_LIST, menuSelectionListener); + + // these are the same object, should be equal. + assertTrue(menuCell.equals(menuCell)); + + // Make sure these are marked as equals, even though they are different objects + assertTrue(menuCell.equals(menuCell2)); + + MenuCell menuCell3 = new MenuCell(Test.GENERAL_STRING, null, Test.GENERAL_STRING_LIST, menuSelectionListener); + + // these should be different + assertFalse(menuCell.equals(menuCell3)); + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java new file mode 100644 index 000000000..73581cab4 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.R; +import com.smartdevicelink.managers.BaseSubManager; +import com.smartdevicelink.managers.CompletionListener; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.rpc.OnCommand; +import com.smartdevicelink.proxy.rpc.OnHMIStatus; +import com.smartdevicelink.proxy.rpc.enums.FileType; +import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.SystemContext; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * the Algorithm specific tests are defined based on: https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0210-mobile-dynamic-menu-cell-updating.md + */ +public class MenuManagerTests extends AndroidTestCase2 { + + private OnRPCNotificationListener onHMIStatusListener, commandListener; + private MenuManager menuManager; + private List<MenuCell> cells; + private MenuCell mainCell1, mainCell4; + + // SETUP / HELPERS + + @Override + public void setUp() throws Exception{ + super.setUp(); + + cells = createTestCells(); + + ISdl internalInterface = mock(ISdl.class); + FileManager fileManager = mock(FileManager.class); + + // When internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, OnRPCNotificationListener) is called + // inside MenuManager's constructor, then keep a reference to the OnRPCNotificationListener so we can trigger it later + // to emulate what Core does when it sends OnHMIStatus notification + Answer<Void> onHMIStatusAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + onHMIStatusListener = (OnRPCNotificationListener) args[1]; + return null; + } + }; + doAnswer(onHMIStatusAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_HMI_STATUS), any(OnRPCNotificationListener.class)); + + Answer<Void> onCommandAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + commandListener = (OnRPCNotificationListener) args[1]; + return null; + } + }; + doAnswer(onCommandAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_COMMAND), any(OnRPCNotificationListener.class)); + + menuManager = new MenuManager(internalInterface, fileManager); + + // Check some stuff during setup + assertEquals(menuManager.currentHMILevel, HMILevel.HMI_NONE); + assertEquals(menuManager.getState(), BaseSubManager.SETTING_UP); + assertEquals(menuManager.currentSystemContext, SystemContext.SYSCTXT_MAIN); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE); + assertEquals(menuManager.lastMenuId, 1); + assertNull(menuManager.menuCells); + assertNull(menuManager.waitingUpdateMenuCells); + assertNull(menuManager.oldMenuCells); + assertNull(menuManager.inProgressUpdate); + assertNull(menuManager.keepsNew); + assertNull(menuManager.keepsOld); + assertNotNull(menuManager.hmiListener); + assertNotNull(menuManager.commandListener); + assertNotNull(menuManager.displayListener); + + } + + @Override + public void tearDown() throws Exception { + + menuManager.dispose(); + + assertEquals(menuManager.currentSystemContext, SystemContext.SYSCTXT_MAIN); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE); + assertEquals(menuManager.lastMenuId, 1); + assertNull(menuManager.menuCells); + assertNull(menuManager.oldMenuCells); + assertNull(menuManager.currentHMILevel); + assertNull(menuManager.displayCapabilities); + assertNull(menuManager.inProgressUpdate); + assertNull(menuManager.waitingUpdateMenuCells); + assertNull(menuManager.keepsNew); + assertNull(menuManager.keepsOld); + + // after everything, make sure we are in the correct state + assertEquals(menuManager.getState(), BaseSubManager.SHUTDOWN); + + super.tearDown(); + } + + public void testStartMenuManager(){ + + menuManager.start(new CompletionListener() { + @Override + public void onComplete(boolean success) { + assertTrue(success); + // Make sure the state has changed, as the Screen Manager is dependant on it + assertEquals(menuManager.getState(), BaseSubManager.READY); + } + }); + } + + public void testHMINotReady(){ + + menuManager.currentHMILevel = HMILevel.HMI_NONE; + menuManager.setMenuCells(cells); + + // updating voice commands before HMI is ready + assertTrue(menuManager.waitingOnHMIUpdate); + // these are the 2 commands we have waiting + assertEquals(menuManager.waitingUpdateMenuCells.size(), 4); + assertEquals(menuManager.currentHMILevel, HMILevel.HMI_NONE); + // The Menu Manager should send new menu once HMI full occurs + sendFakeCoreOnHMIFullNotifications(); + // Listener should be triggered - which sets new HMI level and should proceed to send our pending update + assertEquals(menuManager.currentHMILevel, HMILevel.HMI_FULL); + // This being false means it received the hmi notification and sent the pending commands + assertFalse(menuManager.waitingOnHMIUpdate); + } + + public void testUpdatingOldWay(){ + + // Force Menu Manager to use the old way of deleting / sending all + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_OFF); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_OFF); + // when we only send one command to update, we should only be returned one add command + List<MenuCell> newArray = Arrays.asList(mainCell1, mainCell4); + assertEquals(menuManager.allCommandsForCells(newArray, false).size(), 4); // 1 root cells, 1 sub menu root cell, 2 sub menu cells + menuManager.currentHMILevel = HMILevel.HMI_FULL; + menuManager.setMenuCells(newArray); + // Algorithm should NOT have run + assertNull(menuManager.keepsNew); + assertNull(menuManager.keepsOld); + + // Unlike voice commands, the Menu Manager dynamically assigns Cell ID's. Because of this, we need to get the updated + // cell list after setting it and then test the listeners, as they use the newly assigned cell ID's. + List<MenuCell> updatedCells = menuManager.getMenuCells(); + + for (MenuCell cell : updatedCells){ + + // grab 2 of our newly updated cells - 1 root and 1 sub cell, and make sure they can get triggered + if (cell.getTitle().equalsIgnoreCase("Test Cell 1")){ + // Fake onCommand - we want to make sure that we can pass back onCommand events to our root Menu Cell + OnCommand onCommand = new OnCommand(); + onCommand.setCmdID(cell.getCellId()); + onCommand.setTriggerSource(TriggerSource.TS_MENU); // these are menu commands + commandListener.onNotified(onCommand); // send off the notification + + // verify the mock listener has only been hit once for a root cell + verify(cell.getMenuSelectionListener(), times(1)).onTriggered(TriggerSource.TS_MENU); + } + + if (cell.getTitle().equalsIgnoreCase("SubCell 2")){ + // Fake onCommand - we want to make sure that we can pass back onCommand events to our sub Menu Cell + OnCommand onCommand2 = new OnCommand(); + onCommand2.setCmdID(cell.getCellId()); + onCommand2.setTriggerSource(TriggerSource.TS_MENU); // these are menu commands + commandListener.onNotified(onCommand2); // send off the notification + + // verify the mock listener has only been hit once for a sub cell + verify(cell.getMenuSelectionListener(), times(1)).onTriggered(TriggerSource.TS_MENU); + } + } + } + + public void testAlgorithmTest1(){ + + // Force Menu Manager to use the new way + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON); + + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu1(); + List<MenuCell> newMenu = createDynamicMenu1New(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 4); + + // this happens in the menu manager but lets make sure its behaving + RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu); + + List<Integer> oldMenuScore = Arrays.asList(0,0,0,0); + List<Integer> newMenuScore = Arrays.asList(0,0,0,0,1); + + assertEquals(runScore.getScore(), 1); + assertEquals(runScore.getOldMenu(), oldMenuScore); + assertEquals(runScore.getCurrentMenu(), newMenuScore); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 5); + assertEquals(menuManager.keepsNew.size(), 4); + assertEquals(menuManager.keepsOld.size(), 4); + } + + public void testAlgorithmTest2(){ + + // Force Menu Manager to use the new way + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON); + + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu2(); + List<MenuCell> newMenu = createDynamicMenu2New(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 4); + + // this happens in the menu manager but lets make sure its behaving + RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu); + + List<Integer> oldMenuScore = Arrays.asList(0,0,0,2); + List<Integer> newMenuScore = Arrays.asList(0,0,0); + + assertEquals(runScore.getScore(), 0); + assertEquals(runScore.getOldMenu(), oldMenuScore); + assertEquals(runScore.getCurrentMenu(), newMenuScore); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 3); + assertEquals(menuManager.keepsNew.size(), 3); + assertEquals(menuManager.keepsOld.size(), 3); + } + + public void testAlgorithmTest3(){ + + // Force Menu Manager to use the new way + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON); + + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu3(); + List<MenuCell> newMenu = createDynamicMenu3New(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 3); + + // this happens in the menu manager but lets make sure its behaving + RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu); + + List<Integer> oldMenuScore = Arrays.asList(2,2,2); + List<Integer> newMenuScore = Arrays.asList(1,1,1); + + assertEquals(runScore.getScore(), 3); + assertEquals(runScore.getOldMenu(), oldMenuScore); + assertEquals(runScore.getCurrentMenu(), newMenuScore); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 3); + assertEquals(menuManager.keepsNew.size(), 0); + assertEquals(menuManager.keepsOld.size(), 0); + } + + public void testAlgorithmTest4(){ + + // Force Menu Manager to use the new way + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON); + + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu4(); + List<MenuCell> newMenu = createDynamicMenu4New(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 4); + + // this happens in the menu manager but lets make sure its behaving + RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu); + + List<Integer> oldMenuScore = Arrays.asList(0,2,0,2); + List<Integer> newMenuScore = Arrays.asList(1,0,1,0); + + assertEquals(runScore.getScore(), 2); + assertEquals(runScore.getOldMenu(), oldMenuScore); + assertEquals(runScore.getCurrentMenu(), newMenuScore); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 4); + assertEquals(menuManager.keepsNew.size(), 2); + assertEquals(menuManager.keepsOld.size(), 2); + } + + public void testAlgorithmTest5(){ + + // Force Menu Manager to use the new way + menuManager.setDynamicUpdatesMode(DynamicMenuUpdatesMode.FORCE_ON); + assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON); + + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu5(); + List<MenuCell> newMenu = createDynamicMenu5New(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 4); + + // this happens in the menu manager but lets make sure its behaving + RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu); + + List<Integer> oldMenuScore = Arrays.asList(2,0,0,0); + List<Integer> newMenuScore = Arrays.asList(0,0,0,1); + + assertEquals(runScore.getScore(), 1); + assertEquals(runScore.getOldMenu(), oldMenuScore); + assertEquals(runScore.getCurrentMenu(), newMenuScore); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 4); + assertEquals(menuManager.keepsNew.size(), 3); + assertEquals(menuManager.keepsOld.size(), 3); + } + + public void testClearingMenu(){ + + // Make sure we can send an empty menu with no issues + // start fresh + menuManager.oldMenuCells = null; + menuManager.menuCells = null; + menuManager.inProgressUpdate = null; + menuManager.waitingUpdateMenuCells = null; + menuManager.waitingOnHMIUpdate = false; + + menuManager.currentHMILevel = HMILevel.HMI_FULL; + // send new cells. They should set the old way + List<MenuCell> oldMenu = createDynamicMenu1(); + List<MenuCell> newMenu = Collections.emptyList(); + menuManager.setMenuCells(oldMenu); + assertEquals(menuManager.menuCells.size(), 4); + + menuManager.setMenuCells(newMenu); + assertEquals(menuManager.menuCells.size(), 0); + } + + // HELPERS + + // Emulate what happens when Core sends OnHMIStatus notification + private void sendFakeCoreOnHMIFullNotifications() { + OnHMIStatus onHMIStatusFakeNotification = new OnHMIStatus(); + onHMIStatusFakeNotification.setHmiLevel(HMILevel.HMI_FULL); + onHMIStatusListener.onNotified(onHMIStatusFakeNotification); + } + + // CREATING CELLS FOR TEST CASES + + private List<MenuCell> createTestCells(){ + + // menu cell mock listener + MenuSelectionListener menuSelectionListener1 = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListener2 = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListener3 = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerSub1 = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerSub2 = mock(MenuSelectionListener.class); + + // some arts + SdlArtwork livio = new SdlArtwork("livio", FileType.GRAPHIC_PNG, R.drawable.sdl_lockscreen_icon, false); + + // some menu cells + List<String> voice2 = Collections.singletonList("Cell two"); + + mainCell1 = new MenuCell("Test Cell 1", livio, null, menuSelectionListener1); + MenuCell mainCell2 = new MenuCell("Test Cell 2", livio, voice2, menuSelectionListener2); + MenuCell mainCell3 = new MenuCell("Test Cell 3",null, null, menuSelectionListener3); + + // SUB MENU + MenuCell subCell1 = new MenuCell("SubCell 1",null, null, menuSelectionListenerSub1); + MenuCell subCell2 = new MenuCell("SubCell 2",null, null, menuSelectionListenerSub2); + + mainCell4 = new MenuCell("Test Cell 4", livio, Arrays.asList(subCell1,subCell2)); // sub menu parent cell + + return Arrays.asList(mainCell1, mainCell2, mainCell3, mainCell4); + } + + private List<MenuCell> createDynamicMenu1(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(A, B, C, D); + + } + + private List<MenuCell> createDynamicMenu1New(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerE = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + MenuCell E = new MenuCell("E", null, null, menuSelectionListenerE); + + return Arrays.asList(A, B, C, D, E); + + } + + private List<MenuCell> createDynamicMenu2(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(A, B, C, D); + + } + + private List<MenuCell> createDynamicMenu2New(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + return Arrays.asList(A, B, C); + + } + + private List<MenuCell> createDynamicMenu3(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + return Arrays.asList(A, B, C); + + } + + private List<MenuCell> createDynamicMenu3New(){ + + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerE = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerF = mock(MenuSelectionListener.class); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + MenuCell E = new MenuCell("E", null, null, menuSelectionListenerE); + + MenuCell F = new MenuCell("F", null, null, menuSelectionListenerF); + + return Arrays.asList(D, E, F); + + } + + private List<MenuCell> createDynamicMenu4(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(A, B, C, D); + + } + + private List<MenuCell> createDynamicMenu4New(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(B, A, D, C); + + } + + private List<MenuCell> createDynamicMenu5(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(A, B, C, D); + + } + + private List<MenuCell> createDynamicMenu5New(){ + + MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class); + MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class); + + MenuCell A = new MenuCell("A", null, null, menuSelectionListenerA); + + MenuCell B = new MenuCell("B", null, null, menuSelectionListenerB); + + MenuCell C = new MenuCell("C",null, null, menuSelectionListenerC); + + MenuCell D = new MenuCell("D", null, null, menuSelectionListenerD); + + return Arrays.asList(B, C, D, A); + + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/RunScoreTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/RunScoreTests.java new file mode 100644 index 000000000..15128293c --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/RunScoreTests.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.test.Test; + +public class RunScoreTests extends AndroidTestCase2 { + + + @Override + public void setUp() throws Exception{ + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testSettersAndGetters(){ + + // set everything - we only use the constructor to set variables in the Menu Manager + RunScore runScore = new RunScore(Test.GENERAL_INT, Test.GENERAL_INTEGER_LIST, Test.GENERAL_INTEGER_LIST); + + // use getters and assert equality + assertEquals(runScore.getScore(), Test.GENERAL_INT); + assertEquals(runScore.getCurrentMenu(), Test.GENERAL_INTEGER_LIST); + assertEquals(runScore.getOldMenu(), Test.GENERAL_INTEGER_LIST); + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java new file mode 100644 index 000000000..c25fb7f41 --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.test.Test; + +public class SubCellCommandListTests extends AndroidTestCase2 { + + @Override + public void setUp() throws Exception{ + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testSettersAndGetters() { + + RunScore runScore = new RunScore(Test.GENERAL_INT, Test.GENERAL_INTEGER_LIST, Test.GENERAL_INTEGER_LIST); + + // set everything + SubCellCommandList subCellCommandList = new SubCellCommandList(Test.GENERAL_STRING, Test.GENERAL_INTEGER, runScore, Test.GENERAL_MENUCELL_LIST, Test.GENERAL_MENUCELL_LIST); + + // use getters and assert equality + assertEquals(subCellCommandList.getMenuTitle(), Test.GENERAL_STRING); + assertEquals(subCellCommandList.getParentId(), Test.GENERAL_INTEGER); + assertEquals(runScore, runScore); + assertEquals(subCellCommandList.getNewList(), Test.GENERAL_MENUCELL_LIST); + assertEquals(subCellCommandList.getOldList(), Test.GENERAL_MENUCELL_LIST); + + } +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java new file mode 100644 index 000000000..2b07fcfbf --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.managers.BaseSubManager; +import com.smartdevicelink.managers.CompletionListener; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.rpc.OnCommand; +import com.smartdevicelink.proxy.rpc.OnHMIStatus; +import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class VoiceCommandManagerTests extends AndroidTestCase2 { + + private VoiceCommand command, command3; + private List<VoiceCommand> commands; + private VoiceCommandManager voiceCommandManager; + private static final int voiceCommandIdMin = 1900000000; + private OnRPCNotificationListener onHMIStatusListener, commandListener; + + // SETUP / HELPERS + + @Override + public void setUp() throws Exception{ + super.setUp(); + + VoiceCommandSelectionListener mockListener = mock(VoiceCommandSelectionListener.class); + command = new VoiceCommand(Arrays.asList("Command one", "Command two"), null); + VoiceCommand command2 = new VoiceCommand(Arrays.asList("Command three", "Command four"), null); + command3 = new VoiceCommand(Arrays.asList("Command five", "Command six"), mockListener); + commands = Arrays.asList(command,command2); + + ISdl internalInterface = mock(ISdl.class); + + // When internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, OnRPCNotificationListener) is called + // inside the VoiceCommandManager's constructor, then keep a reference to the OnRPCNotificationListener so we can trigger it later + // to emulate what Core does when it sends OnHMIStatus notification + Answer<Void> onHMIStatusAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + onHMIStatusListener = (OnRPCNotificationListener) args[1]; + return null; + } + }; + doAnswer(onHMIStatusAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_HMI_STATUS), any(OnRPCNotificationListener.class)); + + Answer<Void> onCommandAnswer = new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + commandListener = (OnRPCNotificationListener) args[1]; + return null; + } + }; + doAnswer(onCommandAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_COMMAND), any(OnRPCNotificationListener.class)); + + voiceCommandManager = new VoiceCommandManager(internalInterface); + + // Check some stuff during setup + assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_NONE); + assertEquals(voiceCommandManager.getState(), BaseSubManager.SETTING_UP); + assertEquals(voiceCommandManager.lastVoiceCommandId, voiceCommandIdMin); + assertFalse(voiceCommandManager.hasQueuedUpdate); + assertFalse(voiceCommandManager.waitingOnHMIUpdate); + assertNotNull(voiceCommandManager.commandListener); + assertNotNull(voiceCommandManager.hmiListener); + assertNull(voiceCommandManager.voiceCommands); + assertNull(voiceCommandManager.oldVoiceCommands); + assertNull(voiceCommandManager.inProgressUpdate); + } + + @Override + public void tearDown() throws Exception { + + voiceCommandManager.dispose(); + + assertEquals(voiceCommandManager.lastVoiceCommandId, voiceCommandIdMin); + assertNull(voiceCommandManager.voiceCommands); + assertNull(voiceCommandManager.oldVoiceCommands); + assertNull(voiceCommandManager.currentHMILevel); + assertNull(voiceCommandManager.inProgressUpdate); + assertFalse(voiceCommandManager.hasQueuedUpdate); + assertFalse(voiceCommandManager.waitingOnHMIUpdate); + // after everything, make sure we are in the correct state + assertEquals(voiceCommandManager.getState(), BaseSubManager.SHUTDOWN); + + super.tearDown(); + } + + public void testStartVoiceCommandManager(){ + + voiceCommandManager.start(new CompletionListener() { + @Override + public void onComplete(boolean success) { + assertTrue(success); + // Make sure the state has changed, as the Screen Manager is dependant on it + assertEquals(voiceCommandManager.getState(), BaseSubManager.READY); + } + }); + } + + public void testHMINotReady(){ + + voiceCommandManager.currentHMILevel = HMILevel.HMI_NONE; + voiceCommandManager.setVoiceCommands(commands); + + // updating voice commands before HMI is ready + assertNull(voiceCommandManager.inProgressUpdate); + assertTrue(voiceCommandManager.waitingOnHMIUpdate); + // these are the 2 commands we have waiting + assertEquals(voiceCommandManager.voiceCommands.size(), 2); + assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_NONE); + + // The VCM should send the pending voice commands once HMI full occurs + sendFakeCoreOnHMIFullNotifications(); + // Listener should be triggered - which sets new HMI level and should proceed to send our pending update + assertEquals(voiceCommandManager.currentHMILevel, HMILevel.HMI_FULL); + // This being false means it received the hmi notification and sent the pending commands + assertFalse(voiceCommandManager.waitingOnHMIUpdate); + } + + public void testUpdatingCommands(){ + + // we have previously sent 2 VoiceCommand objects. we will now update it and have just one + + // make sure the system returns us 2 delete commands + assertEquals(voiceCommandManager.deleteCommandsForVoiceCommands(commands).size(), 2); + // when we only send one command to update, we should only be returned one add command + assertEquals(voiceCommandManager.addCommandsForVoiceCommands(Collections.singletonList(command)).size(), 1); + + // Send a new single command, and test that its listener works, as it gets called from the VCM + voiceCommandManager.setVoiceCommands(Collections.singletonList(command3)); + + // Fake onCommand - we want to make sure that we can pass back onCommand events to our VoiceCommand Objects + OnCommand onCommand = new OnCommand(); + onCommand.setCmdID(command3.getCommandId()); + onCommand.setTriggerSource(TriggerSource.TS_VR); // these are voice commands + commandListener.onNotified(onCommand); // send off the notification + + // verify the mock listener has only been hit once + verify(command3.getVoiceCommandSelectionListener(), times(1)).onVoiceCommandSelected(); + } + + // Emulate what happens when Core sends OnHMIStatus notification + private void sendFakeCoreOnHMIFullNotifications() { + OnHMIStatus onHMIStatusFakeNotification = new OnHMIStatus(); + onHMIStatusFakeNotification.setHmiLevel(HMILevel.HMI_FULL); + onHMIStatusListener.onNotified(onHMIStatusFakeNotification); + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java new file mode 100644 index 000000000..f8a8a538d --- /dev/null +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.AndroidTestCase2; +import com.smartdevicelink.test.Test; + +public class VoiceCommandTests extends AndroidTestCase2 { + + private VoiceCommandSelectionListener voiceCommandSelectionListener = new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + // Stuffs + } + }; + + @Override + public void setUp() throws Exception{ + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testSettersAndGetters(){ + VoiceCommand voiceCommand = new VoiceCommand(Test.GENERAL_STRING_LIST, voiceCommandSelectionListener); + + assertEquals(voiceCommand.getVoiceCommands(), Test.GENERAL_STRING_LIST); + assertEquals(voiceCommand.getVoiceCommandSelectionListener(), voiceCommandSelectionListener); + } + +} diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Test.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Test.java index 62176744f..c77894e93 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Test.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Test.java @@ -4,8 +4,12 @@ import android.graphics.Color; import android.util.Log; import com.smartdevicelink.R; -import com.smartdevicelink.SdlConnection.SdlSession2; +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; import com.smartdevicelink.managers.lockscreen.LockScreenConfig; +import com.smartdevicelink.managers.screen.menu.MenuCell; +import com.smartdevicelink.managers.screen.menu.MenuSelectionListener; +import com.smartdevicelink.managers.screen.menu.VoiceCommand; +import com.smartdevicelink.managers.screen.menu.VoiceCommandSelectionListener; import com.smartdevicelink.protocol.SdlProtocol; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.SdlProxyBase; @@ -393,6 +397,7 @@ public class Test { public static final AudioControlData GENERAL_AUDIOCONTROLDATA = new AudioControlData(); public static final LightControlData GENERAL_LIGHTCONTROLDATA = new LightControlData(); public static final HMISettingsControlData GENERAL_HMISETTINGSCONTROLDATA = new HMISettingsControlData(); + public static final SdlArtwork GENERAL_ARTWORK = new SdlArtwork("sdl", FileType.GRAPHIC_PNG, R.drawable.ic_sdl, false); public static final HMICapabilities GENERAL_HMICAPABILITIES = new HMICapabilities(); @@ -449,6 +454,21 @@ public class Test { public static final List<WeatherData> GENERAL_WEATHERDATA_LIST = Arrays.asList(GENERAL_WEATHERDATA); public static final List<WeatherAlert> GENERAL_WEATHERALERT_LIST = Arrays.asList(GENERAL_WEATHERALERT); public static final List<NavigationInstruction> GENERAL_NAVIGATION_INSTRUCTION_LIST = Arrays.asList(GENERAL_NAVIGATION_INSTRUCTION); + public static final int GENERAL_MENU_MAX_ID = 2000000000; + public static final MenuCell GENERAL_MENUCELL = new MenuCell(GENERAL_STRING,null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + // + } + }); + public static final List<MenuCell> GENERAL_MENUCELL_LIST = Arrays.asList(GENERAL_MENUCELL); + public static final VoiceCommand GENERAL_VOICE_COMMAND = new VoiceCommand(GENERAL_STRING_LIST, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + + } + }); + public static final List<VoiceCommand> GENERAL_VOICE_COMMAND_LIST = Arrays.asList(GENERAL_VOICE_COMMAND); public static final JSONArray JSON_TURNS = new JSONArray(); diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java index baa53272b..385a6d12a 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java @@ -37,7 +37,6 @@ import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; import com.smartdevicelink.streaming.audio.AudioStreamingCodec; import com.smartdevicelink.streaming.audio.AudioStreamingParams; import com.smartdevicelink.streaming.video.VideoStreamingParameters; diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/AppServiceCapabilityTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/AppServiceCapabilityTest.java index ca03861cd..997984514 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/AppServiceCapabilityTest.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/AppServiceCapabilityTest.java @@ -5,9 +5,6 @@ import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.rpc.AppServiceCapability; import com.smartdevicelink.proxy.rpc.AppServiceManifest; import com.smartdevicelink.proxy.rpc.AppServiceRecord; -import com.smartdevicelink.proxy.rpc.MediaServiceManifest; -import com.smartdevicelink.proxy.rpc.NavigationServiceManifest; -import com.smartdevicelink.proxy.rpc.SdlMsgVersion; import com.smartdevicelink.proxy.rpc.enums.AppServiceType; import com.smartdevicelink.proxy.rpc.enums.ServiceUpdateReason; import com.smartdevicelink.test.JsonUtils; diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java index 805fd775a..b1885d1a9 100644 --- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java @@ -43,7 +43,7 @@ import com.smartdevicelink.proxy.rpc.enums.StaticIconName; /** * A class that extends SdlFile, representing artwork (JPEG, PNG, or BMP) to be uploaded to core */ -public class SdlArtwork extends SdlFile { +public class SdlArtwork extends SdlFile implements Cloneable{ private boolean isTemplate; private Image imageRPC; @@ -125,14 +125,50 @@ public class SdlArtwork extends SdlFile { */ public Image getImageRPC() { if (imageRPC == null) { - if (isStaticIcon()) { - imageRPC = new Image(getName(), ImageType.STATIC); - imageRPC.setIsTemplate(true); - } else { - imageRPC = new Image(getName(), ImageType.DYNAMIC); - imageRPC.setIsTemplate(isTemplate); - } + imageRPC = createImageRPC(); } return imageRPC; } + + private Image createImageRPC(){ + Image image; + if (isStaticIcon()) { + image = new Image(getName(), ImageType.STATIC); + image.setIsTemplate(true); + } else { + image = new Image(getName(), ImageType.DYNAMIC); + image.setIsTemplate(isTemplate); + } + return image; + } + + /** + * Creates a deep copy of the object + * @return deep copy of the object + */ + @Override + public SdlArtwork clone() { + final SdlArtwork clone; + try { + clone = (SdlArtwork) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("superclass messed up", e); + } + clone.setName(this.getName()); + clone.setResourceId(this.getResourceId()); + clone.setUri(this.getUri() == null ? null : Uri.parse(this.getUri().toString())); + if (this.getFileData() != null){ + byte[] data = new byte[this.getFileData().length]; + for (int i = 0; i < this.getFileData().length; i++) { + data[i] = this.getFileData()[i]; + } + clone.setFileData(data); + } + clone.setType(this.getType()); + clone.setPersistent(this.isPersistent()); + clone.setStaticIcon(this.isStaticIcon()); + clone.isTemplate = this.isTemplate; + clone.imageRPC = this.createImageRPC(); + return clone; + } }
\ No newline at end of file diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java new file mode 100644 index 000000000..4a28a2792 --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.proxy.interfaces.ISdl; + +/** + * <strong>MenuManager</strong> <br> + * + * Note: This class must be accessed through the ScreenManager via the SdlManager. Do not instantiate it by itself. <br> + * + * The MenuManager takes MenuCell objects and creates and sends all necessary RPCs to build out a menu + */ +public class MenuManager extends BaseMenuManager { + + public MenuManager(ISdl internalInterface, FileManager fileManager) { + // setup + super(internalInterface, fileManager); + } + +} diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java new file mode 100644 index 000000000..a892d3a2c --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.proxy.interfaces.ISdl; + +/** + * <strong>VoiceCommandManager</strong> <br> + * + * Note: This class must be accessed through the ScreenManager via the SdlManager. Do not instantiate it by itself. <br> + * + * The VoiceCommandManager takes a List of VoiceCommand objects and sets them on the Head unit for you. + */ +public class VoiceCommandManager extends BaseVoiceCommandManager { + + public VoiceCommandManager(ISdl internalInterface) { + // setup + super(internalInterface); + } + +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java index 015d47767..2afd0f6f6 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java @@ -38,11 +38,12 @@ import com.smartdevicelink.managers.BaseSubManager; import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.file.FileManager; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; -import com.smartdevicelink.managers.screen.choiceset.ChoiceCell; -import com.smartdevicelink.managers.screen.choiceset.ChoiceSet; -import com.smartdevicelink.managers.screen.choiceset.KeyboardListener; +import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdatesMode; +import com.smartdevicelink.managers.screen.menu.MenuCell; +import com.smartdevicelink.managers.screen.menu.MenuManager; +import com.smartdevicelink.managers.screen.menu.VoiceCommand; +import com.smartdevicelink.managers.screen.menu.VoiceCommandManager; import com.smartdevicelink.proxy.interfaces.ISdl; -import com.smartdevicelink.proxy.rpc.enums.InteractionMode; import com.smartdevicelink.proxy.rpc.enums.MetadataType; import com.smartdevicelink.proxy.rpc.enums.TextAlignment; import com.smartdevicelink.util.DebugTool; @@ -61,23 +62,25 @@ abstract class BaseScreenManager extends BaseSubManager { private final WeakReference<FileManager> fileManager; private SoftButtonManager softButtonManager; private TextAndGraphicManager textAndGraphicManager; + private VoiceCommandManager voiceCommandManager; + private MenuManager menuManager; // Sub manager listener private final CompletionListener subManagerListener = new CompletionListener() { @Override public synchronized void onComplete(boolean success) { - if (softButtonManager != null && textAndGraphicManager != null) { - if (softButtonManager.getState() == BaseSubManager.READY && textAndGraphicManager.getState() == BaseSubManager.READY) { + if (softButtonManager != null && textAndGraphicManager != null && voiceCommandManager != null && menuManager != null) { + if (softButtonManager.getState() == BaseSubManager.READY && textAndGraphicManager.getState() == BaseSubManager.READY && voiceCommandManager.getState() == BaseSubManager.READY && menuManager.getState() == BaseSubManager.READY) { DebugTool.logInfo("Starting screen manager, all sub managers are in ready state"); transitionToState(READY); - } else if (softButtonManager.getState() == BaseSubManager.ERROR && textAndGraphicManager.getState() == BaseSubManager.ERROR) { - Log.e(TAG, "ERROR starting screen manager, both sub managers in error state"); + } else if (softButtonManager.getState() == BaseSubManager.ERROR && textAndGraphicManager.getState() == BaseSubManager.ERROR && voiceCommandManager.getState() == BaseSubManager.ERROR && menuManager.getState() == BaseSubManager.ERROR) { + Log.e(TAG, "ERROR starting screen manager, all sub managers are in error state"); transitionToState(ERROR); - } else if (textAndGraphicManager.getState() == BaseSubManager.SETTING_UP || softButtonManager.getState() == BaseSubManager.SETTING_UP) { - DebugTool.logInfo("SETTING UP screen manager, one sub manager is still setting up"); + } else if (textAndGraphicManager.getState() == BaseSubManager.SETTING_UP || softButtonManager.getState() == BaseSubManager.SETTING_UP || voiceCommandManager.getState() == BaseSubManager.SETTING_UP || menuManager.getState() == BaseSubManager.SETTING_UP) { + DebugTool.logInfo("SETTING UP screen manager, at least one sub manager is still setting up"); transitionToState(SETTING_UP); } else { - Log.w(TAG, "LIMITED starting screen manager, one sub manager in error state and the other is ready"); + Log.w(TAG, "LIMITED starting screen manager, at least one sub manager is in error state and the others are ready"); transitionToState(LIMITED); } } else { @@ -99,13 +102,17 @@ abstract class BaseScreenManager extends BaseSubManager { super.start(listener); this.softButtonManager.start(subManagerListener); this.textAndGraphicManager.start(subManagerListener); + this.voiceCommandManager.start(subManagerListener); + this.menuManager.start(subManagerListener); } private void initialize(){ if (fileManager.get() != null) { this.softButtonManager = new SoftButtonManager(internalInterface, fileManager.get()); this.textAndGraphicManager = new TextAndGraphicManager(internalInterface, fileManager.get(), softButtonManager); + this.menuManager = new MenuManager(internalInterface, fileManager.get()); } + this.voiceCommandManager = new VoiceCommandManager(internalInterface); } /** @@ -115,6 +122,8 @@ abstract class BaseScreenManager extends BaseSubManager { public void dispose() { softButtonManager.dispose(); textAndGraphicManager.dispose(); + voiceCommandManager.dispose(); + menuManager.dispose(); super.dispose(); } @@ -355,25 +364,53 @@ abstract class BaseScreenManager extends BaseSubManager { return softButtonManager.getSoftButtonObjectById(buttonId); } - // This can only take up to 100 items, if more are passed the completion handler will be called with an error - public void preloadChoices (List<ChoiceCell> choices, CompletionListener listener){ - + /** + * Get the currently set voice commands + * @return a List of Voice Command objects + */ + public List<VoiceCommand> getVoiceCommands(){ + return voiceCommandManager.getVoiceCommands(); } - public void deleteChoices (List<ChoiceCell> preloadedChoiceKeys){ - + /** + * Set voice commands + * @param voiceCommands the voice commands to be sent to the head unit + */ + public void setVoiceCommands(@NonNull List<VoiceCommand> voiceCommands){ + this.voiceCommandManager.setVoiceCommands(voiceCommands); } - public void presentSearchableChoiceSet (ChoiceSet choiceSet, InteractionMode mode, KeyboardListener listener){ - + /** + * The list of currently set menu cells + * @return a List of the currently set menu cells + */ + public List<MenuCell> getMenu(){ + return this.menuManager.getMenuCells(); } - public void presentChoiceSet (ChoiceSet choiceSet, InteractionMode mode){ - + /** + * Creates and sends all associated Menu RPCs + * Note: the manager will store a deep copy the menuCells internally to be able to handle future updates correctly + * @param menuCells - the menu cells that are to be sent to the head unit, including their sub-cells. + */ + public void setMenu(@NonNull List<MenuCell> menuCells){ + this.menuManager.setMenuCells(menuCells); } - public void presentKeyboardWithInitialText (String initialText, KeyboardListener listener){ + /** + * Sets the behavior of how menus are updated. For explanations of the differences, see {@link DynamicMenuUpdatesMode} + * @param value - the update mode + */ + public void setDynamicMenuUpdatesMode(@NonNull DynamicMenuUpdatesMode value){ + this.menuManager.setDynamicUpdatesMode(value); + } + /** + * + * @return The currently set DynamicMenuUpdatesMode. It defaults to ON_WITH_COMPAT_MODE if not set. + */ + public DynamicMenuUpdatesMode getDynamicMenuUpdatesMode(){ + return this.menuManager.getDynamicMenuUpdatesMode(); } /** diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java new file mode 100644 index 000000000..33b002513 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java @@ -0,0 +1,1188 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import android.support.annotation.NonNull; + +import com.smartdevicelink.managers.BaseSubManager; +import com.smartdevicelink.managers.CompletionListener; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.file.MultipleFileCompletionListener; +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.RPCNotification; +import com.smartdevicelink.proxy.RPCRequest; +import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener; +import com.smartdevicelink.proxy.rpc.AddCommand; +import com.smartdevicelink.proxy.rpc.AddSubMenu; +import com.smartdevicelink.proxy.rpc.DeleteCommand; +import com.smartdevicelink.proxy.rpc.DeleteSubMenu; +import com.smartdevicelink.proxy.rpc.DisplayCapabilities; +import com.smartdevicelink.proxy.rpc.ImageField; +import com.smartdevicelink.proxy.rpc.MenuParams; +import com.smartdevicelink.proxy.rpc.OnCommand; +import com.smartdevicelink.proxy.rpc.OnHMIStatus; +import com.smartdevicelink.proxy.rpc.enums.DisplayType; +import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.ImageFieldName; +import com.smartdevicelink.proxy.rpc.enums.Result; +import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.enums.SystemContext; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.util.DebugTool; + +import org.json.JSONException; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +abstract class BaseMenuManager extends BaseSubManager { + + private static final int KEEP = 0; + private static final int MARKED_FOR_ADDITION = 1; + private static final int MARKED_FOR_DELETION = 2; + + private final WeakReference<FileManager> fileManager; + + List<MenuCell> menuCells, waitingUpdateMenuCells, oldMenuCells, keepsNew, keepsOld; + List<RPCRequest> inProgressUpdate; + DynamicMenuUpdatesMode dynamicMenuUpdatesMode; + private DisplayType displayType; + + boolean waitingOnHMIUpdate; + private boolean hasQueuedUpdate; + HMILevel currentHMILevel; + + OnRPCNotificationListener hmiListener, commandListener; + OnSystemCapabilityListener displayListener; + DisplayCapabilities displayCapabilities; + + private static final int MAX_ID = 2000000000; + private static final int parentIdNotFound = MAX_ID; + private static final int menuCellIdMin = 1; + int lastMenuId; + + SystemContext currentSystemContext; + + BaseMenuManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) { + + super(internalInterface); + + // Set up some Vars + this.fileManager = new WeakReference<>(fileManager); + currentSystemContext = SystemContext.SYSCTXT_MAIN; + currentHMILevel = HMILevel.HMI_NONE; + lastMenuId = menuCellIdMin; + dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE; + + addListeners(); + } + + @Override + public void start(CompletionListener listener) { + transitionToState(READY); + super.start(listener); + } + + @Override + public void dispose(){ + + lastMenuId = menuCellIdMin; + menuCells = null; + oldMenuCells = null; + currentHMILevel = null; + currentSystemContext = SystemContext.SYSCTXT_MAIN; + dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE; + displayCapabilities = null; + inProgressUpdate = null; + hasQueuedUpdate = false; + waitingOnHMIUpdate = false; + waitingUpdateMenuCells = null; + keepsNew = null; + keepsOld = null; + + // remove listeners + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener); + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener); + internalInterface.removeOnSystemCapabilityListener(SystemCapabilityType.DISPLAY, displayListener); + + super.dispose(); + } + + // SETTERS + + public void setDynamicUpdatesMode(@NonNull DynamicMenuUpdatesMode value){ + this.dynamicMenuUpdatesMode = value; + } + + /** + * Creates and sends all associated Menu RPCs + * @param cells - the menu cells that are to be sent to the head unit, including their sub-cells. + */ + public void setMenuCells(@NonNull List<MenuCell> cells){ + + // Create a deep copy of the list so future changes by developers don't affect the algorithm logic + List <MenuCell> clonedCells = cloneMenuCellsList(cells); + + if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)){ + // We are in NONE or the menu is in use, bail out of here + waitingOnHMIUpdate = true; + waitingUpdateMenuCells = new ArrayList<>(clonedCells); + return; + } + waitingOnHMIUpdate = false; + + // Update our Lists + // set old list + if (menuCells != null) { + oldMenuCells = new ArrayList<>(menuCells); + } + // copy new list + menuCells = new ArrayList<>(clonedCells); + + // HashSet order doesnt matter / does not allow duplicates + HashSet<String> titleCheckSet = new HashSet<>(); + HashSet<String> allMenuVoiceCommands = new HashSet<>(); + int voiceCommandCount = 0; + + for (MenuCell cell : menuCells){ + titleCheckSet.add(cell.getTitle()); + if (cell.getVoiceCommands() != null){ + allMenuVoiceCommands.addAll(cell.getVoiceCommands()); + voiceCommandCount += cell.getVoiceCommands().size(); + } + } + // Check for duplicate titles + if (titleCheckSet.size() != menuCells.size()){ + DebugTool.logError("Not all cell titles are unique. The menu will not be set"); + return; + } + + // Check for duplicate voice commands + if (allMenuVoiceCommands.size() != voiceCommandCount){ + DebugTool.logError("Attempted to create a menu with duplicate voice commands. Voice commands must be unique. The menu will not be set"); + return; + } + + // Upload the Artworks + List<SdlArtwork> artworksToBeUploaded = findAllArtworksToBeUploadedFromCells(menuCells); + if (artworksToBeUploaded.size() > 0 && fileManager.get() != null){ + fileManager.get().uploadArtworks(artworksToBeUploaded, new MultipleFileCompletionListener() { + @Override + public void onComplete(Map<String, String> errors) { + + if (errors != null && errors.size() > 0){ + DebugTool.logError("Error uploading Menu Artworks: "+ errors.toString()); + }else{ + DebugTool.logInfo("Menu Artworks Uploaded"); + } + // proceed + updateMenuAndDetermineBestUpdateMethod(); + } + }); + }else{ + // No Artworks to be uploaded, send off + updateMenuAndDetermineBestUpdateMethod(); + } + } + + /** + * Returns current list of menu cells + * @return a List of Currently set menu cells + */ + public List<MenuCell> getMenuCells(){ + + if (menuCells != null) { + return menuCells; + } else if (waitingOnHMIUpdate && waitingUpdateMenuCells != null) { + // this will keep from returning null if the menu list is set and we are pending HMI FULL + return waitingUpdateMenuCells; + } else { + return null; + } + } + + /** + * @return The currently set DynamicMenuUpdatesMode. It defaults to ON_WITH_COMPAT_MODE + */ + public DynamicMenuUpdatesMode getDynamicMenuUpdatesMode() { + return this.dynamicMenuUpdatesMode; + } + + // UPDATING SYSTEM + + // ROOT MENU + + private void updateMenuAndDetermineBestUpdateMethod(){ + + if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)){ + // We are in NONE or the menu is in use, bail out of here + DebugTool.logInfo("HMI in None or System Context Menu, returning"); + waitingOnHMIUpdate = true; + waitingUpdateMenuCells = menuCells; + return; + } + + if (inProgressUpdate != null && inProgressUpdate.size() > 0){ + // there's an in-progress update so this needs to wait + DebugTool.logInfo("There is an in progress Menu Update, returning"); + hasQueuedUpdate = true; + return; + } + + // Checks against what the developer set for update mode and against the display type + // to determine how the menu will be updated. This has the ability to be changed during + // a session. + if (checkUpdateMode(dynamicMenuUpdatesMode, displayType)) { + // run the lists through the new algorithm + RunScore rootScore = runMenuCompareAlgorithm(oldMenuCells, menuCells); + if (rootScore == null) { + // send initial menu (score will return null) + // make a copy of our current cells + DebugTool.logInfo("Creating initial Menu"); + // Set the IDs if needed + lastMenuId = menuCellIdMin; + updateIdsOnMenuCells(menuCells, parentIdNotFound); + this.oldMenuCells = new ArrayList<>(menuCells); + createAndSendEntireMenu(); + } else { + DebugTool.logInfo("Dynamically Updating Menu"); + if (menuCells.size() == 0 && (oldMenuCells != null && oldMenuCells.size() > 0)) { + // the dev wants to clear the menu. We have old cells and an empty array of new ones. + deleteMenuWhenNewCellsEmpty(); + } else { + // lets dynamically update the root menu + dynamicallyUpdateRootMenu(rootScore); + } + } + } else { + // we are in compatibility mode + DebugTool.logInfo("Updating menus in compatibility mode"); + lastMenuId = menuCellIdMin; + updateIdsOnMenuCells(menuCells, parentIdNotFound); + // if the old cell array is not null, we want to delete the entire thing, else copy the new array + if (oldMenuCells == null) { + this.oldMenuCells = new ArrayList<>(menuCells); + } + createAndSendEntireMenu(); + } + } + + private boolean checkUpdateMode(DynamicMenuUpdatesMode updateMode, DisplayType displayType){ + + if (updateMode.equals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE)){ + if (displayType == null){ + return true; + } + return (!displayType.equals(DisplayType.GEN3_8_INCH)); + + } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_OFF)){ + return false; + } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_ON)){ + return true; + } + + return true; + } + + private void deleteMenuWhenNewCellsEmpty(){ + sendDeleteRPCs(createDeleteRPCsForCells(oldMenuCells), new CompletionListener() { + @Override + public void onComplete(boolean success) { + inProgressUpdate = null; + + if (!success){ + DebugTool.logError("Error Sending Current Menu"); + }else{ + DebugTool.logInfo("Successfully Cleared Menu"); + } + oldMenuCells = null; + if (hasQueuedUpdate){ + hasQueuedUpdate = false; + } + } + }); + } + + private void dynamicallyUpdateRootMenu(RunScore bestRootScore){ + + // we need to run through the keeps and see if they have subCells, as they also need to be run + // through the compare function. + List<Integer> newIntArray = bestRootScore.getCurrentMenu(); + List<Integer> oldIntArray = bestRootScore.getOldMenu(); + List<RPCRequest> deleteCommands; + + // Set up deletes + List<MenuCell> deletes = new ArrayList<>(); + keepsOld = new ArrayList<>(); + for (int x = 0; x < oldIntArray.size(); x++) { + Integer old = oldIntArray.get(x); + if (old.equals(MARKED_FOR_DELETION)){ + // grab cell to send to function to create delete commands + deletes.add(oldMenuCells.get(x)); + } else if (old.equals(KEEP)) { + keepsOld.add(oldMenuCells.get(x)); + } + } + // create the delete commands + deleteCommands = createDeleteRPCsForCells(deletes); + + // Set up the adds + List<MenuCell> adds = new ArrayList<>(); + keepsNew = new ArrayList<>(); + for (int x = 0; x < newIntArray.size(); x++) { + Integer newInt = newIntArray.get(x); + if (newInt.equals(MARKED_FOR_ADDITION)){ + // grab cell to send to function to create add commands + adds.add(menuCells.get(x)); + } else if (newInt.equals(KEEP)){ + keepsNew.add(menuCells.get(x)); + } + } + updateIdsOnDynamicCells(adds); + // this is needed for the onCommands to still work + transferIdsToKeptCells(keepsNew); + + if (adds != null && adds.size() > 0){ + DebugTool.logInfo("Sending root menu updates"); + sendDynamicRootMenuRPCs(deleteCommands, adds); + }else{ + DebugTool.logInfo("All root menu items are kept. Check the sub menus"); + runSubMenuCompareAlgorithm(); + } + } + + // OTHER + + private void sendDynamicRootMenuRPCs(List<RPCRequest> deleteCommands,final List<MenuCell> updatedCells){ + sendDeleteRPCs(deleteCommands,new CompletionListener() { + @Override + public void onComplete(boolean success) { + createAndSendMenuCellRPCs(updatedCells, new CompletionListener() { + @Override + public void onComplete(boolean success) { + inProgressUpdate = null; + + if (!success){ + DebugTool.logError("Error Sending Current Menu"); + } + + if (hasQueuedUpdate){ + //setMenuCells(waitingUpdateMenuCells); + hasQueuedUpdate = false; + } + } + }); + } + }); + } + + // SUB MENUS + + // This is called in the listener in the sendMenu and sendSubMenuCommands Methods + private void runSubMenuCompareAlgorithm(){ + // any cells that were re-added have their sub-cells added with them + // at this point all we care about are the cells that were deemed equal and kept. + if (keepsNew == null || keepsNew.size() == 0) { + return; + } + + List<SubCellCommandList> commandLists = new ArrayList<>(); + + for (int i = 0; i < keepsNew.size(); i++) { + + MenuCell keptCell = keepsNew.get(i); + MenuCell oldKeptCell = keepsOld.get(i); + + if (oldKeptCell.getSubCells() != null && oldKeptCell.getSubCells().size() > 0 && keptCell.getSubCells() != null && keptCell.getSubCells().size() > 0){ + // ACTUAL LOGIC + RunScore subScore = compareOldAndNewLists(oldKeptCell.getSubCells(), keptCell.getSubCells()); + + if (subScore != null){ + DebugTool.logInfo("Sub menu Run Score: "+ oldKeptCell.getTitle()+ " Score: "+ subScore.getScore()); + SubCellCommandList commandList = new SubCellCommandList(oldKeptCell.getTitle(), oldKeptCell.getCellId(), subScore, oldKeptCell.getSubCells(), keptCell.getSubCells()); + commandLists.add(commandList); + } + } + } + createSubMenuDynamicCommands(commandLists); + } + + private void createSubMenuDynamicCommands(final List<SubCellCommandList> commandLists){ + + // break out + if (commandLists.size() == 0){ + if (inProgressUpdate != null){ + inProgressUpdate = null; + } + + if (hasQueuedUpdate) { + DebugTool.logInfo("Menu Manager has waiting updates, sending now"); + setMenuCells(waitingUpdateMenuCells); + hasQueuedUpdate = false; + } + DebugTool.logInfo("All menu updates, including sub menus - done."); + return; + } + + final SubCellCommandList commandList = commandLists.remove(0); + + DebugTool.logInfo("Creating and Sending Dynamic Sub Commands For Root Menu Cell: "+ commandList.getMenuTitle()); + + // grab the scores + RunScore score = commandList.getListsScore(); + List<Integer> newIntArray = score.getCurrentMenu(); + List<Integer> oldIntArray = score.getOldMenu(); + + // Grab the sub-menus from the parent cell + final List<MenuCell> oldCells = commandList.getOldList(); + final List<MenuCell> newCells = commandList.getNewList(); + + // Create the list for the adds + List<MenuCell> subCellKeepsNew = new ArrayList<>(); + + List<RPCRequest> deleteCommands; + + // Set up deletes + List<MenuCell> deletes = new ArrayList<>(); + for (int x = 0; x < oldIntArray.size(); x++) { + Integer old = oldIntArray.get(x); + if (old.equals(MARKED_FOR_DELETION)){ + // grab cell to send to function to create delete commands + deletes.add(oldCells.get(x)); + } + } + // create the delete commands + deleteCommands = createDeleteRPCsForCells(deletes); + + // Set up the adds + List<MenuCell> adds = new ArrayList<>(); + for (int x = 0; x < newIntArray.size(); x++) { + Integer newInt = newIntArray.get(x); + if (newInt.equals(MARKED_FOR_ADDITION)){ + // grab cell to send to function to create add commands + adds.add(newCells.get(x)); + } else if (newInt.equals(KEEP)){ + subCellKeepsNew.add(newCells.get(x)); + } + } + final List<MenuCell> addsWithNewIds = updateIdsOnDynamicSubCells(oldCells, adds, commandList.getParentId()); + // this is needed for the onCommands to still work + transferIdsToKeptSubCells(oldCells, subCellKeepsNew); + + sendDeleteRPCs(deleteCommands,new CompletionListener() { + @Override + public void onComplete(boolean success) { + if (addsWithNewIds != null && addsWithNewIds.size() > 0) { + createAndSendDynamicSubMenuRPCs(newCells, addsWithNewIds, new CompletionListener() { + @Override + public void onComplete(boolean success) { + // recurse through next sub list + DebugTool.logInfo("Finished Sending Dynamic Sub Commands For Root Menu Cell: "+ commandList.getMenuTitle()); + createSubMenuDynamicCommands(commandLists); + } + }); + } else{ + // no add commands to send, recurse through next sub list + DebugTool.logInfo("Finished Sending Dynamic Sub Commands For Root Menu Cell: "+ commandList.getMenuTitle()); + createSubMenuDynamicCommands(commandLists); + } + } + }); + } + + // OTHER HELPER METHODS: + + // COMPARISONS + + RunScore runMenuCompareAlgorithm(List<MenuCell> oldCells, List<MenuCell> newCells){ + + if (oldCells == null || oldCells.size() == 0){ + return null; + } + + RunScore bestScore = compareOldAndNewLists(oldCells, newCells); + DebugTool.logInfo("Best menu run score: "+ bestScore.getScore()); + + return bestScore; + } + + private RunScore compareOldAndNewLists(List<MenuCell> oldCells, List<MenuCell> newCells){ + + RunScore bestRunScore = null; + + // This first loop is for each 'run' + for (int run = 0; run < oldCells.size(); run++) { + + List<Integer> oldArray = new ArrayList<>(oldCells.size()); + List<Integer> newArray = new ArrayList<>(newCells.size()); + + // Set the statuses + setDeleteStatus(oldCells.size(), oldArray); + setAddStatus(newCells.size(), newArray); + + int startIndex = 0; + + // Keep items that appear in both lists + for (int oldItems = run; oldItems < oldCells.size(); oldItems++) { + + for (int newItems = startIndex; newItems < newCells.size(); newItems++) { + + if (oldCells.get(oldItems).equals(newCells.get(newItems))){ + oldArray.set(oldItems, KEEP); + newArray.set(newItems, KEEP); + // set the new start index + startIndex = newItems + 1; + break; + } + } + } + + // Calculate number of adds, or the 'score' for this run + int numberOfAdds = 0; + + for (int x = 0; x < newArray.size(); x++) { + if (newArray.get(x).equals(MARKED_FOR_ADDITION)){ + numberOfAdds++; + } + } + + // see if we have a new best score and set it if we do + if (bestRunScore == null || numberOfAdds < bestRunScore.getScore()){ + bestRunScore = new RunScore(numberOfAdds, oldArray, newArray); + } + + } + return bestRunScore; + } + + private void setDeleteStatus(Integer size, List<Integer> oldArray){ + for (int i = 0; i < size; i++) { + oldArray.add(MARKED_FOR_DELETION); + } + } + + private void setAddStatus(Integer size, List<Integer> newArray){ + for (int i = 0; i < size; i++) { + newArray.add(MARKED_FOR_ADDITION); + } + } + + // ARTWORKS + + private List<SdlArtwork> findAllArtworksToBeUploadedFromCells(List<MenuCell> cells){ + // Make sure we can use images in the menus + if (!supportsImages()){ + return new ArrayList<>(); + } + + List<SdlArtwork> artworks = new ArrayList<>(); + for (MenuCell cell : cells){ + if (artworkNeedsUpload(cell.getIcon())){ + artworks.add(cell.getIcon()); + } + if (cell.getSubCells() != null && cell.getSubCells().size() > 0){ + artworks.addAll(findAllArtworksToBeUploadedFromCells(cell.getSubCells())); + } + } + + return artworks; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean supportsImages(){ + if (displayCapabilities != null && displayCapabilities.getImageFields() != null) { + List<ImageField> imageFields = displayCapabilities.getImageFields(); + for (ImageField field : imageFields) { + if (field.getName().equals(ImageFieldName.cmdIcon)) { + return true; + } + } + } + return false; + } + + private boolean artworkNeedsUpload(SdlArtwork artwork){ + if (fileManager.get() != null){ + return (artwork != null && !fileManager.get().hasUploadedFile(artwork) && !artwork.isStaticIcon()); + } + return false; + } + + // IDs + + private void updateIdsOnDynamicCells(List<MenuCell> dynamicCells){ + if (menuCells != null && menuCells.size() > 0 && dynamicCells != null && dynamicCells.size() > 0) { + for (int z = 0; z < menuCells.size(); z++) { + MenuCell mainCell = menuCells.get(z); + for (int i = 0; i < dynamicCells.size(); i++) { + MenuCell dynamicCell = dynamicCells.get(i); + if (mainCell.equals(dynamicCell)) { + int newId = ++lastMenuId; + menuCells.get(z).setCellId(newId); + dynamicCells.get(i).setCellId(newId); + + if (mainCell.getSubCells() != null && mainCell.getSubCells().size() > 0) { + updateIdsOnMenuCells(mainCell.getSubCells(), mainCell.getCellId()); + } + break; + } + } + } + } + } + + private List<MenuCell> updateIdsOnDynamicSubCells(List<MenuCell> oldList, List<MenuCell> dynamicCells, Integer parentId){ + if (oldList != null && oldList.size() > 0 && dynamicCells != null && dynamicCells.size() > 0) { + for (int z = 0; z < oldList.size(); z++) { + MenuCell mainCell = oldList.get(z); + for (int i = 0; i < dynamicCells.size(); i++) { + MenuCell dynamicCell = dynamicCells.get(i); + if (mainCell.equals(dynamicCell)) { + int newId = ++lastMenuId; + oldList.get(z).setCellId(newId); + dynamicCells.get(i).setParentCellId(parentId); + dynamicCells.get(i).setCellId(newId); + }else{ + int newId = ++lastMenuId; + dynamicCells.get(i).setParentCellId(parentId); + dynamicCells.get(i).setCellId(newId); + } + } + } + return dynamicCells; + } + return null; + } + + private void updateIdsOnMenuCells(List<MenuCell> cells, int parentId) { + for (MenuCell cell : cells) { + int newId = ++lastMenuId; + cell.setCellId(newId); + cell.setParentCellId(parentId); + if (cell.getSubCells() != null && cell.getSubCells().size() > 0) { + updateIdsOnMenuCells(cell.getSubCells(), cell.getCellId()); + } + } + } + + private void transferIdsToKeptCells(List<MenuCell> keeps) { + for (int z = 0; z < oldMenuCells.size(); z++) { + MenuCell oldCell = oldMenuCells.get(z); + for (int i = 0; i < keeps.size(); i++) { + MenuCell keptCell = keeps.get(i); + if (oldCell.equals(keptCell)) { + keptCell.setCellId(oldCell.getCellId()); + break; + } + } + } + } + + private void transferIdsToKeptSubCells(List<MenuCell> old, List<MenuCell> keeps) { + for (int z = 0; z < old.size(); z++) { + MenuCell oldCell = old.get(z); + for (int i = 0; i < keeps.size(); i++) { + MenuCell keptCell = keeps.get(i); + if (oldCell.equals(keptCell)) { + keptCell.setCellId(oldCell.getCellId()); + break; + } + } + } + } + + // DELETES + + private List<RPCRequest> createDeleteRPCsForCells(List<MenuCell> cells){ + List<RPCRequest> deletes = new ArrayList<>(); + for (MenuCell cell : cells){ + if (cell.getSubCells() == null){ + DeleteCommand delete = new DeleteCommand(cell.getCellId()); + deletes.add(delete); + }else{ + DeleteSubMenu delete = new DeleteSubMenu(cell.getCellId()); + deletes.add(delete); + } + } + return deletes; + } + + // COMMANDS / SUBMENU RPCs + + private List<RPCRequest> mainMenuCommandsForCells(List<MenuCell> cellsToAdd, boolean shouldHaveArtwork) { + List<RPCRequest> builtCommands = new ArrayList<>(); + + // We need the index so we will use this type of loop + for (int z = 0; z < menuCells.size(); z++) { + MenuCell mainCell = menuCells.get(z); + for (int i = 0; i < cellsToAdd.size(); i++) { + MenuCell addCell = cellsToAdd.get(i); + if (mainCell.equals(addCell)) { + if (addCell.getSubCells() != null && addCell.getSubCells().size() > 0) { + builtCommands.add(subMenuCommandForMenuCell(addCell, shouldHaveArtwork, z)); + } else { + builtCommands.add(commandForMenuCell(addCell, shouldHaveArtwork, z)); + } + break; + } + } + } + return builtCommands; + } + + private List<RPCRequest> subMenuCommandsForCells(List<MenuCell> cells, boolean shouldHaveArtwork){ + List<RPCRequest> builtCommands = new ArrayList<>(); + for (MenuCell cell : cells){ + if (cell.getSubCells() != null && cell.getSubCells().size() > 0){ + builtCommands.addAll(allCommandsForCells(cell.getSubCells(), shouldHaveArtwork)); + } + } + return builtCommands; + } + + List<RPCRequest> allCommandsForCells(List<MenuCell> cells, boolean shouldHaveArtwork){ + List<RPCRequest> builtCommands = new ArrayList<>(); + + // We need the index so we will use this type of loop + for (int i = 0; i < cells.size(); i++) { + MenuCell cell = cells.get(i); + if (cell.getSubCells() != null && cell.getSubCells().size() > 0){ + builtCommands.add(subMenuCommandForMenuCell(cell, shouldHaveArtwork, i)); + // recursively grab the commands for all the sub cells + builtCommands.addAll(allCommandsForCells(cell.getSubCells(), shouldHaveArtwork)); + }else{ + builtCommands.add(commandForMenuCell(cell, shouldHaveArtwork, i)); + } + } + return builtCommands; + } + + private List<RPCRequest> createCommandsForDynamicSubCells(List<MenuCell> oldMenuCells, List<MenuCell> cells, boolean shouldHaveArtwork) { + List<RPCRequest> builtCommands = new ArrayList<>(); + for (int z = 0; z < oldMenuCells.size(); z++) { + MenuCell oldCell = oldMenuCells.get(z); + for (int i = 0; i < cells.size(); i++) { + MenuCell cell = cells.get(i); + if (cell.equals(oldCell)){ + builtCommands.add(commandForMenuCell(cell, shouldHaveArtwork, z)); + break; + } + } + } + return builtCommands; + } + + private AddCommand commandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position){ + + MenuParams params = new MenuParams(cell.getTitle()); + params.setParentID(cell.getParentCellId() != MAX_ID ? cell.getParentCellId() : null); + params.setPosition(position); + + AddCommand command = new AddCommand(cell.getCellId()); + command.setMenuParams(params); + command.setVrCommands(cell.getVoiceCommands()); + command.setCmdIcon((cell.getIcon() != null && shouldHaveArtwork) ? cell.getIcon().getImageRPC() : null); + + return command; + } + + private AddSubMenu subMenuCommandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position){ + AddSubMenu subMenu = new AddSubMenu(cell.getCellId(), cell.getTitle()); + subMenu.setPosition(position); + subMenu.setMenuIcon((shouldHaveArtwork && (cell.getIcon()!= null && cell.getIcon().getImageRPC() != null)) ? cell.getIcon().getImageRPC() : null); + return subMenu; + } + + // CELL COMMAND HANDLING + + private boolean callListenerForCells(List<MenuCell> cells, OnCommand command){ + if (cells != null && cells.size() > 0 && command != null) { + for (MenuCell cell : cells) { + + if (cell.getCellId() == command.getCmdID() && cell.getMenuSelectionListener() != null) { + cell.getMenuSelectionListener().onTriggered(command.getTriggerSource()); + return true; + } + if (cell.getSubCells() != null && cell.getSubCells().size() > 0) { + // for each cell, if it has sub cells, recursively loop through those as well + if (callListenerForCells(cell.getSubCells(), command)) { + return true; + } + } + } + } + return false; + } + + // LISTENERS + + private void addListeners(){ + + // DISPLAY CAPABILITIES - via SCM + displayListener = new OnSystemCapabilityListener() { + @Override + public void onCapabilityRetrieved(Object capability) { + displayCapabilities = (DisplayCapabilities) capability; + if (displayCapabilities != null) { + displayType = displayCapabilities.getDisplayType(); + } + } + + @Override + public void onError(String info) { + DebugTool.logError("Unable to retrieve display capabilities: "+ info); + } + }; + internalInterface.getCapability(SystemCapabilityType.DISPLAY, displayListener); + + // HMI UPDATES + hmiListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + OnHMIStatus hmiStatus = (OnHMIStatus) notification; + HMILevel oldHMILevel = currentHMILevel; + currentHMILevel = hmiStatus.getHmiLevel(); + + // Auto-send an updated menu if we were in NONE and now we are not, and we need an update + if (oldHMILevel.equals(HMILevel.HMI_NONE) && !currentHMILevel.equals(HMILevel.HMI_NONE) && !currentSystemContext.equals(SystemContext.SYSCTXT_MENU)){ + if (waitingOnHMIUpdate){ + DebugTool.logInfo("We now have proper HMI, sending waiting update"); + setMenuCells(waitingUpdateMenuCells); + waitingUpdateMenuCells.clear(); + return; + } + } + + // If we don't check for this and only update when not in the menu, there can be IN_USE errors, especially with submenus. + // We also don't want to encourage changing out the menu while the user is using it for usability reasons. + SystemContext oldContext = currentSystemContext; + currentSystemContext = hmiStatus.getSystemContext(); + + if (oldContext.equals(SystemContext.SYSCTXT_MENU) && !currentSystemContext.equals(SystemContext.SYSCTXT_MENU) && !currentHMILevel.equals(HMILevel.HMI_NONE)){ + if (waitingOnHMIUpdate){ + DebugTool.logInfo("We now have a proper system context, sending waiting update"); + setMenuCells(waitingUpdateMenuCells); + waitingUpdateMenuCells.clear(); + } + } + } + }; + internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener); + + // COMMANDS + commandListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + OnCommand onCommand = (OnCommand) notification; + callListenerForCells(menuCells, onCommand); + } + }; + internalInterface.addOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener); + } + + // SEND NEW MENU ITEMS + + private void createAndSendEntireMenu(){ + + if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE) || currentSystemContext.equals(SystemContext.SYSCTXT_MENU)){ + // We are in NONE or the menu is in use, bail out of here + DebugTool.logInfo("HMI in None or System Context Menu, returning"); + waitingOnHMIUpdate = true; + waitingUpdateMenuCells = menuCells; + return; + } + + if (inProgressUpdate != null && inProgressUpdate.size() > 0){ + // there's an in-progress update so this needs to wait + DebugTool.logInfo("There is an in progress Menu Update, returning"); + hasQueuedUpdate = true; + return; + } + + deleteRootMenu(new CompletionListener() { + @Override + public void onComplete(boolean success) { + createAndSendMenuCellRPCs(menuCells, new CompletionListener() { + @Override + public void onComplete(boolean success) { + inProgressUpdate = null; + + if (!success) { + DebugTool.logError("Error Sending Current Menu"); + } + + if (hasQueuedUpdate) { + setMenuCells(waitingUpdateMenuCells); + hasQueuedUpdate = false; + } + } + }); + } + }); + } + + private void createAndSendMenuCellRPCs(final List<MenuCell> menu, final CompletionListener listener){ + + if (menu.size() == 0){ + if (listener != null){ + // This can be considered a success if the user was clearing out their menu + listener.onComplete(true); + } + return; + } + + List<RPCRequest> mainMenuCommands; + final List<RPCRequest> subMenuCommands; + + if (findAllArtworksToBeUploadedFromCells(menu).size() > 0 || !supportsImages()){ + // Send artwork-less menu + mainMenuCommands = mainMenuCommandsForCells(menu, false); + subMenuCommands = subMenuCommandsForCells(menu, false); + } else { + mainMenuCommands = mainMenuCommandsForCells(menu, true); + subMenuCommands = subMenuCommandsForCells(menu, true); + } + + // add all built commands to inProgressUpdate + inProgressUpdate = new ArrayList<>(mainMenuCommands); + inProgressUpdate.addAll(subMenuCommands); + + internalInterface.sendSequentialRPCs(mainMenuCommands, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + // nothing here + } + + @Override + public void onFinished() { + + if (subMenuCommands.size() > 0) { + sendSubMenuCommandRPCs(subMenuCommands, listener); + DebugTool.logInfo("Finished sending main menu commands. Sending sub menu commands."); + } else { + + if (keepsNew != null && keepsNew.size() > 0){ + runSubMenuCompareAlgorithm(); + }else { + inProgressUpdate = null; + DebugTool.logInfo("Finished sending main menu commands."); + } + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + DebugTool.logError("Result: " + resultCode.toString() + " Info: " + info); + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + try { + DebugTool.logInfo("Main Menu response: " + response.serializeJSON().toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + } + + private void sendSubMenuCommandRPCs(List<RPCRequest> commands, final CompletionListener listener){ + + internalInterface.sendSequentialRPCs(commands, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + + } + + @Override + public void onFinished() { + + if (keepsNew != null && keepsNew.size() > 0){ + runSubMenuCompareAlgorithm(); + }else { + DebugTool.logInfo("Finished Updating Menu"); + inProgressUpdate = null; + + if (listener != null) { + listener.onComplete(true); + } + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + DebugTool.logError("Failed to send sub menu commands: "+ info); + if (listener != null){ + listener.onComplete(false); + } + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + try { + DebugTool.logInfo("Sub Menu response: "+ response.serializeJSON().toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + } + + private void createAndSendDynamicSubMenuRPCs(List<MenuCell> newMenu, final List<MenuCell> adds, final CompletionListener listener){ + + if (adds.size() == 0){ + if (listener != null){ + // This can be considered a success if the user was clearing out their menu + DebugTool.logError("Called createAndSendDynamicSubMenuRPCs with empty menu"); + listener.onComplete(true); + } + return; + } + + List<RPCRequest> mainMenuCommands; + + if (findAllArtworksToBeUploadedFromCells(adds).size() > 0 || !supportsImages()){ + // Send artwork-less menu + mainMenuCommands = createCommandsForDynamicSubCells(newMenu, adds, false); + } else { + mainMenuCommands = createCommandsForDynamicSubCells(newMenu, adds, true); + } + + internalInterface.sendSequentialRPCs(mainMenuCommands, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + // nothing here + } + + @Override + public void onFinished() { + + if (listener != null){ + listener.onComplete(true); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + DebugTool.logError("Result: " + resultCode.toString() + " Info: " + info); + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + try { + DebugTool.logInfo("Dynamic Sub Menu response: " + response.serializeJSON().toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + } + + // DELETE OLD MENU ITEMS + + private void deleteRootMenu(final CompletionListener listener){ + + if (oldMenuCells == null || oldMenuCells.size() == 0) { + if (listener != null){ + // technically this method is successful if there's nothing to delete + DebugTool.logInfo("No old cells to delete, returning"); + listener.onComplete(true); + } + } else { + sendDeleteRPCs(createDeleteRPCsForCells(oldMenuCells), listener); + } + } + + private void sendDeleteRPCs(List<RPCRequest> deleteCommands, final CompletionListener listener){ + if (oldMenuCells != null && oldMenuCells.size() == 0) { + if (listener != null){ + // technically this method is successful if there's nothing to delete + DebugTool.logInfo("No old cells to delete, returning"); + listener.onComplete(true); + } + return; + } + + if (deleteCommands == null || deleteCommands.size() == 0){ + // no dynamic deletes required. return + if (listener != null){ + // technically this method is successful if there's nothing to delete + listener.onComplete(true); + } + return; + } + + internalInterface.sendRequests(deleteCommands, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + + } + + @Override + public void onFinished() { + DebugTool.logInfo("Successfully deleted cells"); + if (listener != null){ + listener.onComplete(true); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + + } + }); + } + + private List<MenuCell> cloneMenuCellsList(List<MenuCell> originalList) { + if (originalList == null) { + return null; + } + + List<MenuCell> clone = new ArrayList<>(); + for (MenuCell menuCell : originalList) { + clone.add(menuCell.clone()); + } + return clone; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java new file mode 100644 index 000000000..e8effa5bf --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import android.support.annotation.NonNull; + +import com.smartdevicelink.managers.BaseSubManager; +import com.smartdevicelink.managers.CompletionListener; +import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.RPCNotification; +import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.rpc.AddCommand; +import com.smartdevicelink.proxy.rpc.DeleteCommand; +import com.smartdevicelink.proxy.rpc.OnCommand; +import com.smartdevicelink.proxy.rpc.OnHMIStatus; +import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.Result; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.util.DebugTool; + +import java.util.ArrayList; +import java.util.List; + +abstract class BaseVoiceCommandManager extends BaseSubManager { + + List<VoiceCommand> voiceCommands; + List<VoiceCommand> oldVoiceCommands; + + List<AddCommand> inProgressUpdate; + + int lastVoiceCommandId; + private static final int voiceCommandIdMin = 1900000000; + + boolean waitingOnHMIUpdate; + boolean hasQueuedUpdate; + + HMILevel currentHMILevel; + OnRPCNotificationListener hmiListener; + OnRPCNotificationListener commandListener; + + // CONSTRUCTORS + + BaseVoiceCommandManager(@NonNull ISdl internalInterface) { + super(internalInterface); + + currentHMILevel = HMILevel.HMI_NONE; + addListeners(); + lastVoiceCommandId = voiceCommandIdMin; + } + + @Override + public void start(CompletionListener listener) { + transitionToState(READY); + super.start(listener); + } + + @Override + public void dispose(){ + + lastVoiceCommandId = voiceCommandIdMin; + voiceCommands = null; + oldVoiceCommands = null; + + waitingOnHMIUpdate = false; + currentHMILevel = null; + inProgressUpdate = null; + hasQueuedUpdate = false; + + // remove listeners + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener); + internalInterface.removeOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener); + + super.dispose(); + } + + // SETTERS + + public void setVoiceCommands(List<VoiceCommand> voiceCommands){ + + // we actually need voice commands to set. + if (voiceCommands == null || voiceCommands.size() == 0){ + DebugTool.logInfo("Trying to set empty list of voice commands, returning"); + return; + } + + // make sure hmi is not none + if (currentHMILevel == null || currentHMILevel == HMILevel.HMI_NONE){ + // Trying to send on HMI_NONE, waiting for full + this.voiceCommands = new ArrayList<>(voiceCommands); + waitingOnHMIUpdate = true; + return; + } + + waitingOnHMIUpdate = false; + lastVoiceCommandId = voiceCommandIdMin; + updateIdsOnVoiceCommands(voiceCommands); + oldVoiceCommands = new ArrayList<>(voiceCommands); + this.voiceCommands = new ArrayList<>(voiceCommands); + + update(); + } + + public List<VoiceCommand> getVoiceCommands(){ + return voiceCommands; + } + + // UPDATING SYSTEM + + private void update(){ + + if (currentHMILevel == null || currentHMILevel.equals(HMILevel.HMI_NONE)){ + waitingOnHMIUpdate = true; + return; + } + + if (inProgressUpdate != null){ + // There's an in-progress update, put this on hold + hasQueuedUpdate = true; + return; + } + + sendDeleteCurrentVoiceCommands(new CompletionListener() { + @Override + public void onComplete(boolean success) { + // we don't care about errors from deleting, send new add commands + sendCurrentVoiceCommands(new CompletionListener() { + @Override + public void onComplete(boolean success2) { + inProgressUpdate = null; + + if (hasQueuedUpdate){ + update(); + hasQueuedUpdate = false; + } + + if (!success2){ + DebugTool.logError("Error sending voice commands"); + } + } + }); + } + }); + + } + + // DELETING OLD MENU ITEMS + + private void sendDeleteCurrentVoiceCommands(final CompletionListener listener){ + + if (oldVoiceCommands == null || oldVoiceCommands.size() == 0){ + if (listener != null){ + listener.onComplete(true); + } + return; + } + + List<DeleteCommand> deleteVoiceCommands = deleteCommandsForVoiceCommands(oldVoiceCommands); + oldVoiceCommands.clear(); + internalInterface.sendRequests(deleteVoiceCommands, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + + } + + @Override + public void onFinished() { + DebugTool.logInfo("Successfully deleted old voice commands"); + if (listener != null){ + listener.onComplete(true); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + + } + + @Override + public void onResponse(int correlationId, RPCResponse response) {} + }); + + } + + // SEND NEW MENU ITEMS + + private void sendCurrentVoiceCommands(final CompletionListener listener){ + + if (voiceCommands == null || voiceCommands.size() == 0){ + if (listener != null){ + listener.onComplete(true); // no voice commands to send doesnt mean that its an error + } + return; + } + + inProgressUpdate = addCommandsForVoiceCommands(voiceCommands); + + internalInterface.sendRequests(inProgressUpdate, new OnMultipleRequestListener() { + @Override + public void onUpdate(int remainingRequests) { + + } + + @Override + public void onFinished() { + DebugTool.logInfo("Sending Voice Commands Complete"); + if (listener != null){ + listener.onComplete(true); + } + oldVoiceCommands = voiceCommands; + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + + } + + @Override + public void onResponse(int correlationId, RPCResponse response) { + } + }); + } + + // DELETES + + List<DeleteCommand> deleteCommandsForVoiceCommands(List<VoiceCommand> voiceCommands){ + List<DeleteCommand> deleteCommandList = new ArrayList<>(); + for (VoiceCommand command : voiceCommands){ + DeleteCommand delete = new DeleteCommand(command.getCommandId()); + deleteCommandList.add(delete); + } + return deleteCommandList; + } + + // COMMANDS + + List<AddCommand> addCommandsForVoiceCommands(List<VoiceCommand> voiceCommands){ + List<AddCommand> addCommandList = new ArrayList<>(); + for (VoiceCommand command : voiceCommands){ + addCommandList.add(commandForVoiceCommand(command)); + } + return addCommandList; + } + + private AddCommand commandForVoiceCommand(VoiceCommand voiceCommand){ + AddCommand command = new AddCommand(voiceCommand.getCommandId()); + command.setVrCommands(voiceCommand.getVoiceCommands()); + return command; + } + + // HELPERS + + private void updateIdsOnVoiceCommands(List<VoiceCommand> voiceCommands){ + for (VoiceCommand command : voiceCommands){ + command.setCommandId(++lastVoiceCommandId); + } + } + + // LISTENERS + + private void addListeners(){ + + // HMI UPDATES + hmiListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + HMILevel oldHMILevel = currentHMILevel; + currentHMILevel = ((OnHMIStatus) notification).getHmiLevel(); + // Auto-send an update if we were in NONE and now we are not + if (oldHMILevel.equals(HMILevel.HMI_NONE) && !currentHMILevel.equals(HMILevel.HMI_NONE)){ + if (waitingOnHMIUpdate){ + setVoiceCommands(voiceCommands); + } + } + } + }; + internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener); + + // COMMANDS + commandListener = new OnRPCNotificationListener() { + @Override + public void onNotified(RPCNotification notification) { + OnCommand onCommand = (OnCommand) notification; + if (voiceCommands != null && voiceCommands.size() > 0){ + for (VoiceCommand command : voiceCommands){ + if (onCommand.getCmdID() == command.getCommandId()){ + if (command.getVoiceCommandSelectionListener() != null) { + command.getVoiceCommandSelectionListener().onVoiceCommandSelected(); + break; + } + } + } + } + } + }; + internalInterface.addOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener); + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesMode.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesMode.java new file mode 100644 index 000000000..b04cb4626 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesMode.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +public enum DynamicMenuUpdatesMode { + + /** + * FORCE_ON: This mode forces the menu manager to always dynamically update menu items for each menu + * update. This will provide the best performance but may cause ordering issues on some SYNC Gen 3 head units. + */ + FORCE_ON, + + /** + * FORCE_OFF: Forces off compatibility mode. This will force the menu manager to delete and re-add + * each menu item for every menu update. This mode is generally not advised due to performance issues. + */ + FORCE_OFF, + + /** + * ON_WITH_COMPAT_MODE: This mode checks whether the phone is connected to a SYNC Gen 3 head unit, which has known + * menu ordering issues. If it is, it will always delete and re-add every menu item, if not, it will dynamically update + * the menus. + */ + ON_WITH_COMPAT_MODE, + + ; + + public static DynamicMenuUpdatesMode valueForString(String value) { + try{ + return valueOf(value); + }catch(Exception e){ + return null; + } + } + +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java new file mode 100644 index 000000000..a772862f8 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; + +import java.util.ArrayList; +import java.util.List; + +public class MenuCell implements Cloneable{ + + /** + * The cell's text to be displayed + */ + private String title; + + /** + * The cell's icon to be displayed + */ + private SdlArtwork icon; + + /** + * The strings the user can say to activate this voice command + */ + private List<String> voiceCommands; + + /** + * If this is not null, this cell will be a sub-menu button, displaying the sub-cells in a menu when pressed. + */ + private List<MenuCell> subCells; + + /** + * The listener that will be called when the command is activated + */ + private MenuSelectionListener menuSelectionListener; + + /** + * Used internally for cell ordering + */ + private int parentCellId; + + /** + * Used internally for cell ordering + */ + private int cellId; + + /** + * MAX ID for cells - Cannot use Integer.MAX_INT as the value is too high. + */ + private static final int MAX_ID = 2000000000; + + // CONSTRUCTORS + + // SINGLE MENU ITEM CONSTRUCTORS + + /** + * Creates a new MenuCell Object with multiple parameters set + * @param title The cell's primary text + * @param icon The cell's image + * @param voiceCommands Voice commands that will activate the menu cell + * @param listener Calls the code that will be run when the menu cell is selected + */ + public MenuCell(@NonNull String title, @Nullable SdlArtwork icon, @Nullable List<String> voiceCommands, @Nullable MenuSelectionListener listener) { + setTitle(title); // title is the only required param + setIcon(icon); + setVoiceCommands(voiceCommands); + setMenuSelectionListener(listener); + setCellId(MAX_ID); + setParentCellId(MAX_ID); + } + + // CONSTRUCTOR FOR CELL THAT WILL LINK TO SUB MENU + + /** + * Creates a new MenuCell Object with multiple parameters set + * <strong>NOTE: because this has sub-cells, there does not need to be a listener</strong> + * @param title The cell's primary text + * @param icon The cell's image + * @param subCells The sub-cells for the sub menu that will appear when the cell is selected + */ + public MenuCell(@NonNull String title, @Nullable SdlArtwork icon, @Nullable List<MenuCell> subCells) { + setTitle(title); // title is the only required param + setIcon(icon); + setSubCells(subCells); + setCellId(MAX_ID); + setParentCellId(MAX_ID); + } + + // SETTERS / GETTERS + + // PUBLIC METHODS + + /** + * Sets the title of the menu cell + * @param title - the title of the cell. Required + */ + public void setTitle(@NonNull String title){ + this.title = title; + } + + /** + * Gets the title of the menu cell + * @return The title of the cell object + */ + public String getTitle(){ + return title; + } + + /** + * Sets the icon of the menu cell + * @param icon - the icon being set, of type {@link SdlArtwork} + */ + public void setIcon(SdlArtwork icon){ + this.icon = icon; + } + + /** + * Gets the icon for the cell + * @return the {@link SdlArtwork} icon for the cell + */ + public SdlArtwork getIcon() { + return icon; + } + + /** + * A list of Strings that will be used for voice commands + * @param voiceCommands - the string list used by the IVI system for voice commands + */ + public void setVoiceCommands(List<String> voiceCommands) { + this.voiceCommands = voiceCommands; + } + + /** + * the string list used by the IVI system for voice commands + * @return The String List used by the menu cell object for voice commands + */ + public List<String> getVoiceCommands() { + return voiceCommands; + } + + /** + * The list of MenuCells that can be set as subCells + * @param subCells - the list of subCells for this menu item + */ + public void setSubCells(List<MenuCell> subCells) { + this.subCells = subCells; + } + + /** + * The list of subCells for this menu item + * @return a list of MenuCells that are the subCells for this menu item + */ + public List<MenuCell> getSubCells() { + return subCells; + } + + /** + * The listener for when a menu item is selected + * @param menuSelectionListener the listener for this menuCell object + */ + public void setMenuSelectionListener(MenuSelectionListener menuSelectionListener) { + this.menuSelectionListener = menuSelectionListener; + } + + /** + * The listener that gets triggered when the menuCell object is selected + * @return the MenuSelectionListener for the cell + */ + public MenuSelectionListener getMenuSelectionListener() { + return menuSelectionListener; + } + + // INTERNALLY USED METHODS + + /** + * Set the cell Id. + * @param cellId - the cell Id + */ + void setCellId(int cellId) { + this.cellId = cellId; + } + + /** + * Get the cellId + * @return the cellId for this menuCell + */ + int getCellId() { + return cellId; + } + + /** + * Sets the ParentCellId + * @param parentCellId the parent cell's Id + */ + void setParentCellId(int parentCellId) { + this.parentCellId = parentCellId; + } + + /** + * Get the parent cell's Id + * @return the parent cell's Id + */ + int getParentCellId() { + return parentCellId; + } + + // HELPER + + /** + * Note: You should compare using the {@link #equals(Object)} method. <br> + * Hash the parameters of the object and return the result for comparison + * For each param, increase the rotation distance by one. + * It is necessary to rotate each of our properties because a simple bitwise OR will produce equivalent results if, for example: + * Object 1: getText() = "Hi", getSecondaryText() = "Hello" + * Object 2: getText() = "Hello", getSecondaryText() = "Hi" + * @return the hash code as an int + */ + @Override + public int hashCode() { + int result = 1; + result += ((getTitle() == null) ? 0 : Integer.rotateLeft(getTitle().hashCode(), 1)); + result += ((getIcon() == null || getIcon().getName() == null) ? 0 : Integer.rotateLeft(getIcon().getName().hashCode(), 2)); + result += ((getVoiceCommands() == null) ? 0 : Integer.rotateLeft(getVoiceCommands().hashCode(), 3)); + result += ((getSubCells() == null) ? 0 : Integer.rotateLeft(1, 4)); + return result; + } + + /** + * Uses our custom hashCode for MenuCell objects, but does <strong>NOT</strong> compare the listener objects + * @param o - The object to compare + * @return boolean of whether the objects are the same or not + */ + @Override + public boolean equals(Object o) { + // if this is the same memory address, its the same + if (this == o) return true; + // if this is not an instance of this class, not the same + if (!(o instanceof MenuCell)) return false; + + // if we get to this point, create the hashes and compare them + return hashCode() == o.hashCode(); + } + + /** + * Creates a deep copy of the object + * @return deep copy of the object + */ + @Override + public MenuCell clone() { + final MenuCell clone; + try { + clone = (MenuCell) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("superclass messed up", e); + } + clone.title = this.title; + clone.icon = this.icon == null ? null : this.icon.clone(); + clone.voiceCommands = null; + if (this.voiceCommands != null){ + clone.voiceCommands = new ArrayList<>(); + for (String voiceCommand : this.voiceCommands) { + clone.voiceCommands.add(voiceCommand); + } + } + clone.subCells = null; + if (this.subCells != null) { + clone.subCells = new ArrayList<>(); + for (MenuCell subCell : this.subCells) { + clone.subCells.add(subCell == null ? null : subCell.clone()); + } + } + clone.menuSelectionListener = this.menuSelectionListener; + clone.parentCellId = this.parentCellId; + clone.cellId = this.cellId; + return clone; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuSelectionListener.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuSelectionListener.java new file mode 100644 index 000000000..5c9ed5ce4 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuSelectionListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; + +public interface MenuSelectionListener { + + void onTriggered(TriggerSource trigger); + +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java new file mode 100644 index 000000000..2cc7db0b8 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import java.util.List; + +class RunScore { + + private int score; + private List<Integer> oldMenu, currentMenu; + + RunScore(int score, List<Integer> oldMenu, List<Integer> currentMenu){ + setScore(score); + setOldMenu(oldMenu); + setCurrentMenu(currentMenu); + } + + private void setCurrentMenu(List<Integer> currentMenu) { + this.currentMenu = currentMenu; + } + + List<Integer> getCurrentMenu() { + return currentMenu; + } + + private void setOldMenu(List<Integer> oldMenu) { + this.oldMenu = oldMenu; + } + + List<Integer> getOldMenu() { + return oldMenu; + } + + private void setScore(int score) { + this.score = score; + } + + public int getScore() { + return score; + } + +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java new file mode 100644 index 000000000..d5eee6ef1 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import java.util.List; + +class SubCellCommandList { + + private RunScore listsScore; + private String menuTitle; + private Integer parentId; + private List<MenuCell> oldList, newList; + + SubCellCommandList(String menuTitle, Integer parentId, RunScore listsScore, List<MenuCell> oldList, List<MenuCell> newList){ + setMenuTitle(menuTitle); + setParentId(parentId); + setListsScore(listsScore); + setOldList(oldList); + setNewList(newList); + } + + private void setParentId(Integer parentId) { + this.parentId = parentId; + } + + Integer getParentId() { + return parentId; + } + + private void setMenuTitle(String menuTitle) { + this.menuTitle = menuTitle; + } + + String getMenuTitle() { + return menuTitle; + } + + private void setListsScore(RunScore listsScore){ + this.listsScore = listsScore; + } + + RunScore getListsScore() { + return listsScore; + } + + private void setOldList(List<MenuCell> oldList) { + this.oldList = oldList; + } + + List<MenuCell> getOldList() { + return oldList; + } + + private void setNewList(List<MenuCell> newList) { + this.newList = newList; + } + + List<MenuCell> getNewList() { + return newList; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java new file mode 100644 index 000000000..8227b3087 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; + +public class VoiceCommand { + + /** + * The strings the user can say to activate this voice command + */ + private List<String> voiceCommands; + + /** + * The listener that will be called when the command is activated + */ + private VoiceCommandSelectionListener voiceCommandSelectionListener; + + /** + * Used Internally to identify the command + */ + private int commandId; + + // CONSTRUCTOR(S) + + /** + * Constructor that sets all parameters for this class + * <strong>NOTE: While it is possible to pass in null for the listener, It is the easiest way to know when it was triggered.</strong> + * @param voiceCommands The strings the user can say to activate this voice command + * @param voiceCommandSelectionListener The listener that will be called when the command is activated + */ + public VoiceCommand(@NonNull List<String> voiceCommands, @Nullable VoiceCommandSelectionListener voiceCommandSelectionListener){ + setVoiceCommands(voiceCommands); + setVoiceCommandSelectionListener(voiceCommandSelectionListener); + } + + // SETTERS / GETTERS + + /** + * The strings the user can say to activate this voice command + * @param voiceCommands - the list of commands to send to the head unit + */ + public void setVoiceCommands(@NonNull List<String> voiceCommands) { + this.voiceCommands = voiceCommands; + } + + /** + * The strings the user can say to activate this voice command + * @return the List of voice commands + */ + public List<String> getVoiceCommands() { + return voiceCommands; + } + + /** + * The listener that will be called when the command is activated + * @param voiceCommandSelectionListener - the listener for this object + */ + public void setVoiceCommandSelectionListener(VoiceCommandSelectionListener voiceCommandSelectionListener) { + this.voiceCommandSelectionListener = voiceCommandSelectionListener; + } + + /** + * The listener that will be called when the command is activated + * @return voiceCommandSelectionListener - the listener for this object + */ + public VoiceCommandSelectionListener getVoiceCommandSelectionListener() { + return voiceCommandSelectionListener; + } + + /** + * set the command' ID + * <strong>NOTE: PLEASE DO NOT SET. This is used internally</strong> + * @param commandId the id to identify the command + */ + void setCommandId(int commandId) { + this.commandId = commandId; + } + + /** + * the id used to identify the command + * @return the id + */ + int getCommandId() { + return commandId; + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandSelectionListener.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandSelectionListener.java new file mode 100644 index 000000000..1af92d3f4 --- /dev/null +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandSelectionListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +public interface VoiceCommandSelectionListener { + + void onVoiceCommandSelected(); + +} diff --git a/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java b/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java index 0330ce192..9af923026 100644 --- a/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java +++ b/hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java @@ -37,6 +37,10 @@ import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.SdlManagerListener; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.screen.menu.MenuCell; +import com.smartdevicelink.managers.screen.menu.MenuSelectionListener; +import com.smartdevicelink.managers.screen.menu.VoiceCommand; +import com.smartdevicelink.managers.screen.menu.VoiceCommandSelectionListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCNotification; import com.smartdevicelink.proxy.TTSChunkFactory; @@ -44,13 +48,14 @@ import com.smartdevicelink.proxy.rpc.*; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.util.DebugTool; -import java.util.Collections; -import java.util.HashMap; -import java.util.Vector; +import java.util.*; + +import static javafx.scene.input.KeyCode.R; public class SdlService { @@ -141,29 +146,14 @@ public class SdlService { public void onNotified(RPCNotification notification) { OnHMIStatus status = (OnHMIStatus) notification; if (status.getHmiLevel() == HMILevel.HMI_FULL && ((OnHMIStatus) notification).getFirstRun()) { - sendCommands(); + setVoiceCommands(); + sendMenus(); performWelcomeSpeak(); performWelcomeShow(); } } }); - notificationListenerHashMap.put(FunctionID.ON_COMMAND, new OnRPCNotificationListener() { - @Override - public void onNotified(RPCNotification notification) { - OnCommand command = (OnCommand) notification; - Integer id = command.getCmdID(); - if(id != null){ - switch(id){ - case TEST_COMMAND_ID: - showTest(); - break; - } - } - } - }); - - // Create App Icon, this is set in the SdlManager builder SdlArtwork appIcon = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, IMAGE_DIR+"sdl_s_green.png", true); @@ -177,17 +167,88 @@ public class SdlService { } } +/** + * Send some voice commands + */ + private void setVoiceCommands(){ + + List<String> list1 = Collections.singletonList("Command One"); + List<String> list2 = Collections.singletonList("Command two"); + + VoiceCommand voiceCommand1 = new VoiceCommand(list1, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 1 triggered"); + } + }); + + VoiceCommand voiceCommand2 = new VoiceCommand(list2, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 2 triggered"); + } + }); + + sdlManager.getScreenManager().setVoiceCommands(Arrays.asList(voiceCommand1,voiceCommand2)); + } + /** - * Add commands for the app on SDL. + * Add menus for the app on SDL. */ - private void sendCommands(){ - AddCommand command = new AddCommand(); - MenuParams params = new MenuParams(); - params.setMenuName(TEST_COMMAND_NAME); - command.setCmdID(TEST_COMMAND_ID); - command.setMenuParams(params); - command.setVrCommands(Collections.singletonList(TEST_COMMAND_NAME)); - sdlManager.sendRPC(command); + private void sendMenus(){ + + // some arts + SdlArtwork livio = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, IMAGE_DIR+"sdl_s_green.png", true); + + // some voice commands + List<String> voice2 = Collections.singletonList("Cell two"); + + MenuCell mainCell1 = new MenuCell("Test Cell 1 (speak)", livio, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 1 triggered. Source: "+ trigger.toString()); + showTest(); + } + }); + + MenuCell mainCell2 = new MenuCell("Test Cell 2", null, voice2, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // SUB MENU + + MenuCell subCell1 = new MenuCell("SubCell 1",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 1 triggered. Source: "+ trigger.toString()); + } + }); + + MenuCell subCell2 = new MenuCell("SubCell 2",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // sub menu parent cell + MenuCell mainCell3 = new MenuCell("Test Cell 3 (sub menu)", null, Arrays.asList(subCell1,subCell2)); + + MenuCell mainCell4 = new MenuCell("Clear the menu",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Clearing Menu. Source: "+ trigger.toString()); + // Clear this thing + sdlManager.getScreenManager().setMenu(Collections.<MenuCell>emptyList()); + showAlert("Menu Cleared"); + } + }); + + // Send the entire menu off to be created + sdlManager.getScreenManager().setMenu(Arrays.asList(mainCell1, mainCell2, mainCell3, mainCell4)); } /** @@ -222,13 +283,20 @@ public class SdlService { */ private void showTest(){ sdlManager.getScreenManager().beginTransaction(); - sdlManager.getScreenManager().setTextField1("Command has been selected"); + sdlManager.getScreenManager().setTextField1("Test Cell 1 has been selected"); sdlManager.getScreenManager().setTextField2(""); sdlManager.getScreenManager().commit(null); sdlManager.sendRPC(new Speak(TTSChunkFactory.createSimpleTTSChunks(TEST_COMMAND_NAME))); } + private void showAlert(String text){ + Alert alert = new Alert(); + alert.setAlertText1(text); + alert.setDuration(5000); + sdlManager.sendRPC(alert); + } + public interface SdlServiceCallback{ void onEnd(); diff --git a/hello_sdl_java_ee/src/main/java/com/smartdevicelink/SdlService.java b/hello_sdl_java_ee/src/main/java/com/smartdevicelink/SdlService.java index 58d98cf8d..aaf887222 100644 --- a/hello_sdl_java_ee/src/main/java/com/smartdevicelink/SdlService.java +++ b/hello_sdl_java_ee/src/main/java/com/smartdevicelink/SdlService.java @@ -5,6 +5,10 @@ import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.SdlManager; import com.smartdevicelink.managers.SdlManagerListener; import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.screen.menu.MenuCell; +import com.smartdevicelink.managers.screen.menu.MenuSelectionListener; +import com.smartdevicelink.managers.screen.menu.VoiceCommand; +import com.smartdevicelink.managers.screen.menu.VoiceCommandSelectionListener; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCNotification; import com.smartdevicelink.proxy.TTSChunkFactory; @@ -12,13 +16,12 @@ import com.smartdevicelink.proxy.rpc.*; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.TriggerSource; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.util.DebugTool; -import java.util.Collections; -import java.util.HashMap; -import java.util.Vector; +import java.util.*; public class SdlService { @@ -109,29 +112,14 @@ public class SdlService { public void onNotified(RPCNotification notification) { OnHMIStatus status = (OnHMIStatus) notification; if (status.getHmiLevel() == HMILevel.HMI_FULL && ((OnHMIStatus) notification).getFirstRun()) { - sendCommands(); + setVoiceCommands(); + sendMenus(); performWelcomeSpeak(); performWelcomeShow(); } } }); - notificationListenerHashMap.put(FunctionID.ON_COMMAND, new OnRPCNotificationListener() { - @Override - public void onNotified(RPCNotification notification) { - OnCommand command = (OnCommand) notification; - Integer id = command.getCmdID(); - if(id != null){ - switch(id){ - case TEST_COMMAND_ID: - showTest(); - break; - } - } - } - }); - - // Create App Icon, this is set in the SdlManager builder SdlArtwork appIcon = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, IMAGE_DIR+"sdl_s_green.png", true); @@ -145,17 +133,88 @@ public class SdlService { } } + /** + * Send some voice commands + */ + private void setVoiceCommands(){ + + List<String> list1 = Collections.singletonList("Command One"); + List<String> list2 = Collections.singletonList("Command two"); + + VoiceCommand voiceCommand1 = new VoiceCommand(list1, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 1 triggered"); + } + }); + + VoiceCommand voiceCommand2 = new VoiceCommand(list2, new VoiceCommandSelectionListener() { + @Override + public void onVoiceCommandSelected() { + Log.i(TAG, "Voice Command 2 triggered"); + } + }); + + sdlManager.getScreenManager().setVoiceCommands(Arrays.asList(voiceCommand1,voiceCommand2)); + } + /** - * Add commands for the app on SDL. + * Add menus for the app on SDL. */ - private void sendCommands(){ - AddCommand command = new AddCommand(); - MenuParams params = new MenuParams(); - params.setMenuName(TEST_COMMAND_NAME); - command.setCmdID(TEST_COMMAND_ID); - command.setMenuParams(params); - command.setVrCommands(Collections.singletonList(TEST_COMMAND_NAME)); - sdlManager.sendRPC(command); + private void sendMenus(){ + + // some arts + SdlArtwork livio = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, IMAGE_DIR+"sdl_s_green.png", true); + + // some voice commands + List<String> voice2 = Collections.singletonList("Cell two"); + + MenuCell mainCell1 = new MenuCell("Test Cell 1 (speak)", livio, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 1 triggered. Source: "+ trigger.toString()); + showTest(); + } + }); + + MenuCell mainCell2 = new MenuCell("Test Cell 2", null, voice2, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Test cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // SUB MENU + + MenuCell subCell1 = new MenuCell("SubCell 1",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 1 triggered. Source: "+ trigger.toString()); + } + }); + + MenuCell subCell2 = new MenuCell("SubCell 2",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Sub cell 2 triggered. Source: "+ trigger.toString()); + } + }); + + // sub menu parent cell + MenuCell mainCell3 = new MenuCell("Test Cell 3 (sub menu)", null, Arrays.asList(subCell1,subCell2)); + + MenuCell mainCell4 = new MenuCell("Clear the menu",null, null, new MenuSelectionListener() { + @Override + public void onTriggered(TriggerSource trigger) { + Log.i(TAG, "Clearing Menu. Source: "+ trigger.toString()); + // Clear this thing + sdlManager.getScreenManager().setMenu(Collections.<MenuCell>emptyList()); + showAlert("Menu Cleared"); + } + }); + + // Send the entire menu off to be created + sdlManager.getScreenManager().setMenu(Arrays.asList(mainCell1, mainCell2, mainCell3, mainCell4)); } /** @@ -190,13 +249,20 @@ public class SdlService { */ private void showTest(){ sdlManager.getScreenManager().beginTransaction(); - sdlManager.getScreenManager().setTextField1("Command has been selected"); + sdlManager.getScreenManager().setTextField1("Test Cell 1 has been selected"); sdlManager.getScreenManager().setTextField2(""); sdlManager.getScreenManager().commit(null); sdlManager.sendRPC(new Speak(TTSChunkFactory.createSimpleTTSChunks(TEST_COMMAND_NAME))); } + private void showAlert(String text){ + Alert alert = new Alert(); + alert.setAlertText1(text); + alert.setDuration(5000); + sdlManager.sendRPC(alert); + } + public interface SdlServiceCallback{ void onEnd(); diff --git a/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java b/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java index 6e8085e0a..f13b523aa 100644 --- a/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java +++ b/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java @@ -40,7 +40,7 @@ import com.smartdevicelink.proxy.rpc.enums.StaticIconName; /** * A class that extends SdlFile, representing artwork (JPEG, PNG, or BMP) to be uploaded to core */ -public class SdlArtwork extends SdlFile { +public class SdlArtwork extends SdlFile implements Cloneable{ private boolean isTemplate; private Image imageRPC; @@ -112,14 +112,49 @@ public class SdlArtwork extends SdlFile { */ public Image getImageRPC() { if (imageRPC == null) { - if (isStaticIcon()) { - imageRPC = new Image(getName(), ImageType.STATIC); - imageRPC.setIsTemplate(true); - } else { - imageRPC = new Image(getName(), ImageType.DYNAMIC); - imageRPC.setIsTemplate(isTemplate); - } + imageRPC = createImageRPC(); } return imageRPC; } + + private Image createImageRPC(){ + Image image; + if (isStaticIcon()) { + image = new Image(getName(), ImageType.STATIC); + image.setIsTemplate(true); + } else { + image = new Image(getName(), ImageType.DYNAMIC); + image.setIsTemplate(isTemplate); + } + return image; + } + + /** + * Creates a deep copy of the object + * @return deep copy of the object + */ + @Override + public SdlArtwork clone() { + final SdlArtwork clone; + try { + clone = (SdlArtwork) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("superclass messed up", e); + } + clone.setName(this.getName()); + clone.setFilePath(this.getFilePath()); + if (this.getFileData() != null){ + byte[] data = new byte[this.getFileData().length]; + for (int i = 0; i < this.getFileData().length; i++) { + data[i] = this.getFileData()[i]; + } + clone.setFileData(data); + } + clone.setType(this.getType()); + clone.setPersistent(this.isPersistent()); + clone.setStaticIcon(this.isStaticIcon()); + clone.isTemplate = this.isTemplate; + clone.imageRPC = this.createImageRPC(); + return clone; + } }
\ No newline at end of file diff --git a/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java b/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java new file mode 100644 index 000000000..4a28a2792 --- /dev/null +++ b/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.proxy.interfaces.ISdl; + +/** + * <strong>MenuManager</strong> <br> + * + * Note: This class must be accessed through the ScreenManager via the SdlManager. Do not instantiate it by itself. <br> + * + * The MenuManager takes MenuCell objects and creates and sends all necessary RPCs to build out a menu + */ +public class MenuManager extends BaseMenuManager { + + public MenuManager(ISdl internalInterface, FileManager fileManager) { + // setup + super(internalInterface, fileManager); + } + +} diff --git a/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java b/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java new file mode 100644 index 000000000..a892d3a2c --- /dev/null +++ b/javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Livio, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the Livio Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartdevicelink.managers.screen.menu; + +import com.smartdevicelink.proxy.interfaces.ISdl; + +/** + * <strong>VoiceCommandManager</strong> <br> + * + * Note: This class must be accessed through the ScreenManager via the SdlManager. Do not instantiate it by itself. <br> + * + * The VoiceCommandManager takes a List of VoiceCommand objects and sets them on the Head unit for you. + */ +public class VoiceCommandManager extends BaseVoiceCommandManager { + + public VoiceCommandManager(ISdl internalInterface) { + // setup + super(internalInterface); + } + +} |