summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilal Alsharifi <599206+bilal-alsharifi@users.noreply.github.com>2021-09-23 16:58:17 -0400
committerGitHub <noreply@github.com>2021-09-23 16:58:17 -0400
commit6834f99aea519f48c17707f8d120d5197c22377f (patch)
tree5e4471fa1c23a1213d80d57838205144bba08046
parent9a2398fc82bf7a1400aa514b4e4f7ec9f2c519f1 (diff)
parent7088a04fadfb2dcc651e7146ad06c2c54f4c99ce (diff)
downloadsdl_android-6834f99aea519f48c17707f8d120d5197c22377f.tar.gz
Merge pull request #1610 from smartdevicelink/feature/issue_1605_menu_manager_refactor
Refactor MenuManager to use queues
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/filetypes/SdlArtworkTests.java3
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/ScreenManagerTests.java10
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScoreTests.java (renamed from android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/RunScoreTests.java)16
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuCellTests.java41
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperationTests.java225
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java710
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperationTests.java287
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilitiesTests.java343
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuShowOperationTests.java144
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java102
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java1477
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java134
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java (renamed from base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java)62
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java37
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java32
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java144
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java (renamed from base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java)42
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java558
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java418
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java91
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java (renamed from android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java)37
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java32
22 files changed, 2961 insertions, 1984 deletions
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/filetypes/SdlArtworkTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/filetypes/SdlArtworkTests.java
index f4fb5e3d2..49a10ab3d 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/filetypes/SdlArtworkTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/filetypes/SdlArtworkTests.java
@@ -37,6 +37,9 @@ public class SdlArtworkTests {
}
public static boolean equalTest(SdlArtwork original, SdlArtwork clone) {
+ if (original == null && clone == null) {
+ return true;
+ }
assertNotNull(original);
assertNotNull(clone);
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 6ccf93aeb..b5cecf26c 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
@@ -68,14 +68,15 @@ public class ScreenManagerTests {
assertNull(screenManager.getTextField2Type());
assertNull(screenManager.getTextField3Type());
assertNull(screenManager.getTextField4Type());
- assertNull(screenManager.getMenu());
+ assertTrue(screenManager.getMenu().isEmpty());
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);
- assertNull(screenManager.getMenuConfiguration());
+ assertNull(screenManager.getMenuConfiguration().getMenuLayout());
+ assertNull(screenManager.getMenuConfiguration().getSubMenuLayout());
}
@Test
@@ -147,10 +148,9 @@ public class ScreenManagerTests {
screenManager.setMenu(TestValues.GENERAL_MENUCELL_LIST);
screenManager.setMenuConfiguration(TestValues.GENERAL_MENU_CONFIGURATION);
- assertEquals(screenManager.getMenu(), TestValues.GENERAL_MENUCELL_LIST);
assertEquals(screenManager.getDynamicMenuUpdatesMode(), DynamicMenuUpdatesMode.FORCE_ON);
- // Should not set because of improper RAI response and improper HMI states
- assertNull(screenManager.getMenuConfiguration());
+ assertEquals(screenManager.getMenu(), TestValues.GENERAL_MENUCELL_LIST);
+ assertEquals(screenManager.getMenuConfiguration(), TestValues.GENERAL_MENU_CONFIGURATION);
}
@Test
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/DynamicMenuUpdateRunScoreTests.java
index e92656846..69350965a 100644
--- 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/DynamicMenuUpdateRunScoreTests.java
@@ -39,21 +39,29 @@ import com.smartdevicelink.test.TestValues;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.ADD;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.DELETE;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.KEEP;
import static junit.framework.TestCase.assertEquals;
@RunWith(AndroidJUnit4.class)
-public class RunScoreTests {
+public class DynamicMenuUpdateRunScoreTests {
@Test
public void testSettersAndGetters() {
// set everything - we only use the constructor to set variables in the Menu Manager
- RunScore runScore = new RunScore(TestValues.GENERAL_INT, TestValues.GENERAL_INTEGER_LIST, TestValues.GENERAL_INTEGER_LIST);
+ List<DynamicMenuUpdateAlgorithm.MenuCellState> oldStatus = Arrays.asList(KEEP, DELETE);
+ List<DynamicMenuUpdateAlgorithm.MenuCellState> updatedStatus = Arrays.asList(KEEP, ADD);
+ DynamicMenuUpdateRunScore runScore = new DynamicMenuUpdateRunScore(oldStatus, updatedStatus, TestValues.GENERAL_INT);
// use getters and assert equality
assertEquals(runScore.getScore(), TestValues.GENERAL_INT);
- assertEquals(runScore.getCurrentMenu(), TestValues.GENERAL_INTEGER_LIST);
- assertEquals(runScore.getOldMenu(), TestValues.GENERAL_INTEGER_LIST);
+ assertEquals(runScore.getOldStatus(), oldStatus);
+ assertEquals(runScore.getUpdatedStatus(), updatedStatus);
}
}
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
index b0e703e33..8088538e1 100644
--- 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
@@ -43,13 +43,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNotSame;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNotEquals;
@RunWith(AndroidJUnit4.class)
public class MenuCellTests {
@@ -63,7 +63,6 @@ public class MenuCellTests {
@Test
public void testSettersAndGetters() {
-
// set everything
MenuCell menuCell = new MenuCell(TestValues.GENERAL_STRING, null, null, menuSelectionListener);
menuCell.setIcon(TestValues.GENERAL_ARTWORK);
@@ -91,7 +90,6 @@ public class MenuCellTests {
@Test
public void testConstructors() {
-
// first constructor was tested in previous method, use the last two here
MenuCell menuCell3 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
@@ -99,20 +97,16 @@ public class MenuCellTests {
assertEquals(menuCell3.getIcon(), TestValues.GENERAL_ARTWORK);
assertEquals(menuCell3.getVoiceCommands(), TestValues.GENERAL_STRING_LIST);
assertEquals(menuCell3.getMenuSelectionListener(), menuSelectionListener);
- assertEquals(menuCell3.getUniqueTitle(), TestValues.GENERAL_STRING);
MenuCell menuCell4 = new MenuCell(TestValues.GENERAL_STRING, null, null, menuSelectionListener);
assertEquals(menuCell4.getTitle(), TestValues.GENERAL_STRING);
assertEquals(menuCell4.getMenuSelectionListener(), menuSelectionListener);
- assertEquals(menuCell4.getUniqueTitle(), TestValues.GENERAL_STRING);
MenuCell menuCell5 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_MENU_LAYOUT, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_MENUCELL_LIST);
assertEquals(menuCell5.getTitle(), TestValues.GENERAL_STRING);
assertEquals(menuCell5.getIcon(), TestValues.GENERAL_ARTWORK);
assertEquals(menuCell5.getSubMenuLayout(), TestValues.GENERAL_MENU_LAYOUT);
assertEquals(menuCell5.getSubCells(), TestValues.GENERAL_MENUCELL_LIST);
- assertEquals(menuCell5.getUniqueTitle(), TestValues.GENERAL_STRING);
-
MenuCell menuCell6 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_STRING, TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
assertEquals(menuCell6.getTitle(), TestValues.GENERAL_STRING);
@@ -139,36 +133,34 @@ public class MenuCellTests {
@Test
public void testEquality() {
+ MenuCell menuCell1 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
+ MenuCell menuCell1_1 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
+ menuCell1.setSubCells(Collections.singletonList(menuCell1_1));
- //We should use assertTrue (or assertFalse) because we want to use the overridden equals() method
-
- MenuCell menuCell = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
- menuCell.setSecondaryText(TestValues.GENERAL_STRING);
- menuCell.setTertiaryText(TestValues.GENERAL_STRING);
- menuCell.setSecondaryArtwork(TestValues.GENERAL_ARTWORK);
MenuCell menuCell2 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
- menuCell2.setSecondaryText(TestValues.GENERAL_STRING);
- menuCell2.setTertiaryText(TestValues.GENERAL_STRING);
- menuCell2.setSecondaryArtwork(TestValues.GENERAL_ARTWORK);
+ MenuCell menuCell2_1 = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
+ menuCell2.setSubCells(Collections.singletonList(menuCell2_1));
// these are the same object, should be equal.
- assertTrue(menuCell.equals(menuCell));
+ assertEquals(menuCell1, menuCell1);
// Make sure these are marked as equals, even though they are different objects
- assertTrue(menuCell.equals(menuCell2));
+ assertEquals(menuCell1, menuCell2);
MenuCell menuCell3 = new MenuCell(TestValues.GENERAL_STRING, null, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
// these should be different
- assertFalse(menuCell.equals(menuCell3));
+ assertNotEquals(menuCell1, menuCell3);
+
+ menuCell1_1.setTitle("new title");
+
+ // Make sure sub cells are not compared
+ assertEquals(menuCell1, menuCell2);
}
@Test
public void testClone() {
MenuCell original = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_STRING_LIST, menuSelectionListener);
- original.setSecondaryText(TestValues.GENERAL_STRING);
- original.setTertiaryText(TestValues.GENERAL_STRING);
- original.setSecondaryArtwork(TestValues.GENERAL_ARTWORK);
MenuCell clone = original.clone();
assertNotNull(clone);
@@ -208,8 +200,5 @@ public class MenuCellTests {
assertNotSame(originalSubCells.get(i), cloneSubCells.get(i));
}
-
-
}
-
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperationTests.java
new file mode 100644
index 000000000..c52cf6186
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperationTests.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2021 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.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Taskmaster;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.util.Version;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class MenuConfigurationUpdateOperationTests {
+
+ private Handler mainHandler;
+ private Taskmaster taskmaster;
+ private Queue transactionQueue;
+
+ @Before
+ public void setUp() throws Exception {
+ mainHandler = new Handler(getInstrumentation().getTargetContext().getMainLooper());
+ taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+ transactionQueue = taskmaster.createQueue("MenuManager", new Random().nextInt(), false);
+ }
+
+ @Test
+ public void testSuccess() {
+ final ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(7, 0, 0)));
+ doAnswer(createSetGlobalPropertiesAnswer(true)).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ WindowCapability windowCapability = createWindowCapability(true, true);
+ MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ verify(internalInterface, Mockito.times(1)).sendRPC(any(SetGlobalProperties.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testFailsRPCVersionOld() {
+ final ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(5, 0, 0)));
+ doAnswer(createSetGlobalPropertiesAnswer(true)).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ WindowCapability windowCapability = createWindowCapability(true, true);
+ MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success);
+ verify(internalInterface, Mockito.times(0)).sendRPC(any(SetGlobalProperties.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testFailsMenuLayoutNotSet() {
+ final ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(7, 0, 0)));
+ doAnswer(createSetGlobalPropertiesAnswer(true)).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ WindowCapability windowCapability = createWindowCapability(true, true);
+ MenuConfiguration menuConfiguration = new MenuConfiguration(null, MenuLayout.LIST);
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success);
+ verify(internalInterface, Mockito.times(0)).sendRPC(any(SetGlobalProperties.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testFailsMenuLayoutsAvailableEmpty() {
+ final ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(7, 0, 0)));
+ doAnswer(createSetGlobalPropertiesAnswer(true)).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ WindowCapability windowCapability = createWindowCapability(true, true);
+ MenuConfiguration menuConfiguration = new MenuConfiguration(null, null);
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success);
+ verify(internalInterface, Mockito.times(0)).sendRPC(any(SetGlobalProperties.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testFailsRPCNotSent() {
+ final ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(7, 0, 0)));
+ doAnswer(createSetGlobalPropertiesAnswer(false)).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ WindowCapability windowCapability = createWindowCapability(true, true);
+ MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(final boolean success) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(success);
+ verify(internalInterface, Mockito.times(1)).sendRPC(any(SetGlobalProperties.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ private Answer<Void> createSetGlobalPropertiesAnswer(final boolean success){
+ return new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ RPCRequest request = (RPCRequest) args[0];
+ RPCResponse response = new RPCResponse(request.getFunctionID().toString());
+ response.setSuccess(success);
+ request.getOnRPCResponseListener().onResponse(request.getCorrelationID(), response);
+ return null;
+ }
+ };
+ }
+
+ private WindowCapability createWindowCapability (boolean supportsList, boolean supportsTile) {
+ WindowCapability windowCapability = new WindowCapability();
+ windowCapability.setMenuLayoutsAvailable(new ArrayList<MenuLayout>());
+ if (supportsList) {
+ windowCapability.getMenuLayoutsAvailable().add(MenuLayout.LIST);
+ }
+ if (supportsTile) {
+ windowCapability.getMenuLayoutsAvailable().add(MenuLayout.TILES);
+ }
+ return windowCapability;
+ }
+
+ // Asserts on Taskmaster threads will fail silently so we need to do the assertions on main thread if the code is triggered from Taskmaster
+ private void assertOnMainThread(Runnable runnable) {
+ mainHandler.post(runnable);
+ }
+}
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
index e71d8295c..6f8bff69d 100644
--- 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
@@ -34,6 +34,7 @@ package com.smartdevicelink.managers.screen.menu;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.R;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
@@ -41,26 +42,23 @@ import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.FileManager;
import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCMessage;
import com.smartdevicelink.proxy.RPCRequest;
import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.ImageField;
import com.smartdevicelink.proxy.rpc.OnCommand;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
-import com.smartdevicelink.proxy.rpc.TextField;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
import com.smartdevicelink.proxy.rpc.enums.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
-import com.smartdevicelink.test.TestValues;
+import com.smartdevicelink.util.Version;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +71,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.ADD;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.DELETE;
+import static com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState.KEEP;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
@@ -81,10 +83,10 @@ import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* the Algorithm specific tests are defined based on: https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0210-mobile-dynamic-menu-cell-updating.md
@@ -96,7 +98,11 @@ public class MenuManagerTests {
private MenuManager menuManager;
private List<MenuCell> cells;
private MenuCell mainCell1, mainCell4;
- final ISdl internalInterface = mock(ISdl.class);
+ private final MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class);
+ private final MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class);
+ private final MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class);
+ private final MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class);
+ private final MenuSelectionListener menuSelectionListenerE = mock(MenuSelectionListener.class);
// SETUP / HELPERS
@@ -105,8 +111,12 @@ public class MenuManagerTests {
cells = createTestCells();
+ final ISdl internalInterface = mock(ISdl.class);
FileManager fileManager = mock(FileManager.class);
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(6, 0, 0)));
+
+
// 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
@@ -130,7 +140,29 @@ public class MenuManagerTests {
};
doAnswer(onCommandAnswer).when(internalInterface).addOnRPCNotificationListener(eq(FunctionID.ON_COMMAND), any(OnRPCNotificationListener.class));
- Answer<Void> answer = new Answer<Void>() {
+ // When internalInterface.sendRPCs() is called, call listener.onFinished() to fake the response
+ final Answer<Void> answer = new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ List<RPCMessage> rpcs = (List<RPCMessage>) args[0];
+ OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
+
+ for (RPCMessage rpcMessage : rpcs) {
+ RPCRequest request = (RPCRequest) rpcMessage;
+ RPCResponse response = new RPCResponse(request.getFunctionID().toString());
+ response.setCorrelationID(request.getCorrelationID());
+ response.setSuccess(true);
+ listener.onResponse(request.getCorrelationID(), response);
+ }
+
+ listener.onFinished();
+ return null;
+ }
+ };
+ doAnswer(answer).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ Answer<Void> setGlobalPropertiesAnswer = new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
@@ -141,110 +173,107 @@ public class MenuManagerTests {
return null;
}
};
- doAnswer(answer).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
-
- SdlMsgVersion version = new SdlMsgVersion();
- version.setMajorVersion(7);
- version.setMinorVersion(0);
- doReturn(version).when(internalInterface).getSdlMsgVersion();
+ doAnswer(setGlobalPropertiesAnswer).when(internalInterface).sendRPC(any(SetGlobalProperties.class));
+ // Create MenuManager
+ Taskmaster taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+ when(internalInterface.getTaskmaster()).thenReturn(taskmaster);
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);
- assertNull(menuManager.menuConfiguration);
+ assertEquals(HMILevel.HMI_NONE, menuManager.currentHMILevel);
+ assertEquals(BaseSubManager.SETTING_UP, menuManager.getState());
+ assertEquals(SystemContext.SYSCTXT_MAIN, menuManager.currentSystemContext);
+ assertEquals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE, menuManager.dynamicMenuUpdatesMode);
+ assertTrue(menuManager.menuCells.isEmpty());
+ assertTrue(menuManager.currentMenuCells.isEmpty());
+ assertNull(menuManager.menuConfiguration.getMenuLayout());
+ assertNull(menuManager.menuConfiguration.getSubMenuLayout());
assertNotNull(menuManager.hmiListener);
assertNotNull(menuManager.commandListener);
assertNotNull(menuManager.onDisplaysCapabilityListener);
-
- }
-
- @After
- 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.defaultMainWindowCapability);
- assertNull(menuManager.inProgressUpdate);
- assertNull(menuManager.waitingUpdateMenuCells);
- assertNull(menuManager.keepsNew);
- assertNull(menuManager.keepsOld);
- assertNull(menuManager.menuConfiguration);
-
- // after everything, make sure we are in the correct state
- assertEquals(menuManager.getState(), BaseSubManager.SHUTDOWN);
-
}
@Test
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);
+ assertEquals(BaseSubManager.READY, menuManager.getState());
}
});
}
@Test
public void testHMINotReady() {
-
- menuManager.currentHMILevel = HMILevel.HMI_NONE;
menuManager.setMenuCells(cells);
+ assertEquals(HMILevel.HMI_NONE, menuManager.currentHMILevel);
+ assertTrue(menuManager.currentMenuCells.isEmpty());
- // 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);
+ assertEquals(HMILevel.HMI_FULL, menuManager.currentHMILevel);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(cells, menuManager.currentMenuCells);
}
@Test
- public void testUpdatingOldWay() {
+ public void testSettingNullMenu() {
+ menuManager.setMenuCells(null);
+ assertEquals(HMILevel.HMI_NONE, menuManager.currentHMILevel);
+ assertTrue(menuManager.currentMenuCells.isEmpty());
+
+ // 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(HMILevel.HMI_FULL, menuManager.currentHMILevel);
+
+ assertTrue(menuManager.currentMenuCells.isEmpty());
+ }
+
+ @Test
+ public void testSettingNonUniqueCells() {
+ MenuSelectionListener listener = null;
+ MenuCell cell1 = new MenuCell("cell", null, null, listener);
+ MenuCell cell2 = new MenuCell("cell", null, null, listener);
+
+ menuManager.setMenuCells(Arrays.asList(cell1, cell2));
+ assertEquals(HMILevel.HMI_NONE, menuManager.currentHMILevel);
+ assertTrue(menuManager.currentMenuCells.isEmpty());
+
+ // 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(HMILevel.HMI_FULL, menuManager.currentHMILevel);
+
+ assertTrue(menuManager.transactionQueue.getTasksAsList().isEmpty());
+ }
+
+ @Test
+ 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
+ assertEquals(MenuReplaceUtilities.allCommandsForCells(newArray, menuManager.fileManager.get(), menuManager.windowCapability, MenuLayout.LIST).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) {
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+ for (MenuCell cell : menuManager.currentMenuCells) {
// 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
@@ -272,39 +301,46 @@ public class MenuManagerTests {
@Test
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.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(menuManager.currentMenuCells.size(), 4);
+
+ menuManager.setMenuCells(newMenu);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
// this happens in the menu manager but lets make sure its behaving
- RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu);
+ DynamicMenuUpdateRunScore runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldMenu, newMenu);
- List<Integer> oldMenuScore = Arrays.asList(0, 0, 0, 0);
- List<Integer> newMenuScore = Arrays.asList(0, 0, 0, 0, 1);
+ List<MenuCellState> oldMenuStatus = Arrays.asList(KEEP, KEEP, KEEP, KEEP);
+ List<MenuCellState> newMenuStatus = Arrays.asList(KEEP, KEEP, KEEP, KEEP, ADD);
- assertEquals(runScore.getScore(), 1);
- assertEquals(runScore.getOldMenu(), oldMenuScore);
- assertEquals(runScore.getCurrentMenu(), newMenuScore);
+ assertEquals(1, runScore.getScore());
+ assertEquals(runScore.getOldStatus(), oldMenuStatus);
+ assertEquals(runScore.getUpdatedStatus(), newMenuStatus);
- menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 5);
- assertEquals(menuManager.keepsNew.size(), 4);
- assertEquals(menuManager.keepsOld.size(), 4);
+ assertEquals(5, menuManager.currentMenuCells.size());
+ List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getOldStatus(), KEEP);
+ List<MenuCell> newKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getUpdatedStatus(), KEEP);
+ assertEquals(4, oldKeeps.size());
+ assertEquals(4, newKeeps.size());
}
@Test
@@ -315,33 +351,41 @@ public class MenuManagerTests {
assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON);
// start fresh
- menuManager.oldMenuCells = null;
- menuManager.menuCells = null;
- menuManager.inProgressUpdate = null;
- menuManager.waitingUpdateMenuCells = null;
- menuManager.waitingOnHMIUpdate = false;
+ menuManager.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(4, menuManager.currentMenuCells.size());
+
+ menuManager.setMenuCells(newMenu);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
// this happens in the menu manager but lets make sure its behaving
- RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu);
+ DynamicMenuUpdateRunScore runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldMenu, newMenu);
- List<Integer> oldMenuScore = Arrays.asList(0, 0, 0, 2);
- List<Integer> newMenuScore = Arrays.asList(0, 0, 0);
+ List<MenuCellState> oldMenuScore = Arrays.asList(KEEP, KEEP, KEEP, DELETE);
+ List<MenuCellState> newMenuScore = Arrays.asList(KEEP, KEEP, KEEP);
assertEquals(runScore.getScore(), 0);
- assertEquals(runScore.getOldMenu(), oldMenuScore);
- assertEquals(runScore.getCurrentMenu(), newMenuScore);
+ assertEquals(runScore.getOldStatus(), oldMenuScore);
+ assertEquals(runScore.getUpdatedStatus(), newMenuScore);
- menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 3);
- assertEquals(menuManager.keepsNew.size(), 3);
- assertEquals(menuManager.keepsOld.size(), 3);
+ assertEquals(3, menuManager.currentMenuCells.size());
+ List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getOldStatus(), KEEP);
+ List<MenuCell> newKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getUpdatedStatus(), KEEP);
+ assertEquals(3, oldKeeps.size());
+ assertEquals(3, newKeeps.size());
}
@Test
@@ -352,33 +396,41 @@ public class MenuManagerTests {
assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON);
// start fresh
- menuManager.oldMenuCells = null;
- menuManager.menuCells = null;
- menuManager.inProgressUpdate = null;
- menuManager.waitingUpdateMenuCells = null;
- menuManager.waitingOnHMIUpdate = false;
+ menuManager.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(menuManager.currentMenuCells.size(), 3);
+
+ menuManager.setMenuCells(newMenu);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
// this happens in the menu manager but lets make sure its behaving
- RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu);
+ DynamicMenuUpdateRunScore runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldMenu, newMenu);
- List<Integer> oldMenuScore = Arrays.asList(2, 2, 2);
- List<Integer> newMenuScore = Arrays.asList(1, 1, 1);
+ List<MenuCellState> oldMenuStatus = Arrays.asList(DELETE, DELETE, DELETE);
+ List<MenuCellState> newMenuStatus = Arrays.asList(ADD, ADD, ADD);
assertEquals(runScore.getScore(), 3);
- assertEquals(runScore.getOldMenu(), oldMenuScore);
- assertEquals(runScore.getCurrentMenu(), newMenuScore);
+ assertEquals(runScore.getOldStatus(), oldMenuStatus);
+ assertEquals(runScore.getUpdatedStatus(), newMenuStatus);
- menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 3);
- assertEquals(menuManager.keepsNew.size(), 0);
- assertEquals(menuManager.keepsOld.size(), 0);
+ assertEquals(menuManager.currentMenuCells.size(), 3);
+ List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getOldStatus(), KEEP);
+ List<MenuCell> newKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getUpdatedStatus(), KEEP);
+ assertEquals(0, newKeeps.size());
+ assertEquals(0, oldKeeps.size());
}
@Test
@@ -389,33 +441,41 @@ public class MenuManagerTests {
assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON);
// start fresh
- menuManager.oldMenuCells = null;
- menuManager.menuCells = null;
- menuManager.inProgressUpdate = null;
- menuManager.waitingUpdateMenuCells = null;
- menuManager.waitingOnHMIUpdate = false;
+ menuManager.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(menuManager.currentMenuCells.size(), 4);
+
+ menuManager.setMenuCells(newMenu);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
// this happens in the menu manager but lets make sure its behaving
- RunScore runScore = menuManager.runMenuCompareAlgorithm(oldMenu, newMenu);
+ DynamicMenuUpdateRunScore runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldMenu, newMenu);
- List<Integer> oldMenuScore = Arrays.asList(0, 2, 0, 2);
- List<Integer> newMenuScore = Arrays.asList(1, 0, 1, 0);
+ List<MenuCellState> oldMenuStatus = Arrays.asList(KEEP, DELETE, KEEP, DELETE);
+ List<MenuCellState> newMenuStatus = Arrays.asList(ADD, KEEP, ADD, KEEP);
assertEquals(runScore.getScore(), 2);
- assertEquals(runScore.getOldMenu(), oldMenuScore);
- assertEquals(runScore.getCurrentMenu(), newMenuScore);
+ assertEquals(runScore.getOldStatus(), oldMenuStatus);
+ assertEquals(runScore.getUpdatedStatus(), newMenuStatus);
- menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 4);
- assertEquals(menuManager.keepsNew.size(), 2);
- assertEquals(menuManager.keepsOld.size(), 2);
+ assertEquals(menuManager.currentMenuCells.size(), 4);
+ List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getOldStatus(), KEEP);
+ List<MenuCell> newKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getUpdatedStatus(), KEEP);
+ assertEquals(2, newKeeps.size());
+ assertEquals(2, oldKeeps.size());
}
@Test
@@ -426,77 +486,68 @@ public class MenuManagerTests {
assertEquals(menuManager.dynamicMenuUpdatesMode, DynamicMenuUpdatesMode.FORCE_ON);
// start fresh
- menuManager.oldMenuCells = null;
- menuManager.menuCells = null;
- menuManager.inProgressUpdate = null;
- menuManager.waitingUpdateMenuCells = null;
- menuManager.waitingOnHMIUpdate = false;
+ menuManager.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
- 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);
+ assertEquals(menuManager.currentMenuCells.size(), 4);
menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 4);
- assertEquals(menuManager.keepsNew.size(), 3);
- assertEquals(menuManager.keepsOld.size(), 3);
- }
- @Test
- public void testSettingNullMenu() {
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
- // 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;
+ // this happens in the menu manager but lets make sure its behaving
+ DynamicMenuUpdateRunScore runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldMenu, newMenu);
- menuManager.currentHMILevel = HMILevel.HMI_FULL;
- // send new cells. They should set the old way
- List<MenuCell> oldMenu = createDynamicMenu1();
- List<MenuCell> newMenu = null;
- menuManager.setMenuCells(oldMenu);
- assertEquals(menuManager.menuCells.size(), 4);
+ List<MenuCellState> oldMenuStatus = Arrays.asList(DELETE, KEEP, KEEP, KEEP);
+ List<MenuCellState> newMenuStatus = Arrays.asList(KEEP, KEEP, KEEP, ADD);
- menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 0);
+ assertEquals(runScore.getScore(), 1);
+ assertEquals(runScore.getOldStatus(), oldMenuStatus);
+ assertEquals(runScore.getUpdatedStatus(), newMenuStatus);
+
+ assertEquals(menuManager.currentMenuCells.size(), 4);
+ List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getOldStatus(), KEEP);
+ List<MenuCell> newKeeps = filterMenuCellsWithStatusList(menuManager.currentMenuCells, runScore.getUpdatedStatus(), KEEP);
+ assertEquals(3, newKeeps.size());
+ assertEquals(3, oldKeeps.size());
}
@Test
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.currentMenuCells = new ArrayList<>();
+ menuManager.menuCells = new ArrayList<>();
+
+ sendFakeCoreOnHMIFullNotifications();
- 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);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(4, menuManager.currentMenuCells.size());
menuManager.setMenuCells(newMenu);
- assertEquals(menuManager.menuCells.size(), 0);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ assertEquals(0 , menuManager.currentMenuCells.size());
}
@Test
@@ -512,7 +563,7 @@ public class MenuManagerTests {
// call open Menu
MenuManager mockMenuManager = mock(MenuManager.class);
MenuCell cell = mock(MenuCell.class);
- mockMenuManager.oldMenuCells = null;
+ mockMenuManager.currentMenuCells = null;
assertFalse(mockMenuManager.openSubMenu(cell));
}
@@ -520,201 +571,40 @@ public class MenuManagerTests {
public void testOpeningSubMenu() {
// call open Menu
List<MenuCell> testCells = createTestCells();
- menuManager.oldMenuCells = testCells;
- menuManager.sdlMsgVersion = new SdlMsgVersion(6, 0);
- // has to get success response to be true
- assertTrue(menuManager.openSubMenu(testCells.get(3)));
- }
-
- @Test
- public void testSetMenuConfiguration() {
- menuManager.currentHMILevel = HMILevel.HMI_FULL;
- menuManager.currentSystemContext = SystemContext.SYSCTXT_MAIN;
- menuManager.sdlMsgVersion = new SdlMsgVersion(6, 0);
- menuManager.defaultMainWindowCapability = new WindowCapability();
-
- List<MenuLayout> menuLayouts = Arrays.asList(MenuLayout.LIST, MenuLayout.TILES);
- menuManager.defaultMainWindowCapability.setMenuLayoutsAvailable(menuLayouts);
-
- MenuConfiguration menuConfigurationTest = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
- menuManager.setMenuConfiguration(menuConfigurationTest);
- assertEquals(menuManager.menuConfiguration, menuConfigurationTest);
+ menuManager.setMenuCells(testCells);
- }
-
- @Test
- public void testSettingUniqueMenuNames() {
- //Testing using SDLMsgVersion 7.0, at this version uniqueTitles will be set
-
- // 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 = createDynamicMenu6_forUniqueNamesTest();
- menuManager.setMenuCells(oldMenu);
- assertEquals(menuManager.menuCells.size(), 4);
- assertEquals(menuManager.menuCells.get(0).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(1).getUniqueTitle(), "A (2)");
- assertEquals(menuManager.menuCells.get(2).getUniqueTitle(), "A (3)");
- assertEquals(menuManager.menuCells.get(3).getUniqueTitle(), "A (4)");
-
- assertEquals((menuManager.menuCells.get(3).getSubCells().size()), 4);
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(0).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(1).getUniqueTitle(), "A (2)");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(2).getUniqueTitle(), "A (3)");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(3).getUniqueTitle(), "A (4)");
- }
-
- @Test
- public void testAllowingNonUniqueTitles() {
- //Testing using SDLMsgVersion 7.1, at this version uniqueTitles will be set
- SdlMsgVersion version = new SdlMsgVersion();
- version.setMajorVersion(7);
- version.setMinorVersion(1);
- doReturn(version).when(internalInterface).getSdlMsgVersion();
+ sendFakeCoreOnHMIFullNotifications();
- // 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;
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
- menuManager.currentHMILevel = HMILevel.HMI_FULL;
- // send new cells. They should set the old way
- List<MenuCell> oldMenu = createDynamicMenu6_forUniqueNamesTest();
- menuManager.setMenuCells(oldMenu);
- assertEquals(menuManager.menuCells.size(), 4);
- assertEquals(menuManager.menuCells.get(0).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(1).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(2).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(3).getUniqueTitle(), "A");
-
- assertEquals((menuManager.menuCells.get(3).getSubCells().size()), 4);
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(0).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(1).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(2).getUniqueTitle(), "A");
- assertEquals(menuManager.menuCells.get(3).getSubCells().get(3).getUniqueTitle(), "A");
+ // Has to get success response to be true
+ MenuCell submenu = testCells.get(3);
+ assertTrue(menuManager.openSubMenu(submenu));
}
@Test
- public void testUniquenessForAvailableFields() {
- WindowCapability windowCapability = new WindowCapability();
- TextField menuSubMenuSecondaryText = new TextField();
- menuSubMenuSecondaryText.setName(TextFieldName.menuSubMenuSecondaryText);
- TextField menuSubMenuTertiaryText = new TextField();
- menuSubMenuTertiaryText.setName(TextFieldName.menuSubMenuTertiaryText);
- TextField menuCommandSecondaryText = new TextField();
- menuCommandSecondaryText.setName(TextFieldName.menuCommandSecondaryText);
- TextField menuCommandTertiaryText = new TextField();
- menuCommandTertiaryText.setName(TextFieldName.menuCommandTertiaryText);
- List<TextField> textFields = new ArrayList<>();
- textFields.add(menuSubMenuSecondaryText);
- textFields.add(menuSubMenuTertiaryText);
- textFields.add(menuCommandSecondaryText);
- textFields.add(menuCommandTertiaryText);
- windowCapability.setTextFields(textFields);
-
- ImageField cmdIcon = new ImageField();
- cmdIcon.setName(ImageFieldName.cmdIcon);
- ImageField menuSubMenuSecondaryImage = new ImageField();
- menuSubMenuSecondaryImage.setName(ImageFieldName.menuSubMenuSecondaryImage);
- ImageField menuCommandSecondaryImage = new ImageField();
- menuCommandSecondaryImage.setName(ImageFieldName.menuCommandSecondaryImage);
- List<ImageField> imageFieldList = new ArrayList<>();
- imageFieldList.add(cmdIcon);
- imageFieldList.add(menuSubMenuSecondaryImage);
- imageFieldList.add(menuCommandSecondaryImage);
- windowCapability.setImageFields(imageFieldList);
- menuManager.defaultMainWindowCapability = windowCapability;
-
- assertNull(menuManager.removeUnusedProperties(null));
-
- MenuCell cell1 = new MenuCell("Text1", "SecondaryText", "TText", TestValues.GENERAL_ARTWORK, TestValues.GENERAL_ARTWORK, null, new MenuSelectionListener() {
- @Override
- public void onTriggered(TriggerSource trigger) {
-
- }
- });
-
- MenuCell cell2 = new MenuCell("Text1", "SecondaryText2", "TText2", null, null, null, new MenuSelectionListener() {
- @Override
- public void onTriggered(TriggerSource trigger) {
-
- }
- });
-
- MenuCell subCell1 = new MenuCell("SubCell1", "Secondary Text", "TText", TestValues.GENERAL_ARTWORK, TestValues.GENERAL_ARTWORK, null, new MenuSelectionListener() {
- @Override
- public void onTriggered(TriggerSource trigger) {
- }
- });
-
- MenuCell subCell2 = new MenuCell("SubCell1", "Secondary Text2", "TText2", null, null, null, new MenuSelectionListener() {
- @Override
- public void onTriggered(TriggerSource trigger) {
- }
- });
-
- List<MenuCell> subCellList = new ArrayList<>();
- subCellList.add(subCell1);
- subCellList.add(subCell2);
-
-
- MenuCell cell3 = new MenuCell("Test Cell 3 (sub menu)", "SecondaryText", "TText", MenuLayout.LIST, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_ARTWORK, subCellList);
- MenuCell cell4 = new MenuCell("Test Cell 3 (sub menu)", null, null, MenuLayout.LIST, null, null, subCellList);
-
- List<MenuCell> menuCellList = new ArrayList<>();
- menuCellList.add(cell1);
- menuCellList.add(cell2);
- menuCellList.add(cell3);
- menuCellList.add(cell4);
-
- List<MenuCell> removedProperties = menuManager.removeUnusedProperties(menuCellList);
- assertNotNull(removedProperties.get(0).getSecondaryText());
- menuManager.addUniqueNamesBasedOnStrippedCells(removedProperties, menuCellList);
- assertEquals(menuCellList.get(1).getUniqueTitle(), "Text1");
-
- // Remove menuCommandSecondaryText as a supported TextField
- textFields.remove(menuCommandSecondaryText);
- textFields.remove(menuCommandTertiaryText);
- imageFieldList.remove(cmdIcon);
- imageFieldList.remove(menuCommandSecondaryImage);
- imageFieldList.remove(menuSubMenuSecondaryImage);
- textFields.remove(menuSubMenuSecondaryText);
- textFields.remove(menuSubMenuTertiaryText);
- textFields.remove(menuSubMenuSecondaryImage);
-
- // Test removeUnusedProperties
- removedProperties = menuManager.removeUnusedProperties(menuCellList);
- assertNull(removedProperties.get(0).getSecondaryText());
- assertNull(removedProperties.get(0).getTertiaryText());
-
- menuManager.addUniqueNamesBasedOnStrippedCells(removedProperties, menuCellList);
- assertEquals(menuCellList.get(1).getUniqueTitle(), "Text1 (2)");
+ public void testSetMenuConfiguration() {
+ sendFakeCoreOnHMIFullNotifications();
+ menuManager.windowCapability = new WindowCapability();
+ menuManager.windowCapability.setMenuLayoutsAvailable(Arrays.asList(MenuLayout.LIST, MenuLayout.TILES));
- // SubCell test
- assertEquals(menuCellList.get(3).getUniqueTitle(), "Test Cell 3 (sub menu) (2)");
- assertEquals(menuCellList.get(2).getSubCells().get(1).getUniqueTitle(), "SubCell1 (2)");
+ MenuConfiguration menuConfigurationTest = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+ menuManager.setMenuConfiguration(menuConfigurationTest);
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+ assertEquals(menuManager.menuConfiguration, menuConfigurationTest);
}
-
-
// HELPERS
// Emulate what happens when Core sends OnHMIStatus notification
private void sendFakeCoreOnHMIFullNotifications() {
OnHMIStatus onHMIStatusFakeNotification = new OnHMIStatus();
onHMIStatusFakeNotification.setHmiLevel(HMILevel.HMI_FULL);
+ onHMIStatusFakeNotification.setSystemContext(SystemContext.SYSCTXT_MAIN);
onHMIStatusListener.onNotified(onHMIStatusFakeNotification);
}
@@ -750,12 +640,6 @@ public class MenuManagerTests {
}
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);
@@ -769,13 +653,6 @@ public class MenuManagerTests {
}
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);
@@ -791,12 +668,6 @@ public class MenuManagerTests {
}
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);
@@ -810,11 +681,6 @@ public class MenuManagerTests {
}
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);
@@ -826,11 +692,6 @@ public class MenuManagerTests {
}
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);
@@ -858,12 +719,6 @@ public class MenuManagerTests {
}
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);
@@ -877,12 +732,6 @@ public class MenuManagerTests {
}
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);
@@ -896,12 +745,6 @@ public class MenuManagerTests {
}
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);
@@ -915,12 +758,6 @@ public class MenuManagerTests {
}
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);
@@ -933,32 +770,21 @@ public class MenuManagerTests {
}
- private List<MenuCell> createDynamicMenu6_forUniqueNamesTest() {
- MenuSelectionListener menuSelectionListenerA = mock(MenuSelectionListener.class);
- MenuSelectionListener menuSelectionListenerB = mock(MenuSelectionListener.class);
- MenuSelectionListener menuSelectionListenerC = mock(MenuSelectionListener.class);
- MenuSelectionListener menuSelectionListenerD = mock(MenuSelectionListener.class);
-
- SdlArtwork icon1 = new SdlArtwork("livio", FileType.GRAPHIC_PNG, R.drawable.sdl_lockscreen_icon, false);
- SdlArtwork icon2 = new SdlArtwork("livio2", FileType.GRAPHIC_PNG, R.drawable.ic_sdl, false);
- SdlArtwork icon3 = new SdlArtwork("livio3", FileType.GRAPHIC_PNG, R.drawable.sdl_tray_icon, false);
- SdlArtwork icon4 = new SdlArtwork("livio4", FileType.GRAPHIC_PNG, R.drawable.spp_error, false);
-
- MenuCell A = new MenuCell("A", icon1, null, menuSelectionListenerA);
-
- MenuCell B = new MenuCell("A", icon2, null, menuSelectionListenerB);
-
- MenuCell C = new MenuCell("A", icon3, null, menuSelectionListenerC);
-
- MenuCell subA = new MenuCell("A", icon1, null, menuSelectionListenerA);
- MenuCell subB = new MenuCell("A", icon2, null, menuSelectionListenerB);
- MenuCell subC = new MenuCell("A", icon3, null, menuSelectionListenerC);
- MenuCell subD = new MenuCell("A", icon4, null, menuSelectionListenerD);
-
- MenuCell D = new MenuCell("A", MenuLayout.LIST, icon4, Arrays.asList(subA, subB, subC, subD));
-
- return Arrays.asList(A, B, C, D);
+ private List<MenuCell> filterMenuCellsWithStatusList(List<MenuCell> menuCells, List<DynamicMenuUpdateAlgorithm.MenuCellState> statusList, DynamicMenuUpdateAlgorithm.MenuCellState menuCellState) {
+ List<MenuCell> filteredCells = new ArrayList<>();
+ for (int index = 0; index < statusList.size(); index++) {
+ if (statusList.get(index).equals(menuCellState)) {
+ filteredCells.add(menuCells.get(index));
+ }
+ }
+ return filteredCells;
}
-
+ private void sleep() {
+ try {
+ Thread.sleep(250);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperationTests.java
new file mode 100644
index 000000000..284f1efd5
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperationTests.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2021 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 static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.cloneMenuCellsList;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Taskmaster;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.TextField;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.test.TestValues;
+import com.smartdevicelink.util.Version;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class MenuReplaceOperationTests {
+ private Handler mainHandler;
+ private Taskmaster taskmaster;
+ private Queue transactionQueue;
+
+ @Before
+ public void setUp() throws Exception {
+ mainHandler = new Handler(getInstrumentation().getTargetContext().getMainLooper());
+ taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+ transactionQueue = taskmaster.createQueue("MenuManager", new Random().nextInt(), false);
+ }
+
+ @Test
+ public void testSuccess() {
+ final ISdl internalInterface = createISdlMock();
+ FileManager fileManager = createFileManagerMock();
+ WindowCapability windowCapability = createWindowCapability(true, true, new ArrayList<TextField>());
+ MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+
+ MenuCell menuCell1_1 = new MenuCell("cell 1_1", TestValues.GENERAL_ARTWORK, null, null);
+ MenuCell menuCell1 = new MenuCell("cell 1", null, TestValues.GENERAL_ARTWORK, Arrays.asList(menuCell1_1));
+ MenuCell menuCell2 = new MenuCell("cell 2", TestValues.GENERAL_ARTWORK, null, null);
+
+ final List<MenuCell> currentMenu = new ArrayList<>();
+ final List<MenuCell> updatedMenu = cloneMenuCellsList(Arrays.asList(menuCell1, menuCell2));
+ MenuReplaceOperation operation = new MenuReplaceOperation(internalInterface, fileManager, windowCapability, menuConfiguration, currentMenu, updatedMenu, true, new MenuManagerCompletionListener() {
+ @Override
+ public void onComplete(final boolean success, final List<MenuCell> currentMenuCells) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(currentMenuCells, updatedMenu);
+ verify(internalInterface, Mockito.times(2)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testSwitchingCellsOrder() {
+ // This unit test is for this bug https://github.com/smartdevicelink/sdl_java_suite/issues/1723
+ final ISdl internalInterface = createISdlMock();
+ final FileManager fileManager = createFileManagerMock();
+ final WindowCapability windowCapability = createWindowCapability(true, true, new ArrayList<TextField>());
+ final MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+
+ MenuSelectionListener listener = null;
+ final MenuCell menuCell1 = new MenuCell("A", "SecondaryText", null, null, null, null, listener);
+ final MenuCell menuCell2 = new MenuCell("A", null, null, null, null, null, listener);
+ final MenuCell menuCell3 = new MenuCell("C", null, null, null, null, null, listener);
+
+ MenuReplaceOperation operation = new MenuReplaceOperation(internalInterface, fileManager, windowCapability, menuConfiguration, new ArrayList<MenuCell>(), cloneMenuCellsList(Arrays.asList(menuCell1, menuCell2, menuCell3)), true, new MenuManagerCompletionListener() {
+ @Override
+ public void onComplete(final boolean success, final List<MenuCell> currentMenuCells1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(3, currentMenuCells1.size());
+ assertEquals("A", currentMenuCells1.get(0).getUniqueTitle());
+ assertEquals("A (2)", currentMenuCells1.get(1).getUniqueTitle());
+ assertEquals("C", currentMenuCells1.get(2).getUniqueTitle());
+
+ verify(internalInterface, Mockito.times(1)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ MenuReplaceOperation operation = new MenuReplaceOperation(internalInterface, fileManager, windowCapability, menuConfiguration, currentMenuCells1, cloneMenuCellsList(Arrays.asList(menuCell2, menuCell1)), true, new MenuManagerCompletionListener() {
+ @Override
+ public void onComplete(final boolean success, final List<MenuCell> currentMenuCells2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(2, currentMenuCells2.size());
+ assertEquals("A", currentMenuCells2.get(0).getUniqueTitle());
+ assertEquals("A (2)", currentMenuCells2.get(1).getUniqueTitle());
+ verify(internalInterface, Mockito.times(2)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ @Test
+ public void testResendingSameCellWithDifferentListener() {
+ final ISdl internalInterface = createISdlMock();
+ final FileManager fileManager = createFileManagerMock();
+ final WindowCapability windowCapability = createWindowCapability(true, true, new ArrayList<TextField>());
+ final MenuConfiguration menuConfiguration = new MenuConfiguration(MenuLayout.LIST, MenuLayout.LIST);
+
+ final MenuSelectionListener listener1 = new MenuSelectionListener() {
+ @Override
+ public void onTriggered(TriggerSource trigger) {}
+ };
+ final MenuSelectionListener listener2 = new MenuSelectionListener() {
+ @Override
+ public void onTriggered(TriggerSource trigger) {}
+ };
+ final MenuCell menuCell1 = new MenuCell("A", null, null, null, null, null, listener1);
+ final MenuCell menuCell2 = new MenuCell("A", null, null, null, null, null, listener2);
+
+ MenuReplaceOperation operation = new MenuReplaceOperation(internalInterface, fileManager, windowCapability, menuConfiguration, new ArrayList<MenuCell>(), cloneMenuCellsList(Arrays.asList(menuCell1)), true, new MenuManagerCompletionListener() {
+ @Override
+ public void onComplete(final boolean success, final List<MenuCell> currentMenuCells1) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(1, currentMenuCells1.size());
+ assertEquals(listener1, currentMenuCells1.get(0).getMenuSelectionListener());
+ verify(internalInterface, Mockito.times(1)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+
+ MenuReplaceOperation operation = new MenuReplaceOperation(internalInterface, fileManager, windowCapability, menuConfiguration, currentMenuCells1, cloneMenuCellsList(Arrays.asList(menuCell2)), true, new MenuManagerCompletionListener() {
+ @Override
+ public void onComplete(final boolean success, final List<MenuCell> currentMenuCells2) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(success);
+ assertEquals(1, currentMenuCells2.size());
+ assertEquals(listener2, currentMenuCells2.get(0).getMenuSelectionListener());
+ verify(internalInterface, Mockito.times(1)).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+ });
+ }
+ });
+ transactionQueue.add(operation, false);
+ }
+
+ private ISdl createISdlMock() {
+ final ISdl internalInterface = mock(ISdl.class);
+
+ // When internalInterface.sendRPCs() is called, call listener.onFinished() to fake the response
+ final Answer<Void> answer = new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ List<RPCMessage> rpcs = (List<RPCMessage>) args[0];
+ OnMultipleRequestListener listener = (OnMultipleRequestListener) args[1];
+
+ for (RPCMessage rpcMessage : rpcs) {
+ RPCRequest request = (RPCRequest) rpcMessage;
+ RPCResponse response = new RPCResponse(request.getFunctionID().toString());
+ response.setCorrelationID(request.getCorrelationID());
+ response.setSuccess(true);
+ listener.onResponse(request.getCorrelationID(), response);
+ }
+
+ listener.onFinished();
+ return null;
+ }
+ };
+ doAnswer(answer).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class));
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(new Version(7, 1, 0)));
+
+ return internalInterface;
+ }
+
+ private FileManager createFileManagerMock() {
+ FileManager fileManager = mock(FileManager.class);
+
+ when(fileManager.hasUploadedFile(any(SdlArtwork.class))).thenReturn(true);
+
+ Answer<Void> onFileManagerUploadAnswer = new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ MultipleFileCompletionListener multipleFileCompletionListener = (MultipleFileCompletionListener) args[1];
+ multipleFileCompletionListener.onComplete(null);
+ return null;
+ }
+ };
+ doAnswer(onFileManagerUploadAnswer).when(fileManager).uploadArtworks(any(List.class), any(MultipleFileCompletionListener.class));
+ return fileManager;
+ }
+
+ private WindowCapability createWindowCapability(boolean supportsList, boolean supportsTile, ArrayList<TextField> supportedTextFields) {
+ WindowCapability windowCapability = new WindowCapability();
+ windowCapability.setTextFields(supportedTextFields);
+ windowCapability.setMenuLayoutsAvailable(new ArrayList<MenuLayout>());
+ if (supportsList) {
+ windowCapability.getMenuLayoutsAvailable().add(MenuLayout.LIST);
+ }
+ if (supportsTile) {
+ windowCapability.getMenuLayoutsAvailable().add(MenuLayout.TILES);
+ }
+ return windowCapability;
+ }
+
+ // Asserts on Taskmaster threads will fail silently so we need to do the assertions on main thread if the code is triggered from Taskmaster
+ private void assertOnMainThread(Runnable runnable) {
+ mainHandler.post(runnable);
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilitiesTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilitiesTests.java
new file mode 100644
index 000000000..61587dbdc
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilitiesTests.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2021 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 androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.rpc.ImageField;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.test.TestValues;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.smartdevicelink.managers.screen.menu.BaseMenuManager.parentIdNotFound;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.addIdsToMenuCells;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by Bilal Alsharifi on 2/4/21.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MenuReplaceUtilitiesTests {
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @Test
+ public void testRemoveMenuCellFromList() {
+ MenuCell menuCellToDelete;
+ boolean cellRemoved;
+ List<MenuCell> actualMenuCellList = createMenuCellList();
+ List<MenuCell> expectedMenuCellList = createMenuCellList();
+
+ // Delete cell c4
+ menuCellToDelete = actualMenuCellList.get(3);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.remove(3);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(3, actualMenuCellList.size());
+
+ // Delete cell c4 again - removal should fail and list should not change
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertFalse(cellRemoved);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(3, actualMenuCellList.size());
+
+ // Delete cell c3
+ menuCellToDelete = actualMenuCellList.get(2);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.remove(2);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(2, actualMenuCellList.size());
+
+ // Delete cell c2-2-2
+ menuCellToDelete = actualMenuCellList.get(1).getSubCells().get(1).getSubCells().get(1);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.get(1).getSubCells().get(1).getSubCells().remove(1);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(2, actualMenuCellList.size());
+ assertEquals(1, actualMenuCellList.get(1).getSubCells().get(1).getSubCells().size());
+
+ // Delete cell c2-2-1
+ menuCellToDelete = actualMenuCellList.get(1).getSubCells().get(1).getSubCells().get(0);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.get(1).getSubCells().get(1).getSubCells().remove(0);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(2, actualMenuCellList.size());
+ assertEquals(0, actualMenuCellList.get(1).getSubCells().get(1).getSubCells().size());
+
+ // Delete cell c2-2
+ menuCellToDelete = actualMenuCellList.get(1).getSubCells().get(1);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.get(1).getSubCells().remove(1);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(2, actualMenuCellList.size());
+ assertEquals(1, actualMenuCellList.get(1).getSubCells().size());
+
+ // Delete cell c2-1
+ menuCellToDelete = actualMenuCellList.get(1).getSubCells().get(0);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.get(1).getSubCells().remove(0);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(2, actualMenuCellList.size());
+ assertEquals(0, actualMenuCellList.get(1).getSubCells().size());
+
+ // Delete cell c2
+ menuCellToDelete = actualMenuCellList.get(1);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.remove(1);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(1, actualMenuCellList.size());
+
+ // Delete cell c1
+ menuCellToDelete = actualMenuCellList.get(0);
+ cellRemoved = MenuReplaceUtilities.removeCellFromList(actualMenuCellList, menuCellToDelete.getCellId());
+ assertTrue(cellRemoved);
+ expectedMenuCellList.remove(0);
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(0, actualMenuCellList.size());
+ }
+
+ @Test
+ public void testAddMenuRequestWithCommandId() {
+ MenuCell menuCellToAdd;
+ boolean cellAdded;
+ List<MenuCell> actualMenuCellList = createMenuCellList();
+ List<MenuCell> expectedMenuCellList = createMenuCellList();
+ List<MenuCell> newMenuList = createNewMenuList();
+
+ // Add cell c5
+ menuCellToAdd = newMenuList.get(0);
+ cellAdded = MenuReplaceUtilities.addCellWithCellId(menuCellToAdd.getCellId(), 4, newMenuList, actualMenuCellList);
+ assertTrue(cellAdded);
+ expectedMenuCellList.add(4, cloneMenuCellAndRemoveSubCells(menuCellToAdd));
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(5, actualMenuCellList.size());
+ assertEquals(0, actualMenuCellList.get(4).getSubCells().size());
+
+ // Add cell c5-1
+ menuCellToAdd = newMenuList.get(0).getSubCells().get(0);
+ cellAdded = MenuReplaceUtilities.addCellWithCellId(menuCellToAdd.getCellId(), 0, newMenuList, actualMenuCellList);
+ assertTrue(cellAdded);
+ expectedMenuCellList.get(4).getSubCells().add(0, cloneMenuCellAndRemoveSubCells(menuCellToAdd));
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(5, actualMenuCellList.size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().size());
+
+ // Add cell c5-1-1
+ menuCellToAdd = newMenuList.get(0).getSubCells().get(0).getSubCells().get(0);
+ cellAdded = MenuReplaceUtilities.addCellWithCellId(menuCellToAdd.getCellId(), 0, newMenuList, actualMenuCellList);
+ assertTrue(cellAdded);
+ expectedMenuCellList.get(4).getSubCells().get(0).getSubCells().add(0, cloneMenuCellAndRemoveSubCells(menuCellToAdd));
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(5, actualMenuCellList.size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().get(0).getSubCells().size());
+
+ // Add cell c5-2
+ menuCellToAdd = newMenuList.get(0).getSubCells().get(1);
+ cellAdded = MenuReplaceUtilities.addCellWithCellId(menuCellToAdd.getCellId(), 1, newMenuList, actualMenuCellList);
+ assertTrue(cellAdded);
+ expectedMenuCellList.get(4).getSubCells().add(1, cloneMenuCellAndRemoveSubCells(menuCellToAdd));
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(5, actualMenuCellList.size());
+ assertEquals(2, actualMenuCellList.get(4).getSubCells().size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().get(0).getSubCells().size());
+ assertEquals(0, actualMenuCellList.get(4).getSubCells().get(1).getSubCells().size());
+
+ // Add cell c5-2-1
+ menuCellToAdd = newMenuList.get(0).getSubCells().get(1).getSubCells().get(0);
+ cellAdded = MenuReplaceUtilities.addCellWithCellId(menuCellToAdd.getCellId(), 0, newMenuList, actualMenuCellList);
+ assertTrue(cellAdded);
+ expectedMenuCellList.get(4).getSubCells().get(1).getSubCells().add(0, cloneMenuCellAndRemoveSubCells(menuCellToAdd));
+ assertEquals(expectedMenuCellList, actualMenuCellList);
+ assertEquals(5, actualMenuCellList.size());
+ assertEquals(2, actualMenuCellList.get(4).getSubCells().size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().get(0).getSubCells().size());
+ assertEquals(1, actualMenuCellList.get(4).getSubCells().get(1).getSubCells().size());
+ }
+
+ @Test
+ public void testShouldCellIncludeImage() {
+ MenuCell menuCell;
+ WindowCapability windowCapability;
+ FileManager fileManager;
+ List<String> voiceCommands = null;
+
+ // Case 1
+ menuCell = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, voiceCommands, null);
+ windowCapability = createWindowCapability(true, true);
+ fileManager = createMockFileManager(true);
+ assertTrue(MenuReplaceUtilities.shouldCellIncludePrimaryImageFromCell(menuCell, fileManager, windowCapability));
+
+ // Case 2 - Image are not supported
+ menuCell = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, voiceCommands, null);
+ windowCapability = createWindowCapability(false, false);
+ fileManager = createMockFileManager(true);
+ assertFalse(MenuReplaceUtilities.shouldCellIncludePrimaryImageFromCell(menuCell, fileManager, windowCapability));
+
+ // Case 3 - Artwork is null
+ menuCell = new MenuCell(TestValues.GENERAL_STRING, null, voiceCommands, null);
+ windowCapability = createWindowCapability(true, true);
+ fileManager = createMockFileManager(true);
+ assertFalse(MenuReplaceUtilities.shouldCellIncludePrimaryImageFromCell(menuCell, fileManager, windowCapability));
+
+ // Case 4 - Artwork has not been uploaded
+ menuCell = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK, voiceCommands, null);
+ windowCapability = createWindowCapability(true, true);
+ fileManager = createMockFileManager(false);
+ assertFalse(MenuReplaceUtilities.shouldCellIncludePrimaryImageFromCell(menuCell, fileManager, windowCapability));
+
+ // Case 5 - Artwork is static icon
+ menuCell = new MenuCell(TestValues.GENERAL_STRING, TestValues.GENERAL_ARTWORK_STATIC, voiceCommands, null);
+ windowCapability = createWindowCapability(true, true);
+ fileManager = createMockFileManager(false);
+ assertTrue(MenuReplaceUtilities.shouldCellIncludePrimaryImageFromCell(menuCell, fileManager, windowCapability));
+ }
+
+ private WindowCapability createWindowCapability (boolean supportsCmdIcon, boolean supportsSubMenuIcon) {
+ WindowCapability windowCapability = new WindowCapability();
+ windowCapability.setImageFields(new ArrayList<ImageField>());
+ if (supportsCmdIcon) {
+ windowCapability.getImageFields().add(new ImageField(ImageFieldName.cmdIcon, null));
+ }
+ if (supportsSubMenuIcon) {
+ windowCapability.getImageFields().add(new ImageField(ImageFieldName.subMenuIcon, null));
+ }
+ return windowCapability;
+ }
+
+ private FileManager createMockFileManager (boolean hasUploadedFile) {
+ FileManager fileManager = mock(FileManager.class);
+ when(fileManager.hasUploadedFile(any(SdlArtwork.class))).thenReturn(hasUploadedFile);
+ return fileManager;
+ }
+
+ private MenuCell cloneMenuCellAndRemoveSubCells(MenuCell menuCell) {
+ MenuCell clonedCell = menuCell.clone();
+ if (clonedCell.getSubCells() != null) {
+ clonedCell.getSubCells().clear();
+ }
+ return clonedCell;
+ }
+
+ private List<MenuCell> createMenuCellList() {
+ /*
+
+ c1 c2 c3 c4
+ / \ / \
+ / \ / \
+ c2-1 c2-2 c4-1 c4-2
+ / \
+ / \
+ c2-2-1 c2-2-2
+
+ */
+
+ SdlArtwork sdlArtwork = null;
+ List<String> voiceCommands = null;
+ MenuSelectionListener listener = null;
+ MenuLayout subMenuLayout = null;
+
+ MenuCell menuCell1 = new MenuCell("c1", sdlArtwork, voiceCommands, listener);
+
+ MenuCell menuCell2_1 = new MenuCell("c2_1", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell2_2_1 = new MenuCell("c2_2_1", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell2_2_2 = new MenuCell("c2_2_2", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell2_2 = new MenuCell("c2_2", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell2_2_1, menuCell2_2_2)));
+ MenuCell menuCell2 = new MenuCell("c2", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell2_1, menuCell2_2)));
+
+ MenuCell menuCell3 = new MenuCell("c3", sdlArtwork, voiceCommands, listener);
+
+ MenuCell menuCell4_1 = new MenuCell("c4_1", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell4_2 = new MenuCell("c4_2", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell4 = new MenuCell("c4", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell4_1, menuCell4_2)));
+
+ List<MenuCell> menuCellList = new ArrayList<>(Arrays.asList(menuCell1, menuCell2, menuCell3, menuCell4));
+ addIdsToMenuCells(menuCellList, parentIdNotFound);
+
+ return menuCellList ;
+ }
+
+ private List<MenuCell> createNewMenuList() {
+ /*
+
+ c5
+ / \
+ / \
+ c5-1 c5-2
+ / /
+ / /
+ c5-1-1 c5-2-1
+
+ */
+
+ SdlArtwork sdlArtwork = null;
+ List<String> voiceCommands = null;
+ MenuSelectionListener listener = null;
+ MenuLayout subMenuLayout = null;
+
+ MenuCell menuCell5_1_1 = new MenuCell("c5_1_1", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell5_1 = new MenuCell("c5_1", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell5_1_1)));
+ MenuCell menuCell5_2_1 = new MenuCell("c5_2_1", sdlArtwork, voiceCommands, listener);
+ MenuCell menuCell5_2 = new MenuCell("c5_2", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell5_2_1)));
+ MenuCell menuCell5 = new MenuCell("c5", subMenuLayout, sdlArtwork, new ArrayList<>(Arrays.asList(menuCell5_1, menuCell5_2)));
+
+ List<MenuCell> newMenuList = new ArrayList<>(Arrays.asList(menuCell5));
+ addIdsToMenuCells(newMenuList, parentIdNotFound);
+
+ return newMenuList ;
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuShowOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuShowOperationTests.java
new file mode 100644
index 000000000..ee90cee49
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuShowOperationTests.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2021 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.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Taskmaster;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.ShowAppMenu;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.test.TestValues;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Random;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+@RunWith(AndroidJUnit4.class)
+public class MenuShowOperationTests {
+ private Handler mainHandler;
+ private Taskmaster taskmaster;
+ private Queue transactionQueue;
+
+ @Before
+ public void setUp() throws Exception {
+ mainHandler = new Handler(getInstrumentation().getTargetContext().getMainLooper());
+ taskmaster = new Taskmaster.Builder().build();
+ taskmaster.start();
+ transactionQueue = taskmaster.createQueue("MenuManager", new Random().nextInt(), false);
+ }
+
+ @Test
+ public void testOpenMainMenu() {
+ final ISdl internalInterface = mock(ISdl.class);
+ MenuCell menuCell = null;
+ Integer menuIdToAssert = menuCell != null ? menuCell.getCellId() : null;
+ Answer<Void> showAppMenuAnswer = createShowAppMenuAnswer(true, menuIdToAssert);
+ doAnswer(showAppMenuAnswer).when(internalInterface).sendRPC(any(ShowAppMenu.class));
+ MenuShowOperation operation = new MenuShowOperation(internalInterface, menuCell);
+ transactionQueue.add(operation, false);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ verify(internalInterface, Mockito.times(1)).sendRPC(any(ShowAppMenu.class));
+ }
+
+ @Test
+ public void testOpenSubMenu() {
+ final ISdl internalInterface = mock(ISdl.class);
+ MenuCell menuCell = new MenuCell(TestValues.GENERAL_STRING, MenuLayout.TILES, null, null);
+ menuCell.setCellId(TestValues.GENERAL_INT);
+ Integer menuIdToAssert = menuCell != null ? menuCell.getCellId() : null;
+ Answer<Void> showAppMenuAnswer = createShowAppMenuAnswer(true, menuIdToAssert);
+ doAnswer(showAppMenuAnswer).when(internalInterface).sendRPC(any(ShowAppMenu.class));
+ MenuShowOperation operation = new MenuShowOperation(internalInterface, menuCell);
+ transactionQueue.add(operation, false);
+
+ // Sleep to give time to Taskmaster to run the operations
+ sleep();
+
+ verify(internalInterface, Mockito.times(1)).sendRPC(any(ShowAppMenu.class));
+ }
+
+ private Answer<Void> createShowAppMenuAnswer(final boolean success, final Integer menuIdToAssert){
+ return new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ final RPCRequest request = (RPCRequest) args[0];
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ ShowAppMenu showAppMenu = (ShowAppMenu) request;
+ assertEquals(showAppMenu.getMenuID(), menuIdToAssert);
+ }
+ });
+ RPCResponse response = new RPCResponse(request.getFunctionID().toString());
+ response.setSuccess(success);
+ request.getOnRPCResponseListener().onResponse(request.getCorrelationID(), response);
+ return null;
+ }
+ };
+ }
+
+ private void sleep() {
+ try {
+ Thread.sleep(250);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Asserts on Taskmaster threads will fail silently so we need to do the assertions on main thread if the code is triggered from Taskmaster
+ private void assertOnMainThread(Runnable runnable) {
+ mainHandler.post(runnable);
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java
index 713770984..da3bb5846 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java
@@ -18,106 +18,7 @@ import com.smartdevicelink.protocol.SdlProtocol;
import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.proxy.rpc.AppCapability;
import com.smartdevicelink.proxy.rpc.*;
-import com.smartdevicelink.proxy.rpc.enums.AmbientLightStatus;
-import com.smartdevicelink.proxy.rpc.enums.AppCapabilityType;
-import com.smartdevicelink.proxy.rpc.enums.AppHMIType;
-import com.smartdevicelink.proxy.rpc.enums.AppInterfaceUnregisteredReason;
-import com.smartdevicelink.proxy.rpc.enums.AppServiceType;
-import com.smartdevicelink.proxy.rpc.enums.AudioStreamingIndicator;
-import com.smartdevicelink.proxy.rpc.enums.AudioStreamingState;
-import com.smartdevicelink.proxy.rpc.enums.AudioType;
-import com.smartdevicelink.proxy.rpc.enums.BitsPerSample;
-import com.smartdevicelink.proxy.rpc.enums.ButtonEventMode;
-import com.smartdevicelink.proxy.rpc.enums.ButtonName;
-import com.smartdevicelink.proxy.rpc.enums.ButtonPressMode;
-import com.smartdevicelink.proxy.rpc.enums.CapacityUnit;
-import com.smartdevicelink.proxy.rpc.enums.CarModeStatus;
-import com.smartdevicelink.proxy.rpc.enums.CharacterSet;
-import com.smartdevicelink.proxy.rpc.enums.CompassDirection;
-import com.smartdevicelink.proxy.rpc.enums.ComponentVolumeStatus;
-import com.smartdevicelink.proxy.rpc.enums.DefrostZone;
-import com.smartdevicelink.proxy.rpc.enums.DeviceLevelStatus;
-import com.smartdevicelink.proxy.rpc.enums.Dimension;
-import com.smartdevicelink.proxy.rpc.enums.Direction;
-import com.smartdevicelink.proxy.rpc.enums.DisplayMode;
-import com.smartdevicelink.proxy.rpc.enums.DisplayType;
-import com.smartdevicelink.proxy.rpc.enums.DistanceUnit;
-import com.smartdevicelink.proxy.rpc.enums.DoorStatusType;
-import com.smartdevicelink.proxy.rpc.enums.DriverDistractionState;
-import com.smartdevicelink.proxy.rpc.enums.ECallConfirmationStatus;
-import com.smartdevicelink.proxy.rpc.enums.EmergencyEventType;
-import com.smartdevicelink.proxy.rpc.enums.FileType;
-import com.smartdevicelink.proxy.rpc.enums.FuelCutoffStatus;
-import com.smartdevicelink.proxy.rpc.enums.FuelType;
-import com.smartdevicelink.proxy.rpc.enums.GlobalProperty;
-import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.HmiZoneCapabilities;
-import com.smartdevicelink.proxy.rpc.enums.HybridAppPreference;
-import com.smartdevicelink.proxy.rpc.enums.IgnitionStableStatus;
-import com.smartdevicelink.proxy.rpc.enums.IgnitionStatus;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
-import com.smartdevicelink.proxy.rpc.enums.ImageType;
-import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardEvent;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardInputMask;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardLayout;
-import com.smartdevicelink.proxy.rpc.enums.KeypressMode;
-import com.smartdevicelink.proxy.rpc.enums.Language;
-import com.smartdevicelink.proxy.rpc.enums.LayoutMode;
-import com.smartdevicelink.proxy.rpc.enums.LightName;
-import com.smartdevicelink.proxy.rpc.enums.LightStatus;
-import com.smartdevicelink.proxy.rpc.enums.LockScreenStatus;
-import com.smartdevicelink.proxy.rpc.enums.MassageCushion;
-import com.smartdevicelink.proxy.rpc.enums.MassageMode;
-import com.smartdevicelink.proxy.rpc.enums.MassageZone;
-import com.smartdevicelink.proxy.rpc.enums.MediaClockFormat;
-import com.smartdevicelink.proxy.rpc.enums.MediaType;
-import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
-import com.smartdevicelink.proxy.rpc.enums.MetadataType;
-import com.smartdevicelink.proxy.rpc.enums.ModuleType;
-import com.smartdevicelink.proxy.rpc.enums.NavigationAction;
-import com.smartdevicelink.proxy.rpc.enums.NavigationJunction;
-import com.smartdevicelink.proxy.rpc.enums.PRNDL;
-import com.smartdevicelink.proxy.rpc.enums.PowerModeQualificationStatus;
-import com.smartdevicelink.proxy.rpc.enums.PowerModeStatus;
-import com.smartdevicelink.proxy.rpc.enums.PrerecordedSpeech;
-import com.smartdevicelink.proxy.rpc.enums.PrimaryAudioSource;
-import com.smartdevicelink.proxy.rpc.enums.RadioBand;
-import com.smartdevicelink.proxy.rpc.enums.RadioState;
-import com.smartdevicelink.proxy.rpc.enums.RequestType;
-import com.smartdevicelink.proxy.rpc.enums.Result;
-import com.smartdevicelink.proxy.rpc.enums.SamplingRate;
-import com.smartdevicelink.proxy.rpc.enums.SeatMemoryActionType;
-import com.smartdevicelink.proxy.rpc.enums.SeekIndicatorType;
-import com.smartdevicelink.proxy.rpc.enums.ServiceUpdateReason;
-import com.smartdevicelink.proxy.rpc.enums.SoftButtonType;
-import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities;
-import com.smartdevicelink.proxy.rpc.enums.SupportedSeat;
-import com.smartdevicelink.proxy.rpc.enums.SystemAction;
-import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
-import com.smartdevicelink.proxy.rpc.enums.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TBTState;
-import com.smartdevicelink.proxy.rpc.enums.TPMS;
-import com.smartdevicelink.proxy.rpc.enums.TemperatureUnit;
-import com.smartdevicelink.proxy.rpc.enums.TextAlignment;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.proxy.rpc.enums.TouchType;
-import com.smartdevicelink.proxy.rpc.enums.TransmissionType;
-import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
-import com.smartdevicelink.proxy.rpc.enums.UpdateMode;
-import com.smartdevicelink.proxy.rpc.enums.VehicleDataEventStatus;
-import com.smartdevicelink.proxy.rpc.enums.VehicleDataNotificationStatus;
-import com.smartdevicelink.proxy.rpc.enums.VehicleDataResultCode;
-import com.smartdevicelink.proxy.rpc.enums.VehicleDataStatus;
-import com.smartdevicelink.proxy.rpc.enums.VehicleDataType;
-import com.smartdevicelink.proxy.rpc.enums.VentilationMode;
-import com.smartdevicelink.proxy.rpc.enums.VideoStreamingCodec;
-import com.smartdevicelink.proxy.rpc.enums.VideoStreamingProtocol;
-import com.smartdevicelink.proxy.rpc.enums.VideoStreamingState;
-import com.smartdevicelink.proxy.rpc.enums.VrCapabilities;
-import com.smartdevicelink.proxy.rpc.enums.WarningLightStatus;
-import com.smartdevicelink.proxy.rpc.enums.WayPointType;
-import com.smartdevicelink.proxy.rpc.enums.WindowType;
+import com.smartdevicelink.proxy.rpc.enums.*;
import com.smartdevicelink.util.Version;
import org.json.JSONArray;
@@ -365,6 +266,7 @@ public class TestValues {
public static final DisplayCapability GENERAL_DISPLAY_CAPABILITY = new DisplayCapability();
public static final SdlArtwork GENERAL_ARTWORK = new SdlArtwork("sdl", FileType.GRAPHIC_PNG, R.drawable.ic_sdl, false);
+ public static final SdlArtwork GENERAL_ARTWORK_STATIC = new SdlArtwork(StaticIconName.BACK);
public static final MenuLayout GENERAL_MENU_LAYOUT = MenuLayout.LIST;
public static final MenuConfiguration GENERAL_MENU_CONFIGURATION = new MenuConfiguration(GENERAL_MENU_LAYOUT, GENERAL_MENU_LAYOUT);
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
index d19bae619..78d7a96e8 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseMenuManager.java
@@ -32,96 +32,67 @@
package com.smartdevicelink.managers.screen.menu;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.cloneMenuCellsList;
+
import androidx.annotation.NonNull;
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Task;
import com.smartdevicelink.managers.BaseSubManager;
import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.managers.ManagerUtility;
import com.smartdevicelink.managers.file.FileManager;
-import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
-import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
import com.smartdevicelink.managers.lifecycle.OnSystemCapabilityListener;
import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager;
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.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.DisplayCapability;
-import com.smartdevicelink.proxy.rpc.MenuParams;
import com.smartdevicelink.proxy.rpc.OnCommand;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
-import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
-import com.smartdevicelink.proxy.rpc.ShowAppMenu;
import com.smartdevicelink.proxy.rpc.WindowCapability;
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.PredefinedWindows;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import com.smartdevicelink.proxy.rpc.enums.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.util.DebugTool;
-import org.json.JSONException;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
abstract class BaseMenuManager extends BaseSubManager {
private static final String TAG = "BaseMenuManager";
- 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;
+ static final int parentIdNotFound = 2000000000;
- List<MenuCell> menuCells, waitingUpdateMenuCells, oldMenuCells, keepsNew, keepsOld;
- List<RPCRequest> inProgressUpdate;
+ final WeakReference<FileManager> fileManager;
+ List<MenuCell> currentMenuCells;
+ List<MenuCell> menuCells;
DynamicMenuUpdatesMode dynamicMenuUpdatesMode;
+ MenuConfiguration currentMenuConfiguration;
MenuConfiguration menuConfiguration;
- SdlMsgVersion sdlMsgVersion;
private String displayType;
-
- boolean waitingOnHMIUpdate;
- private boolean hasQueuedUpdate;
HMILevel currentHMILevel;
-
- OnRPCNotificationListener hmiListener, commandListener;
- OnSystemCapabilityListener onDisplaysCapabilityListener;
- WindowCapability defaultMainWindowCapability;
-
- private static final int MAX_ID = 2000000000;
- private static final int parentIdNotFound = MAX_ID;
- private static final int menuCellIdMin = 1;
- int lastMenuId;
-
SystemContext currentSystemContext;
+ OnRPCNotificationListener hmiListener;
+ OnRPCNotificationListener commandListener;
+ OnSystemCapabilityListener onDisplaysCapabilityListener;
+ WindowCapability windowCapability;
+ Queue transactionQueue;
BaseMenuManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) {
-
super(internalInterface);
- // Set up some Vars
+ this.menuConfiguration = new MenuConfiguration(null, null);
+ this.menuCells = new ArrayList<>();
+ this.currentMenuCells = new ArrayList<>();
+ this.dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
+ this.transactionQueue = newTransactionQueue();
this.fileManager = new WeakReference<>(fileManager);
- currentSystemContext = SystemContext.SYSCTXT_MAIN;
- currentHMILevel = HMILevel.HMI_NONE;
- lastMenuId = menuCellIdMin;
- dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
- sdlMsgVersion = internalInterface.getSdlMsgVersion();
-
+ this.currentSystemContext = SystemContext.SYSCTXT_MAIN;
+ this.currentHMILevel = HMILevel.HMI_NONE;
addListeners();
}
@@ -133,24 +104,22 @@ abstract class BaseMenuManager extends BaseSubManager {
@Override
public void dispose() {
-
- lastMenuId = menuCellIdMin;
- menuCells = null;
- oldMenuCells = null;
- currentHMILevel = null;
+ menuCells = new ArrayList<>();
+ currentMenuCells = new ArrayList<>();
+ currentHMILevel = HMILevel.HMI_NONE;
currentSystemContext = SystemContext.SYSCTXT_MAIN;
dynamicMenuUpdatesMode = DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE;
- defaultMainWindowCapability = null;
- inProgressUpdate = null;
- hasQueuedUpdate = false;
- waitingOnHMIUpdate = false;
- waitingUpdateMenuCells = null;
- keepsNew = null;
- keepsOld = null;
+ windowCapability = null;
menuConfiguration = null;
- sdlMsgVersion = null;
+ currentMenuConfiguration = null;
- // remove listeners
+ // Cancel the operations
+ if (transactionQueue != null) {
+ transactionQueue.close();
+ transactionQueue = null;
+ }
+
+ // Remove listeners
internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
internalInterface.removeOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener);
if (internalInterface.getSystemCapabilityManager() != null) {
@@ -160,103 +129,25 @@ abstract class BaseMenuManager extends BaseSubManager {
super.dispose();
}
- // SETTERS
-
- public void setDynamicUpdatesMode(@NonNull DynamicMenuUpdatesMode value) {
- this.dynamicMenuUpdatesMode = value;
+ private Queue newTransactionQueue() {
+ Queue queue = internalInterface.getTaskmaster().createQueue("MenuManager", 7, false);
+ queue.pause();
+ return queue;
}
- /**
- * 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);
-
- // Check for cell lists with completely duplicate information, or any duplicate voiceCommands and return if it fails (logs are in the called method).
- if (clonedCells != null && !menuCellsAreUnique(clonedCells, new ArrayList<String>())) {
- return;
- }
-
- 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<>();
- if (clonedCells != null && !clonedCells.isEmpty()) {
- waitingUpdateMenuCells.addAll(clonedCells);
- }
- return;
- }
- waitingOnHMIUpdate = false;
-
- // If we're running on a connection < RPC 7.1, we need to de-duplicate cells because presenting them will fail if we have the same cell primary text.
- if (clonedCells != null && internalInterface.getSdlMsgVersion() != null
- && (internalInterface.getSdlMsgVersion().getMajorVersion() < 7
- || (internalInterface.getSdlMsgVersion().getMajorVersion() == 7 && internalInterface.getSdlMsgVersion().getMinorVersion() == 0))) {
- addUniqueNamesToCellsWithDuplicatePrimaryText(clonedCells);
- } else {
- // On > RPC 7.1, at this point all cells are unique when considering all properties,
- // but we also need to check if any cells will _appear_ as duplicates when displayed on the screen.
- // To check that, we'll remove properties from the set cells based on the system capabilities
- // (we probably don't need to consider them changing between now and when they're actually sent to the HU unless the menu layout changes)
- // and check for uniqueness again. Then we'll add unique identifiers to primary text if there are duplicates.
- // Then we transfer the primary text identifiers back to the main cells and add those to an operation to be sent.
- List<MenuCell> strippedCellsClone = removeUnusedProperties(clonedCells);
- addUniqueNamesBasedOnStrippedCells(strippedCellsClone, clonedCells);
-
- }
-
- // Update our Lists
- // set old list
- if (menuCells != null) {
- oldMenuCells = new ArrayList<>(menuCells);
- }
- // copy new list
- menuCells = new ArrayList<>();
- if (clonedCells != null && !clonedCells.isEmpty()) {
- menuCells.addAll(clonedCells);
- }
-
- // 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(TAG, "Error uploading Menu Artworks: " + errors.toString());
- } else {
- DebugTool.logInfo(TAG, "Menu Artworks Uploaded");
- }
- // proceed
- updateMenuAndDetermineBestUpdateMethod();
- }
- });
+ // Suspend the queue if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE
+ private void updateTransactionQueueSuspended() {
+ if (currentHMILevel == HMILevel.HMI_NONE || currentSystemContext == SystemContext.SYSCTXT_MENU) {
+ DebugTool.logInfo(TAG, String.format("Suspending the transaction queue. Current HMI level is: %s, current system context is: %s", currentHMILevel, currentSystemContext));
+ transactionQueue.pause();
} else {
- // No Artworks to be uploaded, send off
- updateMenuAndDetermineBestUpdateMethod();
+ DebugTool.logInfo(TAG, "Starting the transaction queue");
+ transactionQueue.resume();
}
}
- /**
- * 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;
- }
+ public void setDynamicUpdatesMode(@NonNull DynamicMenuUpdatesMode value) {
+ this.dynamicMenuUpdatesMode = value;
}
/**
@@ -266,866 +157,167 @@ abstract class BaseMenuManager extends BaseSubManager {
return this.dynamicMenuUpdatesMode;
}
- // OPEN MENU RPCs
-
/**
- * Opens the Main Menu
- */
- public boolean openMenu() {
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Menu opening is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return false;
- }
-
- ShowAppMenu showAppMenu = new ShowAppMenu();
- showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Open Main Menu Request Successful");
- } else {
- DebugTool.logError(TAG, "Open Main Menu Request Failed");
- }
- }
- });
- internalInterface.sendRPC(showAppMenu);
- return true;
- }
-
- /**
- * Opens a subMenu. The cell you pass in must be constructed with {@link MenuCell(String,SdlArtwork,List)}
- *
- * @param cell - A <Strong>SubMenu</Strong> cell whose sub menu you wish to open
- */
- public boolean openSubMenu(@NonNull MenuCell cell) {
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Sub menu opening is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return false;
- }
-
- if (oldMenuCells == null) {
- DebugTool.logError(TAG, "open sub menu called, but no Menu cells have been set");
- return false;
- }
- // We must see if we have a copy of this cell, since we clone the objects
- for (MenuCell clonedCell : oldMenuCells) {
- if (clonedCell.equals(cell) && clonedCell.getCellId() != MAX_ID) {
- // We've found the correct sub menu cell
- sendOpenSubMenu(clonedCell.getCellId());
- return true;
- }
- }
- return false;
- }
-
- private void sendOpenSubMenu(Integer id) {
-
- ShowAppMenu showAppMenu = new ShowAppMenu();
- showAppMenu.setMenuID(id);
- showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Open Sub Menu Request Successful");
- } else {
- DebugTool.logError(TAG, "Open Sub Menu Request Failed");
- }
- }
- });
-
- internalInterface.sendRPC(showAppMenu);
- }
-
- // MENU CONFIG
-
- /**
- * This method is called via the screen manager to set the menuConfiguration.
- * This will be used when a menu item with sub-cells has a null value for menuConfiguration
+ * Creates and sends all associated Menu RPCs
*
- * @param menuConfiguration - The default menuConfiguration
+ * @param cells - the menu cells that are to be sent to the head unit, including their sub-cells.
*/
- public void setMenuConfiguration(@NonNull final MenuConfiguration menuConfiguration) {
-
- if (sdlMsgVersion == null) {
- DebugTool.logError(TAG, "SDL Message Version is null. Cannot set Menu Configuration");
- return;
- }
-
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Menu configurations is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
- return;
- }
-
- 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.logError(TAG, "Could not set main menu configuration, HMI level: " + currentHMILevel + ", required: 'Not-NONE', system context: " + currentSystemContext + ", required: 'Not MENU'");
- return;
- }
-
- // In the future, when the manager is switched to use queues, the menuConfiguration should be set when SetGlobalProperties response is received
- this.menuConfiguration = menuConfiguration;
-
- if (menuConfiguration.getMenuLayout() != null) {
-
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setMenuLayout(menuConfiguration.getMenuLayout());
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- DebugTool.logInfo(TAG, "Menu Configuration successfully set: " + menuConfiguration.toString());
- } else {
- DebugTool.logError(TAG, "onError: " + response.getResultCode() + " | Info: " + response.getInfo());
- }
- }
- });
- internalInterface.sendRPC(setGlobalProperties);
- } else {
- DebugTool.logInfo(TAG, "Menu Layout is null, not sending setGlobalProperties");
- }
- }
-
- public MenuConfiguration getMenuConfiguration() {
- return this.menuConfiguration;
- }
- // 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(TAG, "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(TAG, "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(TAG, "Creating initial Menu");
- // Set the IDs if needed
- lastMenuId = menuCellIdMin;
- updateIdsOnMenuCells(menuCells, parentIdNotFound);
- this.oldMenuCells = new ArrayList<>(menuCells);
- createAndSendEntireMenu();
- } else {
- DebugTool.logInfo(TAG, "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(TAG, "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, String displayType) {
-
- if (updateMode.equals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE)) {
- if (displayType == null) {
- return true;
- }
- return (!displayType.equals(DisplayType.GEN3_8_INCH.toString()));
-
- } 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(TAG, "Error Sending Current Menu");
- } else {
- DebugTool.logInfo(TAG, "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.size() > 0) {
- DebugTool.logInfo(TAG, "Sending root menu updates");
- sendDynamicRootMenuRPCs(deleteCommands, adds);
- } else {
- DebugTool.logInfo(TAG, "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(TAG, "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) {
+ public void setMenuCells(@NonNull List<MenuCell> cells) {
+ if (cells == null) {
+ DebugTool.logError(TAG, "Cells list is null. Skipping...");
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(TAG, "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(TAG, "Menu Manager has waiting updates, sending now");
- setMenuCells(waitingUpdateMenuCells);
- hasQueuedUpdate = false;
- }
- DebugTool.logInfo(TAG, "All menu updates, including sub menus - done.");
+ if (!menuCellsAreUnique(cells, new ArrayList<String>())) {
+ DebugTool.logError(TAG, "Not all set menu cells are unique, but that is required");
return;
}
- final SubCellCommandList commandList = commandLists.remove(0);
-
- DebugTool.logInfo(TAG, "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);
+ // Create a deep copy of the list so future changes by developers don't affect the algorithm logic
+ this.menuCells = cloneMenuCellsList(cells);
- sendDeleteRPCs(deleteCommands, new CompletionListener() {
+ boolean isDynamicMenuUpdateActive = isDynamicMenuUpdateActive(dynamicMenuUpdatesMode, displayType);
+ Task operation = new MenuReplaceOperation(internalInterface, fileManager.get(), windowCapability, currentMenuConfiguration, currentMenuCells, menuCells, isDynamicMenuUpdateActive, new MenuManagerCompletionListener() {
@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(TAG, "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(TAG, "Finished Sending Dynamic Sub Commands For Root Menu Cell: " + commandList.getMenuTitle());
- createSubMenuDynamicCommands(commandLists);
- }
+ public void onComplete(boolean success, List<MenuCell> currentMenuCells) {
+ BaseMenuManager.this.currentMenuCells = currentMenuCells;
+ updateMenuReplaceOperationsWithNewCurrentMenu();
+ DebugTool.logInfo(TAG, "Finished updating menu");
}
});
- }
-
- // 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(TAG, "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;
- }
- }
+ // Cancel previous MenuReplaceOperations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ task.cancelTask();
}
-
- // 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);
- }
+ transactionQueue.add(operation, false);
}
- // ARTWORKS
-
/**
- * Get an array of artwork that needs to be uploaded from a list of menu cells
- *
- * @param cells List of MenuCell's to check
- * @return List of artwork that needs to be uploaded
- */
- private List<SdlArtwork> findAllArtworksToBeUploadedFromCells(List<MenuCell> cells) {
- // Make sure we can use images in the menus
- if (!hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- return new ArrayList<>();
- }
-
- List<SdlArtwork> artworks = new ArrayList<>();
- for (MenuCell cell : cells) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getIcon())) {
- artworks.add(cell.getIcon());
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworks.add(cell.getSecondaryArtwork());
- }
- } else if ((cell.getSubCells() == null || cell.getSubCells().isEmpty()) && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- if (fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworks.add(cell.getSecondaryArtwork());
- }
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- artworks.addAll(findAllArtworksToBeUploadedFromCells(cell.getSubCells()));
- }
- }
-
- return artworks;
- }
-
- /**
- * Determine if cells should or should not be uploaded to the head unit with artworks.
- *
- * No artworks will be uploaded if:
- *
- * 1. If any cell has a dynamic artwork that is not uploaded
- * 2. If any cell contains a secondary artwork may be used on the head unit, and the cell has a dynamic secondary artwork that is not uploaded
- * 3. If any cell's subcells fail check (1) or (2)
+ * Returns current list of menu cells
*
- * @param cells List of MenuCell's to check
- * @return True if the cells should be uploaded with artwork, false if they should not
+ * @return a List of Currently set menu cells
*/
- private boolean shouldRPCsIncludeImages(List<MenuCell> cells) {
- for (MenuCell cell : cells) {
- SdlArtwork artwork = cell.getIcon();
- SdlArtwork secondaryArtwork = cell.getSecondaryArtwork();
- if (artwork != null && !artwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(artwork)) {
- return false;
- } else if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- if (secondaryArtwork != null && !secondaryArtwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(secondaryArtwork)) {
- return false;
- }
- } else if ((cell.getSubCells() == null || cell.getSubCells().isEmpty()) && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- if (secondaryArtwork != null && !secondaryArtwork.isStaticIcon() && fileManager.get() != null && !fileManager.get().hasUploadedFile(secondaryArtwork)) {
- return false;
- }
- } else if (cell.getSubCells() != null && cell.getSubCells().size() > 0 && !shouldRPCsIncludeImages(cell.getSubCells())) {
- return false;
- }
- }
- return true;
- }
-
- private boolean hasImageFieldOfName(ImageFieldName imageFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, imageFieldName);
- }
-
- private boolean hasTextFieldOfName(TextFieldName textFieldName) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName(defaultMainWindowCapability, textFieldName);
+ public List<MenuCell> getMenuCells() {
+ return menuCells;
}
- // 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);
+ private boolean openMenuPrivate(MenuCell cell) {
+ MenuCell foundClonedCell = null;
- if (mainCell.getSubCells() != null && mainCell.getSubCells().size() > 0) {
- updateIdsOnMenuCells(mainCell.getSubCells(), mainCell.getCellId());
- }
- break;
- }
+ if (cell != null) {
+ // We must see if we have a copy of this cell, since we clone the objects
+ for (MenuCell clonedCell : currentMenuCells) {
+ if (clonedCell.equals(cell) && clonedCell.getCellId() != parentIdNotFound) {
+ // We've found the correct sub menu cell
+ foundClonedCell = clonedCell;
+ 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;
+ if (cell != null && (!cell.isSubMenuCell())) {
+ DebugTool.logError(TAG, String.format("The cell %s does not contain any sub cells, so no submenu can be opened", cell.getTitle()));
+ return false;
+ } else if (cell != null && foundClonedCell == null) {
+ DebugTool.logError(TAG, "This cell has not been sent to the head unit, so no submenu can be opened. Make sure that the cell exists in the SDLManager.menu list");
+ return false;
+ } else if (internalInterface.getSdlMsgVersion().getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "The openSubmenu method is not supported on this head unit.");
+ return false;
}
- return null;
- }
+ // Create the operation
+ MenuShowOperation operation = new MenuShowOperation(internalInterface, foundClonedCell);
- /**
- * Assign cell ids on an array of menu cells given a parent id (or no parent id)
- *
- * @param cells List of menu cells to update
- * @param parentId The parent id to assign if needed
- */
- 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());
+ // Cancel previous open menu operations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuShowOperation) {
+ task.cancelTask();
}
}
- }
- 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;
- }
- }
- }
- }
+ transactionQueue.add(operation, false);
- 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;
- }
- }
- }
+ return true;
}
- // DELETES
-
/**
- * Create an array of DeleteCommand and DeleteSubMenu RPCs from an ArrayList of menu cells
- *
- * @param cells List of menu cells to use
- * @return List of DeleteCommand and DeletedSubmenu RPCs
+ * Opens the Main Menu
*/
- 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;
+ public boolean openMenu() {
+ return openMenuPrivate(null);
}
- // COMMANDS / SUBMENU RPCs
-
/**
- * This method will receive the cells to be added. It will then build an array of add commands using the correct index to position the new items in the correct location.
- * e.g. If the new menu array is [A, B, C, D] but only [C, D] are new we need to pass [A, B , C , D] so C and D can be added to index 2 and 3 respectively.
+ * Opens a subMenu.
*
- * @param cellsToAdd List of MenuCell's that will be added to the menu, this array must contain only cells that are not already in the menu.
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return List of RPCRequest addCommands
+ * @param cell - A <Strong>SubMenu</Strong> cell whose sub menu you wish to open
*/
- 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;
+ public boolean openSubMenu(@NonNull MenuCell cell) {
+ return openMenuPrivate(cell);
}
- /**
- * Creates AddSubMenu RPCs for the passed array of menu cells, AND all of those cells' subcell RPCs, both AddCommands and AddSubMenus
- *
- * @param cells List of MenuCell's to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return An List of RPCs of AddSubMenus and their associated subcell RPCs
- */
- 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;
- }
/**
- * Creates AddCommand and AddSubMenu RPCs for a passed array of cells, AND all of those cells' subcell RPCs, both AddCommands and AddSubmenus
+ * This method is called via the screen manager to set the menuConfiguration.
+ * The menuConfiguration.SubMenuLayout value will be used when a menuCell with sub-cells has a null value for SubMenuLayout
*
- * @param cells List of MenuCell's to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @return An List of RPCs of AddCommand and AddSubMenus for the array of menu cells and their subcells, recursively
+ * @param menuConfiguration - The default menuConfiguration
*/
- 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));
- }
+ public void setMenuConfiguration(@NonNull final MenuConfiguration menuConfiguration) {
+ if (menuConfiguration.equals(this.menuConfiguration)) {
+ DebugTool.logInfo(TAG, "New menu configuration is equal to existing one, will not set new configuration");
+ return;
}
- 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;
+ this.menuConfiguration = menuConfiguration;
+
+ MenuConfigurationUpdateOperation operation = new MenuConfigurationUpdateOperation(internalInterface, windowCapability, menuConfiguration, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success) {
+ DebugTool.logError(TAG, "Error updating menu configuration.");
+ return;
}
+ BaseMenuManager.this.currentMenuConfiguration = menuConfiguration;
+ updateMenuReplaceOperationsWithNewMenuConfiguration();
}
- }
- return builtCommands;
- }
-
- /**
- * An individual AddCommand RPC for a given MenuCell
- *
- * @param cell MenuCell to create RPCs for
- * @param shouldHaveArtwork Boolean that indicates whether artwork should be applied to the RPC or not
- * @param position The position the AddCommand RPC should be given
- * @return The AddCommand RPC
- */
- private AddCommand commandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position) {
-
- MenuParams params = new MenuParams(cell.getUniqueTitle());
- params.setSecondaryText((cell.getSecondaryText() != null && cell.getSecondaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuCommandSecondaryText)) ? cell.getSecondaryText() : null);
- params.setTertiaryText((cell.getTertiaryText() != null && cell.getTertiaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuCommandTertiaryText)) ? cell.getTertiaryText() : null);
- params.setParentID(cell.getParentCellId() != MAX_ID ? cell.getParentCellId() : null);
- params.setPosition(position);
+ });
- AddCommand command = new AddCommand(cell.getCellId());
- command.setMenuParams(params);
- if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
- command.setVrCommands(cell.getVoiceCommands());
- } else {
- command.setVrCommands(null);
+ // Cancel previous menu configuration operations
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuConfigurationUpdateOperation) {
+ task.cancelTask();
+ }
}
- command.setCmdIcon((cell.getIcon() != null && shouldHaveArtwork) ? cell.getIcon().getImageRPC() : null);
- command.setSecondaryImage((cell.getSecondaryArtwork() != null && shouldHaveArtwork && hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage) && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork()))) ? cell.getSecondaryArtwork().getImageRPC() : null);
- return command;
+ transactionQueue.add(operation, false);
}
- /**
- * An individual AddSubMenu RPC for a given MenuCell
- *
- * @param cell The cell to create the RPC for
- * @param shouldHaveArtwork Whether artwork should be applied to the RPC
- * @param position The position the AddSubMenu RPC should be given
- * @return The AddSubMenu RPC
- */
- private AddSubMenu subMenuCommandForMenuCell(MenuCell cell, boolean shouldHaveArtwork, int position) {
- AddSubMenu subMenu = new AddSubMenu(cell.getCellId(), cell.getUniqueTitle());
- subMenu.setSecondaryText((cell.getSecondaryText() != null && cell.getSecondaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuSubMenuSecondaryText)) ? cell.getSecondaryText() : null);
- subMenu.setTertiaryText((cell.getTertiaryText() != null && cell.getTertiaryText().length() > 0 && hasTextFieldOfName(TextFieldName.menuSubMenuTertiaryText)) ? cell.getTertiaryText() : null);
- subMenu.setPosition(position);
- if (cell.getSubMenuLayout() != null) {
- subMenu.setMenuLayout(cell.getSubMenuLayout());
- } else if (menuConfiguration != null && menuConfiguration.getSubMenuLayout() != null) {
- subMenu.setMenuLayout(menuConfiguration.getSubMenuLayout());
- }
- subMenu.setMenuIcon((shouldHaveArtwork && (cell.getIcon() != null && cell.getIcon().getImageRPC() != null)) ? cell.getIcon().getImageRPC() : null);
- subMenu.setSecondaryImage((shouldHaveArtwork && hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage) && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) && (cell.getSecondaryArtwork() != null && cell.getSecondaryArtwork().getImageRPC() != null)) ? cell.getSecondaryArtwork().getImageRPC() : null);
- return subMenu;
- }
-
- // CELL COMMAND HANDLING
-
-
- /**
- * Call a listener for a currently displayed MenuCell based on the incoming OnCommand notification
- *
- * @param cells List of MenuCell's to check (including their subcells)
- * @param command OnCommand notification retrieved
- * @return True if the handler was found, false if it was not found
- */
- 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;
+ public MenuConfiguration getMenuConfiguration() {
+ return this.menuConfiguration;
}
- // LISTENERS
-
private void addListeners() {
- // DISPLAY CAPABILITIES - via SCM
onDisplaysCapabilityListener = new OnSystemCapabilityListener() {
@Override
public void onCapabilityRetrieved(Object capability) {
// instead of using the parameter it's more safe to use the convenience method
List<DisplayCapability> capabilities = SystemCapabilityManager.convertToList(capability, DisplayCapability.class);
if (capabilities == null || capabilities.size() == 0) {
- DebugTool.logError(TAG, "SoftButton Manager - Capabilities sent here are null or empty");
+ DebugTool.logError(TAG, "Capabilities sent here are null or empty");
} else {
DisplayCapability display = capabilities.get(0);
displayType = display.getDisplayName();
for (WindowCapability windowCapability : display.getWindowCapabilities()) {
int currentWindowID = windowCapability.getWindowID() != null ? windowCapability.getWindowID() : PredefinedWindows.DEFAULT_WINDOW.getValue();
if (currentWindowID == PredefinedWindows.DEFAULT_WINDOW.getValue()) {
- defaultMainWindowCapability = windowCapability;
+ BaseMenuManager.this.windowCapability = windowCapability;
+ updateMenuReplaceOperationsWithNewWindowCapability();
}
}
}
@@ -1134,14 +326,13 @@ abstract class BaseMenuManager extends BaseSubManager {
@Override
public void onError(String info) {
DebugTool.logError(TAG, "Display Capability cannot be retrieved");
- defaultMainWindowCapability = null;
+ windowCapability = null;
}
};
if (internalInterface.getSystemCapabilityManager() != null) {
this.internalInterface.getSystemCapabilityManager().addOnSystemCapabilityListener(SystemCapabilityType.DISPLAYS, onDisplaysCapabilityListener);
}
- // HMI UPDATES
hmiListener = new OnRPCNotificationListener() {
@Override
public void onNotified(RPCNotification notification) {
@@ -1149,396 +340,82 @@ abstract class BaseMenuManager extends BaseSubManager {
if (onHMIStatus.getWindowID() != null && onHMIStatus.getWindowID() != PredefinedWindows.DEFAULT_WINDOW.getValue()) {
return;
}
- HMILevel oldHMILevel = currentHMILevel;
currentHMILevel = onHMIStatus.getHmiLevel();
-
- // Auto-send an updated menu if we were in NONE and now we are not, and we need an update
- if (oldHMILevel == HMILevel.HMI_NONE && currentHMILevel != HMILevel.HMI_NONE && currentSystemContext != SystemContext.SYSCTXT_MENU) {
- if (waitingOnHMIUpdate) {
- DebugTool.logInfo(TAG, "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 = onHMIStatus.getSystemContext();
-
- if (oldContext == SystemContext.SYSCTXT_MENU && currentSystemContext != SystemContext.SYSCTXT_MENU && currentHMILevel != HMILevel.HMI_NONE) {
- if (waitingOnHMIUpdate) {
- DebugTool.logInfo(TAG, "We now have a proper system context, sending waiting update");
- setMenuCells(waitingUpdateMenuCells);
- waitingUpdateMenuCells.clear();
- }
- }
+ updateTransactionQueueSuspended();
}
};
internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
- // COMMANDS
commandListener = new OnRPCNotificationListener() {
@Override
public void onNotified(RPCNotification notification) {
OnCommand onCommand = (OnCommand) notification;
- callListenerForCells(menuCells, onCommand);
+ callListenerForCells(currentMenuCells, 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(TAG, "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(TAG, "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(TAG, "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 (!shouldRPCsIncludeImages(menu) || !hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- // Send artwork-less menu
- mainMenuCommands = mainMenuCommandsForCells(menu, false);
- subMenuCommands = subMenuCommandsForCells(menu, false);
- } else {
- mainMenuCommands = mainMenuCommandsForCells(menu, true);
- subMenuCommands = subMenuCommandsForCells(menu, true);
+ private boolean callListenerForCells(List<MenuCell> cells, OnCommand command) {
+ if (cells == null || cells.isEmpty() || command == null) {
+ return false;
}
- // 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(TAG, "Finished sending main menu commands. Sending sub menu commands.");
- } else {
-
- if (keepsNew != null && keepsNew.size() > 0) {
- runSubMenuCompareAlgorithm();
- } else {
- inProgressUpdate = null;
- DebugTool.logInfo(TAG, "Finished sending main menu commands.");
- }
- }
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Main Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo());
- }
- }
- });
- }
-
- 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(TAG, "Finished Updating Menu");
- inProgressUpdate = null;
-
- if (listener != null) {
- listener.onComplete(true);
- }
- }
+ for (MenuCell cell : cells) {
+ if (cell.getCellId() == command.getCmdID() && cell.getMenuSelectionListener() != null) {
+ cell.getMenuSelectionListener().onTriggered(command.getTriggerSource());
+ return true;
}
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Sub Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Failed to send sub menu commands: " + response.getInfo());
- if (listener != null) {
- listener.onComplete(false);
- }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ // for each cell, if it has sub cells, recursively loop through those as well
+ boolean success = callListenerForCells(cell.getSubCells(), command);
+ if (success) {
+ return true;
}
}
- });
- }
-
- 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(TAG, "Called createAndSendDynamicSubMenuRPCs with empty menu");
- listener.onComplete(true);
- }
- return;
}
- List<RPCRequest> mainMenuCommands;
-
- if (!shouldRPCsIncludeImages(adds) || !hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- // 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 onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- try {
- DebugTool.logInfo(TAG, "Dynamic Sub Menu response: " + response.serializeJSON().toString());
- } catch (JSONException e) {
- DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e);
- }
- } else {
- DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo());
- }
- }
- });
+ return false;
}
- // 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(TAG, "No old cells to delete, returning");
- listener.onComplete(true);
+ private void updateMenuReplaceOperationsWithNewCurrentMenu() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setCurrentMenu(this.currentMenuCells);
}
- } 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(TAG, "No old cells to delete, returning");
- listener.onComplete(true);
+ private void updateMenuReplaceOperationsWithNewWindowCapability() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setWindowCapability(this.windowCapability);
}
- 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.sendRPCs(deleteCommands, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
- DebugTool.logInfo(TAG, "Successfully deleted cells");
- if (listener != null) {
- listener.onComplete(true);
- }
- }
-
- @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;
}
- private void addUniqueNamesToCellsWithDuplicatePrimaryText(List<MenuCell> cells) {
- HashMap<String, Integer> dictCounter = new HashMap<>();
-
- for (MenuCell cell : cells) {
- String cellName = cell.getTitle();
- Integer counter = dictCounter.get(cellName);
-
- if (counter != null) {
- dictCounter.put(cellName, ++counter);
- cell.setUniqueTitle(cellName + " (" + counter + ")");
- } else {
- dictCounter.put(cellName, 1);
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- addUniqueNamesToCellsWithDuplicatePrimaryText(cell.getSubCells());
+ private void updateMenuReplaceOperationsWithNewMenuConfiguration() {
+ for (Task task : transactionQueue.getTasksAsList()) {
+ if (task instanceof MenuReplaceOperation) {
+ ((MenuReplaceOperation) task).setMenuConfiguration(currentMenuConfiguration);
}
}
}
- void addUniqueNamesBasedOnStrippedCells(List<MenuCell> strippedCells, List<MenuCell> unstrippedCells) {
- if (strippedCells == null || unstrippedCells == null || strippedCells.size() != unstrippedCells.size()) {
- return;
- }
- // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
- HashMap<MenuCell, Integer> dictCounter = new HashMap<>();
- for (int i = 0; i < strippedCells.size(); i++) {
- MenuCell cell = strippedCells.get(i);
- Integer counter = dictCounter.get(cell);
- if (counter != null) {
- counter = counter + 1;
- dictCounter.put(cell, counter);
- } else {
- dictCounter.put(cell, 1);
- }
- counter = dictCounter.get(cell);
- if (counter > 1) {
- unstrippedCells.get(i).setUniqueTitle(unstrippedCells.get(i).getTitle() + " (" + counter + ")");
- }
-
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
- addUniqueNamesBasedOnStrippedCells(cell.getSubCells(), unstrippedCells.get(i).getSubCells());
+ private boolean isDynamicMenuUpdateActive(DynamicMenuUpdatesMode updateMode, String displayType) {
+ if (updateMode.equals(DynamicMenuUpdatesMode.ON_WITH_COMPAT_MODE)) {
+ if (displayType == null) {
+ return true;
}
-
- }
-
-
- }
-
- List<MenuCell> removeUnusedProperties(List<MenuCell> menuCells) {
- if (menuCells == null) {
- return null;
+ return (!displayType.equals(DisplayType.GEN3_8_INCH.toString()));
+ } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_OFF)) {
+ return false;
+ } else if (updateMode.equals(DynamicMenuUpdatesMode.FORCE_ON)) {
+ return true;
}
- List<MenuCell> removePropertiesClone = cloneMenuCellsList(menuCells);
- for (MenuCell cell : removePropertiesClone) {
- // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
- cell.setVoiceCommands(null);
- // Don't check ImageFieldName.subMenuIcon because it was added in 7.0 when the feature was added in 5.0.
- // Just assume that if cmdIcon is not available, the submenu icon is not either.
- if (!hasImageFieldOfName(ImageFieldName.cmdIcon)) {
- cell.setIcon(null);
- }
- // Check for subMenu fields supported
- if (cell.getSubCells() != null) {
- if (!hasTextFieldOfName(TextFieldName.menuSubMenuSecondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.menuSubMenuTertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.menuSubMenuSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- cell.setSubCells(removeUnusedProperties(cell.getSubCells()));
- } else {
- if (!hasTextFieldOfName(TextFieldName.menuCommandSecondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.menuCommandTertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.menuCommandSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- }
- }
- return removePropertiesClone;
+ return true;
}
/**
@@ -1549,14 +426,14 @@ abstract class BaseMenuManager extends BaseSubManager {
* @return Boolean that indicates whether menuCells are unique or not
*/
private boolean menuCellsAreUnique(List<MenuCell> cells, ArrayList<String> allVoiceCommands) {
- //Check all voice commands for identical items and check each list of cells for identical cells
+ // Check all voice commands for identical items and check each list of cells for identical cells
HashSet<MenuCell> identicalCellsCheckSet = new HashSet<>();
for (MenuCell cell : cells) {
identicalCellsCheckSet.add(cell);
- // Recursively check the subcell lists to see if they are all unique as well. If anything is not, this will chain back up the list to return false.
- if (cell.getSubCells() != null && cell.getSubCells().size() > 0) {
+ // Recursively check the sub-cell lists to see if they are all unique as well. If anything is not, this will chain back up the list to return false.
+ if (cell.isSubMenuCell() && cell.getSubCells().size() > 0) {
boolean subCellsAreUnique = menuCellsAreUnique(cell.getSubCells(), allVoiceCommands);
if (!subCellsAreUnique) {
@@ -1566,16 +443,14 @@ abstract class BaseMenuManager extends BaseSubManager {
}
// Voice commands have to be identical across all lists
- if (cell.getVoiceCommands() == null) {
- continue;
+ if (cell.getVoiceCommands() != null) {
+ allVoiceCommands.addAll(cell.getVoiceCommands());
}
- allVoiceCommands.addAll(cell.getVoiceCommands());
}
-
// Check for duplicate cells
if (identicalCellsCheckSet.size() != cells.size()) {
- DebugTool.logError(TAG, "Not all cells are unique. The menu will not be set.");
+ DebugTool.logError(TAG, "Not all cells are unique. Cells in each list (such as main menu or sub cell list) must have some differentiating property other than the sub cells within a cell. The menu will not be set.");
return false;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java
new file mode 100644
index 000000000..b6fa0266c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateAlgorithm.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2021 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.ArrayList;
+import java.util.List;
+
+class DynamicMenuUpdateAlgorithm {
+ // Cell state that tells the menu manager what it should do with a given MenuCell
+ enum MenuCellState {
+ DELETE, // Marks the cell to be deleted
+ ADD, // Marks the cell to be added
+ KEEP // Marks the cell to be kept
+ }
+
+ static DynamicMenuUpdateRunScore compatibilityRunScoreWithOldMenuCells(List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ return new DynamicMenuUpdateRunScore(buildAllDeleteStatusesForMenu(oldMenuCells), buildAllAddStatusesForMenu(updatedMenuCells), updatedMenuCells.size());
+ }
+
+ static DynamicMenuUpdateRunScore dynamicRunScoreOldMenuCells(List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ if (!oldMenuCells.isEmpty() && updatedMenuCells.isEmpty()) {
+ // Deleting all cells
+ return new DynamicMenuUpdateRunScore(buildAllDeleteStatusesForMenu(oldMenuCells), new ArrayList<MenuCellState>(), 0);
+ } else if (oldMenuCells.isEmpty() && !updatedMenuCells.isEmpty()) {
+ // No cells to delete
+ return new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), buildAllAddStatusesForMenu(updatedMenuCells), updatedMenuCells.size());
+ } else if (oldMenuCells.isEmpty() && updatedMenuCells.isEmpty()) {
+ // Empty menu to empty menu
+ return new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), new ArrayList<MenuCellState>(), 0);
+ }
+ return startCompareAtRun(0, oldMenuCells, updatedMenuCells);
+ }
+
+ private static DynamicMenuUpdateRunScore startCompareAtRun(int startRun, List<MenuCell> oldMenuCells, List<MenuCell> updatedMenuCells) {
+ DynamicMenuUpdateRunScore bestScore = new DynamicMenuUpdateRunScore(new ArrayList<MenuCellState>(), new ArrayList<MenuCellState>(), 0);
+
+ for (int run = startRun; run < oldMenuCells.size(); run++) {
+ // Set the menu status as a 1-1 list, start off will oldMenus = all Deletes, newMenu = all Adds
+ List<MenuCellState> oldMenuStatus = buildAllDeleteStatusesForMenu(oldMenuCells);
+ List<MenuCellState> newMenuStatus = buildAllAddStatusesForMenu(updatedMenuCells);
+
+ int startIndex = 0;
+ for (int oldCellIndex = run; oldCellIndex < oldMenuCells.size(); oldCellIndex++) {
+ // For each old item, create inner loop to compare old cells to new cells to find a match
+ // if a match if found we mark the index at match for both the old and the new status to
+ // keep since we do not want to send RPCs for those cases
+ for (int newCellIndex = startIndex; newCellIndex < updatedMenuCells.size(); newCellIndex++) {
+ if (oldMenuCells.get(oldCellIndex).equalsWithUniqueTitle(updatedMenuCells.get(newCellIndex))) {
+ oldMenuStatus.set(oldCellIndex, MenuCellState.KEEP);
+ newMenuStatus.set(newCellIndex, MenuCellState.KEEP);
+ startIndex = newCellIndex + 1;
+ break;
+ }
+ }
+ }
+
+ // Add RPC are the biggest operation so we need to find the run with the least amount of Adds.
+ // We will reset the run we use each time a runScore is less than the current score.
+ int numberOfAdds = 0;
+ for (int status = 0; status < newMenuStatus.size(); status++) {
+ if (newMenuStatus.get(status).equals(MenuCellState.ADD)) {
+ numberOfAdds++;
+ }
+ }
+
+ // As soon as we a run that requires 0 Adds we will use it since we cant do better then 0
+ if (numberOfAdds == 0) {
+ bestScore = new DynamicMenuUpdateRunScore(oldMenuStatus, newMenuStatus, numberOfAdds);
+ return bestScore;
+ }
+
+ // if we haven't create the bestScore object or if the current score beats the old score then we will create a new bestScore
+ if (bestScore.isEmpty() || numberOfAdds < bestScore.getScore()) {
+ bestScore = new DynamicMenuUpdateRunScore(oldMenuStatus, newMenuStatus, numberOfAdds);
+ }
+
+ }
+ return bestScore;
+ }
+
+ /**
+ * Builds a 1-1 list of Deletes for every element in the array
+ * @param oldMenu The old menu list
+ */
+ static List<MenuCellState> buildAllDeleteStatusesForMenu (List<MenuCell> oldMenu){
+ List<MenuCellState> oldMenuStatus = new ArrayList<>(oldMenu.size());
+ for (int index = 0; index < oldMenu.size(); index++) {
+ oldMenuStatus.add(MenuCellState.DELETE);
+ }
+ return oldMenuStatus;
+ }
+
+ /**
+ * Builds a 1-1 list of Adds for every element in the list
+ * @param newMenu The new menu list
+ */
+ static List<MenuCellState> buildAllAddStatusesForMenu (List<MenuCell> newMenu){
+ List<MenuCellState> newMenuStatus = new ArrayList<>(newMenu.size());
+ for (int index = 0; index < newMenu.size(); index++) {
+ newMenuStatus.add(MenuCellState.ADD);
+ }
+ return newMenuStatus;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java
index 604d52b09..892fd1a8d 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/SubCellCommandList.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/DynamicMenuUpdateRunScore.java
@@ -32,60 +32,46 @@
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);
- }
+import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState;
- private void setParentId(Integer parentId) {
- this.parentId = parentId;
- }
+import java.util.List;
- Integer getParentId() {
- return parentId;
- }
+class DynamicMenuUpdateRunScore {
+ private List<MenuCellState> oldStatus; // Will contain all the Deletes and Keeps
+ private List<MenuCellState> updatedStatus; // Will contain all the Adds and Keeps
+ private int score; // Will contain the score, number of total Adds that will need to be created
- private void setMenuTitle(String menuTitle) {
- this.menuTitle = menuTitle;
+ DynamicMenuUpdateRunScore(List<MenuCellState> oldStatus, List<MenuCellState> updatedStatus, int score) {
+ setOldStatus(oldStatus);
+ setUpdatedStatus(updatedStatus);
+ setScore(score);
}
- String getMenuTitle() {
- return menuTitle;
+ private void setUpdatedStatus(List<MenuCellState> updatedStatus) {
+ this.updatedStatus = updatedStatus;
}
- private void setListsScore(RunScore listsScore) {
- this.listsScore = listsScore;
+ List<MenuCellState> getUpdatedStatus() {
+ return updatedStatus;
}
- RunScore getListsScore() {
- return listsScore;
+ private void setOldStatus(List<MenuCellState> oldStatus) {
+ this.oldStatus = oldStatus;
}
- private void setOldList(List<MenuCell> oldList) {
- this.oldList = oldList;
+ List<MenuCellState> getOldStatus() {
+ return oldStatus;
}
- List<MenuCell> getOldList() {
- return oldList;
+ private void setScore(int score) {
+ this.score = score;
}
- private void setNewList(List<MenuCell> newList) {
- this.newList = newList;
+ public int getScore() {
+ return score;
}
- List<MenuCell> getNewList() {
- return newList;
+ boolean isEmpty() {
+ return oldStatus.size() == 0 && updatedStatus.size() == 0 && score == 0;
}
}
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
index 652891181..eaf91ae76 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuCell.java
@@ -432,6 +432,10 @@ public class MenuCell implements Cloneable {
// HELPER
+ boolean isSubMenuCell() {
+ return getSubCells() != null;
+ }
+
/**
* Note: You should compare using the {@link #equals(Object)} method. <br>
* Hash the parameters of the object and return the result for comparison
@@ -446,17 +450,24 @@ public class MenuCell implements Cloneable {
public int hashCode() {
int result = 1;
result += ((getTitle() == null) ? 0 : Integer.rotateLeft(getTitle().hashCode(), 1));
- result += ((getIcon() == null) ? 0 : Integer.rotateLeft(getIcon().hashCode(), 2));
+ 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));
- result += ((getSecondaryText() == null) ? 0 : Integer.rotateLeft(getSecondaryText().hashCode(), 1));
- result += ((getTertiaryText() == null) ? 0 : Integer.rotateLeft(getTertiaryText().hashCode(), 1));
- result += ((getSecondaryArtwork() == null) ? 0 : Integer.rotateLeft(getSecondaryArtwork().hashCode(), 2));
+ result += ((getSecondaryText() == null) ? 0 : Integer.rotateLeft(getSecondaryText().hashCode(), 5));
+ result += ((getTertiaryText() == null) ? 0 : Integer.rotateLeft(getTertiaryText().hashCode(), 6));
+ result += ((getSecondaryArtwork() == null || getSecondaryArtwork().getName() == null) ? 0 : Integer.rotateLeft(getSecondaryArtwork().getName().hashCode(), 7));
+ result += ((getSubMenuLayout() == null) ? 0 : Integer.rotateLeft(getSubMenuLayout().hashCode(), 8));
+ return result;
+ }
+
+ private int hashCodeWithUniqueTitle() {
+ int result = hashCode();
+ result += ((getUniqueTitle() == null) ? 0 : Integer.rotateLeft(getUniqueTitle().hashCode(), 9));
return result;
}
/**
- * Uses our custom hashCode for MenuCell objects, but does <strong>NOT</strong> compare the listener objects
+ * Uses our custom hashCode for MenuCell objects
*
* @param o - The object to compare
* @return boolean of whether the objects are the same or not
@@ -475,6 +486,22 @@ public class MenuCell implements Cloneable {
}
/**
+ * Uses our custom hashCode for MenuCell objects. This method takes UniqueTitle into consideration when doing the equality check
+ *
+ * @param o - The object to compare
+ * @return boolean of whether the objects are the same or not
+ */
+ boolean equalsWithUniqueTitle(MenuCell o) {
+ if (o == null) {
+ return false;
+ }
+ // if this is the same memory address, its the same
+ if (this == o) return true;
+ // if we get to this point, create the hashes and compare them
+ return hashCodeWithUniqueTitle() == o.hashCodeWithUniqueTitle();
+ }
+
+ /**
* Creates a deep copy of the object
*
* @return deep copy of the object, null if an exception occurred
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
index f478c80fc..9ae1ae28d 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfiguration.java
@@ -101,4 +101,36 @@ public class MenuConfiguration {
return "MenuConfiguration: MenuLayout = " + this.mainMenuLayout + " | SubMenuLayout = " + this.submenuLayout;
}
+ /**
+ * Note: You should compare using the {@link #equals(Object)} method. <br>
+ * Hash the parameters of the object and return the result for comparison
+ * @return the hash code as an int
+ */
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result += ((getMenuLayout() == null) ? 0 : Integer.rotateLeft(getMenuLayout().hashCode(), 1));
+ result += ((getSubMenuLayout() == null) ? 0 : Integer.rotateLeft(getSubMenuLayout().hashCode(), 2));
+ return result;
+ }
+
+ /**
+ * Uses our custom hashCode for MenuConfiguration 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 (o == null) {
+ return false;
+ }
+ // 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 MenuConfiguration)) return false;
+ // if we get to this point, create the hashes and compare them
+ return hashCode() == o.hashCode();
+ }
+
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java
new file mode 100644
index 000000000..430509bea
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuConfigurationUpdateOperation.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2021 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.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+class MenuConfigurationUpdateOperation extends Task {
+ private static final String TAG = "MenuConfigurationUpdateOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final List<MenuLayout> availableMenuLayouts;
+ private final MenuConfiguration updatedMenuConfiguration;
+ private final CompletionListener completionListener;
+
+ MenuConfigurationUpdateOperation(ISdl internalInterface, WindowCapability windowCapability, MenuConfiguration menuConfiguration, CompletionListener completionListener) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.availableMenuLayouts = windowCapability != null ? windowCapability.getMenuLayoutsAvailable() : null;
+ this.updatedMenuConfiguration = menuConfiguration;
+ this.completionListener = completionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ sendSetGlobalProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ }
+ });
+ }
+
+ private void sendSetGlobalProperties(final CompletionListener listener) {
+ if (internalInterface.get() == null) {
+ listener.onComplete(false);
+ return;
+ }
+
+ SdlMsgVersion sdlMsgVersion = internalInterface.get().getSdlMsgVersion();
+ if (sdlMsgVersion == null) {
+ DebugTool.logError(TAG, "SDL Message Version is null. Cannot set Menu Configuration");
+ listener.onComplete(false);
+ return;
+ }
+
+ if (sdlMsgVersion.getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "Menu configurations is only supported on head units with RPC spec version 6.0.0 or later. Currently connected head unit RPC spec version is: " + sdlMsgVersion.getMajorVersion() + "." + sdlMsgVersion.getMinorVersion() + "." + sdlMsgVersion.getPatchVersion());
+ listener.onComplete(false);
+ return;
+ }
+
+ if (updatedMenuConfiguration.getMenuLayout() == null) {
+ DebugTool.logInfo(TAG, "Menu Layout is null, not sending setGlobalProperties");
+ listener.onComplete(false);
+ return;
+ }
+
+ if (availableMenuLayouts == null) {
+ DebugTool.logWarning(TAG, "Could not set the main menu configuration. Which menu layouts can be used is not available");
+ listener.onComplete(false);
+ return;
+ } else if (!availableMenuLayouts.contains(updatedMenuConfiguration.getMenuLayout()) || !availableMenuLayouts.contains(updatedMenuConfiguration.getSubMenuLayout())) {
+ DebugTool.logError(TAG, String.format("One or more of the set menu layouts are not available on this system. The menu configuration will not be set. Available menu layouts: %s, set menu layouts: %s", availableMenuLayouts, updatedMenuConfiguration));
+ listener.onComplete(false);
+ return;
+ }
+
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setMenuLayout(updatedMenuConfiguration.getMenuLayout());
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Menu Configuration successfully set: " + updatedMenuConfiguration.toString());
+ } else {
+ DebugTool.logError(TAG, "onError: " + response.getResultCode() + " | Info: " + response.getInfo());
+ }
+ listener.onComplete(response.getSuccess());
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ }
+ }
+
+ private void finishOperation(boolean success) {
+ if (completionListener != null) {
+ completionListener.onComplete(success);
+ }
+ onFinished();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java
index 6d031bb0e..584fa90ca 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/RunScore.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuManagerCompletionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Livio, Inc.
+ * Copyright (c) 2021 Livio, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -34,39 +34,9 @@ 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;
- }
-
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+interface MenuManagerCompletionListener {
+ void onComplete(boolean success, List<MenuCell> currentMenuCells);
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java
new file mode 100644
index 000000000..7db1a85b9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceOperation.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 2021 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 static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import static com.smartdevicelink.managers.screen.menu.BaseMenuManager.parentIdNotFound;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.addCellWithCellId;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.addIdsToMenuCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.cloneMenuCellsList;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.commandIdForRPCRequest;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.deleteCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.findAllArtworksToBeUploadedFromCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.mainMenuCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.positionForRPCRequest;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.removeCellFromList;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.sendRPCs;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.subMenuCommandsForCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.transferCellIDsFromCells;
+import static com.smartdevicelink.managers.screen.menu.MenuReplaceUtilities.transferCellListenersFromCells;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.managers.screen.menu.DynamicMenuUpdateAlgorithm.MenuCellState;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
+import com.smartdevicelink.util.DebugTool;
+
+import org.json.JSONException;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Bilal Alsharifi on 1/20/21.
+ */
+class MenuReplaceOperation extends Task {
+ private static final String TAG = "MenuReplaceOperation";
+
+ private final WeakReference<ISdl> internalInterface;
+ private final WeakReference<FileManager> fileManager;
+ private WindowCapability windowCapability;
+ private List<MenuCell> currentMenu;
+ private final List<MenuCell> updatedMenu;
+ private final boolean isDynamicMenuUpdateActive;
+ private final MenuManagerCompletionListener operationCompletionListener;
+ private MenuConfiguration menuConfiguration;
+
+ MenuReplaceOperation(ISdl internalInterface, FileManager fileManager, WindowCapability windowCapability, MenuConfiguration menuConfiguration, List<MenuCell> currentMenu, List<MenuCell> updatedMenu, boolean isDynamicMenuUpdateActive, MenuManagerCompletionListener operationCompletionListener) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.windowCapability = windowCapability;
+ this.menuConfiguration = menuConfiguration;
+ this.currentMenu = currentMenu;
+ this.updatedMenu = updatedMenu;
+ this.isDynamicMenuUpdateActive = isDynamicMenuUpdateActive;
+ this.operationCompletionListener = operationCompletionListener;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ updateMenuCells(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ }
+ });
+ }
+
+ private void updateMenuCells(final CompletionListener listener) {
+ addIdsToMenuCells(updatedMenu, parentIdNotFound);
+
+ // Strip the "current menu" and the new menu of properties that are not displayed on the head unit
+ List<MenuCell> updatedStrippedMenu = cellsWithRemovedPropertiesFromCells(updatedMenu, windowCapability);
+ List<MenuCell> currentStrippedMenu = cellsWithRemovedPropertiesFromCells(currentMenu, windowCapability);
+
+ // Check if head unit supports cells with duplicate titles
+ SdlMsgVersion rpcVersion = internalInterface.get().getSdlMsgVersion();
+ boolean supportsMenuUniqueness = rpcVersion.getMajorVersion() > 7 || (rpcVersion.getMajorVersion() == 7 && rpcVersion.getMinorVersion() > 0);
+
+ // Generate unique names and ensure that all menus we are tracking have them so that we can properly compare when using the dynamic algorithm
+ generateUniqueNamesForCells(updatedStrippedMenu, supportsMenuUniqueness);
+ applyUniqueNamesOnCells(updatedStrippedMenu, updatedMenu);
+
+ DynamicMenuUpdateRunScore runScore;
+ if (!isDynamicMenuUpdateActive) {
+ DebugTool.logInfo(TAG, "Dynamic menu update inactive. Forcing the deletion of all old cells and adding all new ones, even if they're the same.");
+ runScore = DynamicMenuUpdateAlgorithm.compatibilityRunScoreWithOldMenuCells(currentStrippedMenu, updatedStrippedMenu);
+ } else {
+ DebugTool.logInfo(TAG, "Dynamic menu update active. Running the algorithm to find the best way to delete / add cells.");
+ runScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(currentStrippedMenu, updatedStrippedMenu);
+ }
+
+ // If both old and new menu cells are empty, nothing needs to be done.
+ if (runScore.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ List<MenuCellState> deleteMenuStatus = runScore.getOldStatus();
+ List<MenuCellState> addMenuStatus = runScore.getUpdatedStatus();
+
+ // Drop the cells into buckets based on the run score
+ final List<MenuCell> cellsToDelete = filterMenuCellsWithStatusList(currentMenu, deleteMenuStatus, MenuCellState.DELETE);
+ final List<MenuCell> cellsToAdd = filterMenuCellsWithStatusList(updatedMenu, addMenuStatus, MenuCellState.ADD);
+
+ // These lists should ONLY contain KEEPS. These will be used for SubMenu compares
+ final List<MenuCell> oldKeeps = filterMenuCellsWithStatusList(currentMenu, deleteMenuStatus, MenuCellState.KEEP);
+ final List<MenuCell> newKeeps = filterMenuCellsWithStatusList(updatedMenu, addMenuStatus, MenuCellState.KEEP);
+
+ // Old kept cells ids need to be moved to the new kept cells so that submenu changes have correct parent ids
+ transferCellIDsFromCells(oldKeeps, newKeeps);
+
+ // Transfer new cells' listeners to the old cells, which are stored in the current menu
+ transferCellListenersFromCells(newKeeps, oldKeeps);
+
+ // Upload the Artworks, then we will start updating the main menu
+ uploadMenuArtworks(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ return;
+ }
+
+ updateMenuWithCellsToDelete(cellsToDelete, cellsToAdd, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ return;
+ }
+
+ updateSubMenuWithOldKeptCells(oldKeeps, newKeeps, 0, listener);
+ }
+ });
+ }
+ });
+ }
+
+ private void uploadMenuArtworks(final CompletionListener listener) {
+ List<SdlArtwork> artworksToBeUploaded = new ArrayList<>(findAllArtworksToBeUploadedFromCells(updatedMenu, fileManager.get(), windowCapability));
+ if (artworksToBeUploaded.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ if (fileManager.get() == null) {
+ listener.onComplete(false);
+ return;
+ }
+
+ fileManager.get().uploadArtworks(artworksToBeUploaded, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && !errors.isEmpty()) {
+ DebugTool.logError(TAG, "Error uploading Menu Artworks: " + errors.toString());
+ listener.onComplete(false);
+ } else {
+ DebugTool.logInfo(TAG, "Menu artwork upload completed, beginning upload of main menu");
+ listener.onComplete(true);
+ }
+ }
+ });
+ }
+
+ /**
+ * Takes the main menu cells to delete and add, and deletes the current menu cells, then adds the new menu cells in the correct locations
+ *
+ * @param deleteCells The cells that need to be deleted
+ * @param addCells The cells that need to be added
+ * @param listener A CompletionListener called when complete
+ */
+ private void updateMenuWithCellsToDelete(List<MenuCell> deleteCells, final List<MenuCell> addCells, final CompletionListener listener) {
+ sendDeleteMenuCells(deleteCells, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ sendAddMenuCells(addCells, updatedMenu, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success) {
+ DebugTool.logError(TAG, "Error Sending Current Menu");
+ }
+
+ listener.onComplete(success);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Takes the submenu cells that are old keeps and new keeps and determines which cells need to be deleted or added
+ *
+ * @param oldKeptCells The old kept cells
+ * @param newKeptCells The new kept cells
+ * @param index The index of the main menu to use
+ * @param listener The listener to call when all submenu updates are complete
+ */
+ private void updateSubMenuWithOldKeptCells(final List<MenuCell> oldKeptCells, final List<MenuCell> newKeptCells, final int index, final CompletionListener listener) {
+ if (oldKeptCells.isEmpty() || index >= oldKeptCells.size()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ if (oldKeptCells.get(index) != null && oldKeptCells.get(index).isSubMenuCell() && !oldKeptCells.get(index).getSubCells().isEmpty()) {
+ DynamicMenuUpdateRunScore tempScore = DynamicMenuUpdateAlgorithm.dynamicRunScoreOldMenuCells(oldKeptCells.get(index).getSubCells(), newKeptCells.get(index).getSubCells());
+
+ // If both old and new menu cells are empty. Then nothing needs to be done.
+ if (tempScore.isEmpty()) {
+ // After the first set of submenu cells were added and deleted we must find the next set of sub cells until we loop through all the elements
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ return;
+ }
+
+ List<MenuCellState> deleteMenuStatus = tempScore.getOldStatus();
+ List<MenuCellState> addMenuStatus = tempScore.getUpdatedStatus();
+
+ final List<MenuCell> cellsToDelete = filterMenuCellsWithStatusList(oldKeptCells.get(index).getSubCells(), deleteMenuStatus, MenuCellState.DELETE);
+ final List<MenuCell> cellsToAdd = filterMenuCellsWithStatusList(newKeptCells.get(index).getSubCells(), addMenuStatus, MenuCellState.ADD);
+
+ final List<MenuCell> oldSubcellKeeps = filterMenuCellsWithStatusList(oldKeptCells.get(index).getSubCells(), deleteMenuStatus, MenuCellState.KEEP);
+ final List<MenuCell> newSubcellKeeps = filterMenuCellsWithStatusList(newKeptCells.get(index).getSubCells(), addMenuStatus, MenuCellState.KEEP);
+
+ transferCellListenersFromCells(newSubcellKeeps, oldSubcellKeeps);
+
+ sendDeleteMenuCells(cellsToDelete, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ }
+
+ sendAddMenuCells(cellsToAdd, newKeptCells.get(index).getSubCells(), new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ if (!success) {
+ listener.onComplete(false);
+ }
+
+ // After the first set of submenu cells were added and deleted we must find the next set of sub cells until we loop through all the elements
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ }
+ });
+ }
+ });
+ } else {
+ // There are no sub cells, we can skip to the next index.
+ updateSubMenuWithOldKeptCells(oldKeptCells, newKeptCells, index + 1, listener);
+ }
+ }
+
+ /**
+ * Send Delete RPCs for given menu cells
+ *
+ * @param deleteMenuCells The menu cells to be deleted
+ * @param listener A CompletionListener called when the RPCs are finished with an error if any failed
+ */
+ private void sendDeleteMenuCells(List<MenuCell> deleteMenuCells, final CompletionListener listener) {
+ if (deleteMenuCells == null || deleteMenuCells.isEmpty()) {
+ listener.onComplete(true);
+ return;
+ }
+
+ List<RPCRequest> deleteMenuCommands = deleteCommandsForCells(deleteMenuCells);
+ sendRPCs(deleteMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logWarning(TAG, "Unable to delete all old menu commands. " + convertErrorsMapToString(errors));
+ } else {
+ DebugTool.logInfo(TAG, "Finished deleting old menu");
+ }
+ listener.onComplete(success);
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and remove it from the current menu list wherever it may have been
+ int commandId = commandIdForRPCRequest(request);
+ removeCellFromList(currentMenu, commandId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Send Add RPCs for given new menu cells compared to old menu cells
+ *
+ * @param addMenuCells The new menu cells we want displayed
+ * @param fullMenu The full menu from which the addMenuCells come. This allows us to grab the positions from that menu for the new cells
+ * @param listener A CompletionListener called when the RPCs are finished with an error if any failed
+ */
+ private void sendAddMenuCells(final List<MenuCell> addMenuCells, final List<MenuCell> fullMenu, final CompletionListener listener) {
+ if (addMenuCells == null || addMenuCells.isEmpty()) {
+ DebugTool.logInfo(TAG, "There are no cells to update.");
+ listener.onComplete(true);
+ return;
+ }
+
+ MenuLayout defaultSubmenuLayout = menuConfiguration != null ? menuConfiguration.getSubMenuLayout() : null;
+
+ // RPCs for cells on the main menu level. They could be AddCommands or AddSubMenus depending on whether the cell has child cells or not.
+ final List<RPCRequest> mainMenuCommands = mainMenuCommandsForCells(addMenuCells, fileManager.get(), fullMenu, windowCapability, defaultSubmenuLayout);
+
+ // RPCs for cells on the second menu level (one level deep). They could be AddCommands or AddSubMenus.
+ final List<RPCRequest> subMenuCommands = subMenuCommandsForCells(addMenuCells, fileManager.get(), windowCapability, defaultSubmenuLayout);
+
+ sendRPCs(mainMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logError(TAG, "Failed to send main menu commands. " + convertErrorsMapToString(errors));
+ listener.onComplete(false);
+ return;
+ } else if (subMenuCommands.isEmpty()) {
+ DebugTool.logInfo(TAG, "Finished sending new cells");
+ listener.onComplete(true);
+ return;
+ }
+
+ sendRPCs(subMenuCommands, internalInterface.get(), new SendingRPCsCompletionListener() {
+ @Override
+ public void onComplete(boolean success, Map<RPCRequest, String> errors) {
+ if (!success) {
+ DebugTool.logError(TAG, "Failed to send sub menu commands. " + convertErrorsMapToString(errors));
+ } else {
+ DebugTool.logInfo(TAG, "Finished sending new cells");
+ }
+ listener.onComplete(success);
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and add it from the current menu list wherever it needs to be
+ int commandId = commandIdForRPCRequest(request);
+ int position = positionForRPCRequest(request);
+ addCellWithCellId(commandId, position, addMenuCells, currentMenu);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onResponse(RPCRequest request, RPCResponse response) {
+ if (response.getSuccess()) {
+ // Find the id of the successful request and add it from the current menu list wherever it needs to be
+ int commandId = commandIdForRPCRequest(request);
+ int position = positionForRPCRequest(request);
+ addCellWithCellId(commandId, position, addMenuCells, currentMenu);
+ }
+ }
+ });
+ }
+
+ private List<MenuCell> filterMenuCellsWithStatusList(List<MenuCell> menuCells, List<MenuCellState> statusList, MenuCellState menuCellState) {
+ List<MenuCell> filteredCells = new ArrayList<>();
+ for (int index = 0; index < statusList.size(); index++) {
+ if (statusList.get(index).equals(menuCellState)) {
+ filteredCells.add(menuCells.get(index));
+ }
+ }
+ return filteredCells;
+ }
+
+ private String convertErrorsMapToString(Map<RPCRequest, String> errors) {
+ if (errors == null) {
+ return null;
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ for (RPCRequest request : errors.keySet()) {
+ stringBuilder.append(errors.get(request));
+ stringBuilder.append(System.getProperty("line.separator"));
+ try {
+ stringBuilder.append(request.serializeJSON().toString(4));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ stringBuilder.append(System.getProperty("line.separator"));
+ }
+ return stringBuilder.toString();
+ }
+
+ List<MenuCell> cellsWithRemovedPropertiesFromCells(List<MenuCell> cells, WindowCapability windowCapability) {
+ if (cells == null) {
+ return null;
+ }
+
+ List<MenuCell> removePropertiesClone = cloneMenuCellsList(cells);
+
+ for (MenuCell cell : removePropertiesClone) {
+ // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
+ cell.setVoiceCommands(null);
+
+ // Don't check ImageFieldName.subMenuIcon because it was added in 7.0 when the feature was added in 5.0.
+ // Just assume that if cmdIcon is not available, the submenu icon is not either.
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon)) {
+ cell.setIcon(null);
+ }
+
+ // Check for subMenu fields supported
+ if (cell.isSubMenuCell()) {
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuSecondaryText)) {
+ cell.setSecondaryText(null);
+ }
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuTertiaryText)) {
+ cell.setTertiaryText(null);
+ }
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.menuSubMenuSecondaryImage)) {
+ cell.setSecondaryArtwork(null);
+ }
+ cell.setSubCells(cellsWithRemovedPropertiesFromCells(cell.getSubCells(), windowCapability));
+ } else {
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuCommandSecondaryText)) {
+ cell.setSecondaryText(null);
+ }
+ if (!hasTextFieldOfName(windowCapability, TextFieldName.menuCommandTertiaryText)) {
+ cell.setTertiaryText(null);
+ }
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage)) {
+ cell.setSecondaryArtwork(null);
+ }
+ }
+ }
+ return removePropertiesClone;
+ }
+
+ private void generateUniqueNamesForCells(List<MenuCell> menuCells, boolean supportsMenuUniqueness) {
+ if (menuCells == null) {
+ return;
+ }
+
+ // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
+ HashMap<String, Integer> dictCounter = new HashMap<>();
+
+ for (MenuCell cell : menuCells) {
+ String key = supportsMenuUniqueness ? String.valueOf(cell.hashCode()) : cell.getTitle();
+ Integer counter = dictCounter.get(key);
+
+ if (counter != null) {
+ dictCounter.put(key, ++counter);
+ cell.setUniqueTitle(cell.getTitle() + " (" + counter + ")");
+ } else {
+ dictCounter.put(key, 1);
+ cell.setUniqueTitle(cell.getTitle());
+ }
+
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ generateUniqueNamesForCells(cell.getSubCells(), supportsMenuUniqueness);
+ }
+ }
+ }
+
+ private void applyUniqueNamesOnCells(List<MenuCell> fromMenuCells, List<MenuCell> toMenuCells) {
+ if (fromMenuCells.size() != toMenuCells.size()) {
+ return;
+ }
+
+ for (int i = 0; i < fromMenuCells.size(); i++) {
+ toMenuCells.get(i).setUniqueTitle(fromMenuCells.get(i).getUniqueTitle());
+ if (fromMenuCells.get(i).isSubMenuCell() && !fromMenuCells.get(i).getSubCells().isEmpty()) {
+ applyUniqueNamesOnCells(fromMenuCells.get(i).getSubCells(), toMenuCells.get(i).getSubCells());
+ }
+ }
+ }
+
+ void setMenuConfiguration(MenuConfiguration menuConfiguration) {
+ this.menuConfiguration = menuConfiguration;
+ }
+
+ void setCurrentMenu(List<MenuCell> currentMenuCells) {
+ this.currentMenu = currentMenuCells;
+ }
+
+ void setWindowCapability(WindowCapability windowCapability) {
+ this.windowCapability = windowCapability;
+ }
+
+ private void finishOperation(boolean success) {
+ if (operationCompletionListener != null) {
+ operationCompletionListener.onComplete(success, currentMenu);
+ }
+ onFinished();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java
new file mode 100644
index 000000000..f274130d3
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2021 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 static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import static com.smartdevicelink.managers.screen.menu.BaseMenuManager.parentIdNotFound;
+
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+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.Image;
+import com.smartdevicelink.proxy.rpc.MenuParams;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.MenuLayout;
+import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by Bilal Alsharifi on 1/25/21.
+ */
+class MenuReplaceUtilities {
+ private static int menuId = 0;
+
+ static int getNextMenuId() {
+ return ++menuId;
+ }
+
+ /**
+ * Assign cell ids on a list of menu cells given a parent id (or no parent id)
+ *
+ * @param menuCells The list of menu cells to update
+ * @param parentId The parent id to assign if needed
+ */
+ static void addIdsToMenuCells(List<MenuCell> menuCells, int parentId) {
+ for (MenuCell cell : menuCells) {
+ cell.setCellId(getNextMenuId());
+ if (parentId != parentIdNotFound) {
+ cell.setParentCellId(parentId);
+ }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ addIdsToMenuCells(cell.getSubCells(), cell.getCellId());
+ }
+ }
+ }
+
+ static void transferCellIDsFromCells(List<MenuCell> fromCells, List<MenuCell> toCells) {
+ if (fromCells == null || toCells == null || fromCells.isEmpty() || fromCells.size() != toCells.size()) {
+ return;
+ }
+ for (int i = 0; i < toCells.size(); i++) {
+ toCells.get(i).setCellId(fromCells.get(i).getCellId());
+ }
+
+ // Update parent ids
+ for (MenuCell cell : toCells) {
+ if (!cell.isSubMenuCell()) {
+ continue;
+ }
+
+ for (MenuCell subCell : cell.getSubCells()) {
+ subCell.setParentCellId(cell.getCellId());
+ }
+ }
+ }
+
+ static void transferCellListenersFromCells(List<MenuCell> fromCells, List<MenuCell> toCells) {
+ if (fromCells == null || toCells == null || fromCells.isEmpty() || fromCells.size() != toCells.size()) {
+ return;
+ }
+ for (int i = 0; i < fromCells.size(); i++) {
+ toCells.get(i).setMenuSelectionListener(fromCells.get(i).getMenuSelectionListener());
+ }
+ }
+
+ static Set<SdlArtwork> findAllArtworksToBeUploadedFromCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability) {
+ // Make sure we can use images in the menus
+ if (!hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon)) {
+ return new HashSet<>();
+ }
+
+ Set<SdlArtwork> artworks = new HashSet<>();
+ for (MenuCell cell : cells) {
+ if (fileManager != null) {
+ if (fileManager.fileNeedsUpload(cell.getIcon())) {
+ artworks.add(cell.getIcon());
+ }
+ if (hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage) && fileManager.fileNeedsUpload(cell.getSecondaryArtwork())) {
+ artworks.add(cell.getSecondaryArtwork());
+ }
+ }
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ artworks.addAll(findAllArtworksToBeUploadedFromCells(cell.getSubCells(), fileManager, windowCapability));
+ }
+ }
+
+ return artworks;
+ }
+
+ // If there is an icon and the icon has been uploaded, or if the icon is a static icon, it should include the image
+ static boolean shouldCellIncludePrimaryImageFromCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability) {
+ boolean supportsImage = cell.isSubMenuCell() ? hasImageFieldOfName(windowCapability, ImageFieldName.subMenuIcon) : hasImageFieldOfName(windowCapability, ImageFieldName.cmdIcon);
+ return cell.getIcon() != null && supportsImage && (fileManager.hasUploadedFile(cell.getIcon()) || cell.getIcon().isStaticIcon());
+ }
+
+ // If there is an icon and the icon has been uploaded, or if the icon is a static icon, it should include the image
+ static boolean shouldCellIncludeSecondaryImageFromCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability) {
+ boolean supportsImage = cell.isSubMenuCell() ? hasImageFieldOfName(windowCapability, ImageFieldName.menuSubMenuSecondaryImage) : hasImageFieldOfName(windowCapability, ImageFieldName.menuCommandSecondaryImage);
+ return cell.getSecondaryArtwork() != null && supportsImage && (fileManager.hasUploadedFile(cell.getSecondaryArtwork()) || cell.getSecondaryArtwork().isStaticIcon());
+ }
+
+ static int commandIdForRPCRequest(RPCRequest request) {
+ int commandId = 0;
+ if (request instanceof AddCommand) {
+ commandId = ((AddCommand) request).getCmdID();
+ } else if (request instanceof AddSubMenu) {
+ commandId = ((AddSubMenu) request).getMenuID();
+ } else if (request instanceof DeleteCommand) {
+ commandId = ((DeleteCommand) request).getCmdID();
+ } else if (request instanceof DeleteSubMenu) {
+ commandId = ((DeleteSubMenu) request).getMenuID();
+ }
+ return commandId;
+ }
+
+ static int positionForRPCRequest(RPCRequest request) {
+ int position = 0;
+ if (request instanceof AddCommand) {
+ position = ((AddCommand) request).getMenuParams().getPosition();
+ } else if (request instanceof AddSubMenu) {
+ position = ((AddSubMenu) request).getPosition();
+ }
+ return position;
+ }
+
+ static List<RPCRequest> deleteCommandsForCells(List<MenuCell> cells) {
+ List<RPCRequest> deletes = new ArrayList<>();
+ for (MenuCell cell : cells) {
+ if (cell.isSubMenuCell()) {
+ DeleteSubMenu delete = new DeleteSubMenu(cell.getCellId());
+ deletes.add(delete);
+ } else {
+ DeleteCommand delete = new DeleteCommand(cell.getCellId());
+ deletes.add(delete);
+ }
+ }
+ return deletes;
+ }
+
+ static List<RPCRequest> mainMenuCommandsForCells(List<MenuCell> cells, FileManager fileManager, List<MenuCell> menu, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+
+ // We need the index to use it as position so we will use this type of loop
+ for (int menuInteger = 0; menuInteger < menu.size(); menuInteger++) {
+ MenuCell mainCell = menu.get(menuInteger);
+ for (int updateCellsIndex = 0; updateCellsIndex < cells.size(); updateCellsIndex++) {
+ MenuCell addCell = cells.get(updateCellsIndex);
+ if (mainCell.equals(addCell)) {
+ if (addCell.isSubMenuCell()) {
+ commands.add(subMenuCommandForMenuCell(addCell, fileManager, windowCapability, menuInteger, defaultSubmenuLayout));
+ } else {
+ commands.add(commandForMenuCell(addCell, fileManager, windowCapability, menuInteger));
+ }
+ break;
+ }
+ }
+ }
+ return commands;
+ }
+
+ static List<RPCRequest> subMenuCommandsForCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+ for (MenuCell cell : cells) {
+ if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ commands.addAll(allCommandsForCells(cell.getSubCells(), fileManager, windowCapability, defaultSubmenuLayout));
+ }
+ }
+ return commands;
+ }
+
+ static List<RPCRequest> allCommandsForCells(List<MenuCell> cells, FileManager fileManager, WindowCapability windowCapability, MenuLayout defaultSubmenuLayout) {
+ List<RPCRequest> commands = new ArrayList<>();
+
+ for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
+ MenuCell cell = cells.get(cellIndex);
+ if (cell.isSubMenuCell()) {
+ commands.add(subMenuCommandForMenuCell(cell, fileManager, windowCapability, cellIndex, defaultSubmenuLayout));
+
+ // Recursively grab the commands for all the sub cells
+ if (!cell.getSubCells().isEmpty()) {
+ commands.addAll(allCommandsForCells(cell.getSubCells(), fileManager, windowCapability, defaultSubmenuLayout));
+ }
+ } else {
+ commands.add(commandForMenuCell(cell, fileManager, windowCapability, cellIndex));
+ }
+ }
+ return commands;
+ }
+
+ static AddCommand commandForMenuCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability, int position) {
+ AddCommand command = new AddCommand(cell.getCellId());
+
+ MenuParams params = new MenuParams(cell.getUniqueTitle());
+ params.setSecondaryText((cell.getSecondaryText() != null && !cell.getSecondaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuCommandSecondaryText)) ? cell.getSecondaryText() : null);
+ params.setTertiaryText((cell.getTertiaryText() != null && !cell.getTertiaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuCommandTertiaryText)) ? cell.getTertiaryText() : null);
+ params.setParentID(cell.getParentCellId() != parentIdNotFound ? cell.getParentCellId() : null);
+ params.setPosition(position);
+
+ command.setMenuParams(params);
+ if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
+ command.setVrCommands(cell.getVoiceCommands());
+ } else {
+ command.setVrCommands(null);
+ }
+ boolean shouldCellIncludePrimaryImage = cell.getIcon() != null && shouldCellIncludePrimaryImageFromCell(cell, fileManager, windowCapability);
+ command.setCmdIcon(shouldCellIncludePrimaryImage ? cell.getIcon().getImageRPC() : null);
+
+ boolean shouldCellIncludeSecondaryImage = cell.getSecondaryArtwork() != null && shouldCellIncludeSecondaryImageFromCell(cell, fileManager, windowCapability);
+ command.setSecondaryImage(shouldCellIncludeSecondaryImage ? cell.getSecondaryArtwork().getImageRPC() : null);
+
+ return command;
+ }
+
+ static AddSubMenu subMenuCommandForMenuCell(MenuCell cell, FileManager fileManager, WindowCapability windowCapability, int position, MenuLayout defaultSubmenuLayout) {
+ boolean shouldCellIncludePrimaryImage = cell.getIcon() != null && cell.getIcon().getImageRPC() != null && shouldCellIncludePrimaryImageFromCell(cell, fileManager, windowCapability);
+ Image icon = (shouldCellIncludePrimaryImage ? cell.getIcon().getImageRPC() : null);
+ boolean shouldCellIncludeSecondaryImage = cell.getSecondaryArtwork() != null && cell.getSecondaryArtwork().getImageRPC() != null && shouldCellIncludeSecondaryImageFromCell(cell, fileManager, windowCapability);
+ Image secondaryIcon = (shouldCellIncludeSecondaryImage ? cell.getSecondaryArtwork().getImageRPC() : null);
+
+ MenuLayout submenuLayout;
+ List<MenuLayout> availableMenuLayouts = windowCapability != null ? windowCapability.getMenuLayoutsAvailable() : null;
+ if (cell.getSubMenuLayout() != null && availableMenuLayouts != null && availableMenuLayouts.contains(cell.getSubMenuLayout())) {
+ submenuLayout = cell.getSubMenuLayout();
+ } else {
+ submenuLayout = defaultSubmenuLayout;
+ }
+
+ return new AddSubMenu(cell.getCellId(), cell.getUniqueTitle())
+ .setParentID(cell.getParentCellId() != parentIdNotFound ? cell.getParentCellId() : null)
+ .setSecondaryText((cell.getSecondaryText() != null && !cell.getSecondaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuSecondaryText)) ? cell.getSecondaryText() : null)
+ .setTertiaryText((cell.getTertiaryText() != null && !cell.getTertiaryText().isEmpty() && hasTextFieldOfName(windowCapability, TextFieldName.menuSubMenuTertiaryText)) ? cell.getTertiaryText() : null)
+ .setPosition(position)
+ .setMenuLayout(submenuLayout)
+ .setMenuIcon(icon)
+ .setSecondaryImage(secondaryIcon);
+ }
+
+ static boolean removeCellFromList(List<MenuCell> menuCellList, int commandId) {
+ for (MenuCell menuCell : menuCellList) {
+ if (menuCell.getCellId() == commandId) {
+ // If the cell id matches the command id, remove it from the list and return
+ menuCellList.remove(menuCell);
+ return true;
+ } else if (menuCell.isSubMenuCell() && !menuCell.getSubCells().isEmpty()) {
+ // If the menu cell has sub cells, we need to recurse and check the sub cells
+ List<MenuCell> newList = menuCell.getSubCells();
+ boolean foundAndRemovedItem = removeCellFromList(newList, commandId);
+ if (foundAndRemovedItem) {
+ menuCell.setSubCells(newList);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static boolean addCellWithCellId(int cellId, int position, List<MenuCell> newMenuList, List<MenuCell> mainMenuList) {
+ MenuCell addedCell = null;
+ for (MenuCell cell : newMenuList) {
+ if (cell.getCellId() == cellId) {
+ addedCell = cell;
+ break;
+ } else if (cell.isSubMenuCell() && !cell.getSubCells().isEmpty()) {
+ boolean success = addCellWithCellId(cellId, position, cell.getSubCells(), mainMenuList);
+ if (success) {
+ return true;
+ }
+ }
+ }
+ if (addedCell != null) {
+ return addMenuCell(addedCell, mainMenuList, position);
+ }
+ return false;
+ }
+
+ private static boolean addMenuCell(MenuCell cell, List<MenuCell> menuCellList, int position) {
+ if (cell.getParentCellId() == parentIdNotFound) {
+ // The cell does not have a parent id, just insert it into the main menu
+ insertMenuCell(cell, menuCellList, position);
+ return true;
+ }
+
+ // If the cell has a parent id, we need to find the cell with a matching cell id and insert it into its submenu
+ for (MenuCell menuCell : menuCellList) {
+ if (menuCell.getCellId() == cell.getParentCellId()) {
+ if (menuCell.getSubCells() == null) {
+ menuCell.setSubCells(new ArrayList<MenuCell>());
+ }
+ // If we found the correct submenu, insert it into that submenu
+ insertMenuCell(cell, menuCell.getSubCells(), position);
+ return true;
+ } else if (menuCell.isSubMenuCell() && !menuCell.getSubCells().isEmpty()) {
+ // Check the sub cells of this cell to see if any of those have cell ids that match the parent cell id
+ List<MenuCell> newList = menuCell.getSubCells();
+ boolean foundAndAddedItem = addMenuCell(cell, newList, position);
+ if (foundAndAddedItem) {
+ menuCell.setSubCells(newList);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void insertMenuCell(MenuCell cell, List<MenuCell> cellList, int position) {
+ MenuCell cellToInsert = cell;
+ if (cellToInsert.isSubMenuCell()) {
+ // We should not add the subCells automatically when adding a parent cell
+ cellToInsert = cell.clone();
+ cellToInsert.getSubCells().clear();
+ }
+ if (position > cellList.size()) {
+ cellList.add(cellToInsert);
+ } else {
+ cellList.add(position, cellToInsert);
+ }
+ }
+
+ static List<MenuCell> cloneMenuCellsList(List<MenuCell> originalList) {
+ if (originalList == null) {
+ return new ArrayList<>();
+ }
+
+ List<MenuCell> clone = new ArrayList<>();
+ for (MenuCell menuCell : originalList) {
+ clone.add(menuCell.clone());
+ }
+ return clone;
+ }
+
+ static void sendRPCs(final List<RPCRequest> requests, ISdl internalInterface, final SendingRPCsCompletionListener listener) {
+ final Map<RPCRequest, String> errors = new HashMap<>();
+ if (requests == null || requests.isEmpty()) {
+ listener.onComplete(true, errors);
+ return;
+ }
+
+ internalInterface.sendRPCs(requests, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+ }
+
+ @Override
+ public void onFinished() {
+ listener.onComplete(errors.isEmpty(), errors);
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ RPCRequest request = null;
+ for (RPCRequest r : requests) {
+ if (response.getCorrelationID().equals(r.getCorrelationID())) {
+ request = r;
+ break;
+ }
+ }
+ if (!response.getSuccess()) {
+ errors.put(request, "Failed to send RPC. Result: " + response.getResultCode() + ". Info: " + response.getInfo());
+ }
+ listener.onResponse(request, response);
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java
new file mode 100644
index 000000000..a685ca966
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuShowOperation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2021 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.livio.taskmaster.Task;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.ShowAppMenu;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Created by Bilal Alsharifi on 1/21/21.
+ */
+class MenuShowOperation extends Task {
+ private static final String TAG = "MenuShowOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final MenuCell submenuCell;
+
+ MenuShowOperation(ISdl internalInterface, MenuCell menuCell) {
+ super(TAG);
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.submenuCell = menuCell;
+ }
+
+ @Override
+ public void onExecute() {
+ start();
+ }
+
+ private void start() {
+ if (getState() == Task.CANCELED) {
+ return;
+ }
+
+ Integer menuId = submenuCell != null ? submenuCell.getCellId() : null;
+ sendShowAppMenu(menuId);
+ }
+
+ private void sendShowAppMenu(Integer id) {
+ ShowAppMenu showAppMenu = new ShowAppMenu();
+ showAppMenu.setMenuID(id);
+ showAppMenu.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Successfully opened application menu");
+ } else {
+ DebugTool.logError(TAG, "Open Menu Request Failed. Result code: " + response.getResultCode() + ". Info: " + response.getInfo());
+ }
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(showAppMenu);
+ }
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java
index 03afc5348..1339df1eb 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/SubCellCommandListTests.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/SendingRPCsCompletionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Livio, Inc.
+ * Copyright (c) 2021 Livio, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,32 +32,15 @@
package com.smartdevicelink.managers.screen.menu;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.test.TestValues;
+import java.util.Map;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static junit.framework.TestCase.assertEquals;
-
-@RunWith(AndroidJUnit4.class)
-public class SubCellCommandListTests {
-
- @Test
- public void testSettersAndGetters() {
-
- RunScore runScore = new RunScore(TestValues.GENERAL_INT, TestValues.GENERAL_INTEGER_LIST, TestValues.GENERAL_INTEGER_LIST);
-
- // set everything
- SubCellCommandList subCellCommandList = new SubCellCommandList(TestValues.GENERAL_STRING, TestValues.GENERAL_INTEGER, runScore, TestValues.GENERAL_MENUCELL_LIST, TestValues.GENERAL_MENUCELL_LIST);
-
- // use getters and assert equality
- assertEquals(subCellCommandList.getMenuTitle(), TestValues.GENERAL_STRING);
- assertEquals(subCellCommandList.getParentId(), TestValues.GENERAL_INTEGER);
- assertEquals(runScore, runScore);
- assertEquals(subCellCommandList.getNewList(), TestValues.GENERAL_MENUCELL_LIST);
- assertEquals(subCellCommandList.getOldList(), TestValues.GENERAL_MENUCELL_LIST);
-
- }
+/**
+ * Created by Bilal Alsharifi on 1/29/21.
+ */
+interface SendingRPCsCompletionListener {
+ void onComplete(boolean success, Map<RPCRequest, String> errors);
+ void onResponse(RPCRequest request, RPCResponse response);
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
index 59caffbdb..0650ab388 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperation.java
@@ -1,3 +1,35 @@
+/*
+ * Copyright (c) 2021 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.livio.taskmaster.Task;