summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnil Dahiya <anil_dahiya@infosys.com>2019-06-10 14:01:16 +0530
committerAnil Dahiya <anil_dahiya@infosys.com>2019-06-10 14:01:16 +0530
commitb21f4df587627c57098de103b87a79b4942875ed (patch)
treedc9df1abd93b41d2c945750a89b1aca2710ea6ed
parent551a9b15e6cc6b47156af903d1a6b8b0b4c435c2 (diff)
parent23bb686f3057084601171368638258f93819f612 (diff)
downloadsdl_android-b21f4df587627c57098de103b87a79b4942875ed.tar.gz
Merge branch 'develop' into feature/gps_shift
-rw-r--r--.gitignore1
-rw-r--r--README.md4
-rwxr-xr-xandroid/hello_sdl_android/src/main/java/com/sdl/hellosdlandroid/SdlService.java129
-rw-r--r--android/sdl_android/build.gradle17
-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/managers/video/VideoStreamManagerTests.java237
-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/androidTest/java/com/smartdevicelink/test/rpc/datatypes/BeltStatusTests.java2
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/notifications/OnVehicleDataTests.java2
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/responses/GetVehicleDataResponseTests.java2
-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--android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java253
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java237
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java76
-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--base/src/main/java/com/smartdevicelink/proxy/rpc/BeltStatus.java7
-rw-r--r--baseAndroid/README.md7
-rw-r--r--baseAndroid/make_symbolic_links.py69
-rw-r--r--baseAndroid/make_symlinks.lnkbin0 -> 2143 bytes
-rw-r--r--baseAndroid/run_as_admin_symlink_script.bat4
-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
44 files changed, 4790 insertions, 370 deletions
diff --git a/.gitignore b/.gitignore
index a87913e43..13eed80b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ javaSE/local.properties
javaEE/out/
javaEE/build/
javaEE/local.properties
+baseAndroid/windows/
##############################
diff --git a/README.md b/README.md
index 52069e7f3..7cca91c77 100644
--- a/README.md
+++ b/README.md
@@ -116,10 +116,10 @@ from within the project (JavaSE or JavaEE) and a JAR should be generated in the
![Java Suite Folder Structure](JavaSuiteFolderStructure.png)
#### base Folder
-The base folder contains the source set that is shared between all of the compilable projects. This folder does not contain a a compilable project.
+The base folder contains the source set that is shared between all of the compilable projects. This folder does not contain a compilable project.
#### baseAndroid Folder
-The baseAndroid folder contains symbolic links to files and folders from the base folder. This has been included since the Java Suite refactor is a minor version release and the base folder contains breaking changes for the Android project. This folder does not contain a a compilable project.
+The baseAndroid folder contains symbolic links to files and folders from the base folder. This has been included since the Java Suite refactor is a minor version release and the base folder contains breaking changes for the Android project. This folder does not contain a compilable project.
#### android Folder
The android folder contains the SDL Android library as well as the sample project for Android. Both of those are compilable projects.
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/build.gradle b/android/sdl_android/build.gradle
index 21ff200cb..88c2617ec 100644
--- a/android/sdl_android/build.gradle
+++ b/android/sdl_android/build.gradle
@@ -1,4 +1,6 @@
apply plugin: 'com.android.library'
+import org.apache.tools.ant.taskdefs.condition.Os
+
android {
compileSdkVersion 28
@@ -34,10 +36,23 @@ android {
}
sourceSets {
- main.java.srcDirs += '../../baseAndroid/src/main/java'
+ if(Os.isFamily(Os.FAMILY_WINDOWS)){
+ //The buildWindowsSymLinks task must be run first if this is
+ //being compiled on a Windows machine
+ main.java.srcDirs += '../../baseAndroid/windows/src/main/java'
+ }else{
+ main.java.srcDirs += '../../baseAndroid/src/main/java'
+ }
}
}
+task buildWindowSymLinks(type:Exec){
+
+ workingDir '../../baseAndroid'
+
+ commandLine 'cmd', '/c', 'make_symlinks.lnk'
+}
+
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'com.smartdevicelink:bson_java_port:1.2.0'
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/managers/video/VideoStreamManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/VideoStreamManagerTests.java
index 5e291c736..76bce2e7a 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/VideoStreamManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/VideoStreamManagerTests.java
@@ -17,6 +17,7 @@ import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
import com.smartdevicelink.proxy.rpc.OnTouchEvent;
+import com.smartdevicelink.proxy.rpc.TouchCoord;
import com.smartdevicelink.proxy.rpc.TouchEvent;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
@@ -31,8 +32,11 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
@@ -245,42 +249,205 @@ public class VideoStreamManagerTests extends AndroidTestCase2 {
}
- public void testConvertTouchEvent(){
+ public void testConvertTouchEvent() {
ISdl internalInterface = mock(ISdl.class);
- when(internalInterface.getProtocolVersion()).thenReturn(new Version(5,1,0));
+ VideoStreamManager videoStreamManager = new VideoStreamManager(internalInterface);
+ List<MotionEvent> motionEventList;
+ long e1TS = 1558124390L, e2TS = 1558125390L, e3TS = 1558126390L;
+ int e1x = 50, e1y = 100, e2x = 150, e2y = 200, e3x = 250, e3y = 300;
+ int e1Id = 100, e2Id = 101, e3Id = 102;
+ int movingStep = 10;
+ OnTouchEvent testOnTouchEvent;
+ MotionEvent motionEvent;
+ TouchEvent touchEvent1 = new TouchEvent(e1Id, Collections.singletonList(e1TS), Collections.singletonList(new TouchCoord(e1x, e1y)));
+ TouchEvent touchEvent2 = new TouchEvent(e2Id, Collections.singletonList(e2TS), Collections.singletonList(new TouchCoord(e2x, e2y)));
+ TouchEvent touchEvent2AfterMovingPointer = new TouchEvent(e2Id, Collections.singletonList(e2TS), Collections.singletonList(new TouchCoord(e2x + movingStep, e2y + movingStep)));
+ TouchEvent touchEvent3 = new TouchEvent(e3Id, Collections.singletonList(e3TS), Collections.singletonList(new TouchCoord(e3x, e3y)));
- final VideoStreamManager videoStreamManager = new VideoStreamManager(internalInterface);
- videoStreamManager.start(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- assertTrue(success);
- OnTouchEvent testOnTouchEvent = new OnTouchEvent();
- TouchEvent touchEvent = Test.GENERAL_TOUCHEVENT;
- testOnTouchEvent.setEvent(Collections.singletonList(touchEvent));
- testOnTouchEvent.setType(Test.GENERAL_TOUCHTYPE);
- MotionEvent motionEvent;
-
- // Touch one pointer (100)
- motionEvent = videoStreamManager.convertTouchEvent(testOnTouchEvent);
- assertEquals(motionEvent.getAction(), MotionEvent.ACTION_DOWN);
-
- // Touch another pointer (101) without release
- touchEvent.setId(Test.GENERAL_INT + 1);
- testOnTouchEvent.setEvent(Collections.singletonList(touchEvent));
- motionEvent = videoStreamManager.convertTouchEvent(testOnTouchEvent);
- assertEquals(motionEvent.getAction(), MotionEvent.ACTION_POINTER_DOWN);
-
- // Release one of the pointers (101)
- testOnTouchEvent.setType(TouchType.END);
- motionEvent = videoStreamManager.convertTouchEvent(testOnTouchEvent);
- assertEquals(motionEvent.getAction(), MotionEvent.ACTION_POINTER_UP);
-
- // Release the other pointer (100)
- touchEvent.setId(Test.GENERAL_INT);
- testOnTouchEvent.setEvent(Collections.singletonList(touchEvent));
- motionEvent = videoStreamManager.convertTouchEvent(testOnTouchEvent);
- assertEquals(motionEvent.getAction(), MotionEvent.ACTION_UP);
- }
- });
+
+
+ /////////////////////////////////////////////////// First OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.BEGIN, Arrays.asList(touchEvent1, touchEvent2));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_DOWN and have 1 pointer
+ motionEvent = motionEventList.get(0);
+ assertEquals(1, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(MotionEvent.ACTION_DOWN, motionEvent.getActionMasked());
+
+
+ // Second MotionEvent should be ACTION_POINTER_DOWN and have 2 pointers
+ motionEvent = motionEventList.get(1);
+ assertEquals(2, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y, Math.round(motionEvent.getY(1)));
+ assertEquals(MotionEvent.ACTION_POINTER_DOWN, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Second OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.BEGIN, Arrays.asList(touchEvent3));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_POINTER_DOWN and have 3 pointers
+ motionEvent = motionEventList.get(0);
+ assertEquals(3, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y, Math.round(motionEvent.getY(1)));
+ assertEquals(e3x, Math.round(motionEvent.getX(2)));
+ assertEquals(e3y, Math.round(motionEvent.getY(2)));
+ assertEquals(MotionEvent.ACTION_POINTER_DOWN, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Third OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.MOVE, Arrays.asList(touchEvent2AfterMovingPointer));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_MOVE and have 3 pointers
+ motionEvent = motionEventList.get(0);
+ assertEquals(3, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x + movingStep, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y + movingStep, Math.round(motionEvent.getY(1)));
+ assertEquals(e3x, Math.round(motionEvent.getX(2)));
+ assertEquals(e3y, Math.round(motionEvent.getY(2)));
+ assertEquals(MotionEvent.ACTION_MOVE, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Fourth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.END, Arrays.asList(touchEvent2AfterMovingPointer));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_POINTER_UP and have 3 pointers
+ motionEvent = motionEventList.get(0);
+ assertEquals(3, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x + movingStep, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y + movingStep, Math.round(motionEvent.getY(1)));
+ assertEquals(e3x, Math.round(motionEvent.getX(2)));
+ assertEquals(e3y, Math.round(motionEvent.getY(2)));
+ assertEquals(MotionEvent.ACTION_POINTER_UP, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Fifth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.END, Arrays.asList(touchEvent3));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_POINTER_UP and have 2 pointers
+ motionEvent = motionEventList.get(0);
+ assertEquals(2, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e3x, Math.round(motionEvent.getX(1)));
+ assertEquals(e3y, Math.round(motionEvent.getY(1)));
+ assertEquals(MotionEvent.ACTION_POINTER_UP, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Sixth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.END, Arrays.asList(touchEvent3));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_UP and have 1 pointer
+ motionEvent = motionEventList.get(0);
+ assertEquals(1, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(MotionEvent.ACTION_UP, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Seventh OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.BEGIN, Arrays.asList(touchEvent1, touchEvent2));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_DOWN and have 1 pointer
+ motionEvent = motionEventList.get(0);
+ assertEquals(1, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(MotionEvent.ACTION_DOWN, motionEvent.getActionMasked());
+
+
+ // Second MotionEvent should be ACTION_POINTER_DOWN and have 2 pointers
+ motionEvent = motionEventList.get(1);
+ assertEquals(2, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y, Math.round(motionEvent.getY(1)));
+ assertEquals(MotionEvent.ACTION_POINTER_DOWN, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Eighth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.CANCEL, Arrays.asList(touchEvent3));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_CANCEL and have 2 pointers
+ motionEvent = motionEventList.get(0);
+ assertEquals(2, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(e2x, Math.round(motionEvent.getX(1)));
+ assertEquals(e2y, Math.round(motionEvent.getY(1)));
+ assertEquals(MotionEvent.ACTION_CANCEL, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Ninth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.BEGIN, Arrays.asList(touchEvent1));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_DOWN and have 1 pointer
+ motionEvent = motionEventList.get(0);
+ assertEquals(1, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(MotionEvent.ACTION_DOWN, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ /////////////////////////////////////////////////// Tenth OnTouchEvent Notification ///////////////////////////////////////////////////
+ testOnTouchEvent = new OnTouchEvent(TouchType.END, Arrays.asList(touchEvent1));
+ motionEventList = videoStreamManager.convertTouchEvent(testOnTouchEvent);
+
+
+ // First MotionEvent should be ACTION_UP and have 1 pointer
+ motionEvent = motionEventList.get(0);
+ assertEquals(1, motionEvent.getPointerCount());
+ assertEquals(e1x, Math.round(motionEvent.getX(0)));
+ assertEquals(e1y, Math.round(motionEvent.getY(0)));
+ assertEquals(MotionEvent.ACTION_UP, motionEvent.getActionMasked());
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
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/androidTest/java/com/smartdevicelink/test/rpc/datatypes/BeltStatusTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/BeltStatusTests.java
index c13e1abd4..a5f613aed 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/BeltStatusTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/BeltStatusTests.java
@@ -107,7 +107,7 @@ public class BeltStatusTests extends TestCase{
try{
reference.put(BeltStatus.KEY_PASSENGER_CHILD_DETECTED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
- reference.put(BeltStatus.KEY_REAR_INFLATABLE_BELTED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
+ reference.put(BeltStatus.KEY_LEFT_REAR_INFLATABLE_BELTED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
reference.put(BeltStatus.KEY_RIGHT_REAR_INFLATABLE_BELTED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
reference.put(BeltStatus.KEY_DRIVER_BELT_DEPLOYED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
reference.put(BeltStatus.KEY_DRIVER_BUCKLE_BELTED, Test.GENERAL_VEHCILEDATAEVENTSTATUS);
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/notifications/OnVehicleDataTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/notifications/OnVehicleDataTests.java
index fd9356cb9..38f55e418 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/notifications/OnVehicleDataTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/notifications/OnVehicleDataTests.java
@@ -276,7 +276,7 @@ public class OnVehicleDataTests extends BaseRpcTests{
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_3_BELTED);
beltStatusObj.put(BeltStatus.KEY_LEFT_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_ROW_3_BELTED);
beltStatusObj.put(BeltStatus.KEY_RIGHT_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_RIGHT_ROW_3_BELTED);
- beltStatusObj.put(BeltStatus.KEY_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_REAR_INFLATABLE_BELTED);
+ beltStatusObj.put(BeltStatus.KEY_LEFT_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_REAR_INFLATABLE_BELTED);
beltStatusObj.put(BeltStatus.KEY_RIGHT_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_RIGHT_REAR_INFLATABLE_BELTED);
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_1_BELT_DEPLOYED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_1_DEPLOYED);
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_1_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_1_BELTED);
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/responses/GetVehicleDataResponseTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/responses/GetVehicleDataResponseTests.java
index 5bab84145..34c4a2711 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/responses/GetVehicleDataResponseTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/responses/GetVehicleDataResponseTests.java
@@ -171,7 +171,7 @@ public class GetVehicleDataResponseTests extends BaseRpcTests{
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_3_BELTED);
beltStatusObj.put(BeltStatus.KEY_LEFT_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_ROW_3_BELTED);
beltStatusObj.put(BeltStatus.KEY_RIGHT_ROW_3_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_RIGHT_ROW_3_BELTED);
- beltStatusObj.put(BeltStatus.KEY_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_REAR_INFLATABLE_BELTED);
+ beltStatusObj.put(BeltStatus.KEY_LEFT_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_LEFT_REAR_INFLATABLE_BELTED);
beltStatusObj.put(BeltStatus.KEY_RIGHT_REAR_INFLATABLE_BELTED, VehicleDataHelper.BELT_STATUS_RIGHT_REAR_INFLATABLE_BELTED);
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_1_BELT_DEPLOYED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_1_DEPLOYED);
beltStatusObj.put(BeltStatus.KEY_MIDDLE_ROW_1_BUCKLE_BELTED, VehicleDataHelper.BELT_STATUS_MIDDLE_ROW_1_BELTED);
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/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
index 45196c6c5..49798078c 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/video/VideoStreamManager.java
@@ -37,7 +37,6 @@ import android.content.Context;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseIntArray;
import android.view.Display;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -72,7 +71,9 @@ import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.Version;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.FutureTask;
@TargetApi(19)
@@ -151,9 +152,11 @@ public class VideoStreamManager extends BaseVideoStreamManager {
@Override
public void onNotified(RPCNotification notification) {
if(notification != null && remoteDisplay != null){
- MotionEvent event = convertTouchEvent((OnTouchEvent)notification);
- if(event!=null){
- remoteDisplay.handleMotionEvent(event);
+ List<MotionEvent> motionEventList = convertTouchEvent((OnTouchEvent)notification);
+ if (motionEventList != null && !motionEventList.isEmpty()) {
+ for (MotionEvent motionEvent : motionEventList) {
+ remoteDisplay.handleMotionEvent(motionEvent);
+ }
}
}
}
@@ -476,165 +479,201 @@ public class VideoStreamManager extends BaseVideoStreamManager {
}
}
- protected MotionEvent convertTouchEvent(OnTouchEvent touchEvent){
- List<TouchEvent> eventList = touchEvent.getEvent();
- if (eventList == null || eventList.size() == 0) return null;
+ @Override
+ protected void onTransportUpdate(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail){
- TouchType touchType = touchEvent.getType();
- if (touchType == null){ return null;}
+ isTransportAvailable = videoStreamTransportAvail;
- int eventListSize = eventList.size();
+ if(internalInterface.getProtocolVersion().isNewerThan(new Version(5,1,0)) >= 0){
+ if(videoStreamTransportAvail){
+ checkState();
+ }
+ }else{
+ //The protocol version doesn't support simultaneous transports.
+ if(!videoStreamTransportAvail){
+ //If video streaming isn't available on primary transport then it is not possible to
+ //use the video streaming manager until a complete register on a transport that
+ //supports video
+ transitionToState(ERROR);
+ }
+ }
+ }
- MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[eventListSize];
- MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[eventListSize];
+ List<MotionEvent> convertTouchEvent(OnTouchEvent onTouchEvent){
+ List<MotionEvent> motionEventList = new ArrayList<MotionEvent>();
- TouchEvent event;
- MotionEvent.PointerProperties properties;
- MotionEvent.PointerCoords coords;
- TouchCoord touchCoord;
+ List<TouchEvent> touchEventList = onTouchEvent.getEvent();
+ if (touchEventList == null || touchEventList.size() == 0) return null;
- for(int i = 0; i < eventListSize; i++){
- event = eventList.get(i);
- if(event == null || event.getId() == null || event.getTouchCoordinates() == null){
- continue;
- }
+ TouchType touchType = onTouchEvent.getType();
+ if (touchType == null) { return null; }
- properties = new MotionEvent.PointerProperties();
- properties.id = event.getId();
- properties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ if(sdlMotionEvent == null) {
+ if (touchType == TouchType.BEGIN) {
+ sdlMotionEvent = new SdlMotionEvent();
+ } else{
+ return null;
+ }
+ }
+ SdlMotionEvent.Pointer pointer;
+ MotionEvent motionEvent;
- List<TouchCoord> coordList = event.getTouchCoordinates();
- if (coordList == null || coordList.size() == 0){ continue; }
+ for (TouchEvent touchEvent : touchEventList) {
+ if (touchEvent == null || touchEvent.getId() == null) {
+ continue;
+ }
- touchCoord = coordList.get(coordList.size() -1);
- if(touchCoord == null){ continue; }
+ List<TouchCoord> touchCoordList = touchEvent.getTouchCoordinates();
+ if (touchCoordList == null || touchCoordList.size() == 0) {
+ continue;
+ }
- coords = new MotionEvent.PointerCoords();
- coords.x = touchCoord.getX() * touchScalar[0];
- coords.y = touchCoord.getY() * touchScalar[1];
- coords.orientation = 0;
- coords.pressure = 1.0f;
- coords.size = 1;
+ TouchCoord touchCoord = touchCoordList.get(touchCoordList.size() - 1);
+ if (touchCoord == null) {
+ continue;
+ }
- //Add the info to lists only after we are sure we have all available info
- pointerProperties[i] = properties;
- pointerCoords[i] = coords;
+ int motionEventAction = sdlMotionEvent.getMotionEventAction(touchType, touchEvent);
+ long downTime = sdlMotionEvent.downTime;
+ long eventTime = sdlMotionEvent.eventTime;
+ pointer = sdlMotionEvent.getPointerById(touchEvent.getId());
+ if (pointer != null) {
+ pointer.setCoords(touchCoord.getX() * touchScalar[0], touchCoord.getY() * touchScalar[1]);
+ }
- }
+ MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[sdlMotionEvent.pointers.size()];
+ MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[sdlMotionEvent.pointers.size()];
+ for (int i = 0; i < sdlMotionEvent.pointers.size(); i++) {
+ pointerProperties[i] = new MotionEvent.PointerProperties();
+ pointerProperties[i].id = sdlMotionEvent.getPointerByIndex(i).id;
+ pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
- if(sdlMotionEvent == null) {
- if (touchType == TouchType.BEGIN) {
- sdlMotionEvent = new SdlMotionEvent();
- }else{
- return null;
+ pointerCoords[i] = new MotionEvent.PointerCoords();
+ pointerCoords[i].x = sdlMotionEvent.getPointerByIndex(i).x;
+ pointerCoords[i].y = sdlMotionEvent.getPointerByIndex(i).y;
+ pointerCoords[i].orientation = 0;
+ pointerCoords[i].pressure = 1.0f;
+ pointerCoords[i].size = 1;
}
- }
- int eventAction = sdlMotionEvent.getMotionEvent(touchType, pointerProperties);
- long startTime = sdlMotionEvent.startOfEvent;
-
- //If the motion event should be finished we should clear our reference
- if(eventAction == MotionEvent.ACTION_UP || eventAction == MotionEvent.ACTION_CANCEL){
- sdlMotionEvent = null;
+ motionEvent = MotionEvent.obtain(downTime, eventTime, motionEventAction,
+ sdlMotionEvent.pointers.size(), pointerProperties, pointerCoords, 0, 0, 1,
+ 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+ motionEventList.add(motionEvent);
+
+ if(motionEventAction == MotionEvent.ACTION_UP || motionEventAction == MotionEvent.ACTION_CANCEL){
+ //If the motion event should be finished we should clear our reference
+ sdlMotionEvent.pointers.clear();
+ sdlMotionEvent = null;
+ break;
+ } else if((motionEventAction & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP){
+ sdlMotionEvent.removePointerById(touchEvent.getId());
+ }
}
- return MotionEvent.obtain(startTime, SystemClock.uptimeMillis(), eventAction, eventListSize, pointerProperties, pointerCoords, 0, 0,1,1,0,0, InputDevice.SOURCE_TOUCHSCREEN,0);
+ return motionEventList;
}
/**
* Keeps track of the current motion event for VPM
*/
- private static class SdlMotionEvent{
- long startOfEvent;
- SparseIntArray pointerStatuses = new SparseIntArray();
+ private static class SdlMotionEvent {
+ class Pointer {
+ int id;
+ float x;
+ float y;
+ Pointer (int id) {
+ this.id = id;
+ this.x = 0.0f;
+ this.y = 0.0f;
+ }
+ void setCoords(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ private CopyOnWriteArrayList<Pointer> pointers = new CopyOnWriteArrayList<>();
+ private long downTime;
+ private long downTimeOnHMI;
+ private long eventTime;
SdlMotionEvent(){
- startOfEvent = SystemClock.uptimeMillis();
+ downTimeOnHMI = 0;
}
/**
- * Handles the SDL Touch Event to keep track of pointer status and returns the appropirate
+ * Handles the SDL Touch Event to keep track of pointer status and returns the appropriate
* Android MotionEvent according to this events status
* @param touchType The SDL TouchType that was received from the module
- * @param pointerProperties the parsed pointer properties built from the OnTouchEvent RPC
- * @return the correct native Andorid MotionEvent action to dispatch
+ * @param touchEvent The SDL TouchEvent that was received from the module
+ * @return the correct native Android MotionEvent action to dispatch
*/
- synchronized int getMotionEvent(TouchType touchType, MotionEvent.PointerProperties[] pointerProperties){
- int motionEvent = MotionEvent.ACTION_DOWN;
+ synchronized int getMotionEventAction(TouchType touchType, TouchEvent touchEvent){
+ eventTime = 0;
+ int motionEventAction = -1;
switch (touchType){
case BEGIN:
- if(pointerStatuses.size() == 0){
+ if(pointers.size() == 0){
//The motion event has just begun
- motionEvent = MotionEvent.ACTION_DOWN;
- }else{
- motionEvent = MotionEvent.ACTION_POINTER_DOWN;
+ motionEventAction = MotionEvent.ACTION_DOWN;
+ downTime = SystemClock.uptimeMillis();
+ downTimeOnHMI = touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1);
+ eventTime = downTime;
+ } else{
+ motionEventAction = MotionEvent.ACTION_POINTER_DOWN | pointers.size() << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
}
- setPointerStatuses(motionEvent, pointerProperties);
+ pointers.add(new Pointer(touchEvent.getId()));
break;
case MOVE:
- motionEvent = MotionEvent.ACTION_MOVE;
- setPointerStatuses(motionEvent, pointerProperties);
-
+ motionEventAction = MotionEvent.ACTION_MOVE;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
case END:
- //Clears out pointers that have ended
- setPointerStatuses(MotionEvent.ACTION_UP, pointerProperties);
-
- if(pointerStatuses.size() == 0){
+ if(pointers.size() <= 1){
//The motion event has just ended
- motionEvent = MotionEvent.ACTION_UP;
- }else{
- motionEvent = MotionEvent.ACTION_POINTER_UP;
+ motionEventAction = MotionEvent.ACTION_UP;
+ } else {
+ int pointerIndex = pointers.indexOf(getPointerById(touchEvent.getId()));
+ if (pointerIndex != -1) {
+ motionEventAction = MotionEvent.ACTION_POINTER_UP | pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ } else {
+ motionEventAction = MotionEvent.ACTION_UP;
+ }
}
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
case CANCEL:
//Assuming this cancels the entire event
- motionEvent = MotionEvent.ACTION_CANCEL;
- pointerStatuses.clear();
+ motionEventAction = MotionEvent.ACTION_CANCEL;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
default:
break;
}
- return motionEvent;
+ return motionEventAction;
}
- private void setPointerStatuses(int motionEvent, MotionEvent.PointerProperties[] pointerProperties){
-
- for(int i = 0; i < pointerProperties.length; i ++){
- MotionEvent.PointerProperties properties = pointerProperties[i];
- if(properties != null){
- if(motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP){
- pointerStatuses.delete(properties.id);
- }else if(motionEvent == MotionEvent.ACTION_DOWN && properties.id == 0){
- pointerStatuses.put(properties.id, MotionEvent.ACTION_DOWN);
- }else{
- pointerStatuses.put(properties.id, motionEvent);
+ Pointer getPointerById(int id){
+ if (pointers != null && !pointers.isEmpty()){
+ for (Pointer pointer : pointers){
+ if (pointer.id == id){
+ return pointer;
}
-
}
}
+ return null;
}
- }
- @Override
- protected void onTransportUpdate(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail){
-
- isTransportAvailable = videoStreamTransportAvail;
+ Pointer getPointerByIndex(int index){
+ return pointers.get(index);
+ }
- if(internalInterface.getProtocolVersion().isNewerThan(new Version(5,1,0)) >= 0){
- if(videoStreamTransportAvail){
- checkState();
- }
- }else{
- //The protocol version doesn't support simultaneous transports.
- if(!videoStreamTransportAvail){
- //If video streaming isn't available on primary transport then it is not possible to
- //use the video streaming manager until a complete register on a transport that
- //supports video
- transitionToState(ERROR);
- }
+ void removePointerById(int id){
+ pointers.remove(getPointerById(id));
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
index 7b901a314..56e1c8544 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
@@ -45,7 +45,6 @@ import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.Display;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -7948,10 +7947,12 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
internalInterface.addOnRPCNotificationListener(FunctionID.ON_TOUCH_EVENT, new OnRPCNotificationListener() {
@Override
public void onNotified(RPCNotification notification) {
- if(notification !=null && remoteDisplay != null){
- MotionEvent event = convertTouchEvent((OnTouchEvent)notification);
- if(event!=null){
- remoteDisplay.handleMotionEvent(event);
+ if (notification != null && remoteDisplay != null) {
+ List<MotionEvent> events = convertTouchEvent((OnTouchEvent) notification);
+ if (events != null && !events.isEmpty()) {
+ for (MotionEvent ev : events) {
+ remoteDisplay.handleMotionEvent(ev);
+ }
}
}
}
@@ -8118,150 +8119,188 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
}
- private MotionEvent convertTouchEvent(OnTouchEvent touchEvent){
- List<TouchEvent> eventList = touchEvent.getEvent();
- if (eventList == null || eventList.size() == 0) return null;
+ private List<MotionEvent> convertTouchEvent(OnTouchEvent onTouchEvent) {
+ List<MotionEvent> motionEventList = new ArrayList<MotionEvent>();
- TouchType touchType = touchEvent.getType();
- if (touchType == null){ return null;}
+ List<TouchEvent> touchEventList = onTouchEvent.getEvent();
+ if (touchEventList == null || touchEventList.size() == 0) return null;
- int eventListSize = eventList.size();
+ TouchType touchType = onTouchEvent.getType();
+ if (touchType == null) {
+ return null;
+ }
- MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[eventListSize];
- MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[eventListSize];
+ if (sdlMotionEvent == null) {
+ if (touchType == TouchType.BEGIN) {
+ sdlMotionEvent = new SdlMotionEvent();
+ } else {
+ return null;
+ }
+ }
- TouchEvent event;
- MotionEvent.PointerProperties properties;
- MotionEvent.PointerCoords coords;
- TouchCoord touchCoord;
+ SdlMotionEvent.Pointer pointer;
+ MotionEvent motionEvent;
- for(int i = 0; i < eventListSize; i++){
- event = eventList.get(i);
- if(event == null || event.getId() == null || event.getTouchCoordinates() == null){
+ for (TouchEvent touchEvent : touchEventList) {
+ if (touchEvent == null || touchEvent.getId() == null) {
continue;
}
- properties = new MotionEvent.PointerProperties();
- properties.id = event.getId();
- properties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-
-
- List<TouchCoord> coordList = event.getTouchCoordinates();
- if (coordList == null || coordList.size() == 0){ continue; }
-
- touchCoord = coordList.get(coordList.size() -1);
- if(touchCoord == null){ continue; }
+ List<TouchCoord> touchCoordList = touchEvent.getTouchCoordinates();
+ if (touchCoordList == null || touchCoordList.size() == 0) {
+ continue;
+ }
- coords = new MotionEvent.PointerCoords();
- coords.x = touchCoord.getX() * touchScalar[0];
- coords.y = touchCoord.getY() * touchScalar[1];
- coords.orientation = 0;
- coords.pressure = 1.0f;
- coords.size = 1;
+ TouchCoord touchCoord = touchCoordList.get(touchCoordList.size() - 1);
+ if (touchCoord == null) {
+ continue;
+ }
- //Add the info to lists only after we are sure we have all available info
- pointerProperties[i] = properties;
- pointerCoords[i] = coords;
+ int motionEventAction = sdlMotionEvent.getMotionEventAction(touchType, touchEvent);
+ long downTime = sdlMotionEvent.downTime;
+ long eventTime = sdlMotionEvent.eventTime;
+ pointer = sdlMotionEvent.getPointerById(touchEvent.getId());
+ if (pointer != null) {
+ pointer.setCoords(touchCoord.getX() * touchScalar[0], touchCoord.getY() * touchScalar[1]);
+ }
- }
+ MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[sdlMotionEvent.pointers.size()];
+ MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[sdlMotionEvent.pointers.size()];
+ for (int i = 0; i < sdlMotionEvent.pointers.size(); i++) {
+ pointerProperties[i] = new MotionEvent.PointerProperties();
+ pointerProperties[i].id = sdlMotionEvent.getPointerByIndex(i).id;
+ pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
- if(sdlMotionEvent == null) {
- if (touchType == TouchType.BEGIN) {
- sdlMotionEvent = new SdlMotionEvent();
- }else{
- return null;
+ pointerCoords[i] = new MotionEvent.PointerCoords();
+ pointerCoords[i].x = sdlMotionEvent.getPointerByIndex(i).x;
+ pointerCoords[i].y = sdlMotionEvent.getPointerByIndex(i).y;
+ pointerCoords[i].orientation = 0;
+ pointerCoords[i].pressure = 1.0f;
+ pointerCoords[i].size = 1;
}
- }
- int eventAction = sdlMotionEvent.getMotionEvent(touchType, pointerProperties);
- long startTime = sdlMotionEvent.startOfEvent;
+ motionEvent = MotionEvent.obtain(downTime, eventTime, motionEventAction,
+ sdlMotionEvent.pointers.size(), pointerProperties, pointerCoords, 0, 0, 1,
+ 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+ motionEventList.add(motionEvent);
- //If the motion event should be finished we should clear our reference
- if(eventAction == MotionEvent.ACTION_UP || eventAction == MotionEvent.ACTION_CANCEL){
- sdlMotionEvent = null;
+ if (motionEventAction == MotionEvent.ACTION_UP || motionEventAction == MotionEvent.ACTION_CANCEL) {
+ //If the motion event should be finished we should clear our reference
+ sdlMotionEvent.pointers.clear();
+ sdlMotionEvent = null;
+ break;
+ } else if ((motionEventAction & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+ sdlMotionEvent.removePointerById(touchEvent.getId());
+ }
}
- return MotionEvent.obtain(startTime, SystemClock.uptimeMillis(), eventAction, eventListSize, pointerProperties, pointerCoords, 0, 0,1,1,0,0, InputDevice.SOURCE_TOUCHSCREEN,0);
-
+ return motionEventList;
}
-
-
-
}
/**
* Keeps track of the current motion event for VPM
*/
- private static class SdlMotionEvent{
- long startOfEvent;
- SparseIntArray pointerStatuses = new SparseIntArray();
+ private static class SdlMotionEvent {
+ class Pointer {
+ int id;
+ float x;
+ float y;
+
+ Pointer(int id) {
+ this.id = id;
+ this.x = 0.0f;
+ this.y = 0.0f;
+ }
+
+ void setCoords(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ private CopyOnWriteArrayList<Pointer> pointers = new CopyOnWriteArrayList<>();
+ private long downTime;
+ private long downTimeOnHMI;
+ private long eventTime;
- SdlMotionEvent(){
- startOfEvent = SystemClock.uptimeMillis();
+ SdlMotionEvent() {
+ downTimeOnHMI = 0;
}
/**
- * Handles the SDL Touch Event to keep track of pointer status and returns the appropirate
+ * Handles the SDL Touch Event to keep track of pointer status and returns the appropriate
* Android MotionEvent according to this events status
- * @param touchType The SDL TouchType that was received from the module
- * @param pointerProperties the parsed pointer properties built from the OnTouchEvent RPC
- * @return the correct native Andorid MotionEvent action to dispatch
+ *
+ * @param touchType The SDL TouchType that was received from the module
+ * @param touchEvent The SDL TouchEvent that was received from the module
+ * @return the correct native Android MotionEvent action to dispatch
*/
- synchronized int getMotionEvent(TouchType touchType, MotionEvent.PointerProperties[] pointerProperties){
- int motionEvent = MotionEvent.ACTION_DOWN;
- switch (touchType){
+ synchronized int getMotionEventAction(TouchType touchType, TouchEvent touchEvent) {
+ eventTime = 0;
+ int motionEventAction = -1;
+ switch (touchType) {
case BEGIN:
- if(pointerStatuses.size() == 0){
+ if (pointers.size() == 0) {
//The motion event has just begun
- motionEvent = MotionEvent.ACTION_DOWN;
- }else{
- motionEvent = MotionEvent.ACTION_POINTER_DOWN;
+ motionEventAction = MotionEvent.ACTION_DOWN;
+ downTime = SystemClock.uptimeMillis();
+ downTimeOnHMI = touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1);
+ eventTime = downTime;
+ } else {
+ motionEventAction = MotionEvent.ACTION_POINTER_DOWN | pointers.size() << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
}
- setPointerStatuses(motionEvent, pointerProperties);
+ pointers.add(new Pointer(touchEvent.getId()));
break;
case MOVE:
- motionEvent = MotionEvent.ACTION_MOVE;
- setPointerStatuses(motionEvent, pointerProperties);
-
+ motionEventAction = MotionEvent.ACTION_MOVE;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
case END:
- //Clears out pointers that have ended
- setPointerStatuses(MotionEvent.ACTION_UP, pointerProperties);
-
- if(pointerStatuses.size() == 0){
+ if(pointers.size() <= 1){
//The motion event has just ended
- motionEvent = MotionEvent.ACTION_UP;
- }else{
- motionEvent = MotionEvent.ACTION_POINTER_UP;
+ motionEventAction = MotionEvent.ACTION_UP;
+ } else {
+ int pointerIndex = pointers.indexOf(getPointerById(touchEvent.getId()));
+ if (pointerIndex != -1) {
+ motionEventAction = MotionEvent.ACTION_POINTER_UP | pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ } else {
+ motionEventAction = MotionEvent.ACTION_UP;
+ }
}
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
case CANCEL:
//Assuming this cancels the entire event
- motionEvent = MotionEvent.ACTION_CANCEL;
- pointerStatuses.clear();
+ motionEventAction = MotionEvent.ACTION_CANCEL;
+ eventTime = downTime + touchEvent.getTimestamps().get(touchEvent.getTimestamps().size() - 1) - downTimeOnHMI;
break;
default:
break;
}
- return motionEvent;
+ return motionEventAction;
}
- private void setPointerStatuses(int motionEvent, MotionEvent.PointerProperties[] pointerProperties){
-
- for(int i = 0; i < pointerProperties.length; i ++){
- MotionEvent.PointerProperties properties = pointerProperties[i];
- if(properties != null){
- if(motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP){
- pointerStatuses.delete(properties.id);
- }else if(motionEvent == MotionEvent.ACTION_DOWN && properties.id == 0){
- pointerStatuses.put(properties.id, MotionEvent.ACTION_DOWN);
- }else{
- pointerStatuses.put(properties.id, motionEvent);
- }
-
+ Pointer getPointerById(int id) {
+ if (pointers != null && !pointers.isEmpty()) {
+ for (Pointer pointer : pointers) {
+ if (pointer.id == id) {
+ return pointer;
}
+ }
}
+ return null;
+ }
+
+ Pointer getPointerByIndex(int index) {
+ return pointers.get(index);
+ }
+
+ void removePointerById(int id) {
+ pointers.remove(getPointerById(id));
}
}
+
} // end-class
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 74ba6fc58..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,6 +38,11 @@ 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.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.MetadataType;
import com.smartdevicelink.proxy.rpc.enums.TextAlignment;
@@ -57,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 {
@@ -95,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);
}
/**
@@ -111,6 +122,8 @@ abstract class BaseScreenManager extends BaseSubManager {
public void dispose() {
softButtonManager.dispose();
textAndGraphicManager.dispose();
+ voiceCommandManager.dispose();
+ menuManager.dispose();
super.dispose();
}
@@ -352,6 +365,55 @@ abstract class BaseScreenManager extends BaseSubManager {
}
/**
+ * Get the currently set voice commands
+ * @return a List of Voice Command objects
+ */
+ public List<VoiceCommand> getVoiceCommands(){
+ return voiceCommandManager.getVoiceCommands();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * The list of currently set menu cells
+ * @return a List of the currently set menu cells
+ */
+ public List<MenuCell> getMenu(){
+ return this.menuManager.getMenuCells();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
* Begin a multiple updates transaction. The updates will be applied when commit() is called<br>
* Note: if we don't use beginTransaction & commit, every update will be sent individually.
*/
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/base/src/main/java/com/smartdevicelink/proxy/rpc/BeltStatus.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/BeltStatus.java
index 1596b851a..0abc83e8c 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/BeltStatus.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/BeltStatus.java
@@ -160,7 +160,8 @@ public class BeltStatus extends RPCStruct {
public static final String KEY_LEFT_ROW_3_BUCKLE_BELTED = "leftRow3BuckleBelted";
public static final String KEY_RIGHT_ROW_3_BUCKLE_BELTED = "rightRow3BuckleBelted";
- public static final String KEY_REAR_INFLATABLE_BELTED = "rearInflatableBelted";
+ @Deprecated public static final String KEY_REAR_INFLATABLE_BELTED = "rearInflatableBelted";
+ public static final String KEY_LEFT_REAR_INFLATABLE_BELTED = "leftRearInflatableBelted";
public static final String KEY_RIGHT_REAR_INFLATABLE_BELTED = "rightRearInflatableBelted";
public static final String KEY_MIDDLE_ROW_1_BELT_DEPLOYED = "middleRow1BeltDeployed";
public static final String KEY_MIDDLE_ROW_1_BUCKLE_BELTED = "middleRow1BuckleBelted";
@@ -267,10 +268,10 @@ public class BeltStatus extends RPCStruct {
return (VehicleDataEventStatus) getObject(VehicleDataEventStatus.class, KEY_RIGHT_ROW_3_BUCKLE_BELTED);
}
public void setLeftRearInflatableBelted(@NonNull VehicleDataEventStatus rearInflatableBelted) {
- setValue(KEY_REAR_INFLATABLE_BELTED, rearInflatableBelted);
+ setValue(KEY_LEFT_REAR_INFLATABLE_BELTED, rearInflatableBelted);
}
public VehicleDataEventStatus getLeftRearInflatableBelted() {
- return (VehicleDataEventStatus) getObject(VehicleDataEventStatus.class, KEY_REAR_INFLATABLE_BELTED);
+ return (VehicleDataEventStatus) getObject(VehicleDataEventStatus.class, KEY_LEFT_REAR_INFLATABLE_BELTED);
}
public void setRightRearInflatableBelted(@NonNull VehicleDataEventStatus rightRearInflatableBelted) {
setValue(KEY_RIGHT_REAR_INFLATABLE_BELTED, rightRearInflatableBelted);
diff --git a/baseAndroid/README.md b/baseAndroid/README.md
index 6ad99fef3..4a143e5f9 100644
--- a/baseAndroid/README.md
+++ b/baseAndroid/README.md
@@ -2,5 +2,10 @@
The Base Android folder symbolically links files used by the Android project that are in the Base folder.
-This folder does not need to be imported. Please refer to the installation instructions in the Android, JavaSE, or JavaEE readme's.
+This folder does not need to be imported. Please refer to the installation instructions in the Android, JavaSE, or JavaEE README's.
+### Windows
+
+The original symbolic links were created for a Unix based operating system. If compiling with Windows the symbolic links will have to be created before compiling the SDL Android project. There is a gradle task added to the `build.gradle` file in the `sdl_android` project that contains a task called `buildWindowSymLinks`. Please note, this task and the supplied script require admin privileges and python to be installed to run. After running this task, Android Studio will recognize the new folder path as the `build.gradle` file contains the necessary conditional that will pick the correct source set to use.
+
+If you are not building the project with the supplied gradle files, you will need to point to the correct path based on the operating system in which you are building the project. \ No newline at end of file
diff --git a/baseAndroid/make_symbolic_links.py b/baseAndroid/make_symbolic_links.py
new file mode 100644
index 000000000..4be7cdeea
--- /dev/null
+++ b/baseAndroid/make_symbolic_links.py
@@ -0,0 +1,69 @@
+import os
+import pathlib
+from pathlib import Path
+import re
+
+
+def has_admin():
+ if os.name == 'nt':
+ try:
+ # only windows users with admin privileges can read the C:\windows\temp
+ temp = os.listdir(os.sep.join([os.environ.get('SystemRoot', 'C:\\windows'), 'temp']))
+ except:
+ return os.environ['USERNAME'],False
+ else:
+ return os.environ['USERNAME'],True
+ else:
+ if 'SUDO_USER' in os.environ and os.geteuid() == 0:
+ return os.environ['SUDO_USER'],True
+ else:
+ return os.environ['USERNAME'],False
+
+
+print('Script Start')
+
+isAdmin = has_admin()
+print('Running As Admin - ', isAdmin[1])
+if not isAdmin[1]:
+ print('Can\'t run without admin privileges')
+ exit()
+
+pathlist = Path('src/').glob('**/*')
+# Delete the old directory
+os.system('echo y | rmdir windows /s')
+
+for path in pathlist:
+ path_in_str = str(path)
+ if os.path.isfile(path):
+ # check if it's a link to a file or folder
+ source_link_str = path_in_str
+ source_link_str = '..\\base\\' + source_link_str
+ # Remove the root folder for the actual link
+ print(source_link_str)
+
+ testDest = 'windows\\' + path_in_str
+
+ directory = pathlib.Path(testDest).parent
+ print(str(directory))
+ prefixDir = (re.sub(r"\\+[^\\]*", r"\\..", str(directory))+'\\..\\')[8:] # 8 to remove windows/
+ # Change all the directory paths into .. so that it will properly move up a folder.
+
+ os.system('mkdir %s' % directory)
+ os.system('icacls %s /grant Everyone:(f)' % directory)
+
+ # Now we need to go through each destination directory and understand that's how many ../ we have to add
+ if path_in_str.endswith('.java'):
+ print('Java file link found')
+
+ command = 'mklink "%s" "%s%s"' % (testDest, prefixDir, source_link_str)
+ print('Performing command %s' % command)
+ os.system(command)
+ else:
+ print('Directory link found')
+ command = 'mklink /D "%s" "%s%s"' % (testDest, prefixDir, source_link_str)
+ print('Performing command %s' % command)
+ os.system(command)
+
+print('Script Ends')
+
+
diff --git a/baseAndroid/make_symlinks.lnk b/baseAndroid/make_symlinks.lnk
new file mode 100644
index 000000000..86e9d3e41
--- /dev/null
+++ b/baseAndroid/make_symlinks.lnk
Binary files differ
diff --git a/baseAndroid/run_as_admin_symlink_script.bat b/baseAndroid/run_as_admin_symlink_script.bat
new file mode 100644
index 000000000..439d7709f
--- /dev/null
+++ b/baseAndroid/run_as_admin_symlink_script.bat
@@ -0,0 +1,4 @@
+pushd %~dp0
+python make_symbolic_links.py
+popd
+pause \ No newline at end of file
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);
+ }
+
+}