summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilal Alsharifi <599206+bilal-alsharifi@users.noreply.github.com>2021-10-27 11:28:25 -0400
committerGitHub <noreply@github.com>2021-10-27 11:28:25 -0400
commit0bda03437ed79d1e4890060ff9a816db3ccd5251 (patch)
tree7bbd8c6e6e432f1c1c0a9ec109170ce3fb0ee6c0
parentc3553b717eef3626d20fafedbfe63eaa5a28bb10 (diff)
parent7ccf3c2a1dcbd2a466eb49e70e68511d58071fbb (diff)
downloadsdl_android-5.3.0.tar.gz
Merge pull request #1747 from smartdevicelink/release/5.3.0_RC5.3.0
Release/5.3.0
-rw-r--r--CHANGELOG.md47
-rw-r--r--VERSION2
-rw-r--r--android/sdl_android/build.gradle4
-rw-r--r--android/sdl_android/gradle.properties2
-rw-r--r--android/sdl_android/libs/TaskMaster-0.5.jarbin17590 -> 0 bytes
-rw-r--r--android/sdl_android/libs/Taskmaster-0.6.jarbin0 -> 17636 bytes
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java36
-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/lifecycle/SystemCapabilityManagerTests.java31
-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/TextAndGraphicManagerTests.java1
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCellTests.java27
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java178
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperationTests.java2
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java193
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperationTests.java443
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperationTests.java295
-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/managers/video/HapticInterfaceManagerTest.java59
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/TestValues.java102
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Validator.java73
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/SecurityQueryPayloadTests.java126
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryErrorCodeTests.java189
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryIDTests.java91
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryTypeTests.java97
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/VehicleTypeTest.java14
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/util/SdlAppInfoTests.java119
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/test/utl/AndroidToolsTests.java37
-rw-r--r--android/sdl_android/src/androidTest/res/xml/supported_vehicle_type.xml12
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java13
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java21
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java27
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java3
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/protocol/heartbeat/HeartbeatMonitor.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java5
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java47
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java109
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java8
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java103
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java169
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java2
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/SdlAppInfo.java193
-rw-r--r--android/sdl_android/src/main/res/values/sdl.xml3
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java47
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java20
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java22
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java309
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java35
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java74
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java274
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java927
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java374
-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.java424
-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
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java20
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java157
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java61
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java105
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java41
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java4
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java2
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java320
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java13
-rw-r--r--base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java85
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/TransportConstants.java1
-rw-r--r--base/src/main/java/com/smartdevicelink/util/BitConverter.java2
m---------generator/rpc_spec0
-rw-r--r--javaEE/javaEE/gradle.properties2
-rw-r--r--javaEE/javaEE/libs/TaskMaster-0.5.jarbin17590 -> 0 bytes
-rw-r--r--javaEE/javaEE/libs/Taskmaster-0.6.jarbin0 -> 17636 bytes
-rw-r--r--javaSE/javaSE/build.gradle2
-rw-r--r--javaSE/javaSE/gradle.properties2
-rw-r--r--javaSE/javaSE/libs/TaskMaster-0.5.jarbin17590 -> 0 bytes
-rw-r--r--javaSE/javaSE/libs/Taskmaster-0.6.jarbin0 -> 17636 bytes
-rw-r--r--javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java2
-rw-r--r--javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java13
-rw-r--r--javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java21
104 files changed, 6901 insertions, 3822 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23e2aa55c..9d760b0d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,50 +1,51 @@
-# 5.2.0 Release Notes
+# 5.3.0 Release Notes
## Summary:
||Version|
|--|--|
-| **Protocol** | 5.4.0
-| **RPC** | 7.1.0
+| **Protocol** | 5.4.1
+| **RPC** | 8.0.0
| **Tested Targeting** | Android 30
-## Bug Fixes / Enhancements:
-
-- [MenuManager sending secondary image with menuCells when menuCommandSecondaryImage is not supported.](https://github.com/smartdevicelink/sdl_java_suite/issues/1688)
+## Features:
-- [IllegalArgumentException when starting video stream with custom RPC MTU](https://github.com/smartdevicelink/sdl_java_suite/issues/1667)
+- [[SDL 0293] Enable OEM exclusive apps support](https://github.com/smartdevicelink/sdl_java_suite/issues/1588)
-- [Send voiceCommand with duplicate strings](https://github.com/smartdevicelink/sdl_java_suite/issues/1664)
-
-- [Two voiceCommands contains the same string](https://github.com/smartdevicelink/sdl_java_suite/issues/1677)
+## Bug Fixes / Enhancements:
-- [java.lang.NegativeArraySizeException Crash at SdlPsm.java line 241 com.smartdevicelink.transport.SdlPsm.transitionOnInput](https://github.com/smartdevicelink/sdl_java_suite/issues/1678)
+- [New TaskMaster release needs to be added to Java Suite Library ](https://github.com/smartdevicelink/sdl_java_suite/issues/1745)
-- [Exception handling variances](https://github.com/smartdevicelink/sdl_java_suite/issues/1687)
+- [File Manager will upload the same file multiple times](https://github.com/smartdevicelink/sdl_java_suite/issues/1736)
-- [Allow SdlDeviceListener to start after BT connection](https://github.com/smartdevicelink/sdl_java_suite/pull/1685)
+- [Fix description for `SeatControlCapabilities` and `RadioControlCapabilities`](https://github.com/smartdevicelink/sdl_java_suite/issues/1739)
-- [voiceCommand that contains no string should be removed](https://github.com/smartdevicelink/sdl_java_suite/issues/1675)
+- [AudioStreamManager and AlertManager have the same queue ID](https://github.com/smartdevicelink/sdl_java_suite/issues/1742)
-- [Image returned as "not uploaded" in certain circumstances when it's already uploaded, leading to the image being unusable](https://github.com/smartdevicelink/sdl_java_suite/issues/1692)
+- [java.lang.IndexOutOfBoundsException Crash: SdlRouterService.java line 3275 ](https://github.com/smartdevicelink/sdl_java_suite/issues/1741)
-- [Primary Graphic not sent to SDL Core for Media Template ](https://github.com/smartdevicelink/sdl_java_suite/issues/1690)
+- [Old devices with SDL 2.0 can't display text fields and SoftButtons](https://github.com/smartdevicelink/sdl_java_suite/issues/1729)
-- [Race condition leads to NPE in TransportManager](https://github.com/smartdevicelink/sdl_java_suite/issues/1703)
+- [Swapping de-duplicated menu items causes incorrect unique titles](https://github.com/smartdevicelink/sdl_java_suite/issues/1723)
-- [Avoid deleting and setting identical voice commands](https://github.com/smartdevicelink/sdl_java_suite/issues/1676)
+- [MenuCell title update issue](https://github.com/smartdevicelink/sdl_java_suite/issues/1651)
-- [Sdl disconnection is not notified to the app](https://github.com/smartdevicelink/sdl_java_suite/issues/1697)
+- [Refactor the Menu Manager to Use Queues ](https://github.com/smartdevicelink/sdl_java_suite/issues/1605)
-- [PredefinedLayout.NON_MEDIA not found in templatesAvailable](https://github.com/smartdevicelink/sdl_java_suite/issues/1705)
+- [Security queries are not implemented to spec](https://github.com/smartdevicelink/sdl_java_suite/issues/1720)
-- [Lockscreen should show again after dismissal if a DD notification is received where DismissalEnabled is false](https://github.com/smartdevicelink/sdl_java_suite/issues/1695)
+- [Choice Set Present followed directly by a Delete can have undefined behavior](https://github.com/smartdevicelink/sdl_java_suite/issues/1718)
-- [Choice Cells and Menu Cells do not take which properties are available into account for uniqueness](https://github.com/smartdevicelink/sdl_java_suite/issues/1682)
+- [Back-to-back choice sets can fail](https://github.com/smartdevicelink/sdl_java_suite/issues/1717)
-- [BSON library should be updated to the latest version (1.2.5)](https://github.com/smartdevicelink/sdl_java_suite/issues/1712)
+- [Github CI fails even though tests pass locally ](https://github.com/smartdevicelink/sdl_java_suite/issues/1731)
+- [Handler instances using deprecated constructors ](https://github.com/smartdevicelink/sdl_java_suite/issues/1696)
+- [[SDL 0236] Update mismatch in TireStatus structure](https://github.com/smartdevicelink/sdl_java_suite/issues/1089)
+- [Choices not saved as preloaded if some choices fail](https://github.com/smartdevicelink/sdl_java_suite/issues/1715)
+- [MenuManager should print warning when trying to send AddSubMenu with voice commands](https://github.com/smartdevicelink/sdl_java_suite/issues/1760)
+- [Spelling/ Grammar Updates](https://github.com/smartdevicelink/sdl_java_suite/issues/1754) \ No newline at end of file
diff --git a/VERSION b/VERSION
index 7cbea073b..03f488b07 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.2.0 \ No newline at end of file
+5.3.0
diff --git a/android/sdl_android/build.gradle b/android/sdl_android/build.gradle
index 73665a4b7..489da844e 100644
--- a/android/sdl_android/build.gradle
+++ b/android/sdl_android/build.gradle
@@ -5,7 +5,7 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
- versionCode 20
+ versionCode 21
versionName new File(projectDir.path, ('/../../VERSION')).text.trim()
buildConfigField "String", "VERSION_NAME", '\"' + versionName + '\"'
resValue "string", "SDL_LIB_VERSION", '\"' + versionName + '\"'
@@ -42,7 +42,7 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
- //api 'com.livio.taskmaster:taskmaster:0.4.0'
+ //api 'com.livio.taskmaster:taskmaster:0.6.0'
api 'com.smartdevicelink:bson_java_port:1.2.5'
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
api 'androidx.annotation:annotation:1.1.0'
diff --git a/android/sdl_android/gradle.properties b/android/sdl_android/gradle.properties
index 3136c040b..d93b695d2 100644
--- a/android/sdl_android/gradle.properties
+++ b/android/sdl_android/gradle.properties
@@ -1,6 +1,6 @@
GROUP=com.smartdevicelink
POM_ARTIFACT_ID=sdl_android
-VERSION_NAME=5.2.0
+VERSION_NAME=5.3.0
POM_NAME=sdl_android
POM_PACKAGING=aar
diff --git a/android/sdl_android/libs/TaskMaster-0.5.jar b/android/sdl_android/libs/TaskMaster-0.5.jar
deleted file mode 100644
index a7881051b..000000000
--- a/android/sdl_android/libs/TaskMaster-0.5.jar
+++ /dev/null
Binary files differ
diff --git a/android/sdl_android/libs/Taskmaster-0.6.jar b/android/sdl_android/libs/Taskmaster-0.6.jar
new file mode 100644
index 000000000..8f249d0f7
--- /dev/null
+++ b/android/sdl_android/libs/Taskmaster-0.6.jar
Binary files differ
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
index 90bedd434..b3d489eaa 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/file/FileManagerTests.java
@@ -851,6 +851,42 @@ public class FileManagerTests {
}
/**
+ * Tests to make sure files are not being uploaded to head unit multiple times in a row
+ */
+ @Test
+ public void testFileNotOnHmi() {
+ final ISdl internalInterface = createISdlMock();
+
+ doAnswer(onListFilesSuccess).when(internalInterface).sendRPC(any(ListFiles.class));
+ doAnswer(onPutFileSuccess).when(internalInterface).sendRPC(any(PutFile.class));
+
+ final SdlArtwork validFile2 = new SdlArtwork(TestValues.GENERAL_STRING + "2", FileType.GRAPHIC_JPEG, TestValues.GENERAL_STRING.getBytes(), false);
+
+ final List<SdlArtwork> list = Arrays.asList(validFile2, validFile2);
+
+ FileManagerConfig fileManagerConfig = new FileManagerConfig();
+
+ final FileManager fileManager = new FileManager(internalInterface, mTestContext, fileManagerConfig);
+ fileManager.start(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ fileManager.uploadArtworks(list, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(final Map<String, String> errors) {
+ assertOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ verify(internalInterface, times(1)).sendRPC(any(PutFile.class));
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+
+ /**
* Test custom overridden SdlFile equals method
*/
@Test
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/lifecycle/SystemCapabilityManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
index 65f9c7bb4..03854d3ee 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lifecycle/SystemCapabilityManagerTests.java
@@ -32,6 +32,7 @@ import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
import com.smartdevicelink.proxy.rpc.SetDisplayLayoutResponse;
import com.smartdevicelink.proxy.rpc.SoftButtonCapabilities;
import com.smartdevicelink.proxy.rpc.SystemCapability;
+import com.smartdevicelink.proxy.rpc.TextField;
import com.smartdevicelink.proxy.rpc.VideoStreamingCapability;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.WindowTypeCapabilities;
@@ -52,6 +53,7 @@ import com.smartdevicelink.proxy.rpc.enums.ServiceUpdateReason;
import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities;
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.enums.WindowType;
import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener;
@@ -689,6 +691,35 @@ public class SystemCapabilityManagerTests {
verify(internalInterface, times(0)).sendRPC(any(GetSystemCapability.class));
}
+ /**
+ * Test to verify that we can get null for templatesAvailable without hitting an NPE and
+ * test media field conversion for NON_MEDIA to NON-MEDIA for Sync bug.
+ */
+ @Test
+ public void testMediaFieldConversion() {
+ SystemCapabilityManager systemCapabilityManager = new SystemCapabilityManager(new InternalSDLInterface());
+
+ RegisterAppInterfaceResponse raiResponse = new RegisterAppInterfaceResponse();
+ DisplayCapabilities displayCapabilities = new DisplayCapabilities();
+ displayCapabilities.setGraphicSupported(false);
+ TextField textField = new TextField();
+ textField.setName(TextFieldName.mainField1);
+ displayCapabilities.setTextFields(Collections.singletonList(textField));
+ raiResponse.setDisplayCapabilities(displayCapabilities);
+ raiResponse.setSuccess(true);
+ systemCapabilityManager.parseRAIResponse(raiResponse);
+
+ WindowCapability windowCapability = systemCapabilityManager.getDefaultMainWindowCapability();
+ assertNull(windowCapability.getTemplatesAvailable());
+
+ List<String> templates = new ArrayList<>();
+ templates.add("NON_MEDIA");
+ displayCapabilities.setTemplatesAvailable(templates);
+ systemCapabilityManager.parseRAIResponse(raiResponse);
+ windowCapability = systemCapabilityManager.getDefaultMainWindowCapability();
+ assertTrue(windowCapability.getTemplatesAvailable().contains("NON-MEDIA"));
+ }
+
@Test
public void testListConversion() {
SystemCapabilityManager systemCapabilityManager = createSampleManager();
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/TextAndGraphicManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicManagerTests.java
index 9b948f808..3999e7d2c 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/TextAndGraphicManagerTests.java
@@ -275,6 +275,7 @@ public class TextAndGraphicManagerTests {
@Test
public void testOperationManagement() {
+ textAndGraphicManager.transactionQueue.pause();
textAndGraphicManager.isDirty = true;
textAndGraphicManager.updateOperation = null;
textAndGraphicManager.update(null);
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCellTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCellTests.java
index 0dde875ea..a761b1f88 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCellTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCellTests.java
@@ -61,7 +61,7 @@ public class ChoiceCellTests {
choiceCell.setVoiceCommands(TestValues.GENERAL_STRING_LIST);
choiceCell.setArtwork(artwork);
choiceCell.setSecondaryArtwork(artwork);
- choiceCell.setUniqueText(TestValues.GENERAL_STRING);
+ choiceCell.setUniqueTextId(TestValues.GENERAL_INT);
// use getters and assert equality
assertEquals(choiceCell.getText(), TestValues.GENERAL_STRING);
@@ -71,7 +71,7 @@ public class ChoiceCellTests {
assertEquals(choiceCell.getArtwork(), artwork);
assertEquals(choiceCell.getSecondaryArtwork(), artwork);
assertEquals(choiceCell.getChoiceId(), MAX_ID);
- assertEquals(choiceCell.getUniqueText(), TestValues.GENERAL_STRING);
+ assertEquals(choiceCell.getUniqueTextId(), TestValues.GENERAL_INTEGER);
}
@Test
@@ -121,14 +121,27 @@ public class ChoiceCellTests {
choiceCell3.setSecondaryText(TestValues.GENERAL_STRING);
choiceCell3.setTertiaryText(TestValues.GENERAL_STRING);
- //UniqueText should not be taken into consideration when checking equality
- choiceCell.setUniqueText(TestValues.GENERAL_STRING);
- choiceCell2.setUniqueText(TestValues.GENERAL_STRING);
- choiceCell3.setUniqueText(TestValues.GENERAL_STRING);
-
// Make sure our overridden method works, even though these are different objects in memory
assertTrue(choiceCell.equals(choiceCell2));
assertFalse(choiceCell.equals(choiceCell3));
}
+
+ @Test
+ public void testGetUniqueCellText() {
+ ChoiceCell choiceCell = new ChoiceCell("Test");
+ ChoiceCell choiceCell2 = new ChoiceCell("Test");
+ choiceCell2.setUniqueTextId(2);
+ ChoiceCell choiceCell3 = new ChoiceCell("Test");
+ choiceCell3.setUniqueTextId(3);
+
+ assertEquals((int) choiceCell.getUniqueTextId(), 1);
+ assertEquals(choiceCell.getUniqueText(), "Test");
+
+ assertEquals((int) choiceCell2.getUniqueTextId(), 2);
+ assertEquals(choiceCell2.getUniqueText(), "Test (2)");
+
+ assertEquals((int) choiceCell3.getUniqueTextId(), 3);
+ assertEquals(choiceCell3.getUniqueText(), "Test (3)");
+ }
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
index 81f912361..f26a7a5c5 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/ChoiceSetManagerTests.java
@@ -38,25 +38,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
import com.smartdevicelink.managers.ISdl;
import com.smartdevicelink.managers.file.FileManager;
-import com.smartdevicelink.proxy.rpc.ImageField;
import com.smartdevicelink.proxy.rpc.KeyboardCapabilities;
import com.smartdevicelink.proxy.rpc.KeyboardLayoutCapability;
import com.smartdevicelink.proxy.rpc.KeyboardProperties;
import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.TextField;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
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.SystemContext;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
-import com.smartdevicelink.test.TestValues;
import org.junit.After;
import org.junit.Before;
@@ -68,13 +64,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedHashSet;
-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.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.Mockito.doReturn;
@@ -103,16 +96,12 @@ public class ChoiceSetManagerTests {
assertEquals(csm.getState(), BaseSubManager.SETTING_UP);
assertEquals(csm.currentSystemContext, SystemContext.SYSCTXT_MAIN);
assertEquals(csm.currentHMILevel, HMILevel.HMI_NONE);
- assertEquals(csm.choiceCellIdMin, 1);
- assertEquals(csm.nextChoiceId, 1);
assertFalse(csm.isVROptional);
assertNotNull(csm.fileManager);
assertNotNull(csm.preloadedChoices);
- assertNotNull(csm.pendingPreloadChoices);
assertNotNull(csm.transactionQueue);
assertNotNull(csm.hmiListener);
assertNotNull(csm.onDisplayCapabilityListener);
- assertNull(csm.pendingPresentOperation);
}
@After
@@ -123,11 +112,8 @@ public class ChoiceSetManagerTests {
assertNull(csm.currentHMILevel);
assertNull(csm.currentSystemContext);
assertNull(csm.defaultMainWindowCapability);
- assertNull(csm.pendingPresentationSet);
- assertNull(csm.pendingPresentOperation);
assertEquals(csm.transactionQueue.getTasksAsList().size(), 0);
- assertEquals(csm.nextChoiceId, 1);
assertFalse(csm.isVROptional);
@@ -160,12 +146,6 @@ public class ChoiceSetManagerTests {
ChoiceSet choiceSet1 = new ChoiceSet("test", Collections.<ChoiceCell>emptyList(), choiceSetSelectionListener);
assertFalse(csm.setUpChoiceSet(choiceSet1));
- // Identical cells will not be allowed
- ChoiceCell cell1 = new ChoiceCell("test");
- ChoiceCell cell2 = new ChoiceCell("test");
- ChoiceSet choiceSet2 = new ChoiceSet("test", Arrays.asList(cell1, cell2), choiceSetSelectionListener);
- assertFalse(csm.setUpChoiceSet(choiceSet2));
-
// cells that have duplicate text will be allowed if there is another property to make them unique because a unique name will be assigned and used
ChoiceCell cell3 = new ChoiceCell("test");
cell3.setSecondaryText("text 1");
@@ -208,103 +188,56 @@ public class ChoiceSetManagerTests {
}
@Test
- public void testUpdateIdsOnChoices() {
-
+ public void preloadChoicesAddsToQueue() {
ChoiceCell cell1 = new ChoiceCell("test");
ChoiceCell cell2 = new ChoiceCell("test2");
ChoiceCell cell3 = new ChoiceCell("test3");
- LinkedHashSet<ChoiceCell> cellSet = new LinkedHashSet<>();
+ ArrayList<ChoiceCell> cellSet = new ArrayList<>();
cellSet.add(cell1);
cellSet.add(cell2);
cellSet.add(cell3);
- // Cells are initially set to MAX_ID
- assertEquals(cell1.getChoiceId(), 2000000000);
- assertEquals(cell2.getChoiceId(), 2000000000);
- assertEquals(cell3.getChoiceId(), 2000000000);
- csm.updateIdsOnChoices(cellSet);
- // We are looking for unique IDs
- assertNotSame(cell1.getChoiceId(), 2000000000);
- assertNotSame(cell2.getChoiceId(), 2000000000);
- assertNotSame(cell3.getChoiceId(), 2000000000);
- }
+ csm.preloadChoices(cellSet, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
- @Test
- public void testAddUniqueNamesToCells() {
- ChoiceCell cell1 = new ChoiceCell("McDonalds", "1 mile away", null, null, null, null);
- ChoiceCell cell2 = new ChoiceCell("McDonalds", "2 mile away", null, null, null, null);
- ChoiceCell cell3 = new ChoiceCell("Starbucks", "3 mile away", null, null, null, null);
- ChoiceCell cell4 = new ChoiceCell("McDonalds", "4 mile away", null, null, null, null);
- ChoiceCell cell5 = new ChoiceCell("Starbucks", "5 mile away", null, null, null, null);
- ChoiceCell cell6 = new ChoiceCell("Meijer", "6 mile away", null, null, null, null);
- List<ChoiceCell> cellList = new ArrayList<>();
-
- cellList.add(cell1);
- cellList.add(cell2);
- cellList.add(cell3);
- cellList.add(cell4);
- cellList.add(cell5);
- cellList.add(cell6);
-
- csm.addUniqueNamesToCells(cellList);
-
- assertEquals(cell1.getUniqueText(), "McDonalds");
- assertEquals(cell2.getUniqueText(), "McDonalds (2)");
- assertEquals(cell3.getUniqueText(), "Starbucks");
- assertEquals(cell4.getUniqueText(), "McDonalds (3)");
- assertEquals(cell5.getUniqueText(), "Starbucks (2)");
- assertEquals(cell6.getUniqueText(), "Meijer");
+ }
+ });
+ assertEquals(csm.transactionQueue.getTasksAsList().size(), 1);
}
@Test
- public void testChoicesToBeRemovedFromPendingWithArray() {
-
- ChoiceCell cell1 = new ChoiceCell("test");
- ChoiceCell cell2 = new ChoiceCell("test2");
- ChoiceCell cell3 = new ChoiceCell("test3");
-
- HashSet<ChoiceCell> pendingPreloadSet = new HashSet<>();
- pendingPreloadSet.add(cell1);
- pendingPreloadSet.add(cell2);
- pendingPreloadSet.add(cell3);
-
- csm.pendingPreloadChoices.clear();
- csm.pendingPreloadChoices = pendingPreloadSet;
-
- List<ChoiceCell> choices = new ArrayList<>();
- choices.add(cell2);
-
- HashSet<ChoiceCell> returnedChoices = csm.choicesToBeRemovedFromPendingWithArray(choices);
+ public void preloadChoicesQueueEmptyWhenNoChoiceCells() {
+ ArrayList<ChoiceCell> cellSet = new ArrayList<>();
+ csm.preloadChoices(cellSet, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
- assertEquals(returnedChoices.size(), 1);
- for (ChoiceCell cell : returnedChoices) {
- assertEquals(cell.getText(), "test2");
- }
+ }
+ });
+ assertEquals(csm.transactionQueue.getTasksAsList().size(), 0);
}
@Test
- public void testChoicesToBeUploadedWithArray() {
-
+ public void testPreloadChoicesQueueEmptyIfFileManagerNull() {
ChoiceCell cell1 = new ChoiceCell("test");
ChoiceCell cell2 = new ChoiceCell("test2");
ChoiceCell cell3 = new ChoiceCell("test3");
+ ArrayList<ChoiceCell> cellSet = new ArrayList<>();
+ cellSet.add(cell1);
+ cellSet.add(cell2);
+ cellSet.add(cell3);
- HashSet<ChoiceCell> pendingDeleteSet = new HashSet<>();
- pendingDeleteSet.add(cell1);
- pendingDeleteSet.add(cell2);
- pendingDeleteSet.add(cell3);
-
- csm.preloadedChoices.clear();
- csm.preloadedChoices = pendingDeleteSet;
-
- List<ChoiceCell> choices = new ArrayList<>();
- choices.add(cell2);
-
- HashSet<ChoiceCell> returnedChoices = csm.choicesToBeDeletedWithArray(choices);
+ ISdl internalInterface = mock(ISdl.class);
+ when(internalInterface.getTaskmaster()).thenReturn(taskmaster);
+ FileManager fileManager = null;
+ ChoiceSetManager newCSM = new ChoiceSetManager(internalInterface, fileManager);
+ newCSM.preloadChoices(cellSet, new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
- assertEquals(returnedChoices.size(), 1);
- for (ChoiceCell cell : returnedChoices) {
- assertEquals(cell.getText(), "test2");
- }
+ }
+ });
+ assertEquals(csm.transactionQueue.getTasksAsList().size(), 0);
}
@Test
@@ -474,51 +407,4 @@ public class ChoiceSetManagerTests {
verify(testKeyboardOp, times(0)).dismissKeyboard();
verify(testKeyboardOp2, times(1)).dismissKeyboard();
}
-
- @Test
- public void testUniquenessForAvailableFields() {
- WindowCapability windowCapability = new WindowCapability();
- TextField secondaryText = new TextField();
- secondaryText.setName(TextFieldName.secondaryText);
- TextField tertiaryText = new TextField();
- tertiaryText.setName(TextFieldName.tertiaryText);
-
- List<TextField> textFields = new ArrayList<>();
- textFields.add(secondaryText);
- textFields.add(tertiaryText);
- windowCapability.setTextFields(textFields);
-
- ImageField choiceImage = new ImageField();
- choiceImage.setName(ImageFieldName.choiceImage);
- ImageField choiceSecondaryImage = new ImageField();
- choiceSecondaryImage.setName(ImageFieldName.choiceSecondaryImage);
- List<ImageField> imageFieldList = new ArrayList<>();
- imageFieldList.add(choiceImage);
- imageFieldList.add(choiceSecondaryImage);
- windowCapability.setImageFields(imageFieldList);
-
- csm.defaultMainWindowCapability = windowCapability;
-
- ChoiceCell cell1 = new ChoiceCell("Item 1", "null", "tertiaryText", null, TestValues.GENERAL_ARTWORK, TestValues.GENERAL_ARTWORK);
- ChoiceCell cell2 = new ChoiceCell("Item 1", "null2", "tertiaryText2", null, null, null);
- List<ChoiceCell> choiceCellList = new ArrayList<>();
- choiceCellList.add(cell1);
- choiceCellList.add(cell2);
-
- List<ChoiceCell> removedProperties = csm.removeUnusedProperties(choiceCellList);
- assertNotNull(removedProperties.get(0).getSecondaryText());
-
- textFields.remove(secondaryText);
- textFields.remove(tertiaryText);
- imageFieldList.remove(choiceImage);
- imageFieldList.remove(choiceSecondaryImage);
-
- removedProperties = csm.removeUnusedProperties(choiceCellList);
- csm.addUniqueNamesBasedOnStrippedCells(removedProperties, choiceCellList);
- assertEquals(choiceCellList.get(1).getUniqueText(), "Item 1 (2)");
-
-
- }
-
-
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperationTests.java
index 2b472eda8..fdb141b07 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperationTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperationTests.java
@@ -66,7 +66,7 @@ public class DeleteChoicesOperationTests {
cellsToDelete.add(cell2);
ISdl internalInterface = mock(ISdl.class);
- deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, cellsToDelete, null);
+ deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, cellsToDelete, null, null);
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java
deleted file mode 100644
index 9e879a73a..000000000
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperationTests.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (c) 2019 Livio, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided with the
- * distribution.
- *
- * Neither the name of the Livio Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.managers.file.FileManager;
-import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
-import com.smartdevicelink.managers.file.filetypes.SdlFile;
-import com.smartdevicelink.proxy.rpc.ImageField;
-import com.smartdevicelink.proxy.rpc.TextField;
-import com.smartdevicelink.proxy.rpc.WindowCapability;
-import com.smartdevicelink.proxy.rpc.enums.CharacterSet;
-import com.smartdevicelink.proxy.rpc.enums.FileType;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
-import com.smartdevicelink.proxy.rpc.enums.ImageType;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.test.TestValues;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-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.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(AndroidJUnit4.class)
-public class PreloadChoicesOperationTests {
-
- private PreloadChoicesOperation preloadChoicesOperation;
- private PreloadChoicesOperation preloadChoicesOperationNullCapability;
- private PreloadChoicesOperation preloadChoicesOperationEmptyCapability;
-
-
- @Before
- public void setUp() throws Exception {
-
- ChoiceCell cell1 = new ChoiceCell("cell 1");
- ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
- LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
- cellsToPreload.add(cell1);
- cellsToPreload.add(cell2);
-
- ImageField imageField = new ImageField(ImageFieldName.choiceImage, Arrays.asList(FileType.GRAPHIC_PNG, FileType.GRAPHIC_JPEG));
- ImageField imageField2 = new ImageField();
- imageField2.setName(ImageFieldName.choiceSecondaryImage);
- TextField textField = new TextField(TextFieldName.menuName, CharacterSet.CID1SET, 2, 2);
-
- TextField textField2 = new TextField();
- TextField textField3 = new TextField();
-
- textField2.setName(TextFieldName.secondaryText);
- textField3.setName(TextFieldName.tertiaryText);
-
-
- WindowCapability windowCapability = new WindowCapability();
- windowCapability.setImageFields(Arrays.asList(imageField, imageField2));
- windowCapability.setImageTypeSupported(Arrays.asList(ImageType.STATIC, ImageType.DYNAMIC));
- windowCapability.setTextFields(Arrays.asList(textField, textField2, textField3));
-
- ISdl internalInterface = mock(ISdl.class);
- FileManager fileManager = mock(FileManager.class);
-
- // We still want the mock fileManager to use the real implementation for fileNeedsUpload()
- when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenCallRealMethod();
-
- preloadChoicesOperation = new PreloadChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null);
- }
-
- /**
- * Sets up PreloadChoicesOperation with WindowCapability being null
- */
- public void setUpNullWindowCapability() {
-
- ChoiceCell cell1 = new ChoiceCell("cell 1");
- ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
- LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
- cellsToPreload.add(cell1);
- cellsToPreload.add(cell2);
-
- ISdl internalInterface = mock(ISdl.class);
- FileManager fileManager = mock(FileManager.class);
- preloadChoicesOperationNullCapability = new PreloadChoicesOperation(internalInterface, fileManager, null, null, true, cellsToPreload, null);
- }
-
- /**
- * Sets up PreloadChoicesOperation with an Capability not being set
- * certain imageFields and TextFields
- */
- public void setUpEmptyWindowCapability() {
-
- ChoiceCell cell1 = new ChoiceCell("cell 1");
- ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
- LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
- cellsToPreload.add(cell1);
- cellsToPreload.add(cell2);
-
- ImageField imageField = new ImageField();
- imageField.setName(ImageFieldName.alertIcon);
-
- TextField textField = new TextField();
- textField.setName(TextFieldName.mainField1);
-
- WindowCapability windowCapability = new WindowCapability();
- windowCapability.setImageFields(Collections.singletonList(imageField));
- windowCapability.setTextFields(Collections.singletonList(textField));
-
- ISdl internalInterface = mock(ISdl.class);
- FileManager fileManager = mock(FileManager.class);
- preloadChoicesOperationEmptyCapability = new PreloadChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null);
- }
-
- @Test
- public void testArtworksToUpload() {
- List<SdlArtwork> artworksToUpload = preloadChoicesOperation.artworksToUpload();
- assertNotNull(artworksToUpload);
- assertEquals(artworksToUpload.size(), 1);
- }
-
- /**
- * Testing shouldSend method's with varying WindowCapability set.
- */
- @Test
- public void testShouldSendText() {
-
- setUpNullWindowCapability();
- assertTrue(preloadChoicesOperationNullCapability.shouldSendChoicePrimaryImage());
- assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceSecondaryImage());
- assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceSecondaryText());
- assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceTertiaryText());
- assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceText());
-
-
- assertTrue(preloadChoicesOperation.shouldSendChoicePrimaryImage());
- assertTrue(preloadChoicesOperation.shouldSendChoiceSecondaryImage());
- assertTrue(preloadChoicesOperation.shouldSendChoiceSecondaryText());
- assertTrue(preloadChoicesOperation.shouldSendChoiceTertiaryText());
- assertTrue(preloadChoicesOperation.shouldSendChoiceText());
-
- setUpEmptyWindowCapability();
- assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoicePrimaryImage());
- assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceSecondaryImage());
- assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceSecondaryText());
- assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceTertiaryText());
- assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceText());
- }
-
-}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperationTests.java
new file mode 100644
index 000000000..0f3725f53
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperationTests.java
@@ -0,0 +1,443 @@
+package com.smartdevicelink.managers.screen.choiceset;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.livio.taskmaster.Queue;
+import com.livio.taskmaster.Task;
+import com.livio.taskmaster.Taskmaster;
+import com.smartdevicelink.managers.ISdl;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.CancelInteraction;
+import com.smartdevicelink.proxy.rpc.ImageField;
+import com.smartdevicelink.proxy.rpc.KeyboardProperties;
+import com.smartdevicelink.proxy.rpc.PerformInteraction;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.TextField;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.CharacterSet;
+import com.smartdevicelink.proxy.rpc.enums.FileType;
+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.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.TextFieldName;
+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.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+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.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class PreloadPresentChoicesOperationTests {
+
+ private PreloadPresentChoicesOperation preloadChoicesOperation;
+ private PreloadPresentChoicesOperation preloadChoicesOperationNullCapability;
+ private PreloadPresentChoicesOperation preloadChoicesOperationEmptyCapability;
+
+ private PreloadPresentChoicesOperation presentChoicesOperation;
+ private ChoiceSet choiceSet;
+ private ISdl internalInterface;
+ private FileManager fileManager;
+ private KeyboardListener keyboardListener;
+ private ChoiceSetSelectionListener choiceSetSelectionListener;
+
+ private Taskmaster taskmaster;
+ private Queue queue;
+
+ @Before
+ public void setUp() throws Exception {
+ ChoiceCell cell1 = new ChoiceCell("cell 1");
+ ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
+ LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
+ cellsToPreload.add(cell1);
+ cellsToPreload.add(cell2);
+
+ ImageField imageField = new ImageField(ImageFieldName.choiceImage, Arrays.asList(FileType.GRAPHIC_PNG, FileType.GRAPHIC_JPEG));
+ ImageField imageField2 = new ImageField();
+ imageField2.setName(ImageFieldName.choiceSecondaryImage);
+ TextField textField = new TextField(TextFieldName.menuName, CharacterSet.CID1SET, 2, 2);
+
+ TextField textField2 = new TextField();
+ TextField textField3 = new TextField();
+
+ textField2.setName(TextFieldName.secondaryText);
+ textField3.setName(TextFieldName.tertiaryText);
+
+
+ WindowCapability windowCapability = new WindowCapability();
+ windowCapability.setImageFields(Arrays.asList(imageField, imageField2));
+ windowCapability.setImageTypeSupported(Arrays.asList(ImageType.STATIC, ImageType.DYNAMIC));
+ windowCapability.setTextFields(Arrays.asList(textField, textField2, textField3));
+
+ internalInterface = mock(ISdl.class);
+ fileManager = mock(FileManager.class);
+
+ // We still want the mock fileManager to use the real implementation for fileNeedsUpload()
+ when(fileManager.fileNeedsUpload(any(SdlFile.class))).thenCallRealMethod();
+
+ preloadChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null, null);
+
+ keyboardListener = mock(KeyboardListener.class);
+ choiceSetSelectionListener = mock(ChoiceSetSelectionListener.class);
+
+ ChoiceCell cell = new ChoiceCell("Cell1");
+ cell.setChoiceId(0);
+ choiceSet = new ChoiceSet("Test", Collections.singletonList(cell), choiceSetSelectionListener);
+
+ taskmaster = new Taskmaster.Builder().build();
+ queue = taskmaster.createQueue("test", 100, false);
+ taskmaster.start();
+ }
+
+ private KeyboardProperties getKeyBoardProperties() {
+ KeyboardProperties properties = new KeyboardProperties();
+ properties.setLanguage(Language.EN_US);
+ properties.setKeyboardLayout(KeyboardLayout.QWERTZ);
+ properties.setKeypressMode(KeypressMode.RESEND_CURRENT_ENTRY);
+ return properties;
+ }
+
+ /**
+ * Sets up PreloadChoicesOperation with WindowCapability being null
+ */
+ public void setUpNullWindowCapability() {
+
+ ChoiceCell cell1 = new ChoiceCell("cell 1");
+ ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
+ LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
+ cellsToPreload.add(cell1);
+ cellsToPreload.add(cell2);
+
+ ISdl internalInterface = mock(ISdl.class);
+ preloadChoicesOperationNullCapability = new PreloadPresentChoicesOperation(internalInterface, fileManager, null, null, true, cellsToPreload, null, null);
+ }
+
+ /**
+ * Sets up PreloadChoicesOperation with an Capability not being set
+ * certain imageFields and TextFields
+ */
+ public void setUpEmptyWindowCapability() {
+
+ ChoiceCell cell1 = new ChoiceCell("cell 1");
+ ChoiceCell cell2 = new ChoiceCell("cell 2", null, TestValues.GENERAL_ARTWORK);
+ LinkedHashSet<ChoiceCell> cellsToPreload = new LinkedHashSet<>();
+ cellsToPreload.add(cell1);
+ cellsToPreload.add(cell2);
+
+ ImageField imageField = new ImageField();
+ imageField.setName(ImageFieldName.alertIcon);
+
+ TextField textField = new TextField();
+ textField.setName(TextFieldName.mainField1);
+
+ WindowCapability windowCapability = new WindowCapability();
+ windowCapability.setImageFields(Collections.singletonList(imageField));
+ windowCapability.setTextFields(Collections.singletonList(textField));
+
+ ISdl internalInterface = mock(ISdl.class);
+ preloadChoicesOperationEmptyCapability = new PreloadPresentChoicesOperation(internalInterface, fileManager, null, windowCapability, true, cellsToPreload, null, null);
+ }
+
+ @Test
+ public void testArtworksToUpload() {
+ HashSet<SdlArtwork> artworksToUpload = preloadChoicesOperation.artworksToUpload();
+ assertNotNull(artworksToUpload);
+ assertEquals(artworksToUpload.size(), 1);
+ }
+
+ /**
+ * Testing shouldSend method's with varying WindowCapability set.
+ */
+ @Test
+ public void testShouldSendText() {
+
+ setUpNullWindowCapability();
+ assertTrue(preloadChoicesOperationNullCapability.shouldSendChoicePrimaryImage());
+ assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceSecondaryImage());
+ assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceSecondaryText());
+ assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceTertiaryText());
+ assertTrue(preloadChoicesOperationNullCapability.shouldSendChoiceText());
+
+
+ assertTrue(preloadChoicesOperation.shouldSendChoicePrimaryImage());
+ assertTrue(preloadChoicesOperation.shouldSendChoiceSecondaryImage());
+ assertTrue(preloadChoicesOperation.shouldSendChoiceSecondaryText());
+ assertTrue(preloadChoicesOperation.shouldSendChoiceTertiaryText());
+ assertTrue(preloadChoicesOperation.shouldSendChoiceText());
+
+ setUpEmptyWindowCapability();
+ assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoicePrimaryImage());
+ assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceSecondaryImage());
+ assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceSecondaryText());
+ assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceTertiaryText());
+ assertFalse(preloadChoicesOperationEmptyCapability.shouldSendChoiceText());
+ }
+
+
+ @Test
+ public void testGetLayoutMode() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(7, 1));
+ // First we will check knowing our keyboard listener is NOT NULL
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, null, null);
+
+ assertEquals(presentChoicesOperation.getLayoutMode(), LayoutMode.LIST_WITH_SEARCH);
+ presentChoicesOperation.keyboardListener = null;
+ assertEquals(presentChoicesOperation.getLayoutMode(), LayoutMode.LIST_ONLY);
+ }
+
+ @Test
+ public void testGetPerformInteraction() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(7, 1));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, null, null);
+
+ PerformInteraction pi = presentChoicesOperation.getPerformInteraction();
+ assertEquals(pi.getInitialText(), "Test");
+ assertNull(pi.getHelpPrompt());
+ assertNull(pi.getTimeoutPrompt());
+ assertNull(pi.getVrHelp());
+ assertEquals(pi.getTimeout(), Integer.valueOf(10000));
+ assertEquals(pi.getCancelID(), TestValues.GENERAL_INTEGER);
+ assertEquals(presentChoicesOperation.getLayoutMode(), LayoutMode.LIST_WITH_SEARCH);
+ }
+
+ @Test
+ public void testSetSelectedCellWithId() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(7, 1));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, null, null);
+
+ assertNull(presentChoicesOperation.selectedCellRow);
+ presentChoicesOperation.setSelectedCellWithId(0);
+ assertEquals(presentChoicesOperation.selectedCellRow, Integer.valueOf(0));
+ }
+
+ private void sleep() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testCancelingChoiceSetSuccessfullyIfThreadIsRunning() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ BaseChoiceSetManager.ChoicesOperationCompletionListener listener = new BaseChoiceSetManager.ChoicesOperationCompletionListener() {
+ @Override
+ public void onComplete(boolean success, HashSet<ChoiceCell> loadedChoiceCells) {
+ choiceSet.cancel();
+ sleep();
+
+ verify(internalInterface, times(1)).sendRPC(any(CancelInteraction.class));
+ verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
+
+ assertEquals(Task.IN_PROGRESS, presentChoicesOperation.getState());
+ }
+ };
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, listener, null);
+ presentChoicesOperation.setLoadedCells(new HashSet<ChoiceCell>());
+ queue.add(presentChoicesOperation, false);
+
+ sleep();
+
+ assertEquals(Task.IN_PROGRESS, presentChoicesOperation.getState());
+
+ Answer<Void> cancelInteractionAnswer = new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ CancelInteraction cancelInteraction = (CancelInteraction) args[0];
+
+ assertEquals(cancelInteraction.getCancelID(), TestValues.GENERAL_INTEGER);
+ assertEquals(cancelInteraction.getInteractionFunctionID().intValue(), FunctionID.PERFORM_INTERACTION.getId());
+
+ RPCResponse response = new RPCResponse(FunctionID.CANCEL_INTERACTION.toString());
+ response.setSuccess(true);
+ cancelInteraction.getOnRPCResponseListener().onResponse(0, response);
+
+ return null;
+ }
+ };
+ doAnswer(cancelInteractionAnswer).when(internalInterface).sendRPC(any(CancelInteraction.class));
+ }
+
+ @Test
+ public void testCancelingChoiceSetUnsuccessfullyIfThreadIsRunning() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ BaseChoiceSetManager.ChoicesOperationCompletionListener listener = new BaseChoiceSetManager.ChoicesOperationCompletionListener() {
+ @Override
+ public void onComplete(boolean success, HashSet<ChoiceCell> loadedChoiceCells) {
+ choiceSet.cancel();
+ sleep();
+
+ verify(internalInterface, times(1)).sendRPC(any(CancelInteraction.class));
+ verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
+
+ assertEquals(Task.IN_PROGRESS, presentChoicesOperation.getState());
+ }
+ };
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, listener, null);
+ presentChoicesOperation.setLoadedCells(new HashSet<ChoiceCell>());
+ queue.add(presentChoicesOperation, false);
+ sleep();
+
+ assertEquals(Task.IN_PROGRESS, presentChoicesOperation.getState());
+
+ Answer<Void> cancelInteractionAnswer = new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ CancelInteraction cancelInteraction = (CancelInteraction) args[0];
+
+ assertEquals(cancelInteraction.getCancelID(), TestValues.GENERAL_INTEGER);
+ assertEquals(cancelInteraction.getInteractionFunctionID().intValue(), FunctionID.PERFORM_INTERACTION.getId());
+
+ RPCResponse response = new RPCResponse(FunctionID.CANCEL_INTERACTION.toString());
+ response.setSuccess(false);
+ cancelInteraction.getOnRPCResponseListener().onResponse(0, response);
+
+ return null;
+ }
+ };
+ doAnswer(cancelInteractionAnswer).when(internalInterface).sendRPC(any(CancelInteraction.class));
+ }
+
+ @Test
+ public void testCancelingChoiceSetIfThreadHasFinished() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER,null, windowCapability, true, loadedCells, null, null);
+ presentChoicesOperation.finishOperation(false);
+
+ assertEquals(Task.FINISHED, presentChoicesOperation.getState());
+
+ choiceSet.cancel();
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+
+ assertEquals(Task.FINISHED, presentChoicesOperation.getState());
+ }
+
+ @Test
+ public void testCancelingChoiceSetIfThreadHasCanceled() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER,null, windowCapability, true, loadedCells, null, null);
+ presentChoicesOperation.cancelTask();
+
+ assertEquals(Task.CANCELED, presentChoicesOperation.getState());
+
+ choiceSet.cancel();
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+
+ assertEquals(Task.CANCELED, presentChoicesOperation.getState());
+ }
+
+ @Test
+ public void testCancelingChoiceSetIfThreadHasNotYetRun() {
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, null, null);
+
+ assertEquals(Task.BLOCKED, presentChoicesOperation.getState());
+
+ choiceSet.cancel();
+
+ // Once the operation has started
+ queue.add(presentChoicesOperation, false);
+ sleep();
+
+ assertEquals(Task.CANCELED, presentChoicesOperation.getState());
+
+ // Make sure neither a `CancelInteraction` or `PerformInteraction` RPC is ever sent
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+ verify(internalInterface, never()).sendRPC(any(PerformInteraction.class));
+ }
+
+ @Test
+ public void testCancelingChoiceSetIfHeadUnitDoesNotSupportFeature() {
+ // Cancel Interaction is only supported on RPC specs v.6.0.0+
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(5, 3));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ BaseChoiceSetManager.ChoicesOperationCompletionListener listener = new BaseChoiceSetManager.ChoicesOperationCompletionListener() {
+ @Override
+ public void onComplete(boolean success, HashSet<ChoiceCell> loadedChoiceCells) {
+ choiceSet.cancel();
+ sleep();
+
+ assertEquals(Task.IN_PROGRESS, presentChoicesOperation.getState());
+
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+ verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
+ }
+ };
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, listener, null);
+ presentChoicesOperation.setLoadedCells(new HashSet<ChoiceCell>());
+ queue.add(presentChoicesOperation, false);
+ }
+
+ @Test
+ public void testCancelingChoiceSetIfHeadUnitDoesNotSupportFeatureButThreadIsNotRunning() {
+ // Cancel Interaction is only supported on RPC specs v.6.0.0+
+ when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(5, 3));
+ WindowCapability windowCapability = new WindowCapability();
+ HashSet<ChoiceCell> loadedCells = new HashSet<>();
+ presentChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager, choiceSet, InteractionMode.MANUAL_ONLY, null, null, TestValues.GENERAL_INTEGER, null, windowCapability, true, loadedCells, null, null);
+
+ assertEquals(Task.BLOCKED, presentChoicesOperation.getState());
+
+ choiceSet.cancel();
+
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+
+ // Once the operation has started
+ queue.add(presentChoicesOperation, false);
+ sleep();
+
+ assertEquals(Task.CANCELED, presentChoicesOperation.getState());
+
+ // Make sure neither a `CancelInteraction` or `PerformInteraction` RPC is ever sent
+ verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
+ verify(internalInterface, never()).sendRPC(any(PerformInteraction.class));
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperationTests.java
deleted file mode 100644
index 3d22f352b..000000000
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperationTests.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (c) 2019 Livio, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided with the
- * distribution.
- *
- * Neither the name of the Livio Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.livio.taskmaster.Queue;
-import com.livio.taskmaster.Task;
-import com.livio.taskmaster.Taskmaster;
-import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.protocol.enums.FunctionID;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.CancelInteraction;
-import com.smartdevicelink.proxy.rpc.KeyboardProperties;
-import com.smartdevicelink.proxy.rpc.PerformInteraction;
-import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
-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.test.TestValues;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.Collections;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RunWith(AndroidJUnit4.class)
-public class PresentChoiceSetOperationTests {
-
- private PresentChoiceSetOperation presentChoiceSetOperation;
- private ChoiceSet choiceSet;
- private ISdl internalInterface;
- private KeyboardListener keyboardListener;
- private ChoiceSetSelectionListener choiceSetSelectionListener;
-
- private Taskmaster taskmaster;
- private Queue queue;
-
- @Before
- public void setUp() throws Exception {
-
- internalInterface = mock(ISdl.class);
-
- keyboardListener = mock(KeyboardListener.class);
- choiceSetSelectionListener = mock(ChoiceSetSelectionListener.class);
-
- ChoiceCell cell1 = new ChoiceCell("Cell1");
- cell1.setChoiceId(0);
- choiceSet = new ChoiceSet("Test", Collections.singletonList(cell1), choiceSetSelectionListener);
-
- taskmaster = new Taskmaster.Builder().build();
- queue = taskmaster.createQueue("test", 100, false);
- taskmaster.start();
- }
-
-
- private KeyboardProperties getKeyBoardProperties() {
- KeyboardProperties properties = new KeyboardProperties();
- properties.setLanguage(Language.EN_US);
- properties.setKeyboardLayout(KeyboardLayout.QWERTZ);
- properties.setKeypressMode(KeypressMode.RESEND_CURRENT_ENTRY);
- return properties;
- }
-
- @Test
- public void testGetLayoutMode() {
- // First we will check knowing our keyboard listener is NOT NULL
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
-
- assertEquals(presentChoiceSetOperation.getLayoutMode(), LayoutMode.LIST_WITH_SEARCH);
- presentChoiceSetOperation.keyboardListener = null;
- assertEquals(presentChoiceSetOperation.getLayoutMode(), LayoutMode.LIST_ONLY);
- }
-
- @Test
- public void testGetPerformInteraction() {
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
-
- PerformInteraction pi = presentChoiceSetOperation.getPerformInteraction();
- assertEquals(pi.getInitialText(), "Test");
- assertNull(pi.getHelpPrompt());
- assertNull(pi.getTimeoutPrompt());
- assertNull(pi.getVrHelp());
- assertEquals(pi.getTimeout(), Integer.valueOf(10000));
- assertEquals(pi.getCancelID(), TestValues.GENERAL_INTEGER);
- assertEquals(presentChoiceSetOperation.getLayoutMode(), LayoutMode.LIST_WITH_SEARCH);
- }
-
- @Test
- public void testSetSelectedCellWithId() {
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, getKeyBoardProperties(), keyboardListener, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
-
- assertNull(presentChoiceSetOperation.selectedCellRow);
- presentChoiceSetOperation.setSelectedCellWithId(0);
- assertEquals(presentChoiceSetOperation.selectedCellRow, Integer.valueOf(0));
- }
-
- private void sleep() {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- @Test
- public void testCancelingChoiceSetSuccessfullyIfThreadIsRunning() {
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
- queue.add(presentChoiceSetOperation, false);
-
- sleep();
-
- assertEquals(Task.IN_PROGRESS, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
- Answer<Void> cancelInteractionAnswer = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) {
- Object[] args = invocation.getArguments();
- CancelInteraction cancelInteraction = (CancelInteraction) args[0];
-
- assertEquals(cancelInteraction.getCancelID(), TestValues.GENERAL_INTEGER);
- assertEquals(cancelInteraction.getInteractionFunctionID().intValue(), FunctionID.PERFORM_INTERACTION.getId());
-
- RPCResponse response = new RPCResponse(FunctionID.CANCEL_INTERACTION.toString());
- response.setSuccess(true);
- cancelInteraction.getOnRPCResponseListener().onResponse(0, response);
-
- return null;
- }
- };
- doAnswer(cancelInteractionAnswer).when(internalInterface).sendRPC(any(CancelInteraction.class));
-
- verify(internalInterface, times(1)).sendRPC(any(CancelInteraction.class));
- verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
-
- assertEquals(Task.IN_PROGRESS, presentChoiceSetOperation.getState());
- }
-
- @Test
- public void testCancelingChoiceSetUnsuccessfullyIfThreadIsRunning() {
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
- queue.add(presentChoiceSetOperation, false);
- sleep();
-
- assertEquals(Task.IN_PROGRESS, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
- Answer<Void> cancelInteractionAnswer = new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) {
- Object[] args = invocation.getArguments();
- CancelInteraction cancelInteraction = (CancelInteraction) args[0];
-
- assertEquals(cancelInteraction.getCancelID(), TestValues.GENERAL_INTEGER);
- assertEquals(cancelInteraction.getInteractionFunctionID().intValue(), FunctionID.PERFORM_INTERACTION.getId());
-
- RPCResponse response = new RPCResponse(FunctionID.CANCEL_INTERACTION.toString());
- response.setSuccess(false);
- cancelInteraction.getOnRPCResponseListener().onResponse(0, response);
-
- return null;
- }
- };
- doAnswer(cancelInteractionAnswer).when(internalInterface).sendRPC(any(CancelInteraction.class));
-
- verify(internalInterface, times(1)).sendRPC(any(CancelInteraction.class));
- verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
-
- assertEquals(Task.IN_PROGRESS, presentChoiceSetOperation.getState());
- }
-
- @Test
- public void testCancelingChoiceSetIfThreadHasFinished() {
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
- presentChoiceSetOperation.finishOperation();
-
- assertEquals(Task.FINISHED, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
- verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
-
- assertEquals(Task.FINISHED, presentChoiceSetOperation.getState());
- }
-
- @Test
- public void testCancelingChoiceSetIfThreadHasNotYetRun() {
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(6, 0));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
-
- assertEquals(Task.BLOCKED, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
-
- // Once the operation has started
- queue.add(presentChoiceSetOperation, false);
- sleep();
-
- assertEquals(Task.CANCELED, presentChoiceSetOperation.getState());
-
- // Make sure neither a `CancelInteraction` or `PerformInteraction` RPC is ever sent
- verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
- verify(internalInterface, never()).sendRPC(any(PerformInteraction.class));
- }
-
- @Test
- public void testCancelingChoiceSetIfHeadUnitDoesNotSupportFeature() {
- // Cancel Interaction is only supported on RPC specs v.6.0.0+
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(5, 3));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
- queue.add(presentChoiceSetOperation, false);
- sleep();
-
- assertEquals(Task.IN_PROGRESS, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
-
- verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
- verify(internalInterface, times(1)).sendRPC(any(PerformInteraction.class));
- }
-
- @Test
- public void testCancelingChoiceSetIfHeadUnitDoesNotSupportFeatureButThreadIsNotRunning() {
- // Cancel Interaction is only supported on RPC specs v.6.0.0+
- when(internalInterface.getSdlMsgVersion()).thenReturn(new SdlMsgVersion(5, 3));
- presentChoiceSetOperation = new PresentChoiceSetOperation(internalInterface, choiceSet, InteractionMode.MANUAL_ONLY, null, null, choiceSetSelectionListener, TestValues.GENERAL_INTEGER);
-
- assertEquals(Task.BLOCKED, presentChoiceSetOperation.getState());
-
- choiceSet.cancel();
-
- verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
-
- // Once the operation has started
- queue.add(presentChoiceSetOperation, false);
- sleep();
-
- assertEquals(Task.CANCELED, presentChoiceSetOperation.getState());
-
- // Make sure neither a `CancelInteraction` or `PerformInteraction` RPC is ever sent
- verify(internalInterface, never()).sendRPC(any(CancelInteraction.class));
- verify(internalInterface, never()).sendRPC(any(PerformInteraction.class));
- }
-} \ No newline at end of file
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/managers/video/HapticInterfaceManagerTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/HapticInterfaceManagerTest.java
index dc74cb12a..bd75e2440 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/HapticInterfaceManagerTest.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/video/HapticInterfaceManagerTest.java
@@ -21,6 +21,7 @@
**************************************************************************************************/
package com.smartdevicelink.managers.video;
+import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -45,9 +46,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -56,7 +55,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -222,36 +220,53 @@ public class HapticInterfaceManagerTest extends TestCase {
}
private View createViews() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
- View view = mock(View.class);
+ final View view = new View(context) {
+ private int count = 0;
- ViewGroup parent1 = mock(ViewGroup.class);
- ViewGroup parent2 = mock(ViewGroup.class);
+ @Override
+ public boolean isClickable() {
+ int curCount = count++;
+ return (curCount >= 1) && (curCount <= 4);
+ }
+ };
- when(parent1.getChildCount()).thenReturn(5);
+ final ViewGroup parent1 = new ViewGroup(context) {
+ @Override
+ protected void onLayout(boolean b, int i, int i1, int i2, int i3) {}
- when(parent1.getChildAt(0)).thenReturn(view);
- when(parent1.getChildAt(1)).thenReturn(view);
- when(parent1.getChildAt(2)).thenReturn(view);
- when(parent1.getChildAt(3)).thenReturn(parent2);
- when(parent1.getChildAt(4)).thenReturn(view);
+ @Override
+ public View getChildAt(int index) {
+ return view;
+ }
- when(parent2.getChildCount()).thenReturn(2);
- when(parent2.getChildAt(0)).thenReturn(view);
- when(parent2.getChildAt(1)).thenReturn(view);
+ @Override
+ public int getChildCount() {
+ return 2;
+ }
+ };
+ final ViewGroup parent2 = new ViewGroup(context) {
+ @Override
+ protected void onLayout(boolean b, int i, int i1, int i2, int i3) {}
- doAnswer(new Answer<Boolean>() {
- private int count = 0;
+ @Override
+ public View getChildAt(int index) {
+ if (index == 3) {
+ return parent1;
+ } else {
+ return view;
+ }
+ }
@Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- int curCount = count++;
- return (curCount >= 1) && (curCount <= 4);
+ public int getChildCount() {
+ return 5;
}
- }).when(view).isClickable();
+ };
- return parent1;
+ return parent2;
}
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/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Validator.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Validator.java
index e8d89c818..094b29794 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Validator.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/Validator.java
@@ -3,6 +3,9 @@ package com.smartdevicelink.test;
import com.smartdevicelink.managers.file.filetypes.SdlFile;
import com.smartdevicelink.protocol.enums.FrameDataControlFrameType;
import com.smartdevicelink.protocol.enums.FrameType;
+import com.smartdevicelink.protocol.enums.SecurityQueryErrorCode;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
import com.smartdevicelink.protocol.enums.SessionType;
import com.smartdevicelink.proxy.rpc.*;
import com.smartdevicelink.proxy.rpc.enums.AppServiceType;
@@ -10,7 +13,6 @@ import com.smartdevicelink.proxy.rpc.enums.DefrostZone;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
import com.smartdevicelink.proxy.rpc.enums.HmiZoneCapabilities;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardLayout;
import com.smartdevicelink.proxy.rpc.enums.PRNDL;
import com.smartdevicelink.proxy.rpc.enums.PrerecordedSpeech;
import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities;
@@ -126,6 +128,75 @@ public class Validator {
return true;
}
+ public static boolean validateQueryTypeArray(SecurityQueryType[] array1, SecurityQueryType[] array2) {
+
+ if (array1 == null) {
+ return (array2 == null);
+ }
+
+ if (array2 == null) {
+ return (array1 == null);
+ }
+
+ if (array1.length != array2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < array1.length; i++) {
+ if (array1[i] != array2[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean validateQueryIDArray(SecurityQueryID[] array1, SecurityQueryID[] array2) {
+
+ if (array1 == null) {
+ return (array2 == null);
+ }
+
+ if (array2 == null) {
+ return (array1 == null);
+ }
+
+ if (array1.length != array2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < array1.length; i++) {
+ if (array1[i] != array2[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean validateQueryErrorCodeArray(SecurityQueryErrorCode[] array1, SecurityQueryErrorCode[] array2) {
+
+ if (array1 == null) {
+ return (array2 == null);
+ }
+
+ if (array2 == null) {
+ return (array1 == null);
+ }
+
+ if (array1.length != array2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < array1.length; i++) {
+ if (array1[i] != array2[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public static boolean validateFrameDataControlFrameTypeArray(FrameDataControlFrameType[] array1, FrameDataControlFrameType[] array2) {
if (array1 == null) {
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/SecurityQueryPayloadTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/SecurityQueryPayloadTests.java
new file mode 100644
index 000000000..8c0c36041
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/SecurityQueryPayloadTests.java
@@ -0,0 +1,126 @@
+package com.smartdevicelink.test.protocol;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.smartdevicelink.protocol.SecurityQueryPayload;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
+import com.smartdevicelink.util.BitConverter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+public class SecurityQueryPayloadTests {
+
+ public static SecurityQueryPayload createDummyBqh() {
+ SecurityQueryPayload bqh = new SecurityQueryPayload();
+ bqh.setCorrelationID(123);
+ bqh.setQueryID(SecurityQueryID.SEND_HANDSHAKE_DATA);
+ bqh.setQueryType(SecurityQueryType.REQUEST);
+ bqh.setBulkData(null);
+ bqh.setJsonSize(0);
+ return bqh;
+ }
+
+ public SecurityQueryPayload safeParse(byte[] array) {
+ try {
+ return SecurityQueryPayload.parseBinaryQueryHeader(array);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ @Test
+ public void testCorrectParsing() {
+ byte[] array = new byte[12];
+ array[0] = 0;
+ array[1] = 0;
+ array[2] = 0;
+ array[3] = 2;
+ array[4] = 0;
+ array[5] = 0;
+ array[6] = 0;
+ array[7] = 3;
+ array[8] = 0;
+ array[9] = 0;
+ array[10] = 0;
+ array[11] = 0;
+
+ SecurityQueryPayload parsedBqh = SecurityQueryPayload.parseBinaryQueryHeader(array);
+ assertEquals(parsedBqh.getQueryType(), SecurityQueryType.REQUEST);
+ assertEquals(parsedBqh.getQueryID(), SecurityQueryID.SEND_INTERNAL_ERROR);
+ assertEquals(parsedBqh.getCorrelationID(), 3);
+ assertEquals(parsedBqh.getJsonSize(), 0);
+ }
+
+ @Test
+ public void testCorrectHeaderAssembly() {
+ SecurityQueryPayload dummyBqh = new SecurityQueryPayload();
+ dummyBqh.setQueryType(SecurityQueryType.REQUEST);
+ dummyBqh.setQueryID(SecurityQueryID.SEND_HANDSHAKE_DATA);
+ dummyBqh.setCorrelationID(3);
+ dummyBqh.setJsonSize(0);
+
+ byte[] assembledHeader = dummyBqh.assembleHeaderBytes();
+ assertEquals(dummyBqh.getQueryType(), SecurityQueryType.valueOf(assembledHeader[0]));
+ byte[] queryIDFromHeader = new byte[3];
+ System.arraycopy(assembledHeader, 1, queryIDFromHeader, 0, 3);
+ assertEquals(dummyBqh.getQueryID(), SecurityQueryID.valueOf(queryIDFromHeader));
+ assertEquals(dummyBqh.getCorrelationID(), BitConverter.intFromByteArray(assembledHeader, 4));
+ assertEquals(dummyBqh.getJsonSize(), BitConverter.intFromByteArray(assembledHeader, 8));
+ }
+
+ @Test
+ public void testAssemblyAndParse() {
+ SecurityQueryPayload bqh = createDummyBqh();
+
+ byte[] bqhBytes = bqh.assembleHeaderBytes();
+ assertNotNull(bqhBytes);
+
+ SecurityQueryPayload parsedBqh = SecurityQueryPayload.parseBinaryQueryHeader(bqhBytes);
+ assertNotNull(parsedBqh);
+
+ assertEquals(bqh.getCorrelationID(), parsedBqh.getCorrelationID());
+ assertEquals(bqh.getQueryID(), parsedBqh.getQueryID());
+ assertEquals(bqh.getQueryType(), parsedBqh.getQueryType());
+ assertEquals(bqh.getBulkData(), parsedBqh.getBulkData());
+ assertEquals(bqh.getJsonData(), parsedBqh.getJsonData());
+ assertEquals(bqh.getJsonSize(), parsedBqh.getJsonSize());
+ }
+
+ @Test
+ public void testCorruptHeader() {
+ SecurityQueryPayload bqh = createDummyBqh();
+
+ byte[] bqhBytes = bqh.assembleHeaderBytes();
+
+ assertNotNull(safeParse(bqhBytes));
+
+ int size = bqhBytes.length;
+ for (int i = 0; i < size; i++) {
+ bqhBytes[i] = (byte) 0x99;
+ }
+
+ assertNull(safeParse(bqhBytes));
+ SecurityQueryPayload head = SecurityQueryPayload.parseBinaryQueryHeader(bqhBytes);
+ assertNull(head);
+ }
+
+ @Test
+ public void testJsonSetException() {
+ try {
+ SecurityQueryPayload bqh = createDummyBqh();
+ bqh.setJsonData(null);
+ fail("Setting JSON data to null should have thrown an exception");
+ } catch (Exception e) {
+ //Pass
+ }
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryErrorCodeTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryErrorCodeTests.java
new file mode 100644
index 000000000..0b6cd3f61
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryErrorCodeTests.java
@@ -0,0 +1,189 @@
+package com.smartdevicelink.test.protocol.enums;
+
+import com.smartdevicelink.protocol.enums.SecurityQueryErrorCode;
+import com.smartdevicelink.test.Validator;
+
+import junit.framework.TestCase;
+
+import java.util.Vector;
+
+public class SecurityQueryErrorCodeTests extends TestCase {
+
+ private Vector<SecurityQueryErrorCode> list = SecurityQueryErrorCode.getList();
+
+ public void testValidEnums() {
+ final byte ERROR_SUCCESS_BYTE = (byte) 0x00;
+ final String ERROR_SUCCESS_STRING = "ERROR_SUCCESS";
+
+ final byte ERROR_INVALID_QUERY_SIZE_BYTE = (byte) 0x01;
+ final String ERROR_INVALID_QUERY_SIZE_STRING = "ERROR_INVALID_QUERY_SIZE";
+
+ final byte ERROR_INVALID_QUERY_ID_BYTE = (byte) 0x02;
+ final String ERROR_INVALID_QUERY_ID_STRING = "ERROR_INVALID_QUERY_ID";
+
+ final byte ERROR_NOT_SUPPORTED_BYTE = (byte) 0x03;
+ final String ERROR_NOT_SUPPORTED_STRING = "ERROR_NOT_SUPPORTED";
+
+ final byte ERROR_SERVICE_ALREADY_PROTECTED_BYTE = (byte) 0x04;
+ final String ERROR_SERVICE_ALREADY_PROTECTED_STRING = "ERROR_SERVICE_ALREADY_PROTECTED";
+
+ final byte ERROR_SERVICE_NOT_PROTECTED_BYTE = (byte) 0x05;
+ final String ERROR_SERVICE_NOT_PROTECTED_STRING = "ERROR_SERVICE_NOT_PROTECTED";
+
+ final byte ERROR_DECRYPTION_FAILED_BYTE = (byte) 0x06;
+ final String ERROR_DECRYPTION_FAILED_STRING = "ERROR_DECRYPTION_FAILED";
+
+ final byte ERROR_ENCRYPTION_FAILED_BYTE = (byte) 0x07;
+ final String ERROR_ENCRYPTION_FAILED_STRING = "ERROR_ENCRYPTION_FAILED";
+
+ final byte ERROR_SSL_INVALID_DATA_BYTE = (byte) 0x08;
+ final String ERROR_SSL_INVALID_DATA_STRING = "ERROR_SSL_INVALID_DATA";
+
+ final byte ERROR_HANDSHAKE_FAILED_BYTE = (byte) 0x09;
+ final String ERROR_HANDSHAKE_FAILED_STRING = "ERROR_HANDSHAKE_FAILED";
+
+ final byte INVALID_CERT_BYTE = (byte) 0x0A;
+ final String INVALID_CERT_STRING = "INVALID_CERT";
+
+ final byte EXPIRED_CERT_BYTE = (byte) 0x0B;
+ final String EXPIRED_CERT_STRING = "EXPIRED_CERT";
+
+ final byte ERROR_INTERNAL_BYTE = (byte) 0xFF;
+ final String ERROR_INTERNAL_STRING = "ERROR_INTERNAL";
+
+ final byte ERROR_UNKNOWN_INTERNAL_ERROR_BYTE = (byte) 0xFE;
+ final String ERROR_UNKNOWN_INTERNAL_ERROR_STRING = "ERROR_UNKNOWN_INTERNAL_ERROR";
+
+ try {
+ assertNotNull("QueryErrorCode list returned null", list);
+
+ SecurityQueryErrorCode enumSuccess = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SUCCESS_BYTE);
+ SecurityQueryErrorCode enumInvalidQuerySize = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INVALID_QUERY_SIZE_BYTE);
+ SecurityQueryErrorCode enumInvalidQueryID = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INVALID_QUERY_ID_BYTE);
+ SecurityQueryErrorCode enumNotSupported = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_NOT_SUPPORTED_BYTE);
+ SecurityQueryErrorCode enumServiceAlreadyProtected = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SERVICE_ALREADY_PROTECTED_BYTE);
+ SecurityQueryErrorCode enumServiceNotProtected = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SERVICE_NOT_PROTECTED_BYTE);
+ SecurityQueryErrorCode enumDecryptionFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_DECRYPTION_FAILED_BYTE);
+ SecurityQueryErrorCode enumEncryptionFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_ENCRYPTION_FAILED_BYTE);
+ SecurityQueryErrorCode enumSSLInvalidData = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SSL_INVALID_DATA_BYTE);
+ SecurityQueryErrorCode enumHandshakeFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_HANDSHAKE_FAILED_BYTE);
+ SecurityQueryErrorCode enumInvalidCert = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, INVALID_CERT_BYTE);
+ SecurityQueryErrorCode enumExpiredCert = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, EXPIRED_CERT_BYTE);
+ SecurityQueryErrorCode enumInternal = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INTERNAL_BYTE);
+ SecurityQueryErrorCode enumUnknownInternalError = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_UNKNOWN_INTERNAL_ERROR_BYTE);
+
+ assertNotNull("Success byte match returned null", enumSuccess);
+ assertNotNull("Invalid Query Size byte match returned null", enumInvalidQuerySize);
+ assertNotNull("Invalid Query ID byte match returned null", enumInvalidQueryID);
+ assertNotNull("Not Supported byte match returned null", enumNotSupported);
+ assertNotNull("Service Already Protected byte match returned null", enumServiceAlreadyProtected);
+ assertNotNull("Service Not Protected byte match returned null", enumServiceNotProtected);
+ assertNotNull("Decryption Failed byte match returned null", enumDecryptionFailed);
+ assertNotNull("Encryption Failed byte match returned null", enumEncryptionFailed);
+ assertNotNull("SSL Invalid Data byte match returned null", enumSSLInvalidData);
+ assertNotNull("Handshake Failed byte match returned null", enumHandshakeFailed);
+ assertNotNull("Invalid Cert byte match returned null", enumInvalidCert);
+ assertNotNull("Expired Cert byte match returned null", enumExpiredCert);
+ assertNotNull("Internal byte match returned null", enumInternal);
+ assertNotNull("Unknown Internal byte match returned null", enumUnknownInternalError);
+
+ enumSuccess = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SUCCESS_STRING);
+ enumInvalidQuerySize = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INVALID_QUERY_SIZE_STRING);
+ enumInvalidQueryID = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INVALID_QUERY_ID_STRING);
+ enumNotSupported = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_NOT_SUPPORTED_STRING);
+ enumServiceAlreadyProtected = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SERVICE_ALREADY_PROTECTED_STRING);
+ enumServiceNotProtected = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SERVICE_NOT_PROTECTED_STRING);
+ enumDecryptionFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_DECRYPTION_FAILED_STRING);
+ enumEncryptionFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_ENCRYPTION_FAILED_STRING);
+ enumSSLInvalidData = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_SSL_INVALID_DATA_STRING);
+ enumHandshakeFailed = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_HANDSHAKE_FAILED_STRING);
+ enumInvalidCert = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, INVALID_CERT_STRING);
+ enumExpiredCert = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, EXPIRED_CERT_STRING);
+ enumInternal = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_INTERNAL_STRING);
+ enumUnknownInternalError = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, ERROR_UNKNOWN_INTERNAL_ERROR_STRING);
+
+ assertNotNull("Success string match returned null", enumSuccess);
+ assertNotNull("Invalid Query Size string match returned null", enumInvalidQuerySize);
+ assertNotNull("Invalid Query ID string match returned null", enumInvalidQueryID);
+ assertNotNull("Not Supported string match returned null", enumNotSupported);
+ assertNotNull("Service Already Protected string match returned null", enumServiceAlreadyProtected);
+ assertNotNull("Service Not Protected string match returned null", enumServiceNotProtected);
+ assertNotNull("Decryption Failed string match returned null", enumDecryptionFailed);
+ assertNotNull("Encryption Failed string match returned null", enumEncryptionFailed);
+ assertNotNull("SSL Invalid Data string match returned null", enumSSLInvalidData);
+ assertNotNull("Handshake Failed string match returned null", enumHandshakeFailed);
+ assertNotNull("Invalid Cert string match returned null", enumInvalidCert);
+ assertNotNull("Expired Cert string match returned null", enumExpiredCert);
+ assertNotNull("Internal string match returned null", enumInternal);
+ assertNotNull("Unknown Internal string match returned null", enumUnknownInternalError);
+ } catch (NullPointerException exception) {
+ fail("Null enum list throws NullPointerException.");
+ }
+ }
+
+ public void testInvalidEnum() {
+ final byte INVALID_BYTE = (byte) 0xAB;
+ final String INVALID_STRING = "Invalid";
+
+ try {
+ SecurityQueryErrorCode enumInvalid = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, INVALID_BYTE);
+ assertNull("Invalid byte match didn't return null", enumInvalid);
+
+ enumInvalid = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, INVALID_STRING);
+ assertNull("Invalid byte match didn't return null", enumInvalid);
+ } catch (IllegalArgumentException exception) {
+ fail("Invalid enum throws IllegalArgumentException.");
+ }
+ }
+
+ public void testNullEnum() {
+ try {
+ SecurityQueryErrorCode enumNull = (SecurityQueryErrorCode) SecurityQueryErrorCode.get(list, null);
+ assertNull("Null lookup returns a value", enumNull);
+ } catch (NullPointerException exception) {
+ fail("Null string throws NullPointerException.");
+ }
+ }
+
+ public void testListEnum() {
+ Vector<SecurityQueryErrorCode> enumTestList = new Vector<>();
+ enumTestList.add(SecurityQueryErrorCode.ERROR_SUCCESS);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_INVALID_QUERY_SIZE);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_INVALID_QUERY_ID);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_NOT_SUPPORTED);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_SERVICE_ALREADY_PROTECTED);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_SERVICE_NOT_PROTECTED);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_DECRYPTION_FAILED);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_ENCRYPTION_FAILED);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_SSL_INVALID_DATA);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_HANDSHAKE_FAILED);
+ enumTestList.add(SecurityQueryErrorCode.INVALID_CERT);
+ enumTestList.add(SecurityQueryErrorCode.EXPIRED_CERT);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_INTERNAL);
+ enumTestList.add(SecurityQueryErrorCode.ERROR_UNKNOWN_INTERNAL_ERROR);
+
+ assertTrue("List does not match enum test list.",
+ list.containsAll(enumTestList) &&
+ enumTestList.containsAll(list));
+
+ SecurityQueryErrorCode[] enumValueArray = SecurityQueryErrorCode.values();
+ SecurityQueryErrorCode[] enumTestArray = {
+ SecurityQueryErrorCode.ERROR_SUCCESS,
+ SecurityQueryErrorCode.ERROR_INVALID_QUERY_SIZE,
+ SecurityQueryErrorCode.ERROR_INVALID_QUERY_ID,
+ SecurityQueryErrorCode.ERROR_NOT_SUPPORTED,
+ SecurityQueryErrorCode.ERROR_SERVICE_ALREADY_PROTECTED,
+ SecurityQueryErrorCode.ERROR_SERVICE_NOT_PROTECTED,
+ SecurityQueryErrorCode.ERROR_DECRYPTION_FAILED,
+ SecurityQueryErrorCode.ERROR_ENCRYPTION_FAILED,
+ SecurityQueryErrorCode.ERROR_SSL_INVALID_DATA,
+ SecurityQueryErrorCode.ERROR_HANDSHAKE_FAILED,
+ SecurityQueryErrorCode.INVALID_CERT,
+ SecurityQueryErrorCode.EXPIRED_CERT,
+ SecurityQueryErrorCode.ERROR_INTERNAL,
+ SecurityQueryErrorCode.ERROR_UNKNOWN_INTERNAL_ERROR
+ };
+ assertTrue("Array does not match enum values array.",
+ Validator.validateQueryErrorCodeArray(enumValueArray, enumTestArray));
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryIDTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryIDTests.java
new file mode 100644
index 000000000..4f73f8139
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryIDTests.java
@@ -0,0 +1,91 @@
+package com.smartdevicelink.test.protocol.enums;
+
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.test.Validator;
+
+import junit.framework.TestCase;
+
+import java.util.Vector;
+
+public class SecurityQueryIDTests extends TestCase {
+
+ private Vector<SecurityQueryID> list = SecurityQueryID.getList();
+
+ public void testValidEnums() {
+ final byte[] SEND_HANDSHAKE_DATA_BYTES = {(byte) 0x00, (byte) 0x00, (byte) 0x01};
+ final String SEND_HANDSHAKE_DATA_STRING = "SEND_HANDSHAKE_DATA";
+
+ final byte[] SEND_INTERNAL_ERROR_BYTES = {(byte) 0x00, (byte) 0x00, (byte) 0x02};
+ final String SEND_INTERNAL_ERROR_STRING = "SEND_INTERNAL_ERROR";
+
+ final byte[] INVALID_QUERY_ID_BYTES = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+ final String INVALID_QUERY_ID_STRING = "INVALID_QUERY_ID";
+
+ try {
+ assertNotNull("QueryID list returned null", list);
+
+ SecurityQueryID enumHandshakeData = (SecurityQueryID) SecurityQueryID.get(list, SEND_HANDSHAKE_DATA_BYTES);
+ SecurityQueryID enumInternalError = (SecurityQueryID) SecurityQueryID.get(list, SEND_INTERNAL_ERROR_BYTES);
+ SecurityQueryID enumInvalidSecurityQueryId = (SecurityQueryID) SecurityQueryID.get(list, INVALID_QUERY_ID_BYTES);
+
+ assertNotNull("Send Handshake Data byte match returned null", enumHandshakeData);
+ assertNotNull("Send Internal Error byte match returned null", enumInternalError);
+ assertNotNull("Send Invalid QueryID byte match returned null", enumInvalidSecurityQueryId);
+
+ enumHandshakeData = (SecurityQueryID) SecurityQueryID.get(list, SEND_HANDSHAKE_DATA_STRING);
+ enumInternalError = (SecurityQueryID) SecurityQueryID.get(list, SEND_INTERNAL_ERROR_STRING);
+ enumInvalidSecurityQueryId = (SecurityQueryID) SecurityQueryID.get(list, INVALID_QUERY_ID_STRING);
+
+ assertNotNull("Send Handshake Data string match returned null", enumHandshakeData);
+ assertNotNull("Send Internal Error string match returned null", enumInternalError);
+ assertNotNull("Send Invalid QueryID string match returned null", enumInvalidSecurityQueryId);
+ } catch(NullPointerException exception) {
+ fail("Null enum list throws NullPointerException.");
+ }
+ }
+
+ public void testInvalidEnum() {
+
+ final byte[] INVALID_BYTE_ARRAY = {(byte) 0xAB, (byte) 0xAB, (byte) 0xAB};
+ final String INVALID_STRING = "Invalid";
+
+ try {
+ SecurityQueryID enumInvalid = (SecurityQueryID) SecurityQueryID.get(list, INVALID_BYTE_ARRAY);
+ assertNull("Invalid byte[] match didn't return null", enumInvalid);
+
+ enumInvalid = (SecurityQueryID) SecurityQueryID.get(list, INVALID_STRING);
+ assertNull("Invalid string match didn't return null", enumInvalid);
+ } catch (IllegalArgumentException exception) {
+ fail("Invalid enum throws IllegalArgumentException.");
+ }
+ }
+
+ public void testNullEnum() {
+ try {
+ SecurityQueryID enumNull = (SecurityQueryID) SecurityQueryID.get(list, (String) null);
+ assertNull("Null lookup returns a null string value", enumNull);
+
+ enumNull = (SecurityQueryID) SecurityQueryID.get(list, (byte[]) null);
+ assertNull("Null lookup returns a null byte[] value", enumNull);
+ }catch (NullPointerException exception) {
+ fail("Null string throws NullPointerException.");
+ }
+ }
+
+ public void testListEnum() {
+ Vector<SecurityQueryID> enumTestList = new Vector<>();
+ enumTestList.add(SecurityQueryID.SEND_HANDSHAKE_DATA);
+ enumTestList.add(SecurityQueryID.SEND_INTERNAL_ERROR);
+ enumTestList.add(SecurityQueryID.INVALID_QUERY_ID);
+
+ assertTrue("List does not match enum test list.",
+ list.containsAll(enumTestList) &&
+ enumTestList.containsAll(list));
+
+ SecurityQueryID[] enumValueArray = SecurityQueryID.values();
+ SecurityQueryID[] enumTestArray = {SecurityQueryID.SEND_HANDSHAKE_DATA, SecurityQueryID.SEND_INTERNAL_ERROR, SecurityQueryID.INVALID_QUERY_ID};
+ assertTrue("Array does not match enum values array.",
+ Validator.validateQueryIDArray(enumValueArray, enumTestArray));
+ }
+
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryTypeTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryTypeTests.java
new file mode 100644
index 000000000..6835da946
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/protocol/enums/SecurityQueryTypeTests.java
@@ -0,0 +1,97 @@
+package com.smartdevicelink.test.protocol.enums;
+
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
+import com.smartdevicelink.test.Validator;
+
+import junit.framework.TestCase;
+
+import java.util.Vector;
+
+public class SecurityQueryTypeTests extends TestCase {
+
+ private Vector<SecurityQueryType> list = SecurityQueryType.getList();
+
+ public void testValidEnums() {
+ final byte REQUEST_BYTE = (byte) 0x00;
+ final String REQUEST_STRING = "REQUEST";
+
+ final byte RESPONSE_BYTE = (byte) 0x10;
+ final String RESPONSE_STRING = "RESPONSE";
+
+ final byte NOTIFICATION_BYTE = (byte) 0x20;
+ final String NOTIFICATION_STRING = "NOTIFICATION";
+
+ final byte INVALID_QUERY_TYPE_BYTE = (byte) 0xFF;
+ final String INVALID_QUERY_TYPE_STRING = "INVALID_QUERY_TYPE";
+
+ try {
+ assertNotNull("QueryType list returned null", list);
+
+ SecurityQueryType enumRequest = (SecurityQueryType) SecurityQueryType.get(list, REQUEST_BYTE);
+ SecurityQueryType enumResponse = (SecurityQueryType) SecurityQueryType.get(list, RESPONSE_BYTE);
+ SecurityQueryType enumNotification = (SecurityQueryType) SecurityQueryType.get(list, NOTIFICATION_BYTE);
+ SecurityQueryType enumInvalidSecurityQueryType = (SecurityQueryType) SecurityQueryType.get(list, INVALID_QUERY_TYPE_BYTE);
+
+ assertNotNull("Request byte match returned null", enumRequest);
+ assertNotNull("Response byte match returned null", enumResponse);
+ assertNotNull("Notification byte match returned null", enumNotification);
+ assertNotNull("Invalid Query Type byte match returned null", enumInvalidSecurityQueryType);
+
+ enumRequest = (SecurityQueryType) SecurityQueryType.get(list, REQUEST_STRING);
+ enumResponse = (SecurityQueryType) SecurityQueryType.get(list, RESPONSE_STRING);
+ enumNotification = (SecurityQueryType) SecurityQueryType.get(list, NOTIFICATION_STRING);
+ enumInvalidSecurityQueryType = (SecurityQueryType) SecurityQueryType.get(list, INVALID_QUERY_TYPE_STRING);
+
+ assertNotNull("Request string match returned null", enumRequest);
+ assertNotNull("Response string match returned null", enumResponse);
+ assertNotNull("Notification string match returned null", enumNotification);
+ assertNotNull("Invalid Query string byte match returned null", enumInvalidSecurityQueryType);
+
+
+ }catch (NullPointerException exception) {
+ fail("Null enum list throws NullPointerException.");
+ }
+ }
+
+ public void testInvalidEnum() {
+
+ final byte INVALID_BYTE = (byte) 0xAB;
+ final String INVALID_STRING = "Invalid";
+
+ try {
+ SecurityQueryType enumInvalid = (SecurityQueryType) SecurityQueryType.get(list, INVALID_BYTE);
+ assertNull("Invalid byte match didn't return null", enumInvalid);
+
+ enumInvalid = (SecurityQueryType) SecurityQueryType.get(list, INVALID_STRING);
+ assertNull("Invalid string match didn't return null", enumInvalid);
+ } catch (IllegalArgumentException exception) {
+ fail("Invalid enum throws IllegalArgumentException.");
+ }
+ }
+
+ public void testNullEnum() {
+ try {
+ SecurityQueryType enumNull = (SecurityQueryType) SecurityQueryType.get(list, null);
+ assertNull("Null lookup returns a value", enumNull);
+ } catch (NullPointerException exception) {
+ fail("Null string throws NullPointerException.");
+ }
+ }
+
+ public void testListEnum() {
+ Vector<SecurityQueryType> enumTestList = new Vector<>();
+ enumTestList.add(SecurityQueryType.REQUEST);
+ enumTestList.add(SecurityQueryType.RESPONSE);
+ enumTestList.add(SecurityQueryType.NOTIFICATION);
+ enumTestList.add(SecurityQueryType.INVALID_QUERY_TYPE);
+
+ assertTrue("List does not match enum test list.",
+ list.containsAll(enumTestList) &&
+ enumTestList.containsAll(list));
+
+ SecurityQueryType[] enumValueArray = SecurityQueryType.values();
+ SecurityQueryType[] enumTestArray = {SecurityQueryType.REQUEST, SecurityQueryType.RESPONSE, SecurityQueryType.NOTIFICATION, SecurityQueryType.INVALID_QUERY_TYPE};
+ assertTrue("Array does not match enum values array.",
+ Validator.validateQueryTypeArray(enumValueArray, enumTestArray));
+ }
+}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/VehicleTypeTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/VehicleTypeTest.java
index a77de7473..9bb432e51 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/VehicleTypeTest.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/rpc/datatypes/VehicleTypeTest.java
@@ -8,7 +8,10 @@ import junit.framework.TestCase;
import org.json.JSONException;
import org.json.JSONObject;
+import org.junit.Assert;
+import java.util.HashMap;
+import java.util.Hashtable;
import java.util.Iterator;
public class VehicleTypeTest extends TestCase {
@@ -72,4 +75,15 @@ public class VehicleTypeTest extends TestCase {
fail(TestValues.JSON_FAIL);
}
}
+
+ public void testHashMapConstructor() {
+ Hashtable<String, Object> store = msg.getStore();
+ HashMap<String, Object> hashMap = new HashMap(store);
+ VehicleType type = new VehicleType(hashMap);
+
+ Assert.assertEquals(type.getMake(), msg.getMake());
+ Assert.assertEquals(type.getModel(), msg.getModel());
+ Assert.assertEquals(type.getModelYear(), msg.getModelYear());
+ Assert.assertEquals(type.getTrim(), msg.getTrim());
+ }
} \ No newline at end of file
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/util/SdlAppInfoTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/util/SdlAppInfoTests.java
index 7909d4997..1dfa0700b 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/util/SdlAppInfoTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/util/SdlAppInfoTests.java
@@ -42,6 +42,8 @@ import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.smartdevicelink.R;
+import com.smartdevicelink.proxy.rpc.VehicleType;
+import com.smartdevicelink.test.TestValues;
import com.smartdevicelink.util.SdlAppInfo;
import org.junit.Before;
@@ -49,10 +51,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
@@ -85,7 +89,7 @@ public class SdlAppInfoTests {
@Test
public void testConstructorWithDefaultData() {
- SdlAppInfo info = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo);
+ SdlAppInfo info = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo, context);
assertNotNull(info);
@@ -105,10 +109,10 @@ public class SdlAppInfoTests {
*/
@Test
public void testCompareVersion() {
- SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo);
+ SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo, context);
int newVersion = context.getResources().getInteger(R.integer.sdl_router_service_version_value) + 1;
- SdlAppInfo testInfo = new SdlAppInfo(createResolveInfo(newVersion, "com.smartdevicelink.test2", "com.smartdevicelink.test2.SdlRouterService", false), defaultPackageInfo);
+ SdlAppInfo testInfo = new SdlAppInfo(createResolveInfo(newVersion, "com.smartdevicelink.test2", "com.smartdevicelink.test2.SdlRouterService", false), defaultPackageInfo, context);
List<SdlAppInfo> infos = new ArrayList<>();
infos.add(defaultInfo);
@@ -126,10 +130,10 @@ public class SdlAppInfoTests {
*/
@Test
public void testCompareVersionAndCustom() {
- SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo);
+ SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo, context);
int newVersion = context.getResources().getInteger(R.integer.sdl_router_service_version_value) + 1;
- SdlAppInfo testInfo = new SdlAppInfo(createResolveInfo(newVersion, "com.smartdevicelink.test2", "com.smartdevicelink.test2.SdlRouterService", true), defaultPackageInfo);
+ SdlAppInfo testInfo = new SdlAppInfo(createResolveInfo(newVersion, "com.smartdevicelink.test2", "com.smartdevicelink.test2.SdlRouterService", true), defaultPackageInfo, context);
List<SdlAppInfo> infos = new ArrayList<>();
infos.add(defaultInfo);
@@ -147,12 +151,12 @@ public class SdlAppInfoTests {
*/
@Test
public void testCompareUpdatedTime() {
- SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo);
+ SdlAppInfo defaultInfo = new SdlAppInfo(defaultResolveInfo, defaultPackageInfo, context);
PackageInfo packageInfo = new PackageInfo();
packageInfo.firstInstallTime = defaultPackageInfo.firstInstallTime;
packageInfo.lastUpdateTime = defaultPackageInfo.lastUpdateTime + 500;
- SdlAppInfo testInfo = new SdlAppInfo(defaultResolveInfo, packageInfo);
+ SdlAppInfo testInfo = new SdlAppInfo(defaultResolveInfo, packageInfo, context);
List<SdlAppInfo> infos = new ArrayList<>();
infos.add(defaultInfo);
@@ -179,5 +183,106 @@ public class SdlAppInfoTests {
return info;
}
+ @Test
+ public void testDeserializeVehicleInfo() {
+ VehicleType type = new VehicleType();
+ type.setMake("SDL");
+ type.setModel("Car");
+ type.setModelYear("2019");
+ type.setTrim("GT");
+ List<VehicleType> deserializedList = SdlAppInfo.deserializeSupportedVehicles(getInstrumentation().getContext().getResources().getXml(com.smartdevicelink.test.R.xml.supported_vehicle_type));
+ assertTrue(deserializedList.contains(type));
+ assertEquals(1, deserializedList.size());
+ }
+
+ @Test
+ public void testVehicleTypeSupported() {
+ // tests check with all params
+ VehicleType type1 = new VehicleType();
+ type1.setMake(TestValues.GENERAL_STRING);
+ type1.setModel(TestValues.GENERAL_STRING);
+ type1.setMake(TestValues.GENERAL_STRING);
+ type1.setTrim(TestValues.GENERAL_STRING);
+
+ VehicleType type2 = new VehicleType();
+ type2.setMake(TestValues.GENERAL_STRING);
+ type2.setModel(TestValues.GENERAL_STRING);
+ type2.setModelYear(TestValues.GENERAL_INTEGER.toString());
+ type2.setTrim(TestValues.GENERAL_STRING);
+
+ List<VehicleType> supportedVehicleList = Arrays.asList(type1, type2);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(supportedVehicleList, type2));
+
+ // tests check with not all params in connectedVehicle
+ VehicleType connectedVehicle = new VehicleType();
+
+ // make only param
+ connectedVehicle.setMake(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(supportedVehicleList, connectedVehicle));
+
+ // make and model params
+ connectedVehicle.setModel(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(supportedVehicleList, connectedVehicle));
+
+ // make, model and year params
+ connectedVehicle.setModelYear(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(supportedVehicleList, connectedVehicle));
+
+ // make, model and trim params
+ connectedVehicle.setModelYear(null);
+ connectedVehicle.setTrim(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(supportedVehicleList, connectedVehicle));
+
+ // tests check with not all params in supportedVehicle
+ VehicleType supportedVehicle = new VehicleType();
+ supportedVehicle.setMake(TestValues.GENERAL_STRING);
+
+ // make param only
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(Collections.singletonList(supportedVehicle), connectedVehicle));
+
+ // make and model params
+ supportedVehicle.setModel(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(Collections.singletonList(supportedVehicle), connectedVehicle));
+
+ // make, model and trim params
+ supportedVehicle.setTrim(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(Collections.singletonList(supportedVehicle), connectedVehicle));
+
+ // make, model and trim params
+ supportedVehicle.setTrim(TestValues.GENERAL_STRING);
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(Collections.singletonList(supportedVehicle), connectedVehicle));
+
+ // make, model and trim params
+ connectedVehicle.setTrim(null);
+ connectedVehicle.setModelYear(TestValues.GENERAL_INTEGER.toString());
+ supportedVehicle.setModelYear(TestValues.GENERAL_INTEGER.toString());
+ assertTrue(SdlAppInfo.checkIfVehicleSupported(Collections.singletonList(supportedVehicle), connectedVehicle));
+ }
+
+ @Test
+ public void testVehicleTypeNotSupported() {
+ VehicleType type1 = new VehicleType();
+
+ type1.setModel(TestValues.GENERAL_STRING);
+ type1.setMake(TestValues.GENERAL_INTEGER.toString());
+ type1.setTrim(TestValues.GENERAL_STRING);
+ type1.setModelYear(TestValues.GENERAL_STRING);
+
+ VehicleType type2 = new VehicleType();
+
+ type2.setModel(TestValues.GENERAL_STRING);
+ type2.setMake(TestValues.GENERAL_INTEGER.toString());
+ type2.setTrim(TestValues.GENERAL_STRING);
+ type2.setModelYear(TestValues.GENERAL_STRING);
+
+ VehicleType type3 = new VehicleType();
+
+ type3.setModel(TestValues.GENERAL_STRING);
+ type3.setMake(TestValues.GENERAL_STRING);
+ type3.setTrim(TestValues.GENERAL_STRING);
+ type3.setModelYear(TestValues.GENERAL_INTEGER.toString());
+
+ assertFalse(SdlAppInfo.checkIfVehicleSupported(Arrays.asList(type1, type2), type3));
+ }
}
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/utl/AndroidToolsTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/utl/AndroidToolsTests.java
index 1ad01a1c3..c4104b585 100644
--- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/utl/AndroidToolsTests.java
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/utl/AndroidToolsTests.java
@@ -1,17 +1,26 @@
package com.smartdevicelink.test.utl;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.util.AndroidTools;
import junit.framework.Assert;
+import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
public class AndroidToolsTests {
@@ -39,4 +48,32 @@ public class AndroidToolsTests {
}
+ @Test
+ public void testVehicleTypeSave() throws JSONException {
+ Context mMockContext = mock(Context.class);
+ VehicleType mMockVehicleType = new VehicleType();
+ String mAddress = "1234";
+
+ mMockVehicleType.setMake("Ford");
+ mMockVehicleType.setTrim("GT");
+ mMockVehicleType.setModel("Mustang");
+ mMockVehicleType.setModelYear("2019");
+
+ SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
+ when(editor.commit()).thenReturn(true);
+
+ SharedPreferences sharedPrefs = Mockito.mock(SharedPreferences.class);
+ when(mMockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);
+ when(sharedPrefs.edit()).thenReturn(editor);
+ when(sharedPrefs.getString(mAddress, null)).thenReturn(mMockVehicleType.serializeJSON().toString());
+
+ AndroidTools.saveVehicleType(mMockContext, mMockVehicleType, mAddress);
+ VehicleType type = new VehicleType(AndroidTools.getVehicleTypeFromPrefs(mMockContext, mAddress));
+
+ org.junit.Assert.assertEquals(type.getMake(), mMockVehicleType.getMake());
+ org.junit.Assert.assertEquals(type.getModel(), mMockVehicleType.getModel());
+ org.junit.Assert.assertEquals(type.getModelYear(), mMockVehicleType.getModelYear());
+ org.junit.Assert.assertEquals(type.getTrim(), mMockVehicleType.getTrim());
+ }
+
}
diff --git a/android/sdl_android/src/androidTest/res/xml/supported_vehicle_type.xml b/android/sdl_android/src/androidTest/res/xml/supported_vehicle_type.xml
new file mode 100644
index 000000000..36e2fd169
--- /dev/null
+++ b/android/sdl_android/src/androidTest/res/xml/supported_vehicle_type.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resource>
+ <vehicle-type
+ make="SDL"
+ model="Car"
+ modelYear="2019"
+ trim="GT" />
+
+ <vehicle-type
+ make="SDL2"
+ modelYear="2019" />
+</resource> \ No newline at end of file
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java
index 7c8df5b98..3eba6af30 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java
@@ -337,7 +337,7 @@ public class VirtualDisplayEncoder {
Looper.prepare();
// create a Handler for this thread
- mHandler = new Handler() {
+ mHandler = new Handler(Looper.myLooper()) {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TICK: {
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
index c8887d4e1..b46745917 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
@@ -40,7 +40,6 @@ import com.smartdevicelink.proxy.rpc.Image;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.ImageType;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
-import com.smartdevicelink.util.DebugTool;
/**
* A class that extends SdlFile, representing artwork (JPEG, PNG, or BMP) to be uploaded to core
@@ -159,16 +158,10 @@ public class SdlArtwork extends SdlFile implements Cloneable {
*/
@Override
public SdlArtwork clone() {
- try {
- SdlArtwork artwork = (SdlArtwork) super.clone();
- if (artwork != null) {
- artwork.imageRPC = artwork.createImageRPC();
- }
+ SdlArtwork artwork = (SdlArtwork) super.clone();
+ if (artwork != null) {
+ artwork.imageRPC = artwork.createImageRPC();
return artwork;
- } catch (CloneNotSupportedException e) {
- if (DebugTool.isDebugEnabled()) {
- throw new RuntimeException("Clone not supported by super class");
- }
}
return null;
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
index 13c7d3119..a0c0d9369 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
@@ -38,6 +38,7 @@ import androidx.annotation.NonNull;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
+import com.smartdevicelink.util.DebugTool;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -46,7 +47,7 @@ import java.util.Arrays;
/**
* A class representing data to be uploaded to core
*/
-public class SdlFile {
+public class SdlFile implements Cloneable {
private String fileName;
private int id = -1;
private Uri uri;
@@ -368,4 +369,22 @@ public class SdlFile {
// return comparison
return hashCode() == o.hashCode();
}
+
+ /**
+ * Creates a deep copy of the object
+ *
+ * @return deep copy of the object, null if an exception occurred
+ */
+ @Override
+ public SdlFile clone() {
+ try {
+ SdlFile fileClone = (SdlFile) super.clone();
+ return fileClone;
+ } catch (CloneNotSupportedException e) {
+ if (DebugTool.isDebugEnabled()) {
+ throw new RuntimeException("Clone not supported by super class");
+ }
+ }
+ return null;
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
index 1b54b0e1f..6d4fddb3d 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java
@@ -40,6 +40,7 @@ import com.smartdevicelink.exception.SdlException;
import com.smartdevicelink.exception.SdlExceptionCause;
import com.smartdevicelink.protocol.ISdlServiceListener;
import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason;
import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import com.smartdevicelink.security.SdlSecurityBase;
@@ -49,9 +50,12 @@ import com.smartdevicelink.transport.BaseTransportConfig;
import com.smartdevicelink.transport.MultiplexTransportConfig;
import com.smartdevicelink.transport.TCPTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.AndroidTools;
import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
+import java.util.List;
/**
* The lifecycle manager creates a central point for all SDL session logic to converge. It should only be used by
@@ -98,6 +102,29 @@ public class LifecycleManager extends BaseLifecycleManager {
}
}
+ @Override
+ void saveVehicleType(String address, VehicleType type) {
+ AndroidTools.saveVehicleType(contextWeakReference.get(), type, address);
+ }
+
+ @Override
+ void saveVehicleType(List<TransportRecord> activeTransports, VehicleType type) {
+ if (activeTransports == null || activeTransports.isEmpty() || type == null) {
+ DebugTool.logWarning(TAG, "Unable to save vehicle type");
+ return;
+ }
+
+ for (TransportRecord record: activeTransports) {
+ if (record.getType() == TransportType.BLUETOOTH) {
+ String address = record.getAddress();
+ if (address != null && !address.isEmpty()) {
+ saveVehicleType(address, type);
+ }
+ break;
+ }
+ }
+ }
+
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void setContext(Context context) {
this.contextWeakReference = new WeakReference<>(context);
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
index 6f0e68aba..97e8c8bca 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lockscreen/LockScreenManager.java
@@ -38,6 +38,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.os.Handler;
+import android.os.Looper;
import androidx.annotation.RestrictTo;
@@ -227,7 +228,7 @@ public class LockScreenManager extends BaseSubManager {
mLockScreenShouldBeAutoDismissed = false;
}
if (!receivedFirstDDNotification) {
- new Handler().postDelayed(new Runnable() {
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
launchLockScreenActivity();
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/protocol/heartbeat/HeartbeatMonitor.java b/android/sdl_android/src/main/java/com/smartdevicelink/protocol/heartbeat/HeartbeatMonitor.java
index c60cdaadf..831e59da4 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/protocol/heartbeat/HeartbeatMonitor.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/protocol/heartbeat/HeartbeatMonitor.java
@@ -155,7 +155,7 @@ public class HeartbeatMonitor implements IHeartbeatMonitor {
Looper.prepare();
mHeartbeatThreadLooper = Looper.myLooper();
- mHeartbeatThreadHandler = new Handler();
+ mHeartbeatThreadHandler = new Handler(mHeartbeatThreadLooper);
mIsAckReceived = true;
isHeartbeatReceived = true;
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
index fba01a561..31cea21c8 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
@@ -348,7 +348,10 @@ public class MultiplexBluetoothTransport extends MultiplexBaseTransport {
}
private void timerDelayRemoveDialog(final BluetoothSocket sock) {
- timeOutHandler = new Handler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ timeOutHandler = new Handler(Looper.myLooper());
socketRunnable = new Runnable() {
public void run() {
//Log.e(TAG, "BLUETOOTH SOCKET CONNECT TIMEOUT - ATTEMPT TO CLOSE SOCKET");
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
index c86cb6d94..588b905cc 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
@@ -49,6 +49,7 @@ import android.util.AndroidRuntimeException;
import androidx.annotation.CallSuper;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.transport.RouterServiceValidator.TrustedListCallback;
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.transport.utl.SdlDeviceListener;
@@ -58,6 +59,8 @@ import com.smartdevicelink.util.IntegrationValidator;
import com.smartdevicelink.util.SdlAppInfo;
import com.smartdevicelink.util.ServiceFinder;
+import java.util.HashMap;
+import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
@@ -145,6 +148,15 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
DebugTool.logError(TAG, "You cannot use the default SdlRouterService class, it must be extended in your project. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!");
}
+ HashMap<String, Object> vehicleInfoStore = (HashMap<String, Object>) intent.getSerializableExtra(TransportConstants.VEHICLE_INFO_EXTRA);
+
+ VehicleType vehicleType;
+ if (vehicleInfoStore == null || vehicleInfoStore.isEmpty()) {
+ vehicleType = null;
+ } else {
+ vehicleType = new VehicleType(vehicleInfoStore);
+ }
+
//This will only be true if we are being told to reopen our SDL service because SDL is enabled
if (action.equalsIgnoreCase(TransportConstants.START_ROUTER_SERVICE_ACTION)) {
if (intent.hasExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_EXTRA)) {
@@ -178,7 +190,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
} else if (intent.getBooleanExtra(TransportConstants.PING_ROUTER_SERVICE_EXTRA, false)) {
//We were told to wake up our router services
boolean altServiceWake = intent.getBooleanExtra(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT, false);
- didStart = wakeUpRouterService(context, false, altServiceWake, device);
+ didStart = wakeUpRouterService(context, false, altServiceWake, device, vehicleType);
}
}
@@ -189,7 +201,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
if (!didStart) {
DebugTool.logInfo(TAG, "attempting to wake up router service");
- didStart = wakeUpRouterService(context, true, false, device);
+ didStart = wakeUpRouterService(context, true, false, device, vehicleType);
}
//So even though we started our own version, on some older phones we find that two services are started up so we want to make sure we send our version that we are working with
@@ -211,8 +223,10 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
* @param componentName the router service that should be started
* @param altTransportWake if the alt transport flag should be set. Only used in debug
* @param device the connected bluetooth device
+ * @param confirmedDevice if the device is confirmed
+ * @param vehicleType vehicle params retrieved from connected device
*/
- private static void startRouterService(Context context, ComponentName componentName, boolean altTransportWake, BluetoothDevice device, boolean confirmedDevice) {
+ private static void startRouterService(Context context, ComponentName componentName, boolean altTransportWake, BluetoothDevice device, boolean confirmedDevice, VehicleType vehicleType) {
if (componentName == null) {
return;
}
@@ -232,6 +246,17 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
serviceIntent.putExtra(TransportConstants.CONFIRMED_SDL_DEVICE, confirmedDevice);
}
+ if (vehicleType == null) {
+ final String address = device != null ? device.getAddress() : null;
+ Hashtable<String, Object> store = AndroidTools.getVehicleTypeFromPrefs(context, address);
+ if (store != null) {
+ vehicleType = new VehicleType(store);
+ }
+ }
+ if (vehicleType != null) {
+ serviceIntent.putExtra(TransportConstants.VEHICLE_INFO_EXTRA, vehicleType.getStore());
+ }
+
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(serviceIntent);
@@ -254,7 +279,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
}
}
- private boolean wakeUpRouterService(final Context context, final boolean ping, final boolean altTransportWake, final BluetoothDevice device) {
+ private boolean wakeUpRouterService(final Context context, final boolean ping, final boolean altTransportWake, final BluetoothDevice device, final VehicleType vehicleType) {
new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() {
@Override
public void onComplete(Vector<ComponentName> routerServices) {
@@ -263,7 +288,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
if (runningBluetoothServicePackage.isEmpty()) {
//If there isn't a service running we should try to start one
//We will try to sort the SDL enabled apps and find the one that's been installed the longest
- final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator(), vehicleType);
synchronized (DEVICE_LISTENER_LOCK) {
final boolean sdlDeviceListenerEnabled = SdlDeviceListener.isFeatureSupported(sdlAppInfoList);
if (sdlDeviceListenerEnabled) {
@@ -294,7 +319,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
}
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
- startRouterService(context, sdlAppInfoList.get(0).getRouterServiceComponentName(), altTransportWake, device, false);
+ startRouterService(context, sdlAppInfoList.get(0).getRouterServiceComponentName(), altTransportWake, device, false, vehicleType);
} else {
DebugTool.logInfo(TAG, "No SDL Router Services found");
DebugTool.logInfo(TAG, "WARNING: This application has not specified its SdlRouterService correctly in the manifest. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!");
@@ -564,10 +589,16 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver {
synchronized (DEVICE_LISTENER_LOCK) {
sdlDeviceListener = null;
if (context != null) {
- final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ VehicleType vehicleType = null;
+ final String address = bluetoothDevice != null ? bluetoothDevice.getAddress() : null;
+ Hashtable<String, Object> store = AndroidTools.getVehicleTypeFromPrefs(context, address);
+ if (store != null) {
+ vehicleType = new VehicleType(store);
+ }
+ final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator(), vehicleType);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
ComponentName routerService = sdlAppInfoList.get(0).getRouterServiceComponentName();
- startRouterService(context, routerService, false, bluetoothDevice, true);
+ startRouterService(context, routerService, false, bluetoothDevice, true, vehicleType);
}
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
index af63c9219..9d24ddfec 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
@@ -89,6 +89,7 @@ import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.protocol.enums.MessageType;
import com.smartdevicelink.protocol.enums.SessionType;
import com.smartdevicelink.proxy.rpc.UnregisterAppInterface;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.transport.utl.ByteAraryMessageAssembler;
import com.smartdevicelink.transport.utl.ByteArrayMessageSpliter;
@@ -141,7 +142,7 @@ public class SdlRouterService extends Service {
/**
* <b> NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
*/
- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 14;
+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 15;
private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router";
@@ -201,6 +202,7 @@ public class SdlRouterService extends Service {
private boolean wrongProcess = false;
private boolean initPassed = false;
private boolean hasCalledStartForeground = false;
+ boolean firstStart = true;
public static HashMap<String, RegisteredApp> registeredApps;
private SparseArray<String> bluetoothSessionMap, usbSessionMap, tcpSessionMap;
@@ -212,6 +214,7 @@ public class SdlRouterService extends Service {
private static Messenger altTransportService = null;
private boolean startSequenceComplete = false;
+ private VehicleType receivedVehicleType;
private ExecutorService packetExecutor = null;
ConcurrentHashMap<TransportType, PacketWriteTaskMaster> packetWriteTaskMasterMap = null;
@@ -1096,6 +1099,28 @@ public class SdlRouterService extends Service {
DebugTool.logError(TAG, "Service isn't exported. Shutting down");
return false;
}
+
+ ComponentName name = new ComponentName(this, this.getClass());
+ SdlAppInfo currentAppInfo = null;
+
+ List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(getApplicationContext(), new SdlAppInfo.BestRouterComparator(), null);
+ for (SdlAppInfo appInfo : sdlAppInfoList) {
+ if (appInfo.getRouterServiceComponentName().equals(name)) {
+ currentAppInfo = appInfo;
+ break;
+ }
+ }
+
+ if (currentAppInfo == null) {
+ DebugTool.logError(TAG, "AppInfo for current package is not available. Shutting down");
+ return false;
+ }
+
+ if (!SdlAppInfo.checkIfVehicleSupported(currentAppInfo.getSupportedVehicles(), receivedVehicleType)) {
+ DebugTool.logError(TAG, "Received vehicle data is not supported. Shutting down");
+ return false;
+ }
+
return true;
}
@@ -1118,37 +1143,13 @@ public class SdlRouterService extends Service {
hasCalledStartForeground = true;
resetForegroundTimeOut(FOREGROUND_TIMEOUT / 1000);
}
-
-
- if (!initCheck()) { // Run checks on process and permissions
- deployNextRouterService();
- closeSelf();
- return;
- }
- initPassed = true;
-
-
- synchronized (REGISTERED_APPS_LOCK) {
- registeredApps = new HashMap<String, RegisteredApp>();
- }
- closing = false;
-
- synchronized (SESSION_LOCK) {
- this.bluetoothSessionMap = new SparseArray<String>();
- this.sessionHashIdMap = new SparseIntArray();
- this.cleanedSessionMap = new SparseIntArray();
- }
-
- packetExecutor = Executors.newSingleThreadExecutor();
-
- startUpSequence();
}
/**
* The method will attempt to start up the next router service in line based on the sorting criteria of best router service.
*/
protected void deployNextRouterService() {
- List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(getApplicationContext(), new SdlAppInfo.BestRouterComparator());
+ List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(getApplicationContext(), new SdlAppInfo.BestRouterComparator(), null);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
ComponentName name = new ComponentName(this, this.getClass());
SdlAppInfo info;
@@ -1250,6 +1251,37 @@ public class SdlRouterService extends Service {
@SuppressLint({"NewApi", "MissingPermission"})
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && intent.hasExtra(TransportConstants.VEHICLE_INFO_EXTRA)) {
+ receivedVehicleType = new VehicleType(
+ (HashMap<String, Object>) intent.getSerializableExtra(TransportConstants.VEHICLE_INFO_EXTRA)
+ );
+ }
+ // Only trusting the first intent received to start the RouterService and run initial checks to avoid a case where an app could send incorrect data after the spp connection has started.
+ if (firstStart) {
+ firstStart = false;
+ if (!initCheck()) { // Run checks on process and permissions
+ deployNextRouterService();
+ closeSelf();
+ return START_REDELIVER_INTENT;
+ }
+ initPassed = true;
+
+
+ synchronized (REGISTERED_APPS_LOCK) {
+ registeredApps = new HashMap<String, RegisteredApp>();
+ }
+ closing = false;
+
+ synchronized (SESSION_LOCK) {
+ this.bluetoothSessionMap = new SparseArray<String>();
+ this.sessionHashIdMap = new SparseIntArray();
+ this.cleanedSessionMap = new SparseIntArray();
+ }
+
+ packetExecutor = Executors.newSingleThreadExecutor();
+
+ startUpSequence();
+ }
if (intent != null) {
if (intent.getBooleanExtra(FOREGROUND_EXTRA, false)) {
hasCalledStartForeground = false;
@@ -1410,7 +1442,10 @@ public class SdlRouterService extends Service {
public void resetForegroundTimeOut(long delay) {
synchronized (FOREGROUND_NOTIFICATION_LOCK) {
if (foregroundTimeoutHandler == null) {
- foregroundTimeoutHandler = new Handler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ foregroundTimeoutHandler = new Handler(Looper.myLooper());
}
if (foregroundTimeoutRunnable == null) {
foregroundTimeoutRunnable = new Runnable() {
@@ -1756,6 +1791,9 @@ public class SdlRouterService extends Service {
startService.putExtra(TransportConstants.FORCE_TRANSPORT_CONNECTED, true);
startService.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_APP_PACKAGE, getBaseContext().getPackageName());
startService.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_CMP_NAME, new ComponentName(this, this.getClass()));
+ if (receivedVehicleType != null) {
+ startService.putExtra(TransportConstants.VEHICLE_INFO_EXTRA, receivedVehicleType.getStore());
+ }
if (record != null && record.getType() != null) {
startService.putExtra(TransportConstants.START_ROUTER_SERVICE_TRANSPORT_CONNECTED, record.getType().toString());
@@ -2416,7 +2454,7 @@ public class SdlRouterService extends Service {
/**
* Set the connection establishment status of the particular device
*
- * @param address address of the device in quesiton
+ * @param address address of the device in question
* @param hasSDLConnected true if a connection has been established, false if not
*/
protected void setSDLConnectedStatus(String address, boolean hasSDLConnected) {
@@ -2501,7 +2539,10 @@ public class SdlRouterService extends Service {
* This method is used to check for the newest version of this class to make sure the latest and greatest is up and running.
*/
private void startAltTransportTimer() {
- altTransportTimerHandler = new Handler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ altTransportTimerHandler = new Handler(Looper.myLooper());
altTransportTimerRunnable = new Runnable() {
public void run() {
altTransportTimerHandler = null;
@@ -2833,6 +2874,9 @@ public class SdlRouterService extends Service {
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_APP_PACKAGE, getBaseContext().getPackageName());
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_CMP_NAME, new ComponentName(SdlRouterService.this, SdlRouterService.this.getClass()));
pingIntent.putExtra(TransportConstants.START_ROUTER_SERVICE_SDL_ENABLED_PING, true);
+ if (receivedVehicleType != null) {
+ pingIntent.putExtra(TransportConstants.VEHICLE_INFO_EXTRA, receivedVehicleType.getStore());
+ }
}
private void startClientPings() {
@@ -3057,7 +3101,10 @@ public class SdlRouterService extends Service {
this.messenger = messenger;
this.sessionIds = new Vector<Long>();
this.queues = new ConcurrentHashMap<>();
- queueWaitHandler = new Handler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ queueWaitHandler = new Handler(Looper.myLooper());
registeredTransports = new SparseArray<ArrayList<TransportType>>();
awaitingSession = new Vector<>();
setDeathNote(); //messaging Version
@@ -3272,7 +3319,7 @@ public class SdlRouterService extends Service {
try {
List<TransportType> transportTypes = (List<TransportType>) obj;
if (transportTypes != null) {
- if (transportTypes.get(0) != null) {
+ if (transportTypes.size() > 0 && transportTypes.get(0) != null) {
return transportTypes.get(0);
}
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
index a1392aace..6c3050301 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
@@ -402,10 +402,10 @@ public class TransportManager extends TransportManagerBase {
return; //Already in legacy mode
}
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
if (transportListener.onLegacyModeEnabled(info)) {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
legacyBluetoothHandler = new LegacyBluetoothHandler(this);
legacyBluetoothTransport = new MultiplexBluetoothTransport(legacyBluetoothHandler);
if (contextWeakReference.get() != null) {
@@ -415,7 +415,7 @@ public class TransportManager extends TransportManagerBase {
contextWeakReference.get().registerReceiver(legacyDisconnectReceiver, intentFilter);
}
} else {
- new Handler().postDelayed(new Runnable() {
+ new Handler(Looper.myLooper()).postDelayed(new Runnable() {
@Override
public void run() {
transportListener.onError(info + " - Legacy mode unacceptable; shutting down.");
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
index 604e3b435..8e54de9a1 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/USBAccessoryAttachmentActivity.java
@@ -143,7 +143,7 @@ public class USBAccessoryAttachmentActivity extends Activity {
//If there isn't a service running we should try to start one
//We will try to sort the SDL enabled apps and find the one that's been installed the longest
Intent serviceIntent;
- List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator(), null);
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
SdlAppInfo optimalRouterService = sdlAppInfoList.get(0);
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
index 23cdd9857..41ada091d 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
@@ -42,13 +42,22 @@ import android.os.Message;
import androidx.annotation.NonNull;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.protocol.SdlPacketFactory;
+import com.smartdevicelink.protocol.enums.ControlFrameTags;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.transport.MultiplexBaseTransport;
import com.smartdevicelink.transport.MultiplexBluetoothTransport;
import com.smartdevicelink.transport.SdlRouterService;
+import com.smartdevicelink.util.AndroidTools;
+import com.smartdevicelink.util.BitConverter;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.SdlAppInfo;
+import com.smartdevicelink.util.Version;
import java.lang.ref.WeakReference;
+import java.util.Hashtable;
import java.util.List;
@@ -59,6 +68,9 @@ public class SdlDeviceListener {
private static final String SDL_DEVICE_STATUS_SHARED_PREFS = "sdl.device.status";
private static final Object LOCK = new Object(), RUNNING_LOCK = new Object();
+ // If increasing MAX PROTOCOL VERSION major version, make sure to alter it in SdlProtocolBase
+ private static final Version MAX_PROTOCOL_VERSION = new Version(5, 4, 0);
+
private final WeakReference<Context> contextWeakReference;
private final Callback callback;
private BluetoothDevice connectedDevice;
@@ -152,16 +164,10 @@ public class SdlDeviceListener {
case SdlRouterService.MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case MultiplexBaseTransport.STATE_CONNECTED:
- if(sdlListener.connectedDevice == null) {
+ if (sdlListener.connectedDevice == null) {
sdlListener.connectedDevice = sdlListener.bluetoothTransport.getConnectedDevice();
}
- sdlListener.setSDLConnectedStatus(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice.getAddress(), true);
- boolean keepConnectionOpen = sdlListener.callback.onTransportConnected(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice);
- if (!keepConnectionOpen) {
- sdlListener.bluetoothTransport.stop();
- sdlListener.bluetoothTransport = null;
- sdlListener.timeoutHandler.removeCallbacks(sdlListener.timeoutRunner);
- }
+ sendStartService();
break;
case MultiplexBaseTransport.STATE_NONE:
// We've just lost the connection
@@ -174,9 +180,90 @@ public class SdlDeviceListener {
break;
case com.smartdevicelink.transport.SdlRouterService.MESSAGE_READ:
+ onPacketRead((SdlPacket) msg.obj);
break;
}
}
+
+ /**
+ * This method will start the RPC service so that we can retrieve vehicle info
+ */
+ public void sendStartService() {
+ SdlDeviceListener sdlListener = this.provider.get();
+ SdlPacket serviceProbe = SdlPacketFactory.createStartSession(SessionType.RPC, 0x00, (byte)0x01, (byte)0x00, false);
+ serviceProbe.putTag(ControlFrameTags.RPC.StartService.PROTOCOL_VERSION, MAX_PROTOCOL_VERSION.toString());
+ byte[] constructed = serviceProbe.constructPacket();
+ if (sdlListener.bluetoothTransport != null && sdlListener.bluetoothTransport.getState() == MultiplexBluetoothTransport.STATE_CONNECTED) {
+ sdlListener.bluetoothTransport.write(constructed, 0, constructed.length);
+ } else {
+ sdlListener.callback.onTransportError(sdlListener.connectedDevice);
+ }
+ }
+
+ /**
+ * This method will pull vehicle data out of a StartServiceACK and then end the RPC session
+ *
+ * @param packet - info received from connected module
+ */
+ public void onPacketRead(SdlPacket packet) {
+ SdlDeviceListener sdlListener = this.provider.get();
+ VehicleType vehicleType = null;
+ if (packet.getFrameInfo() == SdlPacket.FRAME_INFO_START_SERVICE_ACK) {
+ int hashID;
+ if (packet.getVersion() >= 5) {
+ vehicleType = getVehicleType(packet);
+ hashID = (Integer) packet.getTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID);
+ } else {
+ hashID = BitConverter.intFromByteArray(packet.getPayload(), 0);
+ }
+ byte[] stopService = SdlPacketFactory.createEndSession(SessionType.RPC, (byte)packet.getSessionId(), 0, (byte)packet.getVersion(), hashID).constructPacket();
+ if (sdlListener.bluetoothTransport != null && sdlListener.bluetoothTransport.getState() == MultiplexBluetoothTransport.STATE_CONNECTED) {
+ sdlListener.bluetoothTransport.write(stopService, 0, stopService.length);
+ }
+ notifyConnection(vehicleType);
+ }
+ }
+
+ /**
+ * Retrieves vehicle type from the StartServiceACK
+ *
+ * @param packet - info received from connected module
+ * @return vehicle type of connected module
+ */
+ private VehicleType getVehicleType(SdlPacket packet) {
+ String make = (String) packet.getTag(ControlFrameTags.RPC.StartServiceACK.MAKE);
+ String model = (String) packet.getTag(ControlFrameTags.RPC.StartServiceACK.MODEL);
+ String modelYear = (String) packet.getTag(ControlFrameTags.RPC.StartServiceACK.MODEL_YEAR);
+ String vehicleTrim = (String) packet.getTag(ControlFrameTags.RPC.StartServiceACK.TRIM);
+ if (make != null) {
+ // checking if tags have come from core
+ VehicleType type = new VehicleType();
+ type.setMake(make);
+ type.setModel(model);
+ type.setModelYear(modelYear);
+ type.setTrim(vehicleTrim);
+ return type;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Notify connection about vehicle type.
+ *
+ * @param vehicleType the information about the type of vehicle
+ */
+ public void notifyConnection(VehicleType vehicleType) {
+ SdlDeviceListener sdlListener = this.provider.get();
+ sdlListener.setSDLConnectedStatus(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice.getAddress(), true);
+ AndroidTools.saveVehicleType(sdlListener.contextWeakReference.get(), vehicleType, sdlListener.connectedDevice.getAddress());
+ boolean keepConnectionOpen = sdlListener.callback.onTransportConnected(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice);
+ if (!keepConnectionOpen) {
+ sdlListener.bluetoothTransport.stop();
+ sdlListener.bluetoothTransport = null;
+ sdlListener.timeoutHandler.removeCallbacks(sdlListener.timeoutRunner);
+ }
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
index 9109dc3ad..1f4ba2de0 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/AndroidTools.java
@@ -36,18 +36,28 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.BatteryManager;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.proxy.rpc.VehicleType;
import com.smartdevicelink.transport.TransportConstants;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URL;
@@ -56,9 +66,16 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Hashtable;
import java.util.List;
+
public class AndroidTools {
+
+ private static final String TAG = "AndroidTools";
+ private static final String SDL_DEVICE_VEHICLES_PREFS = "sdl.device.vehicles";
+ private static final Object VEHICLE_PREF_LOCK = new Object();
+
/**
* Check to see if a component is exported
*
@@ -97,7 +114,6 @@ public class AndroidTools {
return sdlMultiList;
}
-
/**
* Finds all SDL apps via their SdlRouterService manifest entry. It will return the metadata associated with that router service.
*
@@ -105,6 +121,7 @@ public class AndroidTools {
* @param comparator the Comparator to sort the resulting list. If null is supplied, they will be returned as they are from the system
* @return the sorted list of SdlAppInfo objects that represent SDL apps
*/
+ @Deprecated
public static List<SdlAppInfo> querySdlAppInfo(Context context, Comparator<SdlAppInfo> comparator) {
List<SdlAppInfo> sdlAppInfoList = new ArrayList<>();
Intent intent = new Intent(TransportConstants.ROUTER_SERVICE_ACTION);
@@ -118,7 +135,7 @@ public class AndroidTools {
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(info.serviceInfo.packageName, 0);
- sdlAppInfoList.add(new SdlAppInfo(info, packageInfo));
+ sdlAppInfoList.add(new SdlAppInfo(info, packageInfo, context));
} catch (NameNotFoundException e) {
//Package was not found, likely a sign the resolve info can't be trusted.
}
@@ -134,6 +151,54 @@ public class AndroidTools {
return sdlAppInfoList;
}
+ /**
+ * Finds all SDL apps via their SdlRouterService manifest entry. It will return the metadata associated with that router service.
+ *
+ * @param context a context instance to obtain the package manager
+ * @param comparator the Comparator to sort the resulting list. If null is supplied, they will be returned as they are from the system
+ * @return the sorted list of SdlAppInfo objects that represent SDL apps
+ */
+ public static List<SdlAppInfo> querySdlAppInfo(Context context, Comparator<SdlAppInfo> comparator, VehicleType type) {
+ List<SdlAppInfo> sdlAppInfoList = new ArrayList<>();
+ Intent intent = new Intent(TransportConstants.ROUTER_SERVICE_ACTION);
+ List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentServices(intent, PackageManager.GET_META_DATA);
+
+ if (resolveInfoList != null && resolveInfoList.size() > 0) {
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager != null) {
+
+ for (ResolveInfo info : resolveInfoList) {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfo(info.serviceInfo.packageName, 0);
+ SdlAppInfo appInformation = new SdlAppInfo(info, packageInfo, context);
+ sdlAppInfoList.add(appInformation);
+
+ } catch (NameNotFoundException e) {
+ //Package was not found, likely a sign the resolve info can't be trusted.
+ }
+
+ }
+ }
+
+ List<SdlAppInfo> sdlAppInfoListVehicleType = new ArrayList<>();
+
+ for (SdlAppInfo appInformation : sdlAppInfoList) {
+ if (appInformation.routerServiceVersion < 15) {
+ sdlAppInfoListVehicleType.add(appInformation);
+ } else if (SdlAppInfo.checkIfVehicleSupported(appInformation.supportedVehicles, type)) {
+ sdlAppInfoListVehicleType.add(appInformation);
+ }
+ }
+ sdlAppInfoList = sdlAppInfoListVehicleType;
+
+ if (comparator != null) {
+ Collections.sort(sdlAppInfoList, comparator);
+ }
+ }
+ return sdlAppInfoList;
+ }
+
/**
* Sends the provided intent to the specified destinations making it an explicit intent, rather
@@ -204,4 +269,104 @@ public class AndroidTools {
}
return false;
}
+
+ /**
+ * Saves the mac address along with vehicle details into user's shared prefs.
+ *
+ * @param context a context instance to obtain the shared preferences.
+ * @param vehicleType a RPCStruct that describes the type of vehicle the mobile phone is connected with.
+ * @param address a string containing the Bluetooth Mac address of the connected vehicle.
+ */
+ public static void saveVehicleType(Context context, VehicleType vehicleType, String address) {
+ synchronized (VEHICLE_PREF_LOCK) {
+ if (vehicleType == null || address == null || context == null) {
+ DebugTool.logWarning(TAG, String.format("Unable to save vehicle type. Context is %1$s, Address is %2$s and VehicleType is %3$s", context, address, vehicleType));
+ return;
+ }
+ try {
+ SharedPreferences preferences = context.getSharedPreferences(SDL_DEVICE_VEHICLES_PREFS, Context.MODE_PRIVATE);
+
+ String jsonString = vehicleType.serializeJSON().toString();
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(address, jsonString);
+ editor.commit();
+ } catch (JSONException e) {
+ DebugTool.logError(TAG, "Unable to serialize vehicle type to JSON.", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the vehicle details by the mac address of the connected vehicle.
+ *
+ * @param context a context instance to obtain the shared preferences.
+ * @param address a string containing the Bluetooth Mac address of the connected vehicle.
+ * @return The Hashtable to be used to construct VehicleTyp
+ */
+ public static @Nullable Hashtable<String, Object> getVehicleTypeFromPrefs(Context context, String address) {
+ synchronized (VEHICLE_PREF_LOCK) {
+ if (context == null || address == null) {
+ DebugTool.logWarning(TAG, String.format("Unable to get vehicle type from prefs. Context is %1$s and Address is %2$s", context, address));
+ return null;
+ }
+ try {
+ SharedPreferences preferences = context.getSharedPreferences(SDL_DEVICE_VEHICLES_PREFS, Context.MODE_PRIVATE);
+ String storedVehicleTypeSerialized = preferences.getString(address, null);
+
+ if (storedVehicleTypeSerialized == null) {
+ return null;
+ } else {
+ JSONObject object = new JSONObject(storedVehicleTypeSerialized);
+ return JsonRPCMarshaller.deserializeJSONObject(object);
+ }
+ } catch (JSONException e) {
+ DebugTool.logError(TAG, "Unable to deserialize stored vehicle type.", e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the list of vehicle types that are set in the manifest.
+ *
+ * @param context a context to access Android system services through.
+ * @param component a component name of a LocalRouterService.
+ * @param manifestFieldId a string resources id that indicates an unique name for the vehicle data in the manifest.
+ * @return The list of vehicle types, or null if an error occurred or field was not found.
+ */
+ public static @Nullable List<VehicleType> getVehicleTypesFromManifest(Context context, ComponentName component, int manifestFieldId) {
+ if (context == null || component == null) {
+ DebugTool.logWarning(TAG, String.format("Unable to get vehicle type from manifest. Context is %1$s and Component is %2$s", context, component));
+ return null;
+ }
+ try {
+ PackageManager pm = context.getPackageManager();
+ Resources resources = context.getResources();
+ if (pm == null || resources == null) {
+ DebugTool.logWarning(TAG, String.format("Unable to get vehicle type from manifest. PackageManager is %1$s and Resources is %2$s", pm, resources));
+ return null;
+ }
+
+ Bundle metaData = pm.getServiceInfo(component, PackageManager.GET_META_DATA).metaData;
+ if (metaData == null) {
+ DebugTool.logError(TAG, "metaData isn't available.");
+ return null;
+ }
+
+ int xmlFieldId = metaData.getInt(resources.getString(manifestFieldId));
+ if (xmlFieldId == 0) {
+ DebugTool.logError(TAG, "Field with id " + manifestFieldId + " was not found in manifest");
+ return null;
+ }
+
+ XmlResourceParser parser = resources.getXml(xmlFieldId);
+ return SdlAppInfo.deserializeSupportedVehicles(parser);
+ } catch (PackageManager.NameNotFoundException e) {
+ DebugTool.logError(TAG, "Failed to get OEM vehicle data filter: " + e.getMessage()+ " - assume vehicle data is supported");
+ return null;
+ } catch (Resources.NotFoundException ex) {
+ DebugTool.logError(TAG, "Failed to find resource: " + ex.getMessage()+ " - assume vehicle data is supported");
+ return null;
+ }
+ }
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
index ba15ff4aa..8e904a45f 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/IntegrationValidator.java
@@ -198,7 +198,7 @@ public class IntegrationValidator {
boolean serviceFilterHasAction = false;
String className = localRouterClass.getName();
- List<SdlAppInfo> services = AndroidTools.querySdlAppInfo(context, null);
+ List<SdlAppInfo> services = AndroidTools.querySdlAppInfo(context, null, null);
for (SdlAppInfo sdlAppInfo : services) {
if (sdlAppInfo != null && sdlAppInfo.getRouterServiceComponentName() != null
&& className.equals((sdlAppInfo.getRouterServiceComponentName().getClassName()))) {
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/SdlAppInfo.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/SdlAppInfo.java
index d49e63575..7d5ade055 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/util/SdlAppInfo.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/SdlAppInfo.java
@@ -33,11 +33,23 @@
package com.smartdevicelink.util;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.os.Bundle;
+import com.smartdevicelink.proxy.rpc.VehicleType;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
/**
* Created by Joey Grover on 2/2/18.
@@ -49,15 +61,17 @@ public class SdlAppInfo {
//FIXME we shouldn't be duplicating constants, but this currently keeps us from needing a context instance.
private static final String SDL_ROUTER_VERSION_METADATA = "sdl_router_version";
private static final String SDL_CUSTOM_ROUTER_METADATA = "sdl_custom_router";
+ private static final String SDL_OEM_VEHICLE_TYPE_METADATA = "sdl_oem_vehicle_type";
String packageName;
ComponentName routerServiceComponentName;
int routerServiceVersion = 4; //We use this as a default and assume if the number doesn't exist in meta data it is because the app hasn't updated.
boolean isCustomRouterService = false;
+ List<VehicleType> supportedVehicles = new ArrayList<>();
long lastUpdateTime;
-
+ @Deprecated
public SdlAppInfo(ResolveInfo resolveInfo, PackageInfo packageInfo) {
if (resolveInfo.serviceInfo != null) {
@@ -89,6 +103,75 @@ public class SdlAppInfo {
}
}
+ public SdlAppInfo(ResolveInfo resolveInfo, PackageInfo packageInfo, Context context) {
+ if (resolveInfo.serviceInfo != null) {
+
+ this.packageName = resolveInfo.serviceInfo.packageName;
+ this.routerServiceComponentName = new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+
+ Bundle metadata = resolveInfo.serviceInfo.metaData;
+ if (metadata != null) {
+
+ if (metadata.containsKey(SDL_ROUTER_VERSION_METADATA)) {
+ this.routerServiceVersion = metadata.getInt(SDL_ROUTER_VERSION_METADATA);
+ }
+
+ if (metadata.containsKey(SDL_CUSTOM_ROUTER_METADATA)) {
+ this.isCustomRouterService = metadata.getBoolean(SDL_CUSTOM_ROUTER_METADATA);
+ }
+
+ if (metadata.containsKey(SDL_OEM_VEHICLE_TYPE_METADATA)) {
+ if (context == null) {
+ DebugTool.logWarning(TAG, "Unable to deserialize supported vehicles: supplied Context is null");
+ return;
+ }
+
+ String contextPackageName = context.getPackageName();
+ if (contextPackageName == null || packageName == null) {
+ DebugTool.logWarning(TAG, String.format("Unable to deserialize supported vehicles. ContextPackageName: %1$s and PackageName: %2$s", contextPackageName, packageName));
+ return;
+ }
+
+ Resources resources = null;
+ if (!contextPackageName.equals(packageName)) {
+ try {
+ Context appContext = context.createPackageContext(packageName, 0);
+ if (appContext == null){
+ DebugTool.logError(TAG, "Failed to create context with the given package name");
+ return;
+ }
+ resources = appContext.getResources();
+ } catch (PackageManager.NameNotFoundException e) {
+ DebugTool.logError(TAG, "Failed to create context with the given package name: " + e.getMessage());
+ }
+ } else {
+ resources = context.getResources();
+ }
+
+ if (resources != null) {
+ try {
+ XmlResourceParser parser = resources.getXml(metadata.getInt(SDL_OEM_VEHICLE_TYPE_METADATA));
+ this.supportedVehicles = deserializeSupportedVehicles(parser);
+ } catch (Resources.NotFoundException ex) {
+ DebugTool.logError(TAG, "Failed to find resource: " + ex.getMessage());
+ }
+ }
+ }
+ } else {
+ DebugTool.logWarning(TAG, packageName + " has not supplied metadata with their router service!");
+ }
+ }
+
+ if (packageInfo != null) {
+ this.lastUpdateTime = packageInfo.lastUpdateTime;
+ if (this.lastUpdateTime <= 0) {
+ this.lastUpdateTime = packageInfo.firstInstallTime;
+ }
+ } else {
+ this.lastUpdateTime = 0;
+ }
+ }
+
public int getRouterServiceVersion() {
return routerServiceVersion;
}
@@ -122,12 +205,120 @@ public class SdlAppInfo {
builder.append("\nLast updated: ");
builder.append(this.lastUpdateTime);
+ builder.append("\nVehicle make list: ");
+ builder.append(this.supportedVehicles);
+
builder.append("\n-------- Sdl App Info End------");
return builder.toString();
}
/**
+ * Retrieves the list of vehicle types that are set in the xml file.
+ *
+ * @param parser The xml parsing interface for the vehicle types xml file.
+ * @return The list of vehicle types.
+ */
+ public static List<VehicleType> deserializeSupportedVehicles(XmlResourceParser parser) {
+ List<VehicleType> vehicleMakesList = new ArrayList<>();
+ if (parser == null) {
+ return vehicleMakesList;
+ }
+ try {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName != null && tagName.equalsIgnoreCase("vehicle-type")) {
+ VehicleType vehicleMake = new VehicleType();
+ String make = parser.getAttributeValue(null, "make");
+ if (make != null) {
+ vehicleMake.setMake(make);
+ String model = parser.getAttributeValue(null, "model");
+ String modelYear = parser.getAttributeValue(null, "modelYear");
+ String trim = parser.getAttributeValue(null, "trim");
+
+ if (model == null && modelYear == null && trim == null) {
+ vehicleMakesList.add(vehicleMake);
+ } else if (model != null){
+ vehicleMake.setModel(model);
+ if (modelYear != null) {
+ vehicleMake.setModelYear(modelYear);
+ }
+ if (trim != null) {
+ vehicleMake.setTrim(trim);
+ }
+ vehicleMakesList.add(vehicleMake);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ DebugTool.logError(TAG, "Failed to parse xml file", e);
+ } catch (IOException e) {
+ DebugTool.logError(TAG, "Failed to find next element in the xml file", e);
+ }
+ return vehicleMakesList;
+ }
+
+ /**
+ * Check to see if a vehicle type is supported.
+ *
+ * @param supportedVehicleList the list of supported vehicle types.
+ * @param connectedVehicle the vehicle type to check.
+ * @return true if vehicle type is supported.
+ */
+ public static boolean checkIfVehicleSupported(List<VehicleType> supportedVehicleList, VehicleType connectedVehicle) {
+ if (supportedVehicleList == null || supportedVehicleList.isEmpty() || connectedVehicle == null || connectedVehicle.getStore() == null|| connectedVehicle.getStore().isEmpty()) {
+ return true;
+ }
+ if (supportedVehicleList.contains(connectedVehicle)) {
+ return true;
+ }
+ for (VehicleType supportedVehicle: supportedVehicleList) {
+ boolean areVehicleMakesEqual = CompareUtils.areStringsEqual(supportedVehicle.getMake(), connectedVehicle.getMake(), true, false);
+ if (areVehicleMakesEqual) {
+ String supportedVehicleModel = supportedVehicle.getModel();
+ String connectedVehicleModel = connectedVehicle.getModel();
+ if (supportedVehicleModel != null && connectedVehicleModel != null) {
+ if (connectedVehicleModel.equalsIgnoreCase(supportedVehicleModel)) {
+ boolean ret = true;
+ String supportedVehicleModelYear = supportedVehicle.getModelYear();
+ String connectedVehicleModelYear = connectedVehicle.getModelYear();
+ if (supportedVehicleModelYear != null && connectedVehicleModelYear != null) {
+ ret = connectedVehicleModelYear.equalsIgnoreCase(supportedVehicleModelYear);
+ }
+ String supportedVehicleTrim = supportedVehicle.getTrim();
+ String connectedVehicleTrim = connectedVehicle.getTrim();
+ if (supportedVehicleTrim != null && connectedVehicleTrim != null) {
+ ret &= connectedVehicleTrim.equalsIgnoreCase(supportedVehicleTrim);
+ }
+ if (ret) {
+ // We found matches and return or continue iteration otherwise
+ return true;
+ }
+ }
+ } else {
+ // Return true if only make is defined and it matches
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets app's supported vehicle types.
+ *
+ * @return List<VehicleType> a list of supported vehicle types.
+ */
+ public List<VehicleType> getSupportedVehicles() {
+ return supportedVehicles;
+ }
+
+ /**
* This comparator will sort a list to find the best router service to start out of the known SDL enabled apps
*/
public static class BestRouterComparator implements Comparator<SdlAppInfo> {
diff --git a/android/sdl_android/src/main/res/values/sdl.xml b/android/sdl_android/src/main/res/values/sdl.xml
index 1d5f0897a..7bb919f0b 100644
--- a/android/sdl_android/src/main/res/values/sdl.xml
+++ b/android/sdl_android/src/main/res/values/sdl.xml
@@ -2,7 +2,8 @@
<resources>
<string name="sdl_router_service_version_name" translatable="false">sdl_router_version</string>
- <integer name="sdl_router_service_version_value">14</integer>
+ <integer name="sdl_router_service_version_value">15</integer>
<string name="sdl_router_service_is_custom_name" translatable="false">sdl_custom_router</string>
+ <string name="sdl_oem_vehicle_type_filter_name" translatable="false">sdl_oem_vehicle_type</string>
</resources>
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
index 9412fb122..da6bae4e6 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
@@ -70,6 +70,8 @@ abstract class BaseFileManager extends BaseSubManager {
private HashMap<String, Integer> failedFileUploadsCount;
private final int maxFileUploadAttempts;
private final int maxArtworkUploadAttempts;
+ final String fileManagerCannotOverwriteError = "Cannot overwrite remote file. The remote file system already has a file of this name, and the file manager is set to not automatically overwrite files.";
+
/**
* Constructor for BaseFileManager
@@ -194,13 +196,7 @@ abstract class BaseFileManager extends BaseSubManager {
}
private void deleteRemoteFileWithNamePrivate(@NonNull final String fileName, final FileManagerCompletionListener listener) {
- if (!mutableRemoteFileNames.contains(fileName) && listener != null) {
- String errorMessage = "No such remote file is currently known";
- listener.onComplete(false, bytesAvailable, mutableRemoteFileNames, errorMessage);
- return;
- }
-
- DeleteFileOperation operation = new DeleteFileOperation(internalInterface, fileName, new FileManagerCompletionListener() {
+ DeleteFileOperation operation = new DeleteFileOperation(internalInterface, fileName, mutableRemoteFileNames, new FileManagerCompletionListener() {
@Override
public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
if (success) {
@@ -405,46 +401,29 @@ abstract class BaseFileManager extends BaseSubManager {
return;
}
- // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core
- // had a bug where list files would cache incorrectly. This led to attempted uploads failing
- // due to the system thinking they were already there when they were not. This is only needed
- // if connecting to Core v4.3.1 or less which corresponds to RPC v4.3.1 or less
- Version rpcVersion = new Version(internalInterface.getSdlMsgVersion());
- if (!file.isPersistent() && !hasUploadedFile(file) && new Version(4, 4, 0).isNewerThan(rpcVersion) == 1) {
- file.setOverwrite(true);
- }
-
- // Check our overwrite settings and error out if it would overwrite
- if (!file.getOverwrite() && mutableRemoteFileNames.contains(file.getName())) {
- String errorMessage = "Cannot overwrite remote file. The remote file system already has a file of this name, and the file manager is set to not automatically overwrite files.";
- DebugTool.logWarning(TAG, errorMessage);
- if (listener != null) {
- listener.onComplete(true, bytesAvailable, null, errorMessage);
- }
- return;
- }
-
// If we didn't error out over the overwrite, then continue on
sdl_uploadFilePrivate(file, listener);
}
private void sdl_uploadFilePrivate(@NonNull final SdlFile file, final FileManagerCompletionListener listener) {
- final String fileName = file.getName();
+ final SdlFile fileClone = file.clone();
- SdlFileWrapper fileWrapper = new SdlFileWrapper(file, new FileManagerCompletionListener() {
+ SdlFileWrapper fileWrapper = new SdlFileWrapper(fileClone, new FileManagerCompletionListener() {
@Override
public void onComplete(boolean success, int bytesAvailable, Collection<String> fileNames, String errorMessage) {
if (success) {
BaseFileManager.this.bytesAvailable = bytesAvailable;
- BaseFileManager.this.mutableRemoteFileNames.add(fileName);
- BaseFileManager.this.uploadedEphemeralFileNames.add(fileName);
+ BaseFileManager.this.mutableRemoteFileNames.add(fileClone.getName());
+ if (!file.isPersistent()) {
+ BaseFileManager.this.uploadedEphemeralFileNames.add(fileClone.getName());
+ }
} else {
- incrementFailedUploadCountForFileName(file.getName(), BaseFileManager.this.failedFileUploadsCount);
+ incrementFailedUploadCountForFileName(fileClone.getName(), BaseFileManager.this.failedFileUploadsCount);
- int maxUploadCount = file instanceof SdlArtwork ? maxArtworkUploadAttempts : maxFileUploadAttempts;
- if (canFileBeUploadedAgain(file, maxUploadCount, failedFileUploadsCount)) {
+ int maxUploadCount = fileClone instanceof SdlArtwork ? maxArtworkUploadAttempts : maxFileUploadAttempts;
+ if (canFileBeUploadedAgain(fileClone, maxUploadCount, failedFileUploadsCount)) {
DebugTool.logInfo(TAG, String.format("Attempting to resend file with name %s after a failed upload attempt", file.getName()));
- sdl_uploadFilePrivate(file, listener);
+ sdl_uploadFilePrivate(fileClone, listener);
return;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
index 21df7f7c4..522c5c7e9 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/DeleteFileOperation.java
@@ -38,8 +38,10 @@ import com.smartdevicelink.proxy.RPCResponse;
import com.smartdevicelink.proxy.rpc.DeleteFile;
import com.smartdevicelink.proxy.rpc.DeleteFileResponse;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
+import java.util.Set;
/**
* Created by Bilal Alsharifi on 12/1/20.
@@ -49,12 +51,14 @@ class DeleteFileOperation extends Task {
private final WeakReference<ISdl> internalInterface;
private String fileName;
private FileManagerCompletionListener completionListener;
+ private Set<String> mutableRemoteFileNames;
- DeleteFileOperation(ISdl internalInterface, String fileName, FileManagerCompletionListener completionListener) {
+ DeleteFileOperation(ISdl internalInterface, String fileName, Set<String> mutableRemoteFileNames, FileManagerCompletionListener completionListener) {
super("DeleteFileOperation");
this.internalInterface = new WeakReference<>(internalInterface);
this.fileName = fileName;
this.completionListener = completionListener;
+ this.mutableRemoteFileNames = mutableRemoteFileNames;
}
@Override
@@ -66,6 +70,15 @@ class DeleteFileOperation extends Task {
if (getState() == Task.CANCELED) {
return;
}
+ if (!mutableRemoteFileNames.contains(fileName)) {
+ if (completionListener != null) {
+ String errorMessage = "File to delete is no longer on the head unit, aborting operation";
+ // Returning BaseFileManager.SPACE_AVAILABLE_MAX_VALUE for bytesAvaialble as a placeHolder, it will not get updated in BaseFileManager as long as success returned is false.
+ completionListener.onComplete(false, BaseFileManager.SPACE_AVAILABLE_MAX_VALUE, mutableRemoteFileNames, errorMessage);
+ }
+ onFinished();
+ return;
+ }
deleteFile();
}
@@ -77,12 +90,15 @@ class DeleteFileOperation extends Task {
public void onResponse(int correlationId, RPCResponse response) {
DeleteFileResponse deleteFileResponse = (DeleteFileResponse) response;
boolean success = deleteFileResponse.getSuccess();
+ String errorMessage = success ? null : response.getInfo() + ": " + response.getResultCode();
+ if (errorMessage != null) {
+ DebugTool.logInfo(TAG, "Error deleting file: " + errorMessage);
+ }
// If spaceAvailable is null, set it to the max value
int bytesAvailable = deleteFileResponse.getSpaceAvailable() != null ? deleteFileResponse.getSpaceAvailable() : BaseFileManager.SPACE_AVAILABLE_MAX_VALUE;
if (completionListener != null) {
- String errorMessage = success ? null : response.getInfo() + ": " + response.getResultCode();
completionListener.onComplete(success, bytesAvailable, null, errorMessage);
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
index 4a9b785c6..aab50a280 100644
--- a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java
@@ -44,6 +44,7 @@ import com.smartdevicelink.proxy.rpc.PutFile;
import com.smartdevicelink.proxy.rpc.PutFileResponse;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.Version;
import java.io.IOException;
import java.io.InputStream;
@@ -81,6 +82,27 @@ class UploadFileOperation extends Task {
return;
}
+ SdlFile file = fileWrapper.getFile();
+ // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core
+ // had a bug where list files would cache incorrectly. This led to attempted uploads failing
+ // due to the system thinking they were already there when they were not. This is only needed
+ // if connecting to Core v4.3.1 or less which corresponds to RPC v4.3.1 or less
+ if (internalInterface.get() != null && fileManager.get() != null) {
+ Version rpcVersion = new Version(internalInterface.get().getSdlMsgVersion());
+ if (!file.isPersistent() && !fileManager.get().hasUploadedFile(file) && new Version(4, 4, 0).isNewerThan(rpcVersion) == 1) {
+ file.setOverwrite(true);
+ }
+ // Check our overwrite settings and error out if it would overwrite
+ if (!file.getOverwrite() && fileManager.get().mutableRemoteFileNames.contains(file.getName())) {
+ DebugTool.logWarning(TAG, fileManager.get().fileManagerCannotOverwriteError);
+ if (this.fileWrapper.getCompletionListener() != null) {
+ this.fileWrapper.getCompletionListener().onComplete(true, bytesAvailable, null, fileManager.get().fileManagerCannotOverwriteError);
+ }
+ onFinished();
+ return;
+ }
+ }
+
int mtuSize = 0;
if (internalInterface.get() != null) {
mtuSize = (int) internalInterface.get().getMtu(SessionType.RPC);
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
index 9cf72f7cd..6602e7085 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
@@ -90,6 +90,7 @@ import com.smartdevicelink.session.ISdlSessionListener;
import com.smartdevicelink.session.SdlSession;
import com.smartdevicelink.streaming.video.VideoStreamingParameters;
import com.smartdevicelink.transport.BaseTransportConfig;
+import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.CorrelationIdGenerator;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.FileUtls;
@@ -104,7 +105,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
abstract class BaseLifecycleManager {
static final String TAG = "Lifecycle Manager";
- public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(7, 1, 0);
+ public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(8, 0, 0);
// Protected Correlation IDs
private final int REGISTER_APP_INTERFACE_CORRELATION_ID = 65529,
@@ -398,6 +399,7 @@ abstract class BaseLifecycleManager {
VehicleType vehicleType = raiResponse.getVehicleType();
String systemSoftwareVersion = raiResponse.getSystemSoftwareVersion();
if (vehicleType != null || systemSoftwareVersion != null) {
+ saveVehicleType(session.getActiveTransports(), vehicleType);
SystemInfo systemInfo = new SystemInfo(vehicleType, systemSoftwareVersion, null);
boolean validSystemInfo = lifecycleListener.onSystemInfoReceived(systemInfo);
if (!validSystemInfo) {
@@ -946,6 +948,7 @@ abstract class BaseLifecycleManager {
if (systemInfo != null && lifecycleListener != null) {
didCheckSystemInfo = true;
+ saveVehicleType(session.getActiveTransports(), systemInfo.getVehicleType());
boolean validSystemInfo = lifecycleListener.onSystemInfoReceived(systemInfo);
if (!validSystemInfo) {
DebugTool.logWarning(TAG, "Disconnecting from head unit, the system info was not accepted.");
@@ -1325,6 +1328,12 @@ abstract class BaseLifecycleManager {
abstract void cycle(SdlDisconnectedReason disconnectedReason);
+ void saveVehicleType(String address, VehicleType type){
+ }
+
+ void saveVehicleType(List<TransportRecord> activeTransports, VehicleType type) {
+ }
+
void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) {
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
index 54a343043..33a778e58 100644
--- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java
@@ -135,12 +135,15 @@ abstract class BaseSystemCapabilityManager {
// HAX: Issue #1705, Ford Sync bug returning incorrect template name for "NON-MEDIA" (https://github.com/smartdevicelink/sdl_java_suite/issues/1705).
List<String> templatesAvailable = display.getTemplatesAvailable();
- for (int i = 0; i < templatesAvailable.size(); i++) {
- if (templatesAvailable.get(i).equals("NON_MEDIA")) {
- templatesAvailable.set(i, "NON-MEDIA");
- break;
+ if (templatesAvailable != null) {
+ for (int i = 0; i < templatesAvailable.size(); i++) {
+ if ("NON_MEDIA".equals(templatesAvailable.get(i))) {
+ templatesAvailable.set(i, "NON-MEDIA");
+ break;
+ }
}
}
+
// copy all available display capabilities
defaultWindowCapability.setTemplatesAvailable(templatesAvailable);
defaultWindowCapability.setNumCustomPresetsAvailable(display.getNumCustomPresetsAvailable());
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
index 56aec5c19..10db9c54c 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseAlertManager.java
@@ -182,7 +182,7 @@ abstract class BaseAlertManager extends BaseSubManager {
private Queue newTransactionQueue() {
- Queue queue = internalInterface.getTaskmaster().createQueue("AlertManager", 6, false);
+ Queue queue = internalInterface.getTaskmaster().createQueue("AlertManager", 8, false);
queue.pause();
return queue;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
index 893909718..0aae71751 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/BaseChoiceSetManager.java
@@ -42,7 +42,6 @@ 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.lifecycle.OnSystemCapabilityListener;
import com.smartdevicelink.managers.lifecycle.SystemCapabilityManager;
@@ -55,7 +54,6 @@ import com.smartdevicelink.proxy.rpc.KeyboardProperties;
import com.smartdevicelink.proxy.rpc.OnHMIStatus;
import com.smartdevicelink.proxy.rpc.WindowCapability;
import com.smartdevicelink.proxy.rpc.enums.HMILevel;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
import com.smartdevicelink.proxy.rpc.enums.KeyboardLayout;
import com.smartdevicelink.proxy.rpc.enums.KeypressMode;
@@ -63,14 +61,12 @@ import com.smartdevicelink.proxy.rpc.enums.Language;
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.enums.TriggerSource;
import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
import com.smartdevicelink.util.DebugTool;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@@ -94,18 +90,14 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
WindowCapability defaultMainWindowCapability;
String displayName;
SystemContext currentSystemContext;
- HashSet<ChoiceCell> preloadedChoices, pendingPreloadChoices;
- ChoiceSet pendingPresentationSet;
+ HashSet<ChoiceCell> preloadedChoices;
// We will pass operations into this to be completed
final Queue transactionQueue;
- Task pendingPresentOperation;
PresentKeyboardOperation currentlyPresentedKeyboardOperation;
- int nextChoiceId;
int nextCancelId;
- final int choiceCellIdMin = 1;
private final int choiceCellCancelIdMin = 101;
private final int choiceCellCancelIdMax = 200;
boolean isVROptional;
@@ -126,8 +118,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// setting/instantiating class vars
this.fileManager = new WeakReference<>(fileManager);
preloadedChoices = new HashSet<>();
- pendingPreloadChoices = new HashSet<>();
- nextChoiceId = choiceCellIdMin;
nextCancelId = choiceCellCancelIdMin;
isVROptional = false;
@@ -151,10 +141,8 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
currentSystemContext = null;
defaultMainWindowCapability = null;
- pendingPresentationSet = null;
- pendingPresentOperation = null;
+ preloadedChoices = null;
isVROptional = false;
- nextChoiceId = choiceCellIdMin;
nextCancelId = choiceCellCancelIdMin;
// remove listeners
@@ -191,6 +179,10 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
transactionQueue.add(checkChoiceVR, false);
}
+ interface ChoicesOperationCompletionListener {
+ void onComplete(boolean success, HashSet<ChoiceCell> loadedChoiceCells);
+ }
+
/**
* Preload choices to improve performance while presenting a choice set at a later time
*
@@ -203,12 +195,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- LinkedHashSet<ChoiceCell> mutableChoicesToUpload = getChoicesToBeUploadedWithArray(choices);
-
- mutableChoicesToUpload.removeAll(preloadedChoices);
- mutableChoicesToUpload.removeAll(pendingPreloadChoices);
-
- final LinkedHashSet<ChoiceCell> choicesToUpload = (LinkedHashSet<ChoiceCell>) mutableChoicesToUpload.clone();
+ final LinkedHashSet<ChoiceCell> choicesToUpload = new LinkedHashSet<>(choices);
if (choicesToUpload.size() == 0) {
if (listener != null) {
@@ -217,26 +204,17 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- updateIdsOnChoices(choicesToUpload);
-
- // Add the preload cells to the pending preload choices
- pendingPreloadChoices.addAll(choicesToUpload);
-
if (fileManager.get() != null) {
- PreloadChoicesOperation preloadChoicesOperation = new PreloadChoicesOperation(internalInterface, fileManager.get(), displayName, defaultMainWindowCapability, isVROptional, choicesToUpload, new CompletionListener() {
+ PreloadPresentChoicesOperation preloadChoicesOperation = new PreloadPresentChoicesOperation(internalInterface, fileManager.get(), displayName, defaultMainWindowCapability, isVROptional, choicesToUpload, this.preloadedChoices, new ChoicesOperationCompletionListener() {
@Override
- public void onComplete(boolean success) {
- if (success) {
- preloadedChoices.addAll(choicesToUpload);
- pendingPreloadChoices.removeAll(choicesToUpload);
- if (listener != null) {
- listener.onComplete(true);
- }
- } else {
- DebugTool.logError(TAG, "There was an error pre loading choice cells");
- if (listener != null) {
- listener.onComplete(false);
+ public void onComplete(boolean success, HashSet<ChoiceCell> loadedCells) {
+ preloadedChoices = loadedCells;
+ updatePendingTasksWithCurrentPreloads();
+ if (listener != null) {
+ if (!success) {
+ DebugTool.logError(TAG, "There was an error pre loading choice cells");
}
+ listener.onComplete(success);
}
}
});
@@ -259,45 +237,15 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- // Find cells to be deleted that are already uploaded or are pending upload
- final HashSet<ChoiceCell> cellsToBeDeleted = choicesToBeDeletedWithArray(choices);
- HashSet<ChoiceCell> cellsToBeRemovedFromPending = choicesToBeRemovedFromPendingWithArray(choices);
- // If choices are deleted that are already uploaded or pending and are used by a pending presentation, cancel it and send an error
- HashSet<ChoiceCell> pendingPresentationChoices = new HashSet<>();
- if (pendingPresentationSet != null && pendingPresentationSet.getChoices() != null) {
- pendingPresentationChoices.addAll(pendingPresentationSet.getChoices());
- }
-
- if (pendingPresentOperation != null && pendingPresentOperation.getState() != Task.CANCELED && pendingPresentOperation.getState() != Task.FINISHED && (cellsToBeDeleted.retainAll(pendingPresentationChoices) || cellsToBeRemovedFromPending.retainAll(pendingPresentationChoices))) {
- pendingPresentOperation.cancelTask();
- DebugTool.logWarning(TAG, "Attempting to delete choice cells while there is a pending presentation operation. Pending presentation cancelled.");
- pendingPresentOperation = null;
- }
-
- // Remove cells from pending and delete choices
- pendingPresentationChoices.removeAll(cellsToBeRemovedFromPending);
- for (Task operation : transactionQueue.getTasksAsList()) {
- if (!(operation instanceof PreloadChoicesOperation)) {
- continue;
- }
- ((PreloadChoicesOperation) operation).removeChoicesFromUpload(cellsToBeRemovedFromPending);
- }
-
- // Find Choices to delete
- if (cellsToBeDeleted.size() == 0) {
- DebugTool.logInfo(TAG, "Cells to be deleted size == 0");
- return;
- }
- findIdsOnChoices(cellsToBeDeleted);
-
- DeleteChoicesOperation deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, cellsToBeDeleted, new CompletionListener() {
+ DeleteChoicesOperation deleteChoicesOperation = new DeleteChoicesOperation(internalInterface, new HashSet<>(choices), preloadedChoices, new ChoicesOperationCompletionListener() {
@Override
- public void onComplete(boolean success) {
+ public void onComplete(boolean success, HashSet<ChoiceCell> updatedLoadedChoiceCells) {
if (!success) {
DebugTool.logError(TAG, "Failed to delete choices");
return;
}
- preloadedChoices.removeAll(cellsToBeDeleted);
+ preloadedChoices = updatedLoadedChoiceCells;
+ updatePendingTasksWithCurrentPreloads();
}
});
transactionQueue.add(deleteChoicesOperation, false);
@@ -322,63 +270,56 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return;
}
- if (this.pendingPresentationSet != null && pendingPresentOperation != null) {
- pendingPresentOperation.cancelTask();
- DebugTool.logWarning(TAG, "Presenting a choice set while one is currently presented. Cancelling previous and continuing");
- }
-
- this.pendingPresentationSet = choiceSet;
- preloadChoices(this.pendingPresentationSet.getChoices(), new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- if (!success) {
- choiceSet.getChoiceSetSelectionListener().onError("There was an error pre-loading choice set choices");
- } else {
- sendPresentOperation(keyboardListener, mode);
- }
- }
- });
+ sendPresentOperation(choiceSet, keyboardListener, mode);
}
- private void sendPresentOperation(KeyboardListener keyboardListener, InteractionMode mode) {
+ private void sendPresentOperation(final ChoiceSet choiceSet, KeyboardListener keyboardListener, InteractionMode mode) {
if (mode == null) {
mode = InteractionMode.MANUAL_ONLY;
}
- findIdsOnChoiceSet(pendingPresentationSet);
-
// Pass back the information to the developer
- ChoiceSetSelectionListener privateChoiceListener = new ChoiceSetSelectionListener() {
+ ChoiceSetSelectionListener listener = new ChoiceSetSelectionListener() {
@Override
public void onChoiceSelected(ChoiceCell choiceCell, TriggerSource triggerSource, int rowIndex) {
- if (pendingPresentationSet != null && pendingPresentationSet.getChoiceSetSelectionListener() != null) {
- pendingPresentationSet.getChoiceSetSelectionListener().onChoiceSelected(choiceCell, triggerSource, rowIndex);
+ if (choiceSet != null && choiceSet.getChoiceSetSelectionListener() != null) {
+ choiceSet.getChoiceSetSelectionListener().onChoiceSelected(choiceCell, triggerSource, rowIndex);
}
}
@Override
public void onError(String error) {
- if (pendingPresentationSet != null && pendingPresentationSet.getChoiceSetSelectionListener() != null) {
- pendingPresentationSet.getChoiceSetSelectionListener().onError(error);
+ if (choiceSet != null && choiceSet.getChoiceSetSelectionListener() != null) {
+ if (error != null) {
+ choiceSet.getChoiceSetSelectionListener().onError(error);
+ } else if (getState() == SHUTDOWN) {
+ choiceSet.getChoiceSetSelectionListener().onError("Incorrect State");
+ } else {
+ DebugTool.logError(TAG, "Present finished but an unhandled state occurred and callback failed");
+ choiceSet.getChoiceSetSelectionListener().onError("callback failed");
+ }
}
}
};
- PresentChoiceSetOperation presentOp;
+ PreloadPresentChoicesOperation presentOp;
- if (keyboardListener == null) {
- // Non-searchable choice set
- DebugTool.logInfo(TAG, "Creating non-searchable choice set");
- presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, null, null, privateChoiceListener, getNextCancelId());
+ if (fileManager.get() != null) {
+ if (keyboardListener == null) {
+ // Non-searchable choice set
+ DebugTool.logInfo(TAG, "Creating non-searchable choice set");
+ presentOp = new PreloadPresentChoicesOperation(internalInterface, fileManager.get(), choiceSet, mode, null, null, getNextCancelId(), displayName, defaultMainWindowCapability, isVROptional, this.preloadedChoices, null, listener);
+ } else {
+ // Searchable choice set
+ DebugTool.logInfo(TAG, "Creating searchable choice set");
+ presentOp = new PreloadPresentChoicesOperation(internalInterface, this.fileManager.get(), choiceSet, mode, keyboardConfiguration, keyboardListener, getNextCancelId(), displayName, defaultMainWindowCapability, isVROptional, this.preloadedChoices, null, listener);
+ }
+
+ transactionQueue.add(presentOp, false);
} else {
- // Searchable choice set
- DebugTool.logInfo(TAG, "Creating searchable choice set");
- presentOp = new PresentChoiceSetOperation(internalInterface, pendingPresentationSet, mode, keyboardConfiguration, keyboardListener, privateChoiceListener, getNextCancelId());
+ DebugTool.logError(TAG, "File Manager was null in preload choice operation");
}
-
- transactionQueue.add(presentOp, false);
- pendingPresentOperation = presentOp;
}
/**
@@ -400,12 +341,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return null;
}
- if (pendingPresentationSet != null && pendingPresentOperation != null) {
- pendingPresentOperation.cancelTask();
- pendingPresentationSet = null;
- DebugTool.logWarning(TAG, "There is a current or pending choice set, cancelling and continuing.");
- }
-
customKeyboardConfig = createValidKeyboardConfigurationBasedOnKeyboardCapabilitiesFromConfiguration(customKeyboardConfig);
if (customKeyboardConfig == null) {
if (this.keyboardConfiguration != null) {
@@ -421,7 +356,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
PresentKeyboardOperation keyboardOp = new PresentKeyboardOperation(internalInterface, keyboardConfiguration, initialText, customKeyboardConfig, listener, keyboardCancelID);
currentlyPresentedKeyboardOperation = keyboardOp;
transactionQueue.add(keyboardOp, false);
- pendingPresentOperation = keyboardOp;
return keyboardCancelID;
}
@@ -527,63 +461,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// CHOICE SET MANAGEMENT HELPERS
- HashSet<ChoiceCell> choicesToBeDeletedWithArray(List<ChoiceCell> choices) {
- HashSet<ChoiceCell> choicesSet = new HashSet<>(choices);
- choicesSet.retainAll(this.preloadedChoices);
- return choicesSet;
- }
-
- HashSet<ChoiceCell> choicesToBeRemovedFromPendingWithArray(List<ChoiceCell> choices) {
- HashSet<ChoiceCell> choicesSet = new HashSet<>(choices);
- choicesSet.retainAll(this.pendingPreloadChoices);
- return choicesSet;
- }
-
- /**
- * Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
- * E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
- * @param choices The list of choiceCells to be uploaded.
- */
- void addUniqueNamesToCells(List<ChoiceCell> choices) {
- HashMap<String, Integer> dictCounter = new HashMap<>();
-
- for (ChoiceCell cell : choices) {
- String cellName = cell.getText();
- Integer counter = dictCounter.get(cellName);
-
- if (counter != null) {
- dictCounter.put(cellName, ++counter);
- cell.setUniqueText(cell.getText() + " (" + counter + ")");
- } else {
- dictCounter.put(cellName, 1);
- }
- }
- }
-
- void addUniqueNamesBasedOnStrippedCells(List<ChoiceCell> strippedCells, List<ChoiceCell> 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<ChoiceCell, Integer> dictCounter = new HashMap<>();
- for (int i = 0; i < strippedCells.size(); i++) {
- ChoiceCell cell = strippedCells.get(i);
- Integer counter = dictCounter.get(cell);
- if (counter != null) {
- counter++;
- dictCounter.put(cell, counter);
- } else {
- dictCounter.put(cell, 1);
- }
-
- counter = dictCounter.get(cell);
-
- if (counter > 1) {
- unstrippedCells.get(i).setUniqueText(unstrippedCells.get(i).getText() + " (" + counter + ")");
- }
- }
- }
-
private List<ChoiceCell> cloneChoiceCellList(List<ChoiceCell> originalList) {
if (originalList == null) {
return null;
@@ -596,71 +473,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return clone;
}
- private LinkedHashSet<ChoiceCell> getChoicesToBeUploadedWithArray(List<ChoiceCell> choices) {
- // Clone choices
- List<ChoiceCell> choicesClone = cloneChoiceCellList(choices);
- if (choices != null && internalInterface.getSdlMsgVersion() != null
- && (internalInterface.getSdlMsgVersion().getMajorVersion() < 7
- || (internalInterface.getSdlMsgVersion().getMajorVersion() == 7 && internalInterface.getSdlMsgVersion().getMinorVersion() == 0))) {
- // If we're on < RPC 7.1, all primary texts need to be unique, so we don't need to check removed properties and duplicate cells
- addUniqueNamesToCells(choicesClone);
- } else {
- List<ChoiceCell> strippedCellsClone = removeUnusedProperties(choicesClone);
- addUniqueNamesBasedOnStrippedCells(strippedCellsClone, choicesClone);
- }
- LinkedHashSet<ChoiceCell> choiceCloneLinkedHash = new LinkedHashSet<>(choicesClone);
- choiceCloneLinkedHash.removeAll(preloadedChoices);
- return choiceCloneLinkedHash;
- }
-
- List<ChoiceCell> removeUnusedProperties(List<ChoiceCell> choiceCells) {
- List<ChoiceCell> strippedCellsClone = cloneChoiceCellList(choiceCells);
- //Clone Cells
- for (ChoiceCell cell : strippedCellsClone) {
- // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
- cell.setVoiceCommands(null);
-
- if (!hasImageFieldOfName(ImageFieldName.choiceImage)) {
- cell.setArtwork(null);
- }
- if (!hasTextFieldOfName(TextFieldName.secondaryText)) {
- cell.setSecondaryText(null);
- }
- if (!hasTextFieldOfName(TextFieldName.tertiaryText)) {
- cell.setTertiaryText(null);
- }
- if (!hasImageFieldOfName(ImageFieldName.choiceSecondaryImage)) {
- cell.setSecondaryArtwork(null);
- }
- }
- return strippedCellsClone;
- }
-
- void updateIdsOnChoices(LinkedHashSet<ChoiceCell> choices) {
- for (ChoiceCell cell : choices) {
- cell.setChoiceId(this.nextChoiceId);
- this.nextChoiceId++;
- }
- }
-
- private void findIdsOnChoiceSet(ChoiceSet choiceSet) {
- findIdsOnChoices(new HashSet<>(choiceSet.getChoices()));
- }
-
- private void findIdsOnChoices(HashSet<ChoiceCell> choices) {
- for (ChoiceCell cell : choices) {
- ChoiceCell uploadCell = null;
- if (pendingPreloadChoices.contains(cell)) {
- uploadCell = findIfPresent(cell, pendingPreloadChoices);
- } else if (preloadedChoices.contains(cell)) {
- uploadCell = findIfPresent(cell, preloadedChoices);
- }
- if (uploadCell != null) {
- cell.setChoiceId(uploadCell.getChoiceId());
- }
- }
- }
-
ChoiceCell findIfPresent(ChoiceCell cell, HashSet<ChoiceCell> set) {
if (set.contains(cell)) {
for (ChoiceCell setCell : set) {
@@ -671,6 +483,22 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
return null;
}
+ private void updatePendingTasksWithCurrentPreloads() {
+ for (Task task : this.transactionQueue.getTasksAsList()) {
+ if (task.getState() == Task.IN_PROGRESS || task.getState() == Task.CANCELED) {
+ continue;
+ }
+
+ if (task instanceof PreloadPresentChoicesOperation) {
+ PreloadPresentChoicesOperation preloadOp = (PreloadPresentChoicesOperation) task;
+ preloadOp.setLoadedCells(this.preloadedChoices);
+ } else if (task instanceof DeleteChoicesOperation) {
+ DeleteChoicesOperation deleteOp = (DeleteChoicesOperation) task;
+ deleteOp.setLoadedCells(this.preloadedChoices);
+ }
+ }
+ }
+
// LISTENERS
private void addListeners() {
@@ -740,14 +568,6 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
// ADDITIONAL HELPERS
- 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);
- }
-
boolean setUpChoiceSet(ChoiceSet choiceSet) {
List<ChoiceCell> choices = choiceSet.getChoices();
@@ -765,8 +585,9 @@ abstract class BaseChoiceSetManager extends BaseSubManager {
}
}
- HashSet<ChoiceCell> uniqueChoiceCells = new HashSet<>();
HashSet<String> uniqueVoiceCommands = new HashSet<>();
+ HashSet<ChoiceCell> uniqueChoiceCells = new HashSet<>();
+
int allVoiceCommandsCount = 0;
int choiceCellWithVoiceCommandCount = 0;
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
index 7d59bf555..46d478000 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/ChoiceCell.java
@@ -41,10 +41,10 @@ import java.util.ArrayList;
import java.util.List;
public class ChoiceCell implements Cloneable{
- private String text, secondaryText, tertiaryText, uniqueText;
+ private String text, secondaryText, tertiaryText;
private List<String> voiceCommands;
private SdlArtwork artwork, secondaryArtwork;
- private Integer choiceId;
+ private Integer choiceId, uniqueTextId;
/**
* MAX ID for cells - Cannot use Integer.MAX_INT as the value is too high.
@@ -58,7 +58,7 @@ public class ChoiceCell implements Cloneable{
*/
public ChoiceCell(@NonNull String text) {
setText(text);
- setUniqueText(text);
+ setUniqueTextId(1);
setChoiceId(MAX_ID);
}
@@ -71,7 +71,7 @@ public class ChoiceCell implements Cloneable{
*/
public ChoiceCell(@NonNull String text, List<String> voiceCommands, SdlArtwork artwork) {
setText(text);
- setUniqueText(text);
+ setUniqueTextId(1);
setVoiceCommands(voiceCommands);
setArtwork(artwork);
setChoiceId(MAX_ID);
@@ -91,7 +91,7 @@ public class ChoiceCell implements Cloneable{
setText(text);
setSecondaryText(secondaryText);
setTertiaryText(tertiaryText);
- setUniqueText(text);
+ setUniqueTextId(1);
setVoiceCommands(voiceCommands);
setArtwork(artwork);
setSecondaryArtwork(secondaryArtwork);
@@ -238,20 +238,28 @@ public class ChoiceCell implements Cloneable{
* Attempting to use cells that are exactly the same (all text and artwork fields are the same)
* will not cause this to be used. This will not be used when connected to modules supporting RPC 7.1+.
*
- * @param uniqueText - the uniqueText to be used in place of primaryText when core does not support identical names for ChoiceSets
+ * @param uniqueTextId - the uniqueTextId to be appended to primaryText when core does not support identical names for ChoiceSets
*/
- void setUniqueText(String uniqueText) {
- this.uniqueText = uniqueText;
+ void setUniqueTextId(Integer uniqueTextId) {
+ this.uniqueTextId = uniqueTextId;
}
/**
* NOTE: USED INTERNALLY
- * Get the uniqueText that was used in place of primaryText
+ * Get the uniqueTextId that was used to append to primaryText
*
- * @return the uniqueText for this Choice Cell
+ * @return the uniqueTextId for this Choice Cell
*/
+ Integer getUniqueTextId() {
+ return uniqueTextId;
+ }
+
String getUniqueText() {
- return uniqueText;
+ if (this.uniqueTextId != 1) {
+ return this.text + " (" + this.uniqueTextId + ")";
+ } else {
+ return this.text;
+ }
}
@Override
@@ -292,7 +300,7 @@ public class ChoiceCell implements Cloneable{
@NonNull
public String toString() {
return "ChoiceCell: ID: " + this.choiceId + " Text: " + text + " - Secondary Text: " + secondaryText + " - Tertiary Text: " + tertiaryText + " " +
- (text.equals(uniqueText) ? "" : "| Unique Text: " + uniqueText) + " | Artwork Names: " + ((getArtwork() == null || getArtwork().getName() == null) ? "Primary Art null" : getArtwork().getName())
+ (uniqueTextId == 1 ? "" : "| Unique Text: " + uniqueTextId) + " | Artwork Names: " + ((getArtwork() == null || getArtwork().getName() == null) ? "Primary Art null" : getArtwork().getName())
+ " Secondary Art - " + ((getSecondaryArtwork() == null || getSecondaryArtwork().getName() == null) ? "Secondary Art null" : getSecondaryArtwork().getName()) +
" | Voice Commands Size: " + ((getVoiceCommands() == null) ? 0 : getVoiceCommands().size());
}
@@ -315,6 +323,9 @@ public class ChoiceCell implements Cloneable{
if (this.voiceCommands != null) {
clone.voiceCommands = new ArrayList<>(voiceCommands);
}
+ if (this.uniqueTextId != null) {
+ clone.uniqueTextId = this.uniqueTextId;
+ }
return clone;
} catch (CloneNotSupportedException e) {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
index 53f209eae..a3814c1d2 100644
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/DeleteChoicesOperation.java
@@ -36,7 +36,6 @@
package com.smartdevicelink.managers.screen.choiceset;
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.DeleteInteractionChoiceSet;
@@ -51,25 +50,37 @@ import java.util.List;
class DeleteChoicesOperation extends Task {
private static final String TAG = "DeleteChoicesOperation";
private final WeakReference<ISdl> internalInterface;
- private final HashSet<ChoiceCell> cellsToDelete;
- private final CompletionListener completionListener;
+ private HashSet<ChoiceCell> cellsToDelete;
+ private final BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener;
+ private HashSet<ChoiceCell> loadedCells;
+ private List<DeleteInteractionChoiceSet> deleteChoices;
+ private boolean completionSuccess = false;
- DeleteChoicesOperation(ISdl internalInterface, HashSet<ChoiceCell> cellsToDelete, CompletionListener completionListener) {
+ DeleteChoicesOperation(ISdl internalInterface, HashSet<ChoiceCell> cellsToDelete, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener) {
super("DeleteChoicesOperation");
this.internalInterface = new WeakReference<>(internalInterface);
this.cellsToDelete = cellsToDelete;
this.completionListener = completionListener;
+ this.loadedCells = loadedCells == null ? new HashSet<ChoiceCell>() : new HashSet<>(loadedCells);
}
@Override
public void onExecute() {
DebugTool.logInfo(TAG, "Choice Operation: Executing delete choices operation");
+ updateCellsToDelete();
+ if (this.cellsToDelete == null || this.cellsToDelete.isEmpty()) {
+ if (completionListener != null) {
+ completionListener.onComplete(true, loadedCells);
+ DebugTool.logInfo(TAG, "No cells were provided to delete");
+ }
+ DeleteChoicesOperation.super.onFinished();
+ }
sendDeletions();
}
private void sendDeletions() {
- List<DeleteInteractionChoiceSet> deleteChoices = createDeleteSets();
+ deleteChoices = createDeleteSets();
if (deleteChoices.size() > 0) {
@@ -82,7 +93,7 @@ class DeleteChoicesOperation extends Task {
@Override
public void onFinished() {
if (completionListener != null) {
- completionListener.onComplete(true);
+ completionListener.onComplete(true, loadedCells);
}
DebugTool.logInfo(TAG, "Successfully deleted choices");
@@ -93,21 +104,68 @@ class DeleteChoicesOperation extends Task {
public void onResponse(int correlationId, RPCResponse response) {
if (!response.getSuccess()) {
if (completionListener != null) {
- completionListener.onComplete(false);
+ completionListener.onComplete(false, loadedCells);
}
DebugTool.logError(TAG, "Failed to delete choice: " + response.getInfo() + " | Corr ID: " + correlationId);
DeleteChoicesOperation.super.onFinished();
+ } else {
+ if (loadedCells != null) {
+ loadedCells.remove(loadedCellFromCorrelationId(deleteChoices, correlationId));
+ }
}
}
});
}
} else {
if (completionListener != null) {
- completionListener.onComplete(true);
+ completionListener.onComplete(true, this.loadedCells);
}
DebugTool.logInfo(TAG, "No Choices to delete, continue");
+ DeleteChoicesOperation.super.onFinished();
+ }
+ }
+
+ public void setLoadedCells(HashSet<ChoiceCell> loadedCells) {
+ this.loadedCells = new HashSet<>(loadedCells);
+ }
+
+ private void updateCellsToDelete() {
+ HashSet<ChoiceCell> updatedCellsToDelete = new HashSet<>(this.cellsToDelete);
+ updatedCellsToDelete.retainAll(loadedCells);
+
+ for (ChoiceCell cell : updatedCellsToDelete) {
+ for (ChoiceCell loadedCell : this.loadedCells) {
+ if (loadedCell.equals(cell)) {
+ cell.setChoiceId(loadedCell.getChoiceId());
+ }
+ }
+ }
+
+ this.cellsToDelete = updatedCellsToDelete;
+ }
+
+ private ChoiceCell loadedCellFromCorrelationId(List<DeleteInteractionChoiceSet> deleteRpcs, int correlationId) {
+
+ Integer choiceId = null;
+
+ for (DeleteInteractionChoiceSet rpc : deleteRpcs) {
+ if (rpc.getCorrelationID() == correlationId) {
+ choiceId = rpc.getInteractionChoiceSetID();
+ }
}
+
+ if (choiceId == null) {
+ return null;
+ }
+
+ for (ChoiceCell cell : this.loadedCells) {
+ if (cell.getChoiceId() == choiceId) {
+ return cell;
+ }
+ }
+
+ return null;
}
List<DeleteInteractionChoiceSet> createDeleteSets() {
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
deleted file mode 100644
index 8f47a2880..000000000
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadChoicesOperation.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (c) 2019 Livio, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided with the
- * distribution.
- *
- * Neither the name of the Livio Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import androidx.annotation.NonNull;
-
-import com.livio.taskmaster.Task;
-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.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.Choice;
-import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSet;
-import com.smartdevicelink.proxy.rpc.Image;
-import com.smartdevicelink.proxy.rpc.WindowCapability;
-import com.smartdevicelink.proxy.rpc.enums.DisplayType;
-import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
-import com.smartdevicelink.proxy.rpc.enums.TextFieldName;
-import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
-import com.smartdevicelink.util.DebugTool;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-
-class PreloadChoicesOperation extends Task {
- private static final String TAG = "PreloadChoicesOperation";
- private final WeakReference<ISdl> internalInterface;
- private final WeakReference<FileManager> fileManager;
- private final WindowCapability defaultMainWindowCapability;
- private final String displayName;
- private final HashSet<ChoiceCell> cellsToUpload;
- private final CompletionListener completionListener;
- private boolean isRunning;
- private final boolean isVROptional;
- private boolean choiceError = false;
-
- PreloadChoicesOperation(ISdl internalInterface, FileManager fileManager, String displayName, WindowCapability defaultMainWindowCapability,
- Boolean isVROptional, LinkedHashSet<ChoiceCell> cellsToPreload, CompletionListener listener) {
- super("PreloadChoicesOperation");
- this.internalInterface = new WeakReference<>(internalInterface);
- this.fileManager = new WeakReference<>(fileManager);
- this.displayName = displayName;
- this.defaultMainWindowCapability = defaultMainWindowCapability;
- this.isVROptional = isVROptional;
- this.cellsToUpload = cellsToPreload;
- this.completionListener = listener;
- }
-
- @Override
- public void onExecute() {
- DebugTool.logInfo(TAG, "Choice Operation: Executing preload choices operation");
- preloadCellArtworks(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- preloadCells();
- }
- });
- }
-
- void removeChoicesFromUpload(HashSet<ChoiceCell> choices) {
- if (isRunning) {
- return;
- }
- cellsToUpload.removeAll(choices);
- }
-
- private void preloadCellArtworks(@NonNull final CompletionListener listener) {
- isRunning = true;
-
- List<SdlArtwork> artworksToUpload = artworksToUpload();
-
- if (artworksToUpload.size() == 0) {
- DebugTool.logInfo(TAG, "Choice Preload: No Choice Artworks to upload");
- listener.onComplete(true);
- isRunning = false;
- return;
- }
-
- if (fileManager.get() != null) {
- fileManager.get().uploadArtworks(artworksToUpload, new MultipleFileCompletionListener() {
- @Override
- public void onComplete(Map<String, String> errors) {
- if (errors != null && errors.size() > 0) {
- DebugTool.logError(TAG, "Error uploading choice cell Artworks: " + errors.toString());
- listener.onComplete(false);
- isRunning = false;
- } else {
- DebugTool.logInfo(TAG, "Choice Artworks Uploaded");
- listener.onComplete(true);
- isRunning = false;
- }
- }
- });
- } else {
- DebugTool.logError(TAG, "File manager is null in choice preload operation");
- listener.onComplete(false);
- isRunning = false;
- }
- }
-
- private void preloadCells() {
- isRunning = true;
- List<CreateInteractionChoiceSet> choiceRPCs = new ArrayList<>(cellsToUpload.size());
- for (ChoiceCell cell : cellsToUpload) {
- CreateInteractionChoiceSet csCell = choiceFromCell(cell);
- if (csCell != null) {
- choiceRPCs.add(csCell);
- }
- }
-
- if (choiceRPCs.size() == 0) {
- DebugTool.logError(TAG, " All Choice cells to send are null, so the choice set will not be shown");
- completionListener.onComplete(true);
- isRunning = false;
- return;
- }
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPCs(choiceRPCs, new OnMultipleRequestListener() {
- @Override
- public void onUpdate(int remainingRequests) {
-
- }
-
- @Override
- public void onFinished() {
- isRunning = false;
- DebugTool.logInfo(TAG, "Finished pre loading choice cells");
- completionListener.onComplete(!choiceError);
- choiceError = false;
- PreloadChoicesOperation.super.onFinished();
- }
-
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (!response.getSuccess()) {
- DebugTool.logError(TAG, "There was an error uploading a choice cell: " + response.getInfo() + " resultCode: " + response.getResultCode());
- choiceError = true;
- }
- }
- });
- } else {
- DebugTool.logError(TAG, "Internal Interface null in preload choice operation");
- isRunning = false;
- completionListener.onComplete(false);
- }
- }
-
- private CreateInteractionChoiceSet choiceFromCell(ChoiceCell cell) {
-
- List<String> vrCommands;
- if (cell.getVoiceCommands() == null) {
- vrCommands = isVROptional ? null : Collections.singletonList(String.valueOf(cell.getChoiceId()));
- } else {
- vrCommands = cell.getVoiceCommands();
- }
-
- String menuName = shouldSendChoiceText() ? cell.getUniqueText() : null;
-
- if (menuName == null) {
- DebugTool.logError(TAG, "Could not convert Choice Cell to CreateInteractionChoiceSet. It will not be shown. Cell: " + cell.toString());
- return null;
- }
-
- String secondaryText = shouldSendChoiceSecondaryText() ? cell.getSecondaryText() : null;
- String tertiaryText = shouldSendChoiceTertiaryText() ? cell.getTertiaryText() : null;
-
- Image image = shouldSendChoicePrimaryImage() && cell.getArtwork() != null ? cell.getArtwork().getImageRPC() : null;
- Image secondaryImage = shouldSendChoiceSecondaryImage() && cell.getSecondaryArtwork() != null ? cell.getSecondaryArtwork().getImageRPC() : null;
-
- Choice choice = new Choice(cell.getChoiceId(), menuName);
- choice.setVrCommands(vrCommands);
- choice.setSecondaryText(secondaryText);
- choice.setTertiaryText(tertiaryText);
- choice.setIgnoreAddingVRItems(true);
-
- if (fileManager.get() != null) {
- if (image != null && (cell.getArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getArtwork()))) {
- choice.setImage(image);
- }
- if (secondaryImage != null && (cell.getSecondaryArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getSecondaryArtwork()))) {
- choice.setSecondaryImage(secondaryImage);
- }
- }
-
- return new CreateInteractionChoiceSet(choice.getChoiceID(), Collections.singletonList(choice));
- }
-
- // HELPERS
- boolean shouldSendChoiceText() {
- if (this.displayName != null && this.displayName.equals(DisplayType.GEN3_8_INCH.toString())) {
- return true;
- }
- return templateSupportsTextField(TextFieldName.menuName);
- }
-
- boolean shouldSendChoiceSecondaryText() {
- return templateSupportsTextField(TextFieldName.secondaryText);
- }
-
- boolean shouldSendChoiceTertiaryText() {
- return templateSupportsTextField(TextFieldName.tertiaryText);
- }
-
- boolean shouldSendChoicePrimaryImage() {
- return templateSupportsImageField(ImageFieldName.choiceImage);
- }
-
- boolean shouldSendChoiceSecondaryImage() {
- return templateSupportsImageField(ImageFieldName.choiceSecondaryImage);
- }
-
- boolean templateSupportsTextField(TextFieldName name) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName(defaultMainWindowCapability, name);
- }
-
- boolean templateSupportsImageField(ImageFieldName name) {
- return defaultMainWindowCapability == null || ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName(defaultMainWindowCapability, name);
- }
-
- List<SdlArtwork> artworksToUpload() {
- List<SdlArtwork> artworksToUpload = new ArrayList<>();
- for (ChoiceCell cell : cellsToUpload) {
- if (shouldSendChoicePrimaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getArtwork())) {
- artworksToUpload.add(cell.getArtwork());
- }
- if (shouldSendChoiceSecondaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
- artworksToUpload.add(cell.getSecondaryArtwork());
- }
- }
- return artworksToUpload;
- }
-} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java
new file mode 100644
index 000000000..8c6ebca6f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PreloadPresentChoicesOperation.java
@@ -0,0 +1,927 @@
+/*
+ * 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.
+ *
+ * Created by rhenigan on 8/9/21 1:52 PM
+ *
+ */
+
+package com.smartdevicelink.managers.screen.choiceset;
+
+import androidx.annotation.NonNull;
+
+import com.livio.taskmaster.Task;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.ISdl;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasImageFieldOfName;
+import static com.smartdevicelink.managers.ManagerUtility.WindowCapabilityUtility.hasTextFieldOfName;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.CancelInteraction;
+import com.smartdevicelink.proxy.rpc.Choice;
+import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSet;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.KeyboardProperties;
+import com.smartdevicelink.proxy.rpc.OnKeyboardInput;
+import com.smartdevicelink.proxy.rpc.PerformInteraction;
+import com.smartdevicelink.proxy.rpc.PerformInteractionResponse;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.WindowCapability;
+import com.smartdevicelink.proxy.rpc.enums.DisplayType;
+import com.smartdevicelink.proxy.rpc.enums.ImageFieldName;
+import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
+import com.smartdevicelink.proxy.rpc.enums.KeyboardEvent;
+import com.smartdevicelink.proxy.rpc.enums.LayoutMode;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+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.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+class PreloadPresentChoicesOperation extends Task {
+
+ private static final String TAG = "PreloadPresentChoicesOperation";
+ private final WeakReference<ISdl> internalInterface;
+ private final WeakReference<FileManager> fileManager;
+ private final WindowCapability defaultMainWindowCapability;
+ private final String displayName;
+ private final ArrayList<ChoiceCell> cellsToUpload;
+ private final BaseChoiceSetManager.ChoicesOperationCompletionListener completionListener;
+ private final ChoiceSetSelectionListener selectionListener;
+ private final boolean isVROptional;
+ private boolean choiceError = false;
+ private HashSet<ChoiceCell> loadedCells;
+ private final ChoiceSet choiceSet;
+ // Start choiceId at 1 to ensure all HMIs handle it https://github.com/smartdevicelink/generic_hmi/commit/b292fbbec095b9ce11b520d47ec95b6fcff8e247
+ private static Integer choiceId = 1;
+ private static Boolean reachedMaxIds = false;
+ private static final int MAX_CHOICE_ID = 65535;
+ private final Integer cancelID;
+ private final InteractionMode presentationMode;
+ private final KeyboardProperties originalKeyboardProperties;
+ private KeyboardProperties keyboardProperties;
+ private ChoiceCell selectedCell;
+ private TriggerSource selectedTriggerSource;
+ private boolean updatedKeyboardProperties;
+ private OnRPCNotificationListener keyboardRPCListener;
+ Integer selectedCellRow;
+ KeyboardListener keyboardListener;
+ final SdlMsgVersion sdlMsgVersion;
+ private enum SDLPreloadPresentChoicesOperationState {
+ NOT_STARTED,
+ UPLOADING_IMAGES,
+ UPLOADING_CHOICES,
+ UPDATING_KEYBOARD_PROPERTIES,
+ PRESENTING_CHOICES,
+ CANCELLING_PRESENT_CHOICES,
+ RESETTING_KEYBOARD_PROPERTIES,
+ FINISHING
+ }
+ private SDLPreloadPresentChoicesOperationState currentState;
+
+ PreloadPresentChoicesOperation(ISdl internalInterface, FileManager fileManager, String displayName, WindowCapability defaultWindowCapability,
+ Boolean isVROptional, LinkedHashSet<ChoiceCell> cellsToPreload, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener listener) {
+ super("PreloadPresentChoiceOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.displayName = displayName;
+ this.defaultMainWindowCapability = defaultWindowCapability;
+ this.isVROptional = isVROptional;
+ this.cellsToUpload = new ArrayList<>(cellsToPreload);
+ this.completionListener = listener;
+ this.keyboardListener = null;
+ this.choiceSet = null;
+ this.presentationMode = null;
+ this.cancelID = null;
+ this.originalKeyboardProperties = null;
+ this.keyboardProperties = null;
+ this.selectedCellRow = null;
+ this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
+ this.loadedCells = loadedCells;
+ this.currentState = SDLPreloadPresentChoicesOperationState.NOT_STARTED;
+ this.selectionListener = null;
+ }
+
+ PreloadPresentChoicesOperation(ISdl internalInterface, FileManager fileManager, ChoiceSet choiceSet, InteractionMode mode,
+ KeyboardProperties originalKeyboardProperties, KeyboardListener keyboardListener, Integer cancelID, String displayName, WindowCapability windowCapability,
+ Boolean isVROptional, HashSet<ChoiceCell> loadedCells, BaseChoiceSetManager.ChoicesOperationCompletionListener preloadListener, ChoiceSetSelectionListener listener) {
+ super("PreloadPresentChoiceOperation");
+ this.internalInterface = new WeakReference<>(internalInterface);
+ this.keyboardListener = keyboardListener;
+ this.choiceSet = choiceSet;
+ this.choiceSet.canceledListener = new ChoiceSetCanceledListener() {
+ @Override
+ public void onChoiceSetCanceled() {
+ cancelInteraction();
+ }
+ };
+ this.presentationMode = mode;
+ this.cancelID = cancelID;
+ this.originalKeyboardProperties = originalKeyboardProperties;
+ this.keyboardProperties = originalKeyboardProperties;
+ this.selectedCellRow = null;
+ this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
+ this.fileManager = new WeakReference<>(fileManager);
+ this.displayName = displayName;
+ this.defaultMainWindowCapability = windowCapability;
+ this.isVROptional = isVROptional;
+ this.cellsToUpload = new ArrayList<>(choiceSet.getChoices());
+ this.completionListener = preloadListener;
+ this.selectionListener = listener;
+ this.loadedCells = loadedCells;
+ this.currentState = SDLPreloadPresentChoicesOperationState.NOT_STARTED;
+ }
+
+ @Override
+ public void onExecute() {
+ if (this.getState() == CANCELED) {
+ return;
+ }
+
+ if (this.loadedCells == null || this.loadedCells.isEmpty()) {
+ choiceId = 1;
+ reachedMaxIds = false;
+ }
+
+ DebugTool.logInfo(TAG, "Choice Operation: Executing preload choices operation");
+ // Enforce unique cells and remove cells that are already loaded
+ this.cellsToUpload.removeAll(loadedCells);
+
+ if ((this.loadedCells.size() == MAX_CHOICE_ID) && this.cellsToUpload.size() > 0) {
+ DebugTool.logError(TAG, "Choice Cells to upload exceed maximum");
+ finishOperation(false);
+ }
+
+ assignIdsToCells(this.cellsToUpload);
+ makeCellsToUploadUnique(this.cellsToUpload);
+
+ if (this.choiceSet != null) {
+ updateChoiceSet(this.choiceSet, this.loadedCells, new HashSet<>(this.cellsToUpload));
+ }
+ // Start uploading cell artworks, then cells themselves, then determine if we want to present, then update keyboard properties if necessary, then present the choice set, then revert keyboard properties if necessary
+ preloadCellArtworks(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ // If some artworks failed to upload, we are still going to try to load the cells
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+ preloadCells(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+
+ if (choiceSet == null) {
+ finishOperation(true);
+ return;
+ }
+ DebugTool.logInfo(TAG, "Choice Operation: Executing present choice set operation");
+ updateKeyboardProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (getState()==CANCELED || !success) {
+ finishOperation(false);
+ return;
+ }
+ presentChoiceSet(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ resetKeyboardProperties(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ finishOperation(success);
+ return;
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ // Preload operation methods
+ private void preloadCellArtworks(@NonNull final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPLOADING_IMAGES;
+
+ List<SdlArtwork> artworksToUpload = new ArrayList<>(artworksToUpload());
+
+ if (artworksToUpload.size() == 0) {
+ DebugTool.logInfo(TAG, "Choice Preload: No Choice Artworks to upload");
+ listener.onComplete(true);
+ return;
+ }
+
+ if (fileManager.get() != null) {
+ fileManager.get().uploadArtworks(artworksToUpload, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && errors.size() > 0) {
+ DebugTool.logError(TAG, "Error uploading choice cell Artworks: " + errors.toString());
+ listener.onComplete(false);
+ } else {
+ DebugTool.logInfo(TAG, "Choice Artworks Uploaded");
+ listener.onComplete(true);
+ }
+ }
+ });
+ } else {
+ DebugTool.logError(TAG, "File manager is null in choice preload operation");
+ listener.onComplete(false);
+ }
+ }
+
+ private void preloadCells(@NonNull final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPLOADING_CHOICES;
+
+ final List<CreateInteractionChoiceSet> choiceRPCs = new ArrayList<>(cellsToUpload.size());
+ for (ChoiceCell cell : cellsToUpload) {
+ CreateInteractionChoiceSet csCell = choiceFromCell(cell);
+ if (csCell != null) {
+ choiceRPCs.add(csCell);
+ }
+ }
+
+ if (choiceRPCs.size() == 0) {
+ if (choiceSet == null) {
+ DebugTool.logError(TAG, " All Choice cells to send are null, so the choice set will not be shown");
+ }
+ listener.onComplete(true);
+ return;
+ }
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPCs(choiceRPCs, new OnMultipleRequestListener() {
+ @Override
+ public void onUpdate(int remainingRequests) {
+
+ }
+
+ @Override
+ public void onFinished() {
+ DebugTool.logInfo(TAG, "Finished pre loading choice cells");
+ listener.onComplete(!choiceError);
+ choiceError = false;
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (!response.getSuccess()) {
+ DebugTool.logError(TAG, "There was an error uploading a choice cell: " + response.getInfo() + " resultCode: " + response.getResultCode());
+ choiceError = true;
+ } else {
+ for (CreateInteractionChoiceSet rpc : choiceRPCs) {
+ if (correlationId == rpc.getCorrelationID()) {
+ loadedCells.add(cellFromChoiceId(rpc.getChoiceSet().get(0).getChoiceID()));
+ }
+ }
+ }
+ }
+ });
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null in preload choice operation");
+ listener.onComplete(!choiceError);
+ }
+ }
+
+ private void updateKeyboardProperties(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.UPDATING_KEYBOARD_PROPERTIES;
+ if (keyboardListener == null) {
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ return;
+ }
+
+ addListeners();
+
+ if (keyboardListener != null && choiceSet.getCustomKeyboardConfiguration() != null) {
+ keyboardProperties = choiceSet.getCustomKeyboardConfiguration();
+ updatedKeyboardProperties = true;
+ }
+
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setKeyboardProperties(keyboardProperties);
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+
+ if (!response.getSuccess()) {
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ DebugTool.logError(TAG, "Error Setting keyboard properties in present choice set operation");
+ return;
+ }
+
+ updatedKeyboardProperties = true;
+
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ DebugTool.logInfo(TAG, "Success Setting keyboard properties in present choice set operation");
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ } else {
+ DebugTool.logError(TAG, "Internal interface null - present choice set op - choice");
+ listener.onComplete(false);
+ }
+ }
+
+ void resetKeyboardProperties(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.RESETTING_KEYBOARD_PROPERTIES;
+ if (this.keyboardListener == null || this.originalKeyboardProperties == null) {
+ if(listener != null) {
+ listener.onComplete(true);
+ finishOperation(true);
+ return;
+ }
+ }
+ SetGlobalProperties setProperties = new SetGlobalProperties();
+ setProperties.setKeyboardProperties(this.originalKeyboardProperties);
+ setProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ updatedKeyboardProperties = false;
+ DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
+ } else {
+ DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
+ }
+ if (listener != null) {
+ listener.onComplete(response.getSuccess());
+ if (response.getSuccess()) {
+ finishOperation(true);
+ }
+ }
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setProperties);
+ internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
+ listener.onComplete(false);
+ }
+ }
+
+ private void presentChoiceSet(final CompletionListener listener) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.PRESENTING_CHOICES;
+ PerformInteraction pi = getPerformInteraction();
+ pi.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (!response.getSuccess()) {
+ DebugTool.logError(TAG, "Presenting Choice set failed: " + response.getInfo());
+
+ if (selectionListener != null) {
+ selectionListener.onError(response.getInfo());
+ }
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ finishOperation(false);
+ return;
+ }
+
+ PerformInteractionResponse performInteractionResponse = (PerformInteractionResponse) response;
+ setSelectedCellWithId(performInteractionResponse.getChoiceID());
+ selectedTriggerSource = performInteractionResponse.getTriggerSource();
+
+ if (selectionListener != null && selectedCell != null && selectedTriggerSource != null && selectedCellRow != null) {
+ selectionListener.onChoiceSelected(selectedCell, selectedTriggerSource, selectedCellRow);
+ if (listener != null) {
+ listener.onComplete(true);
+ }
+ } else {
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ }
+ }
+ });
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(pi);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when presenting choice set in operation");
+ if (selectionListener != null) {
+ selectionListener.onError("Internal Interface null");
+ }
+ if (listener != null) {
+ listener.onComplete(false);
+ }
+ }
+ }
+
+ private void cancelInteraction() {
+ if ((getState() == Task.FINISHED)) {
+ DebugTool.logInfo(TAG, "This operation has already finished so it can not be canceled.");
+ return;
+ } else if (getState() == Task.CANCELED) {
+ DebugTool.logInfo(TAG, "This operation has already been canceled. It will be finished at some point during the operation.");
+ return;
+ } else if ((getState() == Task.IN_PROGRESS)) {
+ if (this.currentState != SDLPreloadPresentChoicesOperationState.PRESENTING_CHOICES) {
+ DebugTool.logInfo(TAG, "Canceling the operation before a present.");
+ this.cancelTask();
+ return;
+ }else if (sdlMsgVersion.getMajorVersion() < 6) {
+ DebugTool.logWarning(TAG, "Canceling a presented choice set is not supported on this head unit");
+ this.cancelTask();
+ return;
+ }
+
+ DebugTool.logInfo(TAG, "Canceling the presented choice set interaction.");
+
+ CancelInteraction cancelInteraction = new CancelInteraction(FunctionID.PERFORM_INTERACTION.getId(), cancelID);
+ cancelInteraction.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ DebugTool.logInfo(TAG, "Canceled the presented choice set " + ((response.getResultCode() == Result.SUCCESS) ? "successfully" : "unsuccessfully"));
+ } else {
+ DebugTool.logError(TAG, "Error canceling the presented choice set: " + correlationId + " with error: " + response.getInfo());
+ }
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(cancelInteraction);
+ } else {
+ DebugTool.logError(TAG, "Internal interface null - could not send cancel interaction for choice set");
+ }
+ } else {
+ DebugTool.logInfo(TAG, "Canceling a choice set that has not yet been sent to Core");
+ this.cancelTask();
+ }
+ }
+
+ // Present Helpers
+ void setSelectedCellWithId(Integer cellId) {
+ if (choiceSet.getChoices() != null && cellId != null) {
+ List<ChoiceCell> cells = choiceSet.getChoices();
+ for (int i = 0; i < cells.size(); i++) {
+ if (cells.get(i).getChoiceId() == cellId) {
+ selectedCell = cells.get(i);
+ selectedCellRow = i;
+ return;
+ }
+ }
+ }
+ }
+
+ PerformInteraction getPerformInteraction() {
+ if (this.choiceSet == null) {
+ return new PerformInteraction();
+ }
+ PerformInteraction pi = new PerformInteraction(choiceSet.getTitle(), presentationMode, getChoiceIds());
+ pi.setInitialPrompt(choiceSet.getInitialPrompt());
+ pi.setHelpPrompt(choiceSet.getHelpPrompt());
+ pi.setTimeoutPrompt(choiceSet.getTimeoutPrompt());
+ pi.setVrHelp(choiceSet.getVrHelpList());
+ pi.setTimeout(choiceSet.getTimeout() * 1000);
+ pi.setInteractionLayout(getLayoutMode());
+ pi.setCancelID(cancelID);
+ return pi;
+ }
+
+ private void assignIdsToCells(ArrayList<ChoiceCell> cells) {
+ ArrayList<Integer> usedIds = new ArrayList<>();
+ for (ChoiceCell cell : loadedCells) {
+ usedIds.add(cell.getChoiceId());
+ }
+ Collections.sort(usedIds);
+ ArrayList<Integer> sortedUsedIds = (ArrayList<Integer>) usedIds.clone();
+
+ // Loop through the cells we need ids for. Get and assign those ids
+ for (int i = 0; i < cells.size(); i++) {
+ int cellId = nextChoiceIdBasedOnUsedIds(sortedUsedIds);
+ cells.get(i).setChoiceId(cellId);
+
+ // Insert the ids into the usedIds sorted array in the correct position
+ for (int j = 0; j < sortedUsedIds.size(); j++) {
+ if (sortedUsedIds.get(j) > cellId) {
+ sortedUsedIds.add(j, cellId);
+ break;
+ } else if (j == (sortedUsedIds.size() - 1)) {
+ sortedUsedIds.add(cellId);
+ break;
+ }
+ }
+ }
+ }
+
+ // Find the next available choice is. Takes into account the loaded cells to ensure there are not duplicates
+ // @param usedIds The already loaded cell ids
+ // @return The choice id between 0 - 65535, or Not Found if no cell ids were available
+ private int nextChoiceIdBasedOnUsedIds(ArrayList<Integer> usedIds) {
+ // Check if we are entirely full, or if we've advanced beyond the max value, loop back
+ if (choiceId == MAX_CHOICE_ID) {
+ choiceId = 1;
+ reachedMaxIds = true;
+ }
+
+ if (reachedMaxIds) {
+ // We've looped all the way around, so we need to check loaded cells
+ // Sort the set of cells by the choice id so that we can more easily check which slots are available
+ if (usedIds.size() >= (MAX_CHOICE_ID + 1)) {
+ //If we've maxed out our slots, return the max value
+ choiceId = MAX_CHOICE_ID;
+ return choiceId;
+ }
+
+ // If the last value isn't the max value, just keep grabbing towards the max value
+ int lastUsedId = usedIds.get(usedIds.size() - 1);
+ if (lastUsedId < MAX_CHOICE_ID) {
+ choiceId = lastUsedId + 1;
+ return choiceId;
+ }
+
+ // All our easy options are gone. Find and grab an empty slot from within the sorted list
+ for (int i = 0; i < usedIds.size(); i++) {
+ int loopId = usedIds.get(i);
+ if (i != loopId) {
+ // This slot is open because the cell id does not match an open sorted slot
+ choiceId = i;
+ return choiceId;
+ }
+ }
+
+ // This *shouldn't* be possible
+ choiceId = MAX_CHOICE_ID;
+ return choiceId;
+ } else {
+ // We haven't looped all the way around yet, so we'll just take the current number, then advance the item
+ return choiceId++;
+ }
+ }
+
+ // Choice Uniqueness
+ void makeCellsToUploadUnique(ArrayList<ChoiceCell> cellsToUpload) {
+ if (cellsToUpload.size() == 0) {
+ return;
+ }
+
+ ArrayList<ChoiceCell> strippedCellsToUpload = cloneChoiceCellList(cellsToUpload);
+ ArrayList<ChoiceCell> strippedLoadedCells = cloneChoiceCellList(new ArrayList<>(loadedCells));
+ boolean supportsChoiceUniqueness = !(sdlMsgVersion.getMajorVersion() < 7 || (sdlMsgVersion.getMajorVersion() == 7 && sdlMsgVersion.getMinorVersion() == 0));
+ if (supportsChoiceUniqueness) {
+ removeUnusedProperties(strippedCellsToUpload);
+ removeUnusedProperties(strippedLoadedCells);
+ }
+
+ addUniqueNamesToCells(strippedCellsToUpload, strippedLoadedCells, supportsChoiceUniqueness);
+ transferUniqueNamesFromCells(strippedCellsToUpload, cellsToUpload);
+ }
+
+ private void updateChoiceSet(ChoiceSet choiceSet, HashSet<ChoiceCell> loadedCells, HashSet<ChoiceCell> cellsToUpload) {
+ ArrayList<ChoiceCell> choiceSetCells = new ArrayList<>();
+ ArrayList<ChoiceCell> loadedCellsList = new ArrayList<>(loadedCells);
+ ArrayList<ChoiceCell> CellsToUploadList = new ArrayList<>(cellsToUpload);
+ for (ChoiceCell cell : choiceSet.getChoices()) {
+ if (loadedCells.contains(cell)) {
+ choiceSetCells.add(loadedCellsList.get(loadedCellsList.indexOf(cell)));
+ } else if (cellsToUpload.contains(cell)) {
+ choiceSetCells.add(CellsToUploadList.get(CellsToUploadList.indexOf(cell)));
+ }
+ }
+ this.choiceSet.setChoices((List<ChoiceCell>) choiceSetCells.clone());
+ }
+
+ private void transferUniqueNamesFromCells(ArrayList<ChoiceCell> fromCells, ArrayList<ChoiceCell> toCells) {
+ for (int i = 0; i< fromCells.size(); i++) {
+ toCells.get(i).setUniqueTextId(fromCells.get(i).getUniqueTextId());
+ }
+ }
+
+ void removeUnusedProperties(List<ChoiceCell> choiceCells) {
+ for (ChoiceCell cell : choiceCells) {
+ // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
+ cell.setVoiceCommands(null);
+
+ if (!shouldSendChoicePrimaryImage()) {
+ cell.setArtwork(null);
+ }
+ if (!shouldSendChoiceSecondaryText()) {
+ cell.setSecondaryText(null);
+ }
+ if (!shouldSendChoiceTertiaryText()) {
+ cell.setTertiaryText(null);
+ }
+ if (!shouldSendChoiceSecondaryImage()) {
+ cell.setSecondaryArtwork(null);
+ }
+ }
+ }
+
+ private ArrayList<ChoiceCell> cloneChoiceCellList(List<ChoiceCell> originalList) {
+ if (originalList == null) {
+ return null;
+ }
+ ArrayList<ChoiceCell> clone = new ArrayList<>();
+ for (ChoiceCell choiceCell : originalList) {
+ clone.add(choiceCell.clone());
+ }
+ return clone;
+ }
+
+ /**
+ * Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
+ * E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
+ */
+ void addUniqueNamesToCells(List<ChoiceCell> cellsToUpload, List<ChoiceCell> loadedCells, boolean supportsChoiceUniqueness) {
+ HashMap<Object, ArrayList<Integer>> dictCounter = new HashMap<>();
+
+ for (ChoiceCell loadedCell : loadedCells) {
+ Object cellKey = supportsChoiceUniqueness ? loadedCell : loadedCell.getText();
+ int cellUniqueId = loadedCell.getUniqueTextId();
+ if (dictCounter.get(cellKey) == null) {
+ ArrayList<Integer> uniqueIds = new ArrayList<>();
+ uniqueIds.add(cellUniqueId);
+ dictCounter.put(cellKey, uniqueIds);
+ } else {
+ dictCounter.get(cellKey).add(cellUniqueId);
+ }
+ }
+
+ for (ChoiceCell cell : cellsToUpload) {
+ Object cellKey = supportsChoiceUniqueness ? cell : cell.getText();
+ if (dictCounter.get(cellKey) == null) {
+ ArrayList<Integer> uniqueTextIds = new ArrayList<>();
+ uniqueTextIds.add(cell.getUniqueTextId());
+ dictCounter.put(cellKey, uniqueTextIds);
+ } else {
+ ArrayList<Integer> uniqueIds = dictCounter.get(cellKey);
+ Integer lowestMissingUniqueId = uniqueIds.get(uniqueIds.size() - 1) + 1;
+ for (int i = 1; i < dictCounter.get(cellKey).size() + 1; i++) {
+ if (i != dictCounter.get(cellKey).get(i -1)) {
+ lowestMissingUniqueId = i;
+ break;
+ }
+ }
+
+ cell.setUniqueTextId(lowestMissingUniqueId);
+ dictCounter.get(cellKey).add(cell.getUniqueTextId() - 1, cell.getUniqueTextId());
+ }
+ }
+ }
+
+ // Finding Cells
+ private ChoiceCell cellFromChoiceId(int choiceId) {
+ for (ChoiceCell cell : this.cellsToUpload) {
+ if (cell.getChoiceId() == choiceId) {
+ return cell;
+ }
+ }
+ return null;
+ }
+
+ // Assembling Choice RPCs
+ private CreateInteractionChoiceSet choiceFromCell(ChoiceCell cell) {
+
+ List<String> vrCommands;
+ if (cell.getVoiceCommands() == null) {
+ vrCommands = isVROptional ? null : Collections.singletonList(String.valueOf(cell.getChoiceId()));
+ } else {
+ vrCommands = cell.getVoiceCommands();
+ }
+
+ String menuName = shouldSendChoiceText() ? cell.getUniqueText() : null;
+
+ if (menuName == null) {
+ DebugTool.logError(TAG, "Could not convert Choice Cell to CreateInteractionChoiceSet. It will not be shown. Cell: " + cell.toString());
+ return null;
+ }
+
+ String secondaryText = shouldSendChoiceSecondaryText() ? cell.getSecondaryText() : null;
+ String tertiaryText = shouldSendChoiceTertiaryText() ? cell.getTertiaryText() : null;
+
+ Image image = shouldSendChoicePrimaryImage() && cell.getArtwork() != null ? cell.getArtwork().getImageRPC() : null;
+ Image secondaryImage = shouldSendChoiceSecondaryImage() && cell.getSecondaryArtwork() != null ? cell.getSecondaryArtwork().getImageRPC() : null;
+
+ Choice choice = new Choice(cell.getChoiceId(), menuName);
+ choice.setVrCommands(vrCommands);
+ choice.setSecondaryText(secondaryText);
+ choice.setTertiaryText(tertiaryText);
+ choice.setIgnoreAddingVRItems(true);
+
+ if (fileManager.get() != null) {
+ if (image != null && (cell.getArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getArtwork()))) {
+ choice.setImage(image);
+ }
+ if (secondaryImage != null && (cell.getSecondaryArtwork().isStaticIcon() || fileManager.get().hasUploadedFile(cell.getSecondaryArtwork()))) {
+ choice.setSecondaryImage(secondaryImage);
+ }
+ }
+
+ return new CreateInteractionChoiceSet(choice.getChoiceID(), Collections.singletonList(choice));
+ }
+
+ boolean shouldSendChoiceText() {
+ if (this.displayName != null && this.displayName.equals(DisplayType.GEN3_8_INCH.toString())) {
+ return true;
+ }
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.menuName);
+ }
+
+ boolean shouldSendChoiceSecondaryText() {
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.secondaryText);
+ }
+
+ boolean shouldSendChoiceTertiaryText() {
+ return defaultMainWindowCapability == null || hasTextFieldOfName(defaultMainWindowCapability, TextFieldName.tertiaryText);
+ }
+
+ boolean shouldSendChoicePrimaryImage() {
+ return defaultMainWindowCapability == null || hasImageFieldOfName(defaultMainWindowCapability, ImageFieldName.choiceImage);
+ }
+
+ boolean shouldSendChoiceSecondaryImage() {
+ return defaultMainWindowCapability == null || hasImageFieldOfName(defaultMainWindowCapability, ImageFieldName.choiceSecondaryImage);
+ }
+
+ // SDL Notifications
+ private void addListeners() {
+
+ keyboardRPCListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ if (getState() == Task.CANCELED) {
+ finishOperation(false);
+ return;
+ }
+
+ if (keyboardListener == null) {
+ DebugTool.logError(TAG, "Received Keyboard Input But Listener is null");
+ return;
+ }
+
+ OnKeyboardInput onKeyboard = (OnKeyboardInput) notification;
+ keyboardListener.onKeyboardDidSendEvent(onKeyboard.getEvent(), onKeyboard.getData());
+
+ if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_VOICE) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_SUBMITTED)) {
+ // Submit Voice or Text
+ keyboardListener.onUserDidSubmitInput(onKeyboard.getData(), onKeyboard.getEvent());
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.KEYPRESS)) {
+ // Notify of Keypress
+ keyboardListener.updateAutocompleteWithInput(onKeyboard.getData(), new KeyboardAutocompleteCompletionListener() {
+ @Override
+ public void onUpdatedAutoCompleteList(List<String> updatedAutoCompleteList) {
+ keyboardProperties.setAutoCompleteList(updatedAutoCompleteList != null ? updatedAutoCompleteList : new ArrayList<String>());
+ keyboardProperties.setAutoCompleteText(updatedAutoCompleteList != null && !updatedAutoCompleteList.isEmpty() ? updatedAutoCompleteList.get(0) : null);
+ updateKeyboardProperties(null);
+ }
+ });
+
+ keyboardListener.updateCharacterSetWithInput(onKeyboard.getData(), new KeyboardCharacterSetCompletionListener() {
+ @Override
+ public void onUpdatedCharacterSet(List<String> updatedCharacterSet) {
+ keyboardProperties.setLimitedCharacterList(updatedCharacterSet);
+ updateKeyboardProperties(null);
+ }
+ });
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_ABORTED) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_CANCELLED)) {
+ // Notify of abort / Cancellation
+ keyboardListener.onKeyboardDidAbortWithReason(onKeyboard.getEvent());
+ } else if (onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_ENABLED) || onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_DISABLED)) {
+ keyboardListener.onKeyboardDidUpdateInputMask(onKeyboard.getEvent());
+ }
+ }
+ };
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().addOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Present Choice Set Keyboard Listener Not Added");
+ }
+ }
+
+ public void setLoadedCells(HashSet<ChoiceCell> loadedCells) {
+ this.loadedCells = loadedCells;
+ }
+
+ HashSet<SdlArtwork> artworksToUpload() {
+ HashSet<SdlArtwork> artworksToUpload = new HashSet<>();
+ for (ChoiceCell cell : cellsToUpload) {
+ if (shouldSendChoicePrimaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getArtwork())) {
+ artworksToUpload.add(cell.getArtwork());
+ }
+ if (shouldSendChoiceSecondaryImage() && fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) {
+ artworksToUpload.add(cell.getSecondaryArtwork());
+ }
+ }
+ return artworksToUpload;
+ }
+
+ LayoutMode getLayoutMode() {
+ switch (choiceSet.getLayout()) {
+ case CHOICE_SET_LAYOUT_LIST:
+ return keyboardListener != null ? LayoutMode.LIST_WITH_SEARCH : LayoutMode.LIST_ONLY;
+ case CHOICE_SET_LAYOUT_TILES:
+ return keyboardListener != null ? LayoutMode.ICON_WITH_SEARCH : LayoutMode.ICON_ONLY;
+ }
+ return LayoutMode.LIST_ONLY; // default
+ }
+
+ private List<Integer> getChoiceIds() {
+
+ List<Integer> choiceIds = new ArrayList<>(choiceSet.getChoices().size());
+ for (ChoiceCell cell : choiceSet.getChoices()) {
+ choiceIds.add(cell.getChoiceId());
+ }
+ return choiceIds;
+ }
+
+ void finishOperation(boolean success) {
+ this.currentState = SDLPreloadPresentChoicesOperationState.FINISHING;
+
+ if (this.completionListener != null) {
+ this.completionListener.onComplete(success, loadedCells);
+ }
+
+ if (this.choiceSet == null || this.choiceSet.getChoiceSetSelectionListener() == null) {
+ DebugTool.logWarning(TAG, "");
+ }
+
+
+ if (updatedKeyboardProperties) {
+ // We need to reset the keyboard properties
+ SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
+ setGlobalProperties.setKeyboardProperties(originalKeyboardProperties);
+ setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if (response.getSuccess()) {
+ updatedKeyboardProperties = false;
+ DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
+ } else {
+ DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
+ }
+ onFinished();
+ }
+ });
+
+ if (internalInterface.get() != null) {
+ internalInterface.get().sendRPC(setGlobalProperties);
+ internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
+ } else {
+ DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
+ }
+ } else {
+ onFinished();
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java b/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
deleted file mode 100644
index b5d3945d1..000000000
--- a/base/src/main/java/com/smartdevicelink/managers/screen/choiceset/PresentChoiceSetOperation.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (c) 2019 Livio, Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided with the
- * distribution.
- *
- * Neither the name of the Livio Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * Created by brettywhite on 6/12/19 1:52 PM
- *
- */
-
-package com.smartdevicelink.managers.screen.choiceset;
-
-import com.livio.taskmaster.Task;
-import com.smartdevicelink.managers.CompletionListener;
-import com.smartdevicelink.managers.ISdl;
-import com.smartdevicelink.protocol.enums.FunctionID;
-import com.smartdevicelink.proxy.RPCNotification;
-import com.smartdevicelink.proxy.RPCResponse;
-import com.smartdevicelink.proxy.rpc.CancelInteraction;
-import com.smartdevicelink.proxy.rpc.KeyboardProperties;
-import com.smartdevicelink.proxy.rpc.OnKeyboardInput;
-import com.smartdevicelink.proxy.rpc.PerformInteraction;
-import com.smartdevicelink.proxy.rpc.PerformInteractionResponse;
-import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
-import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
-import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
-import com.smartdevicelink.proxy.rpc.enums.KeyboardEvent;
-import com.smartdevicelink.proxy.rpc.enums.LayoutMode;
-import com.smartdevicelink.proxy.rpc.enums.Result;
-import com.smartdevicelink.proxy.rpc.enums.TriggerSource;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
-import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
-import com.smartdevicelink.util.DebugTool;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-class PresentChoiceSetOperation extends Task {
- private static final String TAG = "PresentChoiceSetOperation";
- private final WeakReference<ISdl> internalInterface;
- private final ChoiceSet choiceSet;
- private final Integer cancelID;
- private final InteractionMode presentationMode;
- private final KeyboardProperties originalKeyboardProperties;
- private KeyboardProperties keyboardProperties;
- private ChoiceCell selectedCell;
- private TriggerSource selectedTriggerSource;
- private boolean updatedKeyboardProperties;
- private OnRPCNotificationListener keyboardRPCListener;
- private final ChoiceSetSelectionListener choiceSetSelectionListener;
- Integer selectedCellRow;
- KeyboardListener keyboardListener;
- final SdlMsgVersion sdlMsgVersion;
-
- PresentChoiceSetOperation(ISdl internalInterface, ChoiceSet choiceSet, InteractionMode mode,
- KeyboardProperties originalKeyboardProperties, KeyboardListener keyboardListener, ChoiceSetSelectionListener choiceSetSelectionListener, Integer cancelID) {
- super("PresentChoiceSetOperation");
- this.internalInterface = new WeakReference<>(internalInterface);
- this.keyboardListener = keyboardListener;
- this.choiceSet = choiceSet;
- this.choiceSet.canceledListener = new ChoiceSetCanceledListener() {
- @Override
- public void onChoiceSetCanceled() {
- cancelInteraction();
- }
- };
- this.presentationMode = mode;
- this.cancelID = cancelID;
- this.originalKeyboardProperties = originalKeyboardProperties;
- this.keyboardProperties = originalKeyboardProperties;
- this.selectedCellRow = null;
- this.choiceSetSelectionListener = choiceSetSelectionListener;
- this.sdlMsgVersion = internalInterface.getSdlMsgVersion();
- }
-
- @Override
- public void onExecute() {
- DebugTool.logInfo(TAG, "Choice Operation: Executing present choice set operation");
- addListeners();
- start();
- }
-
- private void start() {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
-
- // Check if we're using a keyboard (searchable) choice set and setup keyboard properties if we need to
- if (keyboardListener != null && choiceSet.getCustomKeyboardConfiguration() != null) {
- keyboardProperties = choiceSet.getCustomKeyboardConfiguration();
- updatedKeyboardProperties = true;
- }
-
- updateKeyboardProperties(new CompletionListener() {
- @Override
- public void onComplete(boolean success) {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
- presentChoiceSet();
- }
- });
- }
-
- // SENDING REQUESTS
-
- private void updateKeyboardProperties(final CompletionListener listener) {
- if (keyboardProperties == null) {
- if (listener != null) {
- listener.onComplete(false);
- }
- return;
- }
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setKeyboardProperties(keyboardProperties);
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
-
- if (!response.getSuccess()) {
- if (listener != null) {
- listener.onComplete(false);
- }
- DebugTool.logError(TAG, "Error Setting keyboard properties in present choice set operation");
- return;
- }
-
- updatedKeyboardProperties = true;
-
- if (listener != null) {
- listener.onComplete(true);
- }
- DebugTool.logInfo(TAG, "Success Setting keyboard properties in present choice set operation");
- }
- });
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(setGlobalProperties);
- } else {
- DebugTool.logError(TAG, "Internal interface null - present choice set op - choice");
- }
- }
-
- private void presentChoiceSet() {
- PerformInteraction pi = getPerformInteraction();
- pi.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (!response.getSuccess()) {
- DebugTool.logError(TAG, "Presenting Choice set failed: " + response.getInfo());
-
- if (choiceSetSelectionListener != null) {
- choiceSetSelectionListener.onError(response.getInfo());
- }
- finishOperation();
- return;
- }
-
- PerformInteractionResponse performInteractionResponse = (PerformInteractionResponse) response;
- setSelectedCellWithId(performInteractionResponse.getChoiceID());
- selectedTriggerSource = performInteractionResponse.getTriggerSource();
-
- if (choiceSetSelectionListener != null && selectedCell != null && selectedTriggerSource != null && selectedCellRow != null) {
- choiceSetSelectionListener.onChoiceSelected(selectedCell, selectedTriggerSource, selectedCellRow);
- }
-
- finishOperation();
- }
- });
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(pi);
- } else {
- DebugTool.logError(TAG, "Internal Interface null when presenting choice set in operation");
- }
- }
-
- void finishOperation() {
- if (updatedKeyboardProperties) {
- // We need to reset the keyboard properties
- SetGlobalProperties setGlobalProperties = new SetGlobalProperties();
- setGlobalProperties.setKeyboardProperties(originalKeyboardProperties);
- setGlobalProperties.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- if (response.getSuccess()) {
- updatedKeyboardProperties = false;
- DebugTool.logInfo(TAG, "Successfully reset choice keyboard properties to original config");
- } else {
- DebugTool.logError(TAG, "Failed to reset choice keyboard properties to original config " + response.getResultCode() + ", " + response.getInfo());
- }
- PresentChoiceSetOperation.super.onFinished();
- }
- });
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(setGlobalProperties);
- internalInterface.get().removeOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
- } else {
- DebugTool.logError(TAG, "Internal Interface null when finishing choice keyboard reset");
- }
- } else {
- PresentChoiceSetOperation.super.onFinished();
- }
- }
-
- /*
- * Cancels the choice set. If the choice set has not yet been sent to Core, it will not be sent. If the choice set is already presented on Core, the choice set will be dismissed using the `CancelInteraction` RPC.
- */
- private void cancelInteraction() {
- if ((getState() == Task.FINISHED)) {
- DebugTool.logInfo(TAG, "This operation has already finished so it can not be canceled.");
- return;
- } else if (getState() == Task.CANCELED) {
- DebugTool.logInfo(TAG, "This operation has already been canceled. It will be finished at some point during the operation.");
- return;
- } else if ((getState() == Task.IN_PROGRESS)) {
- if (sdlMsgVersion.getMajorVersion() < 6) {
- DebugTool.logWarning(TAG, "Canceling a presented choice set is not supported on this head unit");
- return;
- }
-
- DebugTool.logInfo(TAG, "Canceling the presented choice set interaction.");
-
- CancelInteraction cancelInteraction = new CancelInteraction(FunctionID.PERFORM_INTERACTION.getId(), cancelID);
- cancelInteraction.setOnRPCResponseListener(new OnRPCResponseListener() {
- @Override
- public void onResponse(int correlationId, RPCResponse response) {
- DebugTool.logInfo(TAG, "Canceled the presented choice set " + ((response.getResultCode() == Result.SUCCESS) ? "successfully" : "unsuccessfully"));
- }
- });
-
- if (internalInterface.get() != null) {
- internalInterface.get().sendRPC(cancelInteraction);
- } else {
- DebugTool.logError(TAG, "Internal interface null - could not send cancel interaction for choice set");
- }
- } else {
- DebugTool.logInfo(TAG, "Canceling a choice set that has not yet been sent to Core");
- this.cancelTask();
- }
- }
-
- // GETTERS
-
- PerformInteraction getPerformInteraction() {
- PerformInteraction pi = new PerformInteraction(choiceSet.getTitle(), presentationMode, getChoiceIds());
- pi.setInitialPrompt(choiceSet.getInitialPrompt());
- pi.setHelpPrompt(choiceSet.getHelpPrompt());
- pi.setTimeoutPrompt(choiceSet.getTimeoutPrompt());
- pi.setVrHelp(choiceSet.getVrHelpList());
- pi.setTimeout(choiceSet.getTimeout() * 1000);
- pi.setInteractionLayout(getLayoutMode());
- pi.setCancelID(cancelID);
- return pi;
- }
-
- LayoutMode getLayoutMode() {
- switch (choiceSet.getLayout()) {
- case CHOICE_SET_LAYOUT_LIST:
- return keyboardListener != null ? LayoutMode.LIST_WITH_SEARCH : LayoutMode.LIST_ONLY;
- case CHOICE_SET_LAYOUT_TILES:
- return keyboardListener != null ? LayoutMode.ICON_WITH_SEARCH : LayoutMode.ICON_ONLY;
- }
- return LayoutMode.LIST_ONLY; // default
- }
-
- private List<Integer> getChoiceIds() {
-
- List<Integer> choiceIds = new ArrayList<>(choiceSet.getChoices().size());
- for (ChoiceCell cell : choiceSet.getChoices()) {
- choiceIds.add(cell.getChoiceId());
- }
- return choiceIds;
- }
-
- // HELPERS
-
- void setSelectedCellWithId(Integer cellId) {
- if (choiceSet.getChoices() != null && cellId != null) {
- List<ChoiceCell> cells = choiceSet.getChoices();
- for (int i = 0; i < cells.size(); i++) {
- if (cells.get(i).getChoiceId() == cellId) {
- selectedCell = cells.get(i);
- selectedCellRow = i;
- return;
- }
- }
- }
- }
-
- // LISTENERS
-
- private void addListeners() {
-
- keyboardRPCListener = new OnRPCNotificationListener() {
- @Override
- public void onNotified(RPCNotification notification) {
- if (getState() == Task.CANCELED) {
- finishOperation();
- return;
- }
-
- if (keyboardListener == null) {
- DebugTool.logError(TAG, "Received Keyboard Input But Listener is null");
- return;
- }
-
- OnKeyboardInput onKeyboard = (OnKeyboardInput) notification;
- keyboardListener.onKeyboardDidSendEvent(onKeyboard.getEvent(), onKeyboard.getData());
-
- if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_VOICE) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_SUBMITTED)) {
- // Submit Voice or Text
- keyboardListener.onUserDidSubmitInput(onKeyboard.getData(), onKeyboard.getEvent());
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.KEYPRESS)) {
- // Notify of Keypress
- keyboardListener.updateAutocompleteWithInput(onKeyboard.getData(), new KeyboardAutocompleteCompletionListener() {
- @Override
- public void onUpdatedAutoCompleteList(List<String> updatedAutoCompleteList) {
- keyboardProperties.setAutoCompleteList(updatedAutoCompleteList != null ? updatedAutoCompleteList : new ArrayList<String>());
- keyboardProperties.setAutoCompleteText(updatedAutoCompleteList != null && !updatedAutoCompleteList.isEmpty() ? updatedAutoCompleteList.get(0) : null);
- updateKeyboardProperties(null);
- }
- });
-
- keyboardListener.updateCharacterSetWithInput(onKeyboard.getData(), new KeyboardCharacterSetCompletionListener() {
- @Override
- public void onUpdatedCharacterSet(List<String> updatedCharacterSet) {
- keyboardProperties.setLimitedCharacterList(updatedCharacterSet);
- updateKeyboardProperties(null);
- }
- });
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_ABORTED) || onKeyboard.getEvent().equals(KeyboardEvent.ENTRY_CANCELLED)) {
- // Notify of abort / Cancellation
- keyboardListener.onKeyboardDidAbortWithReason(onKeyboard.getEvent());
- } else if (onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_ENABLED) || onKeyboard.getEvent().equals(KeyboardEvent.INPUT_KEY_MASK_DISABLED)) {
- keyboardListener.onKeyboardDidUpdateInputMask(onKeyboard.getEvent());
- }
- }
- };
-
- if (internalInterface.get() != null) {
- internalInterface.get().addOnRPCNotificationListener(FunctionID.ON_KEYBOARD_INPUT, keyboardRPCListener);
- } else {
- DebugTool.logError(TAG, "Present Choice Set Keyboard Listener Not Added");
- }
- }
-} \ No newline at end of file
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..210b24ba9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/MenuReplaceUtilities.java
@@ -0,0 +1,424 @@
+/*
+ * 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 com.smartdevicelink.util.DebugTool;
+
+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 final String TAG = "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);
+
+ if (cell.getVoiceCommands() != null && !cell.getVoiceCommands().isEmpty()) {
+ DebugTool.logWarning(TAG, "Setting voice commands for submenu cells is not supported. The voice commands will not be set.");
+ }
+
+ 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;
diff --git a/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java b/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
index 9694d3f06..fdbba048d 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/BaseSdlPacket.java
@@ -264,10 +264,15 @@ class BaseSdlPacket {
this.priorityCoefficient = priority;
}
+ @Deprecated
public int getPrioirtyCoefficient() {
return this.priorityCoefficient;
}
+ public int getPriorityCoefficient() {
+ return this.priorityCoefficient;
+ }
+
public void setTransportRecord(TransportRecord transportRecord) {
this.transportRecord = transportRecord;
}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
index 8a1968f36..9e2ab38d0 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
@@ -182,7 +182,12 @@ public class ProtocolMessage {
this.priorityCoefficient = priority;
}
+ @Deprecated
public int getPrioirtyCoefficient() {
return this.priorityCoefficient;
}
+
+ public int getPriorityCoefficient() {
+ return this.priorityCoefficient;
+ }
} // end-class \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
index 5cac1302c..f7dd797d0 100644
--- a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocolBase.java
@@ -89,7 +89,7 @@ public class SdlProtocolBase {
public static final int V2_HEADER_SIZE = 12;
//If increasing MAX PROTOCOL VERSION major version, make sure to alter it in SdlPsm
- private static final Version MAX_PROTOCOL_VERSION = new Version(5, 4, 0);
+ private static final Version MAX_PROTOCOL_VERSION = new Version(5, 4, 1);
public static final int V1_V2_MTU_SIZE = 1500;
public static final int V3_V4_MTU_SIZE = 131072;
@@ -572,11 +572,7 @@ public class SdlProtocolBase {
if (sessionType.eq(SessionType.CONTROL)) {
final byte[] secureData = protocolMsg.getData().clone();
data = new byte[headerSize + secureData.length];
-
- final BinaryFrameHeader binFrameHeader =
- SdlPacketFactory.createBinaryFrameHeader(protocolMsg.getRPCType(), protocolMsg.getFunctionID(), protocolMsg.getCorrID(), 0);
- System.arraycopy(binFrameHeader.assembleHeaderBytes(), 0, data, 0, headerSize);
- System.arraycopy(secureData, 0, data, headerSize, secureData.length);
+ System.arraycopy(secureData, 0, data, 0, secureData.length);
} else if (protocolMsg.getBulkData() != null) {
data = new byte[12 + protocolMsg.getJsonSize() + protocolMsg.getBulkData().length];
sessionType = SessionType.BULK_DATA;
@@ -1566,5 +1562,17 @@ public class SdlProtocolBase {
} // end-method
} // end-class
+ /**
+ * This method will return copy of active transports
+ *
+ * @return a list of active transports
+ * */
+ public List<TransportRecord> getActiveTransports() {
+ List<TransportRecord> records = new ArrayList<>();
+ for (TransportRecord record : activeTransports.values()) {
+ records.add(new TransportRecord(record.getType(), record.getAddress()));
+ }
+ return records;
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java b/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java
new file mode 100644
index 000000000..c7bc1326d
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/SecurityQueryPayload.java
@@ -0,0 +1,157 @@
+package com.smartdevicelink.protocol;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.protocol.enums.SecurityQueryErrorCode;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
+import com.smartdevicelink.util.BitConverter;
+import com.smartdevicelink.util.DebugTool;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryPayload {
+ private static final String TAG = "BinaryQueryHeader";
+
+ private SecurityQueryType _securityQueryType;
+ private SecurityQueryID _securityQueryID;
+ private int _correlationID;
+ private int _jsonSize;
+ private SecurityQueryErrorCode _errorCode;
+
+ private byte[] _jsonData = null;
+ private byte[] _bulkData = null;
+
+ private static final int SECURITY_QUERY_HEADER_SIZE = 12;
+
+ public SecurityQueryPayload() {
+ }
+
+ public static SecurityQueryPayload parseBinaryQueryHeader(byte[] binHeader) {
+ if (binHeader == null || binHeader.length < SECURITY_QUERY_HEADER_SIZE) {
+ DebugTool.logError(TAG, "Security Payload error: not enough data to form a Security Query Header. Data length: " + (binHeader != null ? binHeader.length : "null"));
+ return null;
+ }
+
+ SecurityQueryPayload msg = new SecurityQueryPayload();
+
+ //Set QueryType from the first 8 bits
+ byte QUERY_Type = (byte) (binHeader[0]);
+ msg.setQueryType(SecurityQueryType.valueOf(QUERY_Type));
+
+ //Set queryID from the last 24 bits of the first 32 bits
+ byte[] _queryID = new byte[3];
+ System.arraycopy(binHeader, 1, _queryID, 0, 3);
+ msg.setQueryID(SecurityQueryID.valueOf(_queryID));
+
+ //set correlationID from the 32 bits after the first 32 bits
+ int corrID = BitConverter.intFromByteArray(binHeader, 4);
+ msg.setCorrelationID(corrID);
+
+ //set jsonSize from the last 32 bits after the first 64 bits
+ int _jsonSize = BitConverter.intFromByteArray(binHeader, 8);
+ msg.setJsonSize(_jsonSize);
+
+ //If we get an error message we want the error code from the last 8 bits
+ if (msg.getQueryType() == SecurityQueryType.NOTIFICATION && msg.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR) {
+ msg.setErrorCode(SecurityQueryErrorCode.valueOf(binHeader[binHeader.length - 1]));
+ }
+
+ try {
+ //Get the JsonData after the header (after 96 bits) based on the jsonData size
+ if (_jsonSize > 0 && _jsonSize <= (binHeader.length - SECURITY_QUERY_HEADER_SIZE)) {
+ byte[] _jsonData = new byte[_jsonSize];
+ System.arraycopy(binHeader, SECURITY_QUERY_HEADER_SIZE, _jsonData, 0, _jsonSize);
+ msg.setJsonData(_jsonData);
+ }
+
+ //Get the binaryData after the header (after 96 bits) and the jsonData size
+ if (binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE > 0) {
+ byte[] _bulkData;
+ if (msg.getQueryType() == SecurityQueryType.NOTIFICATION && msg.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR) {
+ _bulkData = new byte[binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE - 1];
+ } else {
+ _bulkData = new byte[binHeader.length - _jsonSize - SECURITY_QUERY_HEADER_SIZE];
+ }
+ System.arraycopy(binHeader, SECURITY_QUERY_HEADER_SIZE + _jsonSize, _bulkData, 0, _bulkData.length);
+ msg.setBulkData(_bulkData);
+ }
+
+ } catch (OutOfMemoryError | ArrayIndexOutOfBoundsException e) {
+ DebugTool.logError(TAG, "Unable to process data to form header");
+ return null;
+ }
+
+ return msg;
+ }
+
+ public byte[] assembleHeaderBytes() {
+ // From the properties, create a data buffer
+ // Query Type - first 8 bits
+ // Query ID - next 24 bits
+ // Sequence Number - next 32 bits
+ // JSON size - next 32 bits
+ byte[] ret = new byte[SECURITY_QUERY_HEADER_SIZE];
+ ret[0] = _securityQueryType.getValue();
+ System.arraycopy(_securityQueryID.getValue(), 0, ret, 1, 3);
+ System.arraycopy(BitConverter.intToByteArray(_correlationID), 0, ret, 4, 4);
+ System.arraycopy(BitConverter.intToByteArray(_jsonSize), 0, ret, 8, 4);
+ return ret;
+ }
+
+ public SecurityQueryType getQueryType() {
+ return _securityQueryType;
+ }
+
+ public void setQueryType(SecurityQueryType _securityQueryType) {
+ this._securityQueryType = _securityQueryType;
+ }
+
+ public SecurityQueryID getQueryID() {
+ return _securityQueryID;
+ }
+
+ public void setQueryID(SecurityQueryID _securityQueryID) {
+ this._securityQueryID = _securityQueryID;
+ }
+
+ public int getCorrelationID() {
+ return _correlationID;
+ }
+
+ public void setCorrelationID(int _correlationID) {
+ this._correlationID = _correlationID;
+ }
+
+ public int getJsonSize() {
+ return _jsonSize;
+ }
+
+ public void setJsonSize(int _jsonSize) {
+ this._jsonSize = _jsonSize;
+ }
+
+ public SecurityQueryErrorCode getErrorCode() {
+ return _errorCode;
+ }
+
+ public void setErrorCode(SecurityQueryErrorCode _errorCode) {
+ this._errorCode = _errorCode;
+ }
+
+ public byte[] getJsonData() {
+ return _jsonData;
+ }
+
+ public void setJsonData(byte[] _jsonData) {
+ this._jsonData = new byte[this._jsonSize];
+ System.arraycopy(_jsonData, 0, this._jsonData, 0, _jsonSize);
+ }
+
+ public byte[] getBulkData() {
+ return _bulkData;
+ }
+
+ public void setBulkData(byte[] _bulkData) {
+ this._bulkData = _bulkData;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java
new file mode 100644
index 000000000..5601271d3
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryErrorCode.java
@@ -0,0 +1,61 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.ByteEnumer;
+
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryErrorCode extends ByteEnumer {
+
+ private static final Vector<SecurityQueryErrorCode> theList = new Vector<>();
+
+ public static Vector<SecurityQueryErrorCode> getList() {
+ return theList;
+ }
+
+ protected SecurityQueryErrorCode(byte value, String name) {
+ super(value, name);
+ }
+
+ public final static SecurityQueryErrorCode ERROR_SUCCESS = new SecurityQueryErrorCode((byte) 0x00, "ERROR_SUCCESS");
+ public final static SecurityQueryErrorCode ERROR_INVALID_QUERY_SIZE = new SecurityQueryErrorCode((byte) 0x01, "ERROR_INVALID_QUERY_SIZE");
+ public final static SecurityQueryErrorCode ERROR_INVALID_QUERY_ID = new SecurityQueryErrorCode((byte) 0x02, "ERROR_INVALID_QUERY_ID");
+ public final static SecurityQueryErrorCode ERROR_NOT_SUPPORTED = new SecurityQueryErrorCode((byte) 0x03, "ERROR_NOT_SUPPORTED");
+ public final static SecurityQueryErrorCode ERROR_SERVICE_ALREADY_PROTECTED = new SecurityQueryErrorCode((byte) 0x04, "ERROR_SERVICE_ALREADY_PROTECTED");
+ public final static SecurityQueryErrorCode ERROR_SERVICE_NOT_PROTECTED = new SecurityQueryErrorCode((byte) 0x05, "ERROR_SERVICE_NOT_PROTECTED");
+ public final static SecurityQueryErrorCode ERROR_DECRYPTION_FAILED = new SecurityQueryErrorCode((byte) 0x06, "ERROR_DECRYPTION_FAILED");
+ public final static SecurityQueryErrorCode ERROR_ENCRYPTION_FAILED = new SecurityQueryErrorCode((byte) 0x07, "ERROR_ENCRYPTION_FAILED");
+ public final static SecurityQueryErrorCode ERROR_SSL_INVALID_DATA = new SecurityQueryErrorCode((byte) 0x08, "ERROR_SSL_INVALID_DATA");
+ public final static SecurityQueryErrorCode ERROR_HANDSHAKE_FAILED = new SecurityQueryErrorCode((byte) 0x09, "ERROR_HANDSHAKE_FAILED");
+ public final static SecurityQueryErrorCode INVALID_CERT = new SecurityQueryErrorCode((byte) 0x0A, "INVALID_CERT");
+ public final static SecurityQueryErrorCode EXPIRED_CERT = new SecurityQueryErrorCode((byte) 0x0B, "EXPIRED_CERT");
+ public final static SecurityQueryErrorCode ERROR_INTERNAL = new SecurityQueryErrorCode((byte) 0xFF, "ERROR_INTERNAL");
+ public final static SecurityQueryErrorCode ERROR_UNKNOWN_INTERNAL_ERROR = new SecurityQueryErrorCode((byte) 0xFE, "ERROR_UNKNOWN_INTERNAL_ERROR");
+
+ static {
+ theList.addElement(ERROR_SUCCESS);
+ theList.addElement(ERROR_INVALID_QUERY_SIZE);
+ theList.addElement(ERROR_INVALID_QUERY_ID);
+ theList.addElement(ERROR_NOT_SUPPORTED);
+ theList.addElement(ERROR_SERVICE_ALREADY_PROTECTED);
+ theList.addElement(ERROR_SERVICE_NOT_PROTECTED);
+ theList.addElement(ERROR_DECRYPTION_FAILED);
+ theList.addElement(ERROR_ENCRYPTION_FAILED);
+ theList.addElement(ERROR_SSL_INVALID_DATA);
+ theList.addElement(ERROR_HANDSHAKE_FAILED);
+ theList.addElement(INVALID_CERT);
+ theList.addElement(EXPIRED_CERT);
+ theList.addElement(ERROR_INTERNAL);
+ theList.addElement(ERROR_UNKNOWN_INTERNAL_ERROR);
+ }
+
+ public static SecurityQueryErrorCode valueOf(byte passedByte) {
+ return (SecurityQueryErrorCode) get(theList, passedByte);
+ }
+
+ public static SecurityQueryErrorCode[] values() {
+ return theList.toArray(new SecurityQueryErrorCode[theList.size()]);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java
new file mode 100644
index 000000000..c1b799ec9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryID.java
@@ -0,0 +1,105 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.BitConverter;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Objects;
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryID {
+
+ private static final Vector<SecurityQueryID> theList = new Vector<>();
+
+ public static Vector<SecurityQueryID> getList() {
+ return theList;
+ }
+
+ private static final byte[] sendHandshakeDataByteArray = {(byte) 0x00, (byte) 0x00, (byte) 0x01};
+ private static final byte[] sendInternalErrorByteArray = {(byte) 0x00, (byte) 0x00, (byte) 0x02};
+ private static final byte[] invalidQueryIdByteArray = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+ public final static SecurityQueryID SEND_HANDSHAKE_DATA = new SecurityQueryID(sendHandshakeDataByteArray, "SEND_HANDSHAKE_DATA");
+ public final static SecurityQueryID SEND_INTERNAL_ERROR = new SecurityQueryID(sendInternalErrorByteArray, "SEND_INTERNAL_ERROR");
+ public final static SecurityQueryID INVALID_QUERY_ID = new SecurityQueryID(invalidQueryIdByteArray, "INVALID_QUERY_ID");
+
+ static {
+ theList.addElement(SEND_HANDSHAKE_DATA);
+ theList.addElement(SEND_INTERNAL_ERROR);
+ theList.addElement(INVALID_QUERY_ID);
+ }
+
+ protected SecurityQueryID(byte[] value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ private final byte[] value;
+ private final String name;
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ public int getIntValue() {
+ byte[] copy = new byte[4];
+ System.arraycopy(value, 0, copy, 1, 3);
+ return BitConverter.intFromByteArray(copy, 0);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean equals(SecurityQueryID other) {
+ return Objects.equals(name, other.getName());
+ }
+
+ public boolean eq(SecurityQueryID other) {
+ return equals(other);
+ }
+
+ public byte[] value() {
+ return value;
+ }
+
+ public static SecurityQueryID get(Vector<?> theList, byte[] value) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ SecurityQueryID current = (SecurityQueryID) enumer.nextElement();
+ if (Arrays.equals(current.getValue(), value)) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static SecurityQueryID get(Vector<?> theList, String name) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ SecurityQueryID current = (SecurityQueryID) enumer.nextElement();
+ if (current.getName().equals(name)) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static SecurityQueryID valueOf(byte[] passedByteArray) {
+ return (SecurityQueryID) get(theList, passedByteArray);
+ }
+
+ public static SecurityQueryID[] values() {
+ return theList.toArray(new SecurityQueryID[theList.size()]);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java
new file mode 100644
index 000000000..bb021e79c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/enums/SecurityQueryType.java
@@ -0,0 +1,41 @@
+package com.smartdevicelink.protocol.enums;
+
+import androidx.annotation.RestrictTo;
+
+import com.smartdevicelink.util.ByteEnumer;
+
+import java.util.Vector;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SecurityQueryType extends ByteEnumer {
+
+ private static final Vector<SecurityQueryType> theList = new Vector<>();
+
+ public static Vector<SecurityQueryType> getList() {
+ return theList;
+ }
+
+ protected SecurityQueryType(byte value, String name) {
+ super(value, name);
+ }
+
+ public final static SecurityQueryType REQUEST = new SecurityQueryType((byte) 0x00, "REQUEST");
+ public final static SecurityQueryType RESPONSE = new SecurityQueryType((byte) 0x10, "RESPONSE");
+ public final static SecurityQueryType NOTIFICATION = new SecurityQueryType((byte) 0x20, "NOTIFICATION");
+ public final static SecurityQueryType INVALID_QUERY_TYPE = new SecurityQueryType((byte) 0xFF, "INVALID_QUERY_TYPE");
+
+ static {
+ theList.addElement(REQUEST);
+ theList.addElement(RESPONSE);
+ theList.addElement(NOTIFICATION);
+ theList.addElement(INVALID_QUERY_TYPE);
+ }
+
+ public static SecurityQueryType valueOf(byte passedByte) {
+ return (SecurityQueryType) get(theList, passedByte);
+ }
+
+ public static SecurityQueryType[] values() {
+ return theList.toArray(new SecurityQueryType[theList.size()]);
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
index 7ead85fef..edff66488 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/AudioControlCapabilities.java
@@ -64,7 +64,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Constructs a newly allocated AudioControlCapabilities object
*
- * @param moduleName short friendly name of the light control module.
+ * @param moduleName short friendly name of the audio control module.
*/
public AudioControlCapabilities(@NonNull String moduleName) {
this();
@@ -74,7 +74,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the AudioControlCapabilities class
*
- * @param moduleName The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @param moduleName The short friendly name of the audio control module. It should not be used to identify a module by mobile application.
*/
public AudioControlCapabilities setModuleName(@NonNull String moduleName) {
setValue(KEY_MODULE_NAME, moduleName);
@@ -84,7 +84,7 @@ public class AudioControlCapabilities extends RPCStruct {
/**
* Gets the moduleName portion of the AudioControlCapabilities class
*
- * @return String - The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @return String - The short friendly name of the audio control module. It should not be used to identify a module by mobile application.
*/
public String getModuleName() {
return getString(KEY_MODULE_NAME);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
index 05dccbc88..a120e66bc 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/RadioControlCapabilities.java
@@ -68,7 +68,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Constructs a new RadioControlCapabilities object
*
- * @param moduleName The short friendly name of the climate control module.
+ * @param moduleName The short friendly name of the radio control module.
* It should not be used to identify a module by mobile application.
*/
public RadioControlCapabilities(@NonNull String moduleName) {
@@ -79,7 +79,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the RadioControlCapabilities class
*
- * @param moduleName The short friendly name of the climate control module.
+ * @param moduleName The short friendly name of the radio control module.
* It should not be used to identify a module by mobile application.
*/
public RadioControlCapabilities setModuleName(@NonNull String moduleName) {
@@ -90,7 +90,7 @@ public class RadioControlCapabilities extends RPCStruct {
/**
* Gets the moduleName portion of the RadioControlCapabilities class
*
- * @return String - Short friendly name of the climate control module.
+ * @return String - Short friendly name of the radio control module.
*/
public String getModuleName() {
return getString(KEY_MODULE_NAME);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
index ef82a3686..cbb44fcdd 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SeatControlCapabilities.java
@@ -75,7 +75,7 @@ public class SeatControlCapabilities extends RPCStruct {
/**
* Constructs a newly allocated SeatControlCapabilities object
*
- * @param moduleName short friendly name of the light control module.
+ * @param moduleName short friendly name of the seat control module.
*/
public SeatControlCapabilities(@NonNull String moduleName) {
this();
@@ -94,7 +94,7 @@ public class SeatControlCapabilities extends RPCStruct {
/**
* Sets the moduleName portion of the SeatControlCapabilities class
*
- * @param moduleName - The short friendly name of the light control module. It should not be used to identify a module by mobile application.
+ * @param moduleName - The short friendly name of the seat control module. It should not be used to identify a module by mobile application.
*/
public SeatControlCapabilities setModuleName(@NonNull String moduleName) {
setValue(KEY_MODULE_NAME, moduleName);
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
index 60b9c524f..099224638 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SetMediaClockTimer.java
@@ -72,7 +72,7 @@ import java.util.Hashtable;
* <td>endTime</td>
* <td>StartTime</td>
* <td> EndTime can be provided for "COUNTUP" and "COUNTDOWN"; to be used to calculate any visual progress bar (if not provided, this feature is ignored)
- * If endTime is greater then startTime for COUNTDOWN or less than startTime for COUNTUP, then the request will return an INVALID_DATA.
+ * If endTime is greater than startTime for COUNTDOWN or less than startTime for COUNTUP, then the request will return an INVALID_DATA.
* endTime will be ignored for "RESUME", and "CLEAR"
* endTime can be sent for "PAUSE", in which case it will update the paused endTime</td>
* <td>N</td>
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
index 5962a04fd..e9bdcd905 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/Show.java
@@ -534,7 +534,7 @@ public class Show extends RPCRequest {
}
/**
- * Sets the the Soft buttons defined by the App
+ * Sets the Soft buttons defined by the App
*
* @param softButtons a List value representing the Soft buttons defined by the
* App
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
index e930bf533..c39ef4703 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/SystemCapability.java
@@ -39,7 +39,7 @@ import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
import java.util.Hashtable;
/**
- * Struct that indicates the a SystemCapabilityType and houses different structs to describe particular capabilities
+ * Struct that indicates the SystemCapabilityType and houses different structs to describe particular capabilities
*/
public class SystemCapability extends RPCStruct {
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
index 5db768cb5..68c47cf50 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/TireStatus.java
@@ -34,82 +34,131 @@ package com.smartdevicelink.proxy.rpc;
import androidx.annotation.NonNull;
import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.proxy.rpc.enums.ComponentVolumeStatus;
import com.smartdevicelink.proxy.rpc.enums.WarningLightStatus;
+import com.smartdevicelink.util.DebugTool;
import java.util.Hashtable;
/**
- * <p>The status and pressure of the tires.</p>
- * <p><b> Parameter List:</b></p>
+ * The status and pressure of the tires.
+ *
+ * <p><b>Parameter List</b></p>
*
* <table border="1" rules="all">
- * <tr>
- * <th>Param Name</th>
- * <th>Type</th>
- * <th>Description</th>
- * <th>Version</th>
- * </tr>
- * <tr>
- * <td>PressureTellTale</td>
- * <td>WarningLightStatus</td>
- * <td>Status of the Tire Pressure TellTale</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>LeftFront</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the left front tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>RightFront</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the right front tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>LeftRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the left rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>RightRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the right rear tire</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>InnerLeftRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the inner left rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * <tr>
- * <td>InnerRightRear</td>
- * <td>SingleTireStatus</td>
- * <td>The status of the inner right rear tire.</td>
- * <td>SmartDeviceLink 2.0</td>
- * </tr>
- * </table>
- * <p>
- * @since SmartDeviceLink 2.0
+ * <tr>
+ * <th>Param Name</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * <th>Required</th>
+ * <th>Notes</th>
+ * <th>Version Available</th>
+ * </tr>
+ * <tr>
+ * <td>pressureTelltale</td>
+ * <td>WarningLightStatus</td>
+ * <td>Status of the Tire Pressure Telltale. See WarningLightStatus.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>leftFront</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the left front tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ *
+ * </tr>
+ * <tr>
+ * <td>rightFront</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the right front tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>leftRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the left rear tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>rightRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the right rear tire.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>innerLeftRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the inner left rear.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>innerRightRear</td>
+ * <td>SingleTireStatus</td>
+ * <td>The status of the inner right rear.</td>
+ * <td>N</td>
+ * <td></td>
+ * <td>
+ * @since SmartDeviceLink 2.0.0
+ * </td>
+ * </tr>
+ * </table>
*
- * @see WarningLightStatus
- * @see SingleTireStatus
- * @see GetVehicleData
- * @see OnVehicleData
+ * @since SmartDeviceLink 2.0.0
*/
-
public class TireStatus extends RPCStruct {
+ private static final String TAG = "TireStatus";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_PRESSURE_TELL_TALE = "pressureTelltale";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_LEFT_FRONT = "leftFront";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_RIGHT_FRONT = "rightFront";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_LEFT_REAR = "leftRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_INNER_LEFT_REAR = "innerLeftRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_INNER_RIGHT_REAR = "innerRightRear";
+ /**
+ * @since SmartDeviceLink 2.0.0
+ */
public static final String KEY_RIGHT_REAR = "rightRear";
-
public TireStatus() {
}
@@ -125,7 +174,7 @@ public class TireStatus extends RPCStruct {
/**
* Constructs a new TireStatus object
*
- * @param pressureTellTale Status of the Tire Pressure TellTale
+ * @param pressureTelltale Status of the Tire Pressure TellTale
* @param leftFront The status of the left front tire.
* @param rightFront The status of the right front tire.
* @param leftRear The status of the left rear tire.
@@ -133,9 +182,9 @@ public class TireStatus extends RPCStruct {
* @param innerLeftRear The status of the inner left rear tire.
* @param innerRightRear The status of the inner right rear tire.
*/
- public TireStatus(@NonNull WarningLightStatus pressureTellTale, @NonNull SingleTireStatus leftFront, @NonNull SingleTireStatus rightFront, @NonNull SingleTireStatus leftRear, @NonNull SingleTireStatus rightRear, @NonNull SingleTireStatus innerLeftRear, @NonNull SingleTireStatus innerRightRear) {
+ public TireStatus(WarningLightStatus pressureTelltale, SingleTireStatus leftFront, SingleTireStatus rightFront, SingleTireStatus leftRear, SingleTireStatus rightRear, SingleTireStatus innerLeftRear, SingleTireStatus innerRightRear) {
this();
- setPressureTelltale(pressureTellTale);
+ setPressureTelltale(pressureTelltale);
setLeftFront(leftFront);
setRightFront(rightFront);
setLeftRear(leftRear);
@@ -176,60 +225,181 @@ public class TireStatus extends RPCStruct {
* @return the status of the tire pressure Telltale.
*/
public WarningLightStatus getPressureTelltale() {
- return (WarningLightStatus) getObject(WarningLightStatus.class, KEY_PRESSURE_TELL_TALE);
+ WarningLightStatus warningLightStatus = (WarningLightStatus) getObject(WarningLightStatus.class, KEY_PRESSURE_TELL_TALE);
+ if (warningLightStatus == null) {
+ WarningLightStatus newWarningLightStatus = WarningLightStatus.NOT_USED;
+ setValue(KEY_PRESSURE_TELL_TALE, newWarningLightStatus);
+ warningLightStatus = newWarningLightStatus;
+ DebugTool.logWarning(TAG, "TireStatus.pressureTelltale was null and will be set to .notUsed. In the future, this will change to be nullable.");
+ }
+ return warningLightStatus;
}
- public TireStatus setLeftFront(@NonNull SingleTireStatus leftFront) {
+ /**
+ * Sets the leftFront.
+ *
+ * @param leftFront The status of the left front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setLeftFront(SingleTireStatus leftFront) {
setValue(KEY_LEFT_FRONT, leftFront);
return this;
}
+ /**
+ * Gets the leftFront.
+ *
+ * @return SingleTireStatus The status of the left front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getLeftFront() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_FRONT);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_FRONT);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_LEFT_FRONT, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.leftFront was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setRightFront(@NonNull SingleTireStatus rightFront) {
+ /**
+ * Sets the rightFront.
+ *
+ * @param rightFront The status of the right front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setRightFront(SingleTireStatus rightFront) {
setValue(KEY_RIGHT_FRONT, rightFront);
return this;
}
+ /**
+ * Gets the rightFront.
+ *
+ * @return SingleTireStatus The status of the right front tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getRightFront() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_FRONT);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_FRONT);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_RIGHT_FRONT, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.rightFront was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setLeftRear(@NonNull SingleTireStatus leftRear) {
+ /**
+ * Sets the leftRear.
+ *
+ * @param leftRear The status of the left rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setLeftRear(SingleTireStatus leftRear) {
setValue(KEY_LEFT_REAR, leftRear);
return this;
}
+ /**
+ * Gets the leftRear.
+ *
+ * @return SingleTireStatus The status of the left rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getLeftRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_LEFT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_LEFT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.leftRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setRightRear(@NonNull SingleTireStatus rightRear) {
+ /**
+ * Sets the rightRear.
+ *
+ * @param rightRear The status of the right rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setRightRear(SingleTireStatus rightRear) {
setValue(KEY_RIGHT_REAR, rightRear);
return this;
}
+ /**
+ * Gets the rightRear.
+ *
+ * @return SingleTireStatus The status of the right rear tire.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getRightRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_RIGHT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_RIGHT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.rightRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setInnerLeftRear(@NonNull SingleTireStatus innerLeftRear) {
+ /**
+ * Sets the innerLeftRear.
+ *
+ * @param innerLeftRear The status of the inner left rear.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setInnerLeftRear(SingleTireStatus innerLeftRear) {
setValue(KEY_INNER_LEFT_REAR, innerLeftRear);
return this;
}
+ /**
+ * Gets the innerLeftRear.
+ *
+ * @return SingleTireStatus The status of the inner left rear.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getInnerLeftRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_LEFT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_LEFT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_INNER_LEFT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.innerLeftRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
- public TireStatus setInnerRightRear(@NonNull SingleTireStatus innerRightRear) {
+ /**
+ * Sets the innerRightRear.
+ *
+ * @param innerRightRear The status of the inner right rear.
+ * @since SmartDeviceLink 2.0.0
+ */
+ public TireStatus setInnerRightRear(SingleTireStatus innerRightRear) {
setValue(KEY_INNER_RIGHT_REAR, innerRightRear);
return this;
}
+ /**
+ * Gets the innerRightRear.
+ *
+ * @return SingleTireStatus The status of the inner right rear.
+ * @since SmartDeviceLink 2.0.0
+ */
public SingleTireStatus getInnerRightRear() {
- return (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_RIGHT_REAR);
+ SingleTireStatus tireStatus = (SingleTireStatus) getObject(SingleTireStatus.class, KEY_INNER_RIGHT_REAR);
+ if (tireStatus == null) {
+ SingleTireStatus newTireStatus = new SingleTireStatus().setStatus(ComponentVolumeStatus.UNKNOWN);
+ setValue(KEY_INNER_RIGHT_REAR, newTireStatus);
+ tireStatus = newTireStatus;
+ DebugTool.logWarning(TAG, "TireStatus.innerRightRear was null and will be set to .unknown. In the future, this will change to be nullable.");
+ }
+ return tireStatus;
}
}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
index a05216661..8841126c3 100644
--- a/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
+++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/VehicleType.java
@@ -33,6 +33,7 @@ package com.smartdevicelink.proxy.rpc;
import com.smartdevicelink.proxy.RPCStruct;
+import java.util.HashMap;
import java.util.Hashtable;
/**
@@ -111,6 +112,18 @@ public class VehicleType extends RPCStruct {
}
/**
+ * <p>
+ * Constructs a new VehicleType object indicated by the Hashtable
+ * parameter
+ * </p>
+ *
+ * @param hash The Hashtable to use
+ */
+ public VehicleType(HashMap<String, Object> hash) {
+ super(new Hashtable<>(hash));
+ }
+
+ /**
* get the make of the vehicle
*
* @return the make of the vehicle
diff --git a/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java b/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
index 8b35541b4..5fcc8d8ca 100644
--- a/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
+++ b/base/src/main/java/com/smartdevicelink/session/BaseSdlSession.java
@@ -32,16 +32,20 @@
package com.smartdevicelink.session;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import com.smartdevicelink.exception.SdlException;
import com.smartdevicelink.managers.lifecycle.RpcConverter;
+import com.smartdevicelink.protocol.SecurityQueryPayload;
import com.smartdevicelink.protocol.ISdlProtocol;
import com.smartdevicelink.protocol.ISdlServiceListener;
import com.smartdevicelink.protocol.ProtocolMessage;
import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.protocol.SdlProtocolBase;
import com.smartdevicelink.protocol.enums.ControlFrameTags;
+import com.smartdevicelink.protocol.enums.SecurityQueryID;
+import com.smartdevicelink.protocol.enums.SecurityQueryType;
import com.smartdevicelink.protocol.enums.SessionType;
import com.smartdevicelink.proxy.RPCMessage;
import com.smartdevicelink.proxy.rpc.VehicleType;
@@ -52,10 +56,12 @@ import com.smartdevicelink.security.SdlSecurityBase;
import com.smartdevicelink.streaming.video.VideoStreamingParameters;
import com.smartdevicelink.transport.BaseTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.SystemInfo;
import com.smartdevicelink.util.Version;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -185,21 +191,76 @@ public abstract class BaseSdlSession implements ISdlProtocol, ISecurityInitializ
protected void processControlService(ProtocolMessage msg) {
- if (sdlSecurity == null)
+ if (sdlSecurity == null || msg.getData() == null)
return;
+
+
+ if (msg.getData().length < 12) {
+ DebugTool.logError(TAG, "Security message is malformed, less than 12 bytes. It does not have a security payload header.");
+ }
+ // Check the client's message header for any internal errors
+ // NOTE: Before Core v8.0.0, all these messages will be notifications. In Core v8.0.0 and later, received messages will have the proper query type. Therefore, we cannot do things based only on the query type being request or response.
+ SecurityQueryPayload receivedHeader = SecurityQueryPayload.parseBinaryQueryHeader(msg.getData().clone());
+ if (receivedHeader == null) {
+ DebugTool.logError(TAG, "Module Security Query could not convert to object.");
+ return;
+ }
+
int iLen = msg.getData().length - 12;
byte[] data = new byte[iLen];
System.arraycopy(msg.getData(), 12, data, 0, iLen);
byte[] dataToRead = new byte[4096];
- Integer iNumBytes = sdlSecurity.runHandshake(data, dataToRead);
+ Integer iNumBytes = null;
- if (iNumBytes == null || iNumBytes <= 0)
+ // If the query is of type `Notification` and the id represents a client internal error, we abort the response message and the encryptionManager will not be in state ready.
+ if (receivedHeader.getQueryID() == SecurityQueryID.SEND_INTERNAL_ERROR
+ && receivedHeader.getQueryType() == SecurityQueryType.NOTIFICATION) {
+ if (receivedHeader.getErrorCode() != null) {
+ DebugTool.logError(TAG, "Security Query module internal error: " + receivedHeader.getErrorCode().getName());
+ } else {
+ DebugTool.logError(TAG, "Security Query module error: No information provided");
+ }
return;
+ }
+
+ if (receivedHeader.getQueryID() != SecurityQueryID.SEND_HANDSHAKE_DATA) {
+ DebugTool.logError(TAG, "Security Query module error: Message is not a SEND_HANDSHAKE_DATA REQUEST");
+ return;
+ }
+
+ if (receivedHeader.getQueryType() == SecurityQueryType.RESPONSE) {
+ DebugTool.logError(TAG, "Security Query module error: Message is a response, which is not supported");
+ return;
+ }
+
+ iNumBytes = sdlSecurity.runHandshake(data, dataToRead);
+
+ // Assemble a security query payload header for our response
+ SecurityQueryPayload responseHeader = new SecurityQueryPayload();
+
+ byte[] returnBytes;
+ if (iNumBytes == null || iNumBytes <= 0) {
+ DebugTool.logError(TAG, "Internal Error processing control service");
+
+ responseHeader.setQueryID(SecurityQueryID.SEND_INTERNAL_ERROR);
+ responseHeader.setQueryType(SecurityQueryType.NOTIFICATION);
+ responseHeader.setCorrelationID(msg.getCorrID());
+ responseHeader.setJsonSize(0);
+ returnBytes = new byte[12];
+ } else {
+ responseHeader.setQueryID(SecurityQueryID.SEND_HANDSHAKE_DATA);
+ responseHeader.setQueryType(SecurityQueryType.RESPONSE);
+ responseHeader.setCorrelationID(msg.getCorrID());
+ responseHeader.setJsonSize(0);
+ returnBytes = new byte[iNumBytes + 12];
+ System.arraycopy(dataToRead, 0, returnBytes, 12, iNumBytes);
+ }
+
+
+ System.arraycopy(responseHeader.assembleHeaderBytes(), 0, returnBytes, 0, 12);
- byte[] returnBytes = new byte[iNumBytes];
- System.arraycopy(dataToRead, 0, returnBytes, 0, iNumBytes);
ProtocolMessage protocolMessage = new ProtocolMessage();
protocolMessage.setSessionType(SessionType.CONTROL);
protocolMessage.setData(returnBytes);
@@ -415,4 +476,18 @@ public abstract class BaseSdlSession implements ISdlProtocol, ISecurityInitializ
public boolean isTransportForServiceAvailable(SessionType sessionType) {
return sdlProtocol != null && sdlProtocol.isTransportForServiceAvailable(sessionType);
}
+
+ /**
+ * Retrieves list of the active transports
+ *
+ * @return a list of active transports
+ * */
+ @Nullable
+ public List<TransportRecord> getActiveTransports() {
+ if (this.sdlProtocol != null) {
+ return this.sdlProtocol.getActiveTransports();
+ } else {
+ return null;
+ }
+ }
}
diff --git a/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
index d2a45fcb9..79a055bc9 100644
--- a/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
+++ b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
@@ -44,6 +44,7 @@ public class TransportConstants {
public static final String ROUTER_SERVICE_ACTION = "com.smartdevicelink.router.service";
public static final String FOREGROUND_EXTRA = "foreground";
public static final String CONFIRMED_SDL_DEVICE = "confirmed_sdl_device";
+ public static final String VEHICLE_INFO_EXTRA = "vehicle_info";
public static final String BIND_LOCATION_PACKAGE_NAME_EXTRA = "BIND_LOCATION_PACKAGE_NAME_EXTRA";
public static final String BIND_LOCATION_CLASS_NAME_EXTRA = "BIND_LOCATION_CLASS_NAME_EXTRA";
diff --git a/base/src/main/java/com/smartdevicelink/util/BitConverter.java b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
index 94695802f..df507d6fe 100644
--- a/base/src/main/java/com/smartdevicelink/util/BitConverter.java
+++ b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
@@ -163,7 +163,7 @@ public class BitConverter {
* Converts the byte array into a string of hex values.
*
* @param bytes byte array that will be converted to hex
- * @param end EXCLUSIVE so if it it receives 10 it will print 0-9
+ * @param end EXCLUSIVE so if it receives 10 it will print 0-9
* @return the String containing converted hex values or null if byte array is null
*/
public static String bytesToHex(byte[] bytes, int end) {
diff --git a/generator/rpc_spec b/generator/rpc_spec
-Subproject 72632f946941d63a57ee5e99896e3eae3627f7d
+Subproject 6537500b45f65e02d884da9d73d6820ba7b0b1f
diff --git a/javaEE/javaEE/gradle.properties b/javaEE/javaEE/gradle.properties
index 9dbf6adf2..6f84e23f1 100644
--- a/javaEE/javaEE/gradle.properties
+++ b/javaEE/javaEE/gradle.properties
@@ -1,6 +1,6 @@
GROUP=com.smartdevicelink
POM_ARTIFACT_ID=sdl_java_ee
-VERSION_NAME=5.2.0
+VERSION_NAME=5.3.0
POM_NAME=sdl_java_ee
POM_PACKAGING=jar
diff --git a/javaEE/javaEE/libs/TaskMaster-0.5.jar b/javaEE/javaEE/libs/TaskMaster-0.5.jar
deleted file mode 100644
index a7881051b..000000000
--- a/javaEE/javaEE/libs/TaskMaster-0.5.jar
+++ /dev/null
Binary files differ
diff --git a/javaEE/javaEE/libs/Taskmaster-0.6.jar b/javaEE/javaEE/libs/Taskmaster-0.6.jar
new file mode 100644
index 000000000..8f249d0f7
--- /dev/null
+++ b/javaEE/javaEE/libs/Taskmaster-0.6.jar
Binary files differ
diff --git a/javaSE/javaSE/build.gradle b/javaSE/javaSE/build.gradle
index 94b7a54d1..aa97ea53a 100644
--- a/javaSE/javaSE/build.gradle
+++ b/javaSE/javaSE/build.gradle
@@ -32,7 +32,7 @@ dependencies {
extraLibs 'org.mongodb:bson:4.0.5'
extraLibs 'androidx.annotation:annotation:1.1.0'
extraLibs 'org.java-websocket:Java-WebSocket:1.3.9'
- //extraLibs 'com.livio.taskmaster:taskmaster:0.4.0'
+ //extraLibs 'com.livio.taskmaster:taskmaster:0.6.0'
configurations.api.extendsFrom(configurations.extraLibs)
}
diff --git a/javaSE/javaSE/gradle.properties b/javaSE/javaSE/gradle.properties
index b8f580351..a9e29ea87 100644
--- a/javaSE/javaSE/gradle.properties
+++ b/javaSE/javaSE/gradle.properties
@@ -1,6 +1,6 @@
GROUP=com.smartdevicelink
POM_ARTIFACT_ID=sdl_java_se
-VERSION_NAME=5.2.0
+VERSION_NAME=5.3.0
POM_NAME=sdl_java_se
POM_PACKAGING=jar
diff --git a/javaSE/javaSE/libs/TaskMaster-0.5.jar b/javaSE/javaSE/libs/TaskMaster-0.5.jar
deleted file mode 100644
index a7881051b..000000000
--- a/javaSE/javaSE/libs/TaskMaster-0.5.jar
+++ /dev/null
Binary files differ
diff --git a/javaSE/javaSE/libs/Taskmaster-0.6.jar b/javaSE/javaSE/libs/Taskmaster-0.6.jar
new file mode 100644
index 000000000..8f249d0f7
--- /dev/null
+++ b/javaSE/javaSE/libs/Taskmaster-0.6.jar
Binary files differ
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
index 30aaeaed4..51add4ed2 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java
@@ -32,5 +32,5 @@ package com.smartdevicelink;
// THIS FILE IS AUTO GENERATED, DO NOT MODIFY!!
public final class BuildConfig {
- public static final String VERSION_NAME = "5.2.0";
+ public static final String VERSION_NAME = "5.3.0";
} \ No newline at end of file
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
index affa4ec36..2c31c848a 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlArtwork.java
@@ -37,7 +37,6 @@ import com.smartdevicelink.proxy.rpc.Image;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.ImageType;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
-import com.smartdevicelink.util.DebugTool;
import java.net.URI;
@@ -159,16 +158,10 @@ public class SdlArtwork extends SdlFile implements Cloneable {
*/
@Override
public SdlArtwork clone() {
- try {
- SdlArtwork artwork = (SdlArtwork) super.clone();
- if (artwork != null) {
- artwork.imageRPC = artwork.createImageRPC();
- }
+ SdlArtwork artwork = (SdlArtwork) super.clone();
+ if (artwork != null) {
+ artwork.imageRPC = artwork.createImageRPC();
return artwork;
- } catch (CloneNotSupportedException e) {
- if (DebugTool.isDebugEnabled()) {
- throw new RuntimeException("Clone not supported by super class");
- }
}
return null;
}
diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
index 6fd91b8a0..727413bf2 100644
--- a/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
+++ b/javaSE/javaSE/src/main/java/com/smartdevicelink/managers/file/filetypes/SdlFile.java
@@ -35,6 +35,7 @@ import androidx.annotation.NonNull;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.StaticIconName;
+import com.smartdevicelink.util.DebugTool;
import java.net.URI;
import java.security.MessageDigest;
@@ -44,7 +45,7 @@ import java.util.Arrays;
/**
* A class representing data to be uploaded to core
*/
-public class SdlFile {
+public class SdlFile implements Cloneable {
private String fileName;
private String filePath;
private URI uri;
@@ -366,4 +367,22 @@ public class SdlFile {
// return comparison
return hashCode() == o.hashCode();
}
+
+ /**
+ * Creates a deep copy of the object
+ *
+ * @return deep copy of the object, null if an exception occurred
+ */
+ @Override
+ public SdlFile clone() {
+ try {
+ SdlFile fileClone = (SdlFile) super.clone();
+ return fileClone;
+ } catch (CloneNotSupportedException e) {
+ if (DebugTool.isDebugEnabled()) {
+ throw new RuntimeException("Clone not supported by super class");
+ }
+ }
+ return null;
+ }
}