summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrettyWhite <geekman3454@protonmail.com>2019-06-04 10:27:25 -0400
committerBrettyWhite <geekman3454@protonmail.com>2019-06-04 10:27:25 -0400
commit031dea755899a2b06ff65422baa88a0b2aad1ade (patch)
tree06c8907a1c6da94c702cea2373404bff9b1eea8f
parent6287c6a82a167ed2059b65eb636fce958285dd07 (diff)
parent5601d6132e14f6f52ac94d96c82f8653fba4d0f8 (diff)
downloadsdl_android-031dea755899a2b06ff65422baa88a0b2aad1ade.tar.gz
Merge branch 'develop' into feature/choice_set_manager
# Conflicts: # base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java
-rw-r--r--.travis.yml4
-rw-r--r--android/build.gradle2
-rw-r--r--android/gradle/wrapper/gradle-wrapper.properties4
-rwxr-xr-xandroid/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java129
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java18
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesModeTests.java99
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuCellTests.java110
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java651
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/RunScoreTests.java62
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java65
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java199
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java64
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Test.java22
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/proxy/SystemCapabilityManagerTests.java1
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/AppServiceCapabilityTest.java3
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java52
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java52
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java51
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java79
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java1188
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java329
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdatesMode.java66
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java310
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuSelectionListener.java41
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java72
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java91
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java120
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandSelectionListener.java39
-rw-r--r--hello_sdl_java/src/main/java/com/smartdevicelink/java/SdlService.java128
-rw-r--r--hello_sdl_java_ee/src/main/java/com/smartdevicelink/SdlService.java126
-rw-r--r--javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java51
-rw-r--r--javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManager.java52
-rw-r--r--javaSE/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManager.java51
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);
+ }
+
+}