diff options
author | Bilal Alsharifi <599206+bilal-alsharifi@users.noreply.github.com> | 2021-06-30 11:19:03 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-30 11:19:03 -0400 |
commit | c3553b717eef3626d20fafedbfe63eaa5a28bb10 (patch) | |
tree | e42721a35de30fdcfd5775a8b2590f9f85bc454e | |
parent | 65c43df37ac13ca650a5548efd4c87a65a90d8cf (diff) | |
parent | 405d9a47e11222d52073d9f077bed1c9f28feb51 (diff) | |
download | sdl_android-c3553b717eef3626d20fafedbfe63eaa5a28bb10.tar.gz |
Merge pull request #1709 from smartdevicelink/release/5.2.0_RC
5.2.0 Release
40 files changed, 1132 insertions, 167 deletions
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 721ac4915..0aa33c484 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,6 @@ name: GitHub CI -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index d03b78541..23e2aa55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 5.1.1 Release Notes +# 5.2.0 Release Notes ## Summary: ||Version| @@ -8,13 +8,41 @@ | **Tested Targeting** | Android 30 -## Bug Fixes: +## Bug Fixes / Enhancements: -- [Add null checks and synch around session in LCM](https://github.com/smartdevicelink/sdl_java_suite/pull/1659) +- [MenuManager sending secondary image with menuCells when menuCommandSecondaryImage is not supported.](https://github.com/smartdevicelink/sdl_java_suite/issues/1688) -- [Add NPE check in BaseChoiceSetManager](https://github.com/smartdevicelink/sdl_java_suite/pull/1648) +- [IllegalArgumentException when starting video stream with custom RPC MTU](https://github.com/smartdevicelink/sdl_java_suite/issues/1667) -- [Attempt to fix unreproducible ClassCastException](https://github.com/smartdevicelink/sdl_java_suite/pull/1660) +- [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) + +- [java.lang.NegativeArraySizeException Crash at SdlPsm.java line 241 com.smartdevicelink.transport.SdlPsm.transitionOnInput](https://github.com/smartdevicelink/sdl_java_suite/issues/1678) + +- [Exception handling variances](https://github.com/smartdevicelink/sdl_java_suite/issues/1687) + +- [Allow SdlDeviceListener to start after BT connection](https://github.com/smartdevicelink/sdl_java_suite/pull/1685) + +- [voiceCommand that contains no string should be removed](https://github.com/smartdevicelink/sdl_java_suite/issues/1675) + +- [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) + +- [Primary Graphic not sent to SDL Core for Media Template ](https://github.com/smartdevicelink/sdl_java_suite/issues/1690) + +- [Race condition leads to NPE in TransportManager](https://github.com/smartdevicelink/sdl_java_suite/issues/1703) + +- [Avoid deleting and setting identical voice commands](https://github.com/smartdevicelink/sdl_java_suite/issues/1676) + +- [Sdl disconnection is not notified to the app](https://github.com/smartdevicelink/sdl_java_suite/issues/1697) + +- [PredefinedLayout.NON_MEDIA not found in templatesAvailable](https://github.com/smartdevicelink/sdl_java_suite/issues/1705) + +- [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 Cells and Menu Cells do not take which properties are available into account for uniqueness](https://github.com/smartdevicelink/sdl_java_suite/issues/1682) + +- [BSON library should be updated to the latest version (1.2.5)](https://github.com/smartdevicelink/sdl_java_suite/issues/1712) @@ -1 +1 @@ -5.1.1 +5.2.0
\ No newline at end of file diff --git a/android/sdl_android/build.gradle b/android/sdl_android/build.gradle index a87611ecd..73665a4b7 100644 --- a/android/sdl_android/build.gradle +++ b/android/sdl_android/build.gradle @@ -5,7 +5,7 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 30 - versionCode 19 + versionCode 20 versionName new File(projectDir.path, ('/../../VERSION')).text.trim() buildConfigField "String", "VERSION_NAME", '\"' + versionName + '\"' resValue "string", "SDL_LIB_VERSION", '\"' + versionName + '\"' @@ -43,7 +43,7 @@ android { dependencies { api fileTree(dir: 'libs', include: ['*.jar']) //api 'com.livio.taskmaster:taskmaster:0.4.0' - api 'com.smartdevicelink:bson_java_port:1.2.4' + api 'com.smartdevicelink:bson_java_port:1.2.5' api 'androidx.lifecycle:lifecycle-extensions:2.2.0' api 'androidx.annotation:annotation:1.1.0' annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0' diff --git a/android/sdl_android/gradle.properties b/android/sdl_android/gradle.properties index 2ff7b607b..3136c040b 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.1.1 +VERSION_NAME=5.2.0 POM_NAME=sdl_android POM_PACKAGING=aar 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 28f09f8bb..65f9c7bb4 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 @@ -22,6 +22,7 @@ import com.smartdevicelink.proxy.rpc.DisplayCapability; import com.smartdevicelink.proxy.rpc.GetSystemCapability; import com.smartdevicelink.proxy.rpc.GetSystemCapabilityResponse; import com.smartdevicelink.proxy.rpc.HMICapabilities; +import com.smartdevicelink.proxy.rpc.ImageField; import com.smartdevicelink.proxy.rpc.OnHMIStatus; import com.smartdevicelink.proxy.rpc.OnSystemCapabilityUpdated; import com.smartdevicelink.proxy.rpc.PhoneCapability; @@ -37,10 +38,13 @@ import com.smartdevicelink.proxy.rpc.WindowTypeCapabilities; import com.smartdevicelink.proxy.rpc.enums.AppServiceType; import com.smartdevicelink.proxy.rpc.enums.AudioStreamingState; import com.smartdevicelink.proxy.rpc.enums.DisplayType; +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.ImageFieldName; import com.smartdevicelink.proxy.rpc.enums.ImageType; import com.smartdevicelink.proxy.rpc.enums.MediaClockFormat; +import com.smartdevicelink.proxy.rpc.enums.PredefinedLayout; import com.smartdevicelink.proxy.rpc.enums.PredefinedWindows; import com.smartdevicelink.proxy.rpc.enums.PrerecordedSpeech; import com.smartdevicelink.proxy.rpc.enums.Result; @@ -938,6 +942,31 @@ public class SystemCapabilityManagerTests { assertNull(systemCapabilityManager.getWindowCapability(PredefinedWindows.PRIMARY_WIDGET.getValue())); } + /** + * Test that when we receive template "NON_MEDIA" it gets converted to "NON-MEDIA" + */ + @Test + public void testSyncNonMediaBug() { + InternalSDLInterface iSDL = new InternalSDLInterface(); + SystemCapabilityManager systemCapabilityManager = createSampleManager(iSDL); + OnRPCListener dlRpcListener = iSDL.rpcListeners.get(FunctionID.SET_DISPLAY_LAYOUT.getId()).get(0); + + DisplayCapabilities displayCapabilities = new DisplayCapabilities(); + displayCapabilities.setGraphicSupported(true); + List<String> templatesAvailable = new ArrayList<>(); + templatesAvailable.add("NON_MEDIA"); + templatesAvailable.add("MEDIA"); + displayCapabilities.setTemplatesAvailable(templatesAvailable); + + SetDisplayLayoutResponse newLayout = new SetDisplayLayoutResponse(); + newLayout.setDisplayCapabilities(displayCapabilities); + newLayout.setSuccess(true); + newLayout.setResultCode(Result.SUCCESS); + dlRpcListener.onReceived(newLayout); + + assertTrue(systemCapabilityManager.getDefaultMainWindowCapability().getTemplatesAvailable().contains("NON-MEDIA")); + } + private class InternalSDLInterface implements ISdl { private final Object RPC_LISTENER_LOCK = new Object(); SparseArray<CopyOnWriteArrayList<OnRPCListener>> rpcListeners = new SparseArray<>(); @@ -1075,4 +1104,33 @@ public class SystemCapabilityManagerTests { return null; } } + + @Test + public void testFixingIncorrectCapabilities() { + SetDisplayLayoutResponse setDisplayLayoutResponse; + + DisplayCapabilities RegisterAppInterFaceCapabilities = new DisplayCapabilities() + .setImageFields(Collections.singletonList(new ImageField(ImageFieldName.graphic, Collections.singletonList(FileType.GRAPHIC_PNG)))); + + DisplayCapabilities setDisplayLayoutCapabilities = new DisplayCapabilities() + .setImageFields(new ArrayList<ImageField>()); + + LifecycleManager lcm = new LifecycleManager(new BaseLifecycleManager.AppConfig(), null, null); + lcm.initialMediaCapabilities = RegisterAppInterFaceCapabilities; + + + // Test switching to MEDIA template - Capabilities in setDisplayLayoutResponse should be replaced with the ones from RAIR + lcm.lastDisplayLayoutRequestTemplate = PredefinedLayout.MEDIA.toString(); + setDisplayLayoutResponse = new SetDisplayLayoutResponse() + .setDisplayCapabilities(setDisplayLayoutCapabilities); + lcm.fixIncorrectDisplayCapabilities(setDisplayLayoutResponse); + assertEquals(RegisterAppInterFaceCapabilities, setDisplayLayoutResponse.getDisplayCapabilities()); + + // Test switching to non-MEDIA template - Capabilities in setDisplayLayoutResponse should not be altered + lcm.lastDisplayLayoutRequestTemplate = PredefinedLayout.TEXT_WITH_GRAPHIC.toString(); + setDisplayLayoutResponse = new SetDisplayLayoutResponse() + .setDisplayCapabilities(setDisplayLayoutCapabilities); + lcm.fixIncorrectDisplayCapabilities(setDisplayLayoutResponse); + assertEquals(setDisplayLayoutCapabilities, setDisplayLayoutResponse.getDisplayCapabilities()); + } } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lockscreen/LockScreenManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lockscreen/LockScreenManagerTests.java index 924ad6680..ff205f39a 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lockscreen/LockScreenManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/lockscreen/LockScreenManagerTests.java @@ -1,6 +1,8 @@ package com.smartdevicelink.managers.lockscreen; import android.content.Context; +import android.content.Intent; +import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -170,4 +172,34 @@ public class LockScreenManagerTests { assertTrue(lockScreenManager.isLockscreenDismissible); } + @Test + public void testShowingLockscreenAfterDismissibleFalse() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + lockScreenManager.enableDismissGesture = true; + lockScreenManager.displayMode = LockScreenConfig.DISPLAY_MODE_ALWAYS; + + // Send first notification (DD=OFF, Dismissible=true) + OnDriverDistraction onDriverDistraction = new OnDriverDistraction(); + onDriverDistraction.setLockscreenDismissibility(true); + onDriverDistraction.setState(DriverDistractionState.DD_OFF); + onDDListener.onNotified(onDriverDistraction); + + // Dismiss lock screen activity + lockScreenManager.mLockscreenDismissedReceiver.onReceive(null, new Intent(SDLLockScreenActivity.KEY_LOCKSCREEN_DISMISSED, null)); + + // Lock screen should be set to auto dismiss in future + assertTrue(lockScreenManager.mLockScreenShouldBeAutoDismissed); + + // Send second notification (DD=On, Dismissible=false) + onDriverDistraction = new OnDriverDistraction(); + onDriverDistraction.setLockscreenDismissibility(false); + onDriverDistraction.setState(DriverDistractionState.DD_ON); + onDDListener.onNotified(onDriverDistraction); + + // Lock screen should be set to NOT auto dismiss in future + assertFalse(lockScreenManager.mLockScreenShouldBeAutoDismissed); + } + }
\ No newline at end of file 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 16ea857f5..81f912361 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 @@ -40,18 +40,23 @@ import com.livio.taskmaster.Taskmaster; import com.smartdevicelink.managers.BaseSubManager; 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; @@ -231,7 +236,8 @@ public class ChoiceSetManagerTests { 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); - LinkedHashSet<ChoiceCell> cellList = new LinkedHashSet<>(); + List<ChoiceCell> cellList = new ArrayList<>(); + cellList.add(cell1); cellList.add(cell2); cellList.add(cell3); @@ -468,4 +474,51 @@ 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/menu/MenuManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/MenuManagerTests.java index 2419023f6..e71d8295c 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 @@ -43,17 +43,22 @@ import com.smartdevicelink.managers.file.filetypes.SdlArtwork; import com.smartdevicelink.protocol.enums.FunctionID; 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.OnRPCNotificationListener; +import com.smartdevicelink.test.TestValues; import org.junit.After; import org.junit.Before; @@ -63,6 +68,7 @@ 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.Collections; import java.util.List; @@ -598,6 +604,111 @@ public class MenuManagerTests { assertEquals(menuManager.menuCells.get(3).getSubCells().get(3).getUniqueTitle(), "A"); } + @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)"); + + // SubCell test + assertEquals(menuCellList.get(3).getUniqueTitle(), "Test Cell 3 (sub menu) (2)"); + assertEquals(menuCellList.get(2).getSubCells().get(1).getUniqueTitle(), "SubCell1 (2)"); + + + } + + + // HELPERS // Emulate what happens when Core sends OnHMIStatus notification @@ -849,4 +960,5 @@ public class MenuManagerTests { return Arrays.asList(A, B, C, D); } + } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java index 8fd025aac..4d5786899 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandManagerTests.java @@ -39,6 +39,7 @@ import com.smartdevicelink.managers.BaseSubManager; import com.smartdevicelink.managers.CompletionListener; import com.smartdevicelink.managers.ISdl; import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.proxy.RPCRequest; import com.smartdevicelink.proxy.rpc.OnCommand; import com.smartdevicelink.proxy.rpc.OnHMIStatus; import com.smartdevicelink.proxy.rpc.enums.HMILevel; @@ -52,8 +53,10 @@ import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import static junit.framework.TestCase.assertEquals; @@ -174,10 +177,12 @@ public class VoiceCommandManagerTests { public void testUpdatingCommands() { // Send a new single command, and test that its listener works, as it gets called from the VCM voiceCommandManager.setVoiceCommands(Collections.singletonList(command3)); + HashMap<RPCRequest, String> errorObject = new HashMap<>(); + voiceCommandManager.updateOperation.voiceCommandListener.updateVoiceCommands(voiceCommandManager.voiceCommands, errorObject); // Fake onCommand - we want to make sure that we can pass back onCommand events to our VoiceCommand Objects OnCommand onCommand = new OnCommand(); - onCommand.setCmdID(command3.getCommandId()); + onCommand.setCmdID(voiceCommandManager.getVoiceCommands().get(voiceCommandManager.getVoiceCommands().indexOf(command3)).getCommandId()); onCommand.setTriggerSource(TriggerSource.TS_VR); // these are voice commands commandListener.onNotified(onCommand); // send off the notification @@ -192,4 +197,64 @@ public class VoiceCommandManagerTests { onHMIStatusListener.onNotified(onHMIStatusFakeNotification); } + /** + * Test If voice commands do not have unique strings, they will not be uploaded + */ + @Test + public void testUniqueStrings() { + List<VoiceCommand> voiceCommandList = new ArrayList<>(); + VoiceCommand command1 = new VoiceCommand(Arrays.asList("Command one", "Command two"), null); + VoiceCommand command2 = new VoiceCommand(Arrays.asList("Command one", "Command two"), null); + + voiceCommandList.add(command1); + voiceCommandList.add(command2); + voiceCommandManager.currentHMILevel = HMILevel.HMI_NONE; + voiceCommandManager.setVoiceCommands(voiceCommandList); + + assertNull(voiceCommandManager.voiceCommands); + } + + /** + * Test trim whitespace from voice commands and not uploading empty strings. + */ + @Test + public void testEmptyStringsAndWhiteSpaceRemoval() { + List<VoiceCommand> voiceCommandList = new ArrayList<>(); + VoiceCommand command1 = new VoiceCommand(Arrays.asList(" Command one "), null); + + voiceCommandList.add(command1); + voiceCommandManager.currentHMILevel = HMILevel.HMI_NONE; + voiceCommandManager.setVoiceCommands(voiceCommandList); + assertEquals(voiceCommandManager.voiceCommands.get(0).getVoiceCommands().get(0), "Command one"); + + voiceCommandManager.voiceCommands = null; + voiceCommandList = new ArrayList<>(); + command1 = new VoiceCommand(Arrays.asList(" "), null); + + voiceCommandList.add(command1); + voiceCommandManager.setVoiceCommands(voiceCommandList); + assertNull(voiceCommandManager.voiceCommands); + + VoiceCommand command2 = new VoiceCommand(Arrays.asList("Command two"), null); + voiceCommandList.add(command2); + voiceCommandManager.setVoiceCommands(voiceCommandList); + assertEquals(voiceCommandManager.voiceCommands.size(), 1); + + voiceCommandManager.setVoiceCommands(voiceCommandList); + assertEquals(voiceCommandManager.voiceCommands.size(), 1); + + voiceCommandManager.voiceCommands = null; + voiceCommandManager.setVoiceCommands(null); + assertNull(voiceCommandManager.voiceCommands); + + voiceCommandList = new ArrayList<>(); + VoiceCommand command3 = new VoiceCommand(Arrays.asList("Command three", null, " "), null); + voiceCommandList.add(command3); + voiceCommandList.add(null); + voiceCommandManager.setVoiceCommands(voiceCommandList); + assertEquals(voiceCommandManager.voiceCommands.size(), 1); + + } + + } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java index 0a7673c0c..6acdbeae0 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandTests.java @@ -39,6 +39,9 @@ import com.smartdevicelink.test.TestValues; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + import static junit.framework.TestCase.assertEquals; @RunWith(AndroidJUnit4.class) @@ -59,4 +62,25 @@ public class VoiceCommandTests { assertEquals(voiceCommand.getVoiceCommandSelectionListener(), voiceCommandSelectionListener); } + @Test + public void testDuplicateStrings() { + List<String> voiceCommandsList = new ArrayList<>(); + voiceCommandsList.add("Test1"); + voiceCommandsList.add("Test1"); + voiceCommandsList.add("Test1"); + VoiceCommand voiceCommand = new VoiceCommand(voiceCommandsList, voiceCommandSelectionListener); + + assertEquals(1, voiceCommand.getVoiceCommands().size()); + assertEquals("Test1", voiceCommand.getVoiceCommands().get(0)); + + voiceCommandsList = new ArrayList<>(); + voiceCommandsList.add("Test1"); + voiceCommandsList.add("Test2"); + voiceCommandsList.add("Test1"); + VoiceCommand voiceCommand2 = new VoiceCommand(voiceCommandsList, voiceCommandSelectionListener); + + assertEquals(2, voiceCommand2.getVoiceCommands().size()); + assertEquals("Test1", voiceCommand2.getVoiceCommands().get(0)); + assertEquals("Test2", voiceCommand2.getVoiceCommands().get(1)); + } } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java index 1c6f2b1c4..67885a902 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/screen/menu/VoiceCommandUpdateOperationTests.java @@ -14,6 +14,7 @@ import com.smartdevicelink.util.DebugTool; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -74,6 +75,7 @@ public class VoiceCommandUpdateOperationTests { List<VoiceCommand> deleteList = new ArrayList<>(); List<VoiceCommand> addList = new ArrayList<>(); + @Before public void setup() { deleteList.clear(); @@ -294,4 +296,50 @@ public class VoiceCommandUpdateOperationTests { verify(listenerSpy, times(1)).updateVoiceCommands(any(List.class), any(HashMap.class)); } + + + + @Test + public void testVoiceCommandsInListNotInSecondList() { + VoiceCommand command1 = new VoiceCommand(Collections.singletonList("Command 1"), null); + VoiceCommand command2 = new VoiceCommand(Collections.singletonList("Command 2"), null); + VoiceCommand command3 = new VoiceCommand(Collections.singletonList("Command 3"), null); + + VoiceCommand command1Clone = new VoiceCommand(Collections.singletonList("Command 1"), null); + + List<VoiceCommand> voiceCommandList = new ArrayList<>(); + voiceCommandList.add(command1); + voiceCommandList.add(command2); + + List<VoiceCommand> voiceCommandList2 = new ArrayList<>(); + voiceCommandList2.add(command1Clone); + voiceCommandList2.add(command3); + VoiceCommandUpdateOperation voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface,null,null,null); + + List<VoiceCommand> differencesList = voiceCommandUpdateOperation.voiceCommandsInListNotInSecondList(voiceCommandList, voiceCommandList2); + assertEquals(differencesList.size(), 1); + } + + @Test + public void testDelete() { + internalInterface = mock(ISdl.class); + + VoiceCommand command1 = new VoiceCommand(Collections.singletonList("Command 1"), null); + VoiceCommand command2 = new VoiceCommand(Collections.singletonList("Command 2"), null); + + VoiceCommand command1Clone = new VoiceCommand(Collections.singletonList("Command 1"), null); + VoiceCommand command3 = new VoiceCommand(Collections.singletonList("Command 3"), null); + + List<VoiceCommand> voiceCommandList = new ArrayList<>(); + voiceCommandList.add(command1); + voiceCommandList.add(command2); + + List<VoiceCommand> voiceCommandList2 = new ArrayList<>(); + voiceCommandList2.add(command1Clone); + voiceCommandList2.add(command3); + VoiceCommandUpdateOperation voiceCommandUpdateOperation = new VoiceCommandUpdateOperation(internalInterface, voiceCommandList, voiceCommandList2, null); + voiceCommandUpdateOperation.onExecute(); + verify(internalInterface, times(1)).sendRPCs(ArgumentMatchers.<DeleteCommand>anyList(), any(OnMultipleRequestListener.class)); + } + } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/streaming/video/SdlRemoteDisplayTest.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/streaming/video/SdlRemoteDisplayTest.java index 7f4d23c96..5282456e1 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/streaming/video/SdlRemoteDisplayTest.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/streaming/video/SdlRemoteDisplayTest.java @@ -58,22 +58,20 @@ public class SdlRemoteDisplayTest extends TestCase { @TargetApi(19) public void testTouchEvents() { - VirtualDisplayEncoder encoder = createVDE(); - assertNotNull(encoder); - MockRemoteDisplay remoteDisplay = new MockRemoteDisplay(InstrumentationRegistry.getInstrumentation().getContext(), encoder.getVirtualDisplay()); - assertNotNull(remoteDisplay); - remoteDisplay.show(); - - assertNotNull(remoteDisplay.getMainView()); - try { + VirtualDisplayEncoder encoder = createVDE(); + assertNotNull(encoder); + MockRemoteDisplay remoteDisplay = new MockRemoteDisplay(InstrumentationRegistry.getInstrumentation().getContext(), encoder.getVirtualDisplay()); + assertNotNull(remoteDisplay); + remoteDisplay.show(); + + assertNotNull(remoteDisplay.getMainView()); remoteDisplay.handleMotionEvent(MotionEvent.obtain(10, System.currentTimeMillis(), MotionEvent.ACTION_DOWN, 100, 100, 0)); - } catch (Exception e) { - assert false; - } - remoteDisplay.dismiss(); - encoder.shutDown(); + remoteDisplay.dismiss(); + encoder.shutDown(); + } catch (Exception ignored) { + } } diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/SdlPsmTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/SdlPsmTests.java index 64b0cb768..78db56c32 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/SdlPsmTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/SdlPsmTests.java @@ -3,12 +3,17 @@ package com.smartdevicelink.test.transport; import android.util.Log; import com.smartdevicelink.protocol.SdlPacket; +import com.smartdevicelink.protocol.SdlPacketFactory; import com.smartdevicelink.protocol.SdlProtocol; +import com.smartdevicelink.protocol.enums.ControlFrameTags; +import com.smartdevicelink.protocol.enums.SessionType; import com.smartdevicelink.test.TestValues; import com.smartdevicelink.transport.SdlPsm; import junit.framework.TestCase; +import org.junit.Assert; + import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -18,12 +23,15 @@ import java.lang.reflect.Method; */ public class SdlPsmTests extends TestCase { private static final String TAG = "SdlPsmTests"; - private static final int MAX_DATA_LENGTH = SdlProtocol.V1_V2_MTU_SIZE - SdlProtocol.V1_HEADER_SIZE; + private static final int MAX_DATA_LENGTH_V1 = SdlProtocol.V1_V2_MTU_SIZE - SdlProtocol.V1_HEADER_SIZE; + private static final int MAX_DATA_LENGTH_V2 = SdlProtocol.V1_V2_MTU_SIZE - SdlProtocol.V2_HEADER_SIZE; SdlPsm sdlPsm; Field frameType, dataLength, version, controlFrameInfo; Method transitionOnInput; byte rawByte = (byte) 0x0; + SdlPacket startServiceACK; + protected void setUp() throws Exception { super.setUp(); sdlPsm = new SdlPsm(); @@ -38,9 +46,44 @@ public class SdlPsmTests extends TestCase { dataLength.setAccessible(true); version.setAccessible(true); controlFrameInfo.setAccessible(true); + + startServiceACK = SdlPacketFactory.createStartSessionACK(SessionType.RPC, (byte) 0x01, (byte) 0x05, (byte) 0x05); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID, "3bb34978fe3a"); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.MTU, "150000"); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.PROTOCOL_VERSION, "5.1.0"); } + public void testHappyPath() { + + + byte[] packetBytes = startServiceACK.constructPacket(); + + SdlPsm sdlPsm = new SdlPsm(); + boolean didTransition = false; + + for (byte packetByte : packetBytes) { + didTransition = sdlPsm.handleByte(packetByte); + assertTrue(didTransition); + } + + assertEquals(SdlPsm.FINISHED_STATE, sdlPsm.getState()); + SdlPacket parsedPacket = sdlPsm.getFormedPacket(); + assertNotNull(parsedPacket); + + assertEquals(startServiceACK.getVersion(), parsedPacket.getVersion()); + assertEquals(startServiceACK.getServiceType(), parsedPacket.getServiceType()); + assertEquals(startServiceACK.getFrameInfo(), parsedPacket.getFrameInfo()); + assertEquals(startServiceACK.getFrameType(), parsedPacket.getFrameType()); + assertEquals(startServiceACK.getDataSize(), parsedPacket.getDataSize()); + assertEquals(startServiceACK.getMessageId(), parsedPacket.getMessageId()); + + assertTrue(startServiceACK.getTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID).equals(parsedPacket.getTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID))); + assertTrue(startServiceACK.getTag(ControlFrameTags.RPC.StartServiceACK.MTU).equals(parsedPacket.getTag(ControlFrameTags.RPC.StartServiceACK.MTU))); + assertTrue(startServiceACK.getTag(ControlFrameTags.RPC.StartServiceACK.PROTOCOL_VERSION).equals(parsedPacket.getTag(ControlFrameTags.RPC.StartServiceACK.PROTOCOL_VERSION))); + + } + public void testGarbledControlFrame() { try { rawByte = 0x0; @@ -48,27 +91,46 @@ public class SdlPsmTests extends TestCase { controlFrameInfo.set(sdlPsm, SdlPacket.FRAME_INFO_START_SERVICE); frameType.set(sdlPsm, SdlPacket.FRAME_TYPE_CONTROL); - dataLength.set(sdlPsm, MAX_DATA_LENGTH + 1); + dataLength.set(sdlPsm, MAX_DATA_LENGTH_V1 + 1); int STATE = (Integer) transitionOnInput.invoke(sdlPsm, rawByte, SdlPsm.DATA_SIZE_4_STATE); assertEquals(TestValues.MATCH, SdlPsm.ERROR_STATE, STATE); } catch (Exception e) { + Assert.fail(e.toString()); Log.e(TAG, e.toString()); } } - public void testMaximumControlFrame() { + public void testMaximumControlFrameForVersion1() { try { rawByte = 0x0; version.set(sdlPsm, 1); controlFrameInfo.set(sdlPsm, SdlPacket.FRAME_INFO_START_SERVICE); frameType.set(sdlPsm, SdlPacket.FRAME_TYPE_CONTROL); - dataLength.set(sdlPsm, MAX_DATA_LENGTH); + dataLength.set(sdlPsm, MAX_DATA_LENGTH_V1); int STATE = (Integer) transitionOnInput.invoke(sdlPsm, rawByte, SdlPsm.DATA_SIZE_4_STATE); assertEquals(TestValues.MATCH, SdlPsm.DATA_PUMP_STATE, STATE); } catch (Exception e) { + Assert.fail(e.toString()); + Log.e(TAG, e.toString()); + } + } + + public void testMaximumControlFrameForVersion2Plus() { + try { + rawByte = 0x0; + version.set(sdlPsm, 2); + controlFrameInfo.set(sdlPsm, SdlPacket.FRAME_INFO_START_SERVICE); + frameType.set(sdlPsm, SdlPacket.FRAME_TYPE_CONTROL); + + dataLength.set(sdlPsm, MAX_DATA_LENGTH_V2); + int STATE = (Integer) transitionOnInput.invoke(sdlPsm, rawByte, SdlPsm.DATA_SIZE_4_STATE); + + assertEquals(TestValues.MATCH, SdlPsm.MESSAGE_1_STATE, STATE); + } catch (Exception e) { + Assert.fail(e.toString()); Log.e(TAG, e.toString()); } } @@ -80,14 +142,117 @@ public class SdlPsmTests extends TestCase { frameType.set(sdlPsm, SdlPacket.FRAME_TYPE_SINGLE); dataLength.set(sdlPsm, 2147483647); - int STATE = (Integer) transitionOnInput.invoke(sdlPsm, rawByte, SdlPsm.DATA_SIZE_4_STATE); + int STATE = (Integer) transitionOnInput.invoke(sdlPsm, rawByte, SdlPsm.MESSAGE_4_STATE); assertEquals(TestValues.MATCH, SdlPsm.ERROR_STATE, STATE); } catch (Exception e) { + Assert.fail(e.toString()); Log.e(TAG, e.toString()); } } + public void testNegativeDataSize() { + byte[] packetBytes = startServiceACK.constructPacket(); + + SdlPsm sdlPsm = new SdlPsm(); + boolean didTransition = false; + + for (byte packetByte : packetBytes) { + int state = sdlPsm.getState(); + switch (state) { + case SdlPsm.MESSAGE_4_STATE: + didTransition = sdlPsm.handleByte(packetByte); + assertFalse(didTransition); + assertEquals(SdlPsm.ERROR_STATE, sdlPsm.getState()); + return; + case SdlPsm.DATA_SIZE_1_STATE: + case SdlPsm.DATA_SIZE_2_STATE: + case SdlPsm.DATA_SIZE_3_STATE: + case SdlPsm.DATA_SIZE_4_STATE: + didTransition = sdlPsm.handleByte((byte) 0xFF); + assertTrue(didTransition); + break; + default: + didTransition = sdlPsm.handleByte(packetByte); + assertTrue(didTransition); + } + } + } + + public void testIncorrectVersion() { + SdlPacket startServiceACK = SdlPacketFactory.createStartSessionACK(SessionType.RPC, (byte) 0x01, (byte) 0x05, (byte) 0x06); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID, "3bb34978fe3a"); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.MTU, "150000"); + startServiceACK.putTag(ControlFrameTags.RPC.StartServiceACK.PROTOCOL_VERSION, "5.1.0"); + byte[] packetBytes = startServiceACK.constructPacket(); + + SdlPsm sdlPsm = new SdlPsm(); + boolean didTransition = sdlPsm.handleByte(packetBytes[0]); + assertFalse(didTransition); + } + + public void testIncorrectService() { + + byte[] packetBytes = startServiceACK.constructPacket(); + + SdlPsm sdlPsm = new SdlPsm(); + boolean didTransition = false; + + for (byte packetByte : packetBytes) { + int state = sdlPsm.getState(); + switch (state) { + case SdlPsm.SERVICE_TYPE_STATE: + didTransition = sdlPsm.handleByte((byte) 0xFF); + assertFalse(didTransition); + assertEquals(SdlPsm.ERROR_STATE, sdlPsm.getState()); + return; + default: + didTransition = sdlPsm.handleByte(packetByte); + assertTrue(didTransition); + } + } + } + + public void testRecovery() { + byte[] packetBytes = startServiceACK.constructPacket(); + byte[] processingBytes = new byte[packetBytes.length + 15]; + + System.arraycopy(packetBytes, 10, processingBytes, 0, 15); + System.arraycopy(packetBytes, 0, processingBytes, 15, packetBytes.length); + + + SdlPsm sdlPsm = new SdlPsm(); + boolean didTransition = false; + byte packetByte; + int state = SdlPsm.START_STATE, i = 0, limit = 0; + + while (state != SdlPsm.FINISHED_STATE && limit < 10) { + + packetByte = processingBytes[i]; + didTransition = sdlPsm.handleByte(packetByte); + state = sdlPsm.getState(); + if (!didTransition) { + assertEquals(SdlPsm.ERROR_STATE, state); + sdlPsm.reset(); + } else if (state == SdlPsm.FINISHED_STATE) { + break; + } + + if (i == processingBytes.length - 1) { + i = 0; + limit++; + } else { + i++; + } + } + + assertEquals(SdlPsm.FINISHED_STATE, sdlPsm.getState()); + SdlPacket parsedPacket = sdlPsm.getFormedPacket(); + assertNotNull(parsedPacket); + + } + + protected void tearDown() throws Exception { super.tearDown(); } 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 66894df8b..6f0e68aba 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 @@ -87,9 +87,10 @@ public class LockScreenManager extends BaseSubManager { final int customView; int displayMode; Bitmap deviceLogo; - private boolean mLockScreenHasBeenDismissed, lockscreenDismissReceiverRegistered, receivedFirstDDNotification; + private boolean lockscreenDismissReceiverRegistered, receivedFirstDDNotification; + boolean mLockScreenShouldBeAutoDismissed; private String mLockscreenWarningMsg; - private BroadcastReceiver mLockscreenDismissedReceiver; + BroadcastReceiver mLockscreenDismissedReceiver; private final LockScreenDeviceIconManager mLockScreenDeviceIconManager; private String lastIntentUsed; @@ -220,6 +221,11 @@ public class LockScreenManager extends BaseSubManager { // enable the dismissal. There is a delay added to allow time for the activity // time to completely start and handle the new intent. There seems to be odd behavior // in Android when startActivity is called multiple times too quickly. + if (previousDismissibleState) { + // If lockscreen was dismissible, got dismissed by the user, then became not dismissible, the lockscreen activity should be allowed to launch again + // https://github.com/smartdevicelink/sdl_java_suite/issues/1695 + mLockScreenShouldBeAutoDismissed = false; + } if (!receivedFirstDDNotification) { new Handler().postDelayed(new Runnable() { @Override @@ -296,7 +302,7 @@ public class LockScreenManager extends BaseSubManager { @Override public void onReceive(Context context, Intent intent) { if (SDLLockScreenActivity.KEY_LOCKSCREEN_DISMISSED.equals(intent.getAction())) { - mLockScreenHasBeenDismissed = true; + mLockScreenShouldBeAutoDismissed = true; lastIntentUsed = null; } } @@ -318,7 +324,7 @@ public class LockScreenManager extends BaseSubManager { private void launchLockScreenActivity() { synchronized (LOCKSCREEN_LAUNCH_LOCK) { // If the user has dismissed the lockscreen for this run or has disabled it, do not show it - if (mLockScreenHasBeenDismissed || displayMode == LockScreenConfig.DISPLAY_MODE_NEVER) { + if (mLockScreenShouldBeAutoDismissed || displayMode == LockScreenConfig.DISPLAY_MODE_NEVER) { return; } // intent to open SDLLockScreenActivity diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java b/android/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java index 097afd089..5bb682741 100644 --- a/android/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java +++ b/android/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java @@ -86,7 +86,7 @@ public class StreamPacketizer extends AbstractPacketizer implements IVideoStream bufferSize = BUFF_READ_SIZE;
buffer = new byte[bufferSize];
}
- mOutputQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE / bufferSize);
+ mOutputQueue = new LinkedBlockingQueue<>(Math.max(MAX_QUEUE_SIZE / bufferSize, 1));
}
public void start() throws IOException {
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 6f02088cc..fba01a561 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 @@ -77,7 +77,8 @@ public class MultiplexBluetoothTransport extends MultiplexBaseTransport { Handler timeOutHandler; Runnable socketRunnable; boolean keepSocketAlive = true; - + BluetoothDevice connectedDevice; + /** * Constructor. Prepares a new BluetoothChat session. * @@ -113,6 +114,14 @@ public class MultiplexBluetoothTransport extends MultiplexBaseTransport { } /** + * A method to retrieve the currently connected bluetooth device + * @return the connected bluetooth device if connected, null otherwise. + */ + public BluetoothDevice getConnectedDevice(){ + return connectedDevice; + } + + /** * Start the chat service. Specifically start AcceptThread to begin a * session in listening (server) mode. Called by the Activity onResume() */ @@ -225,6 +234,7 @@ public class MultiplexBluetoothTransport extends MultiplexBaseTransport { //Store a static name of the device that is connected. if (device != null) { + connectedDevice = device; connectedDeviceName = device.getName(); connectedDeviceAddress = device.getAddress(); if (connectedDeviceAddress != null) { 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 605200ce5..c86cb6d94 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 @@ -275,9 +275,16 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver { DebugTool.logInfo(TAG, ": This app's package: " + myPackage); DebugTool.logInfo(TAG, ": Router service app's package: " + routerServicePackage); if (myPackage != null && myPackage.equalsIgnoreCase(routerServicePackage)) { - SdlDeviceListener sdlDeviceListener = getSdlDeviceListener(context, device); - if (!sdlDeviceListener.isRunning()) { - sdlDeviceListener.start(); + //If the device is not null the listener should start as well as the + //case where this app was installed after BT connected and is the + //only SDL app installed on the device. (Rare corner case) + if(device != null || sdlAppInfoList.size() == 1) { + SdlDeviceListener sdlDeviceListener = getSdlDeviceListener(context, device); + if (!sdlDeviceListener.isRunning()) { + sdlDeviceListener.start(); + } + } else { + DebugTool.logInfo(TAG, "Not starting device listener, bluetooth device is null and other SDL apps installed."); } } else { DebugTool.logInfo(TAG, ": Not the app to start the router service nor device listener"); 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 8dbbe1eef..a1392aace 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 @@ -119,8 +119,7 @@ public class TransportManager extends TransportManagerBase { transport.removeSession(sessionId); transport.stop(); } else if (legacyBluetoothTransport != null) { - legacyBluetoothTransport.stop(); - legacyBluetoothTransport = null; + exitLegacyMode(null); } } @@ -426,25 +425,32 @@ public class TransportManager extends TransportManagerBase { } } + /** + * A synchronized method to exit out of legacy mode + * @param info if this is null, the transport listener callback will not be called + */ @Override synchronized void exitLegacyMode(String info) { - TransportRecord legacyTransportRecord = null; - if (legacyBluetoothTransport != null) { - legacyTransportRecord = legacyBluetoothTransport.getTransportRecord(); + if (legacyBluetoothTransport != null) { //If this is null, there is no reason to proceed + TransportRecord legacyTransportRecord = legacyBluetoothTransport.getTransportRecord(); legacyBluetoothTransport.stop(); legacyBluetoothTransport = null; - } - legacyBluetoothHandler = null; - synchronized (TRANSPORT_STATUS_LOCK) { - TransportManager.this.transportStatus.clear(); - } - if (contextWeakReference != null) { - try { - contextWeakReference.get().unregisterReceiver(legacyDisconnectReceiver); - } catch (Exception e) { + + legacyBluetoothHandler = null; + synchronized (TRANSPORT_STATUS_LOCK) { + TransportManager.this.transportStatus.clear(); + } + if (contextWeakReference != null) { + try { + contextWeakReference.get().unregisterReceiver(legacyDisconnectReceiver); + } catch (Exception e) { + DebugTool.logError(TAG, "Error attempting to unregister legacy mode receiver", e); + } + } + if (transportListener != null && info != null) { + transportListener.onTransportDisconnected(info, legacyTransportRecord, null); } } - transportListener.onTransportDisconnected(info, legacyTransportRecord, null); } 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 251eb130f..23cdd9857 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 @@ -61,7 +61,7 @@ public class SdlDeviceListener { private final WeakReference<Context> contextWeakReference; private final Callback callback; - private final BluetoothDevice connectedDevice; + private BluetoothDevice connectedDevice; private MultiplexBluetoothTransport bluetoothTransport; private TransportHandler bluetoothHandler; private Handler timeoutHandler; @@ -87,24 +87,21 @@ public class SdlDeviceListener { public void start() { if (connectedDevice == null) { DebugTool.logInfo(TAG, ": No supplied bluetooth device"); - if (callback != null) { - callback.onTransportError(null); - } - return; - } - - if (hasSDLConnected(contextWeakReference.get(), connectedDevice.getAddress())) { + } else if (hasSDLConnected(contextWeakReference.get(), connectedDevice.getAddress())) { DebugTool.logInfo(TAG, ": Confirmed SDL device, should start router service"); //This device has connected to SDL previously, it is ok to start the RS right now callback.onTransportConnected(contextWeakReference.get(), connectedDevice); return; } + synchronized (RUNNING_LOCK) { isRunning = true; // set timeout = if first time seeing BT device, 30s, if not 15s - int timeout = isFirstStatusCheck(connectedDevice.getAddress()) ? 30000 : 15000; + int timeout = connectedDevice != null && isFirstStatusCheck(connectedDevice.getAddress()) ? 30000 : 15000; //Set our preference as false for this device for now - setSDLConnectedStatus(contextWeakReference.get(), connectedDevice.getAddress(), false); + if(connectedDevice != null) { + setSDLConnectedStatus(contextWeakReference.get(), connectedDevice.getAddress(), false); + } bluetoothHandler = new TransportHandler(this); bluetoothTransport = new MultiplexBluetoothTransport(bluetoothHandler); bluetoothTransport.start(); @@ -155,6 +152,9 @@ public class SdlDeviceListener { case SdlRouterService.MESSAGE_STATE_CHANGE: switch (msg.arg1) { case MultiplexBaseTransport.STATE_CONNECTED: + 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) { diff --git a/base/src/main/java/com/smartdevicelink/exception/SdlException.java b/base/src/main/java/com/smartdevicelink/exception/SdlException.java index e55c80334..e6afa1c88 100644 --- a/base/src/main/java/com/smartdevicelink/exception/SdlException.java +++ b/base/src/main/java/com/smartdevicelink/exception/SdlException.java @@ -71,7 +71,6 @@ public class SdlException extends Exception { }
if (detail != null) {
ret += "\nnested: " + detail.toString();
- detail.printStackTrace();
}
return ret;
}
diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java index 24b0d8455..db775e10e 100644 --- a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java @@ -205,7 +205,7 @@ abstract class BaseSdlManager { try { DebugTool.logInfo(TAG, response.serializeJSON().toString()); } catch (JSONException e) { - e.printStackTrace(); + DebugTool.logError(TAG, "Error attempting to serialize ChangeRegistrationResponse", e); } // go through and change sdlManager properties that were changed via the LCU update 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 d0b72eb1f..9412fb122 100644 --- a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java @@ -271,11 +271,17 @@ abstract class BaseFileManager extends BaseSubManager { */ public boolean hasUploadedFile(@NonNull SdlFile file) { // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly. - if (file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName())) { - // If it's a persistent file, the bug won't present itself; just check if it's on the remote system - return true; - } else if (!file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName()) && uploadedEphemeralFileNames.contains(file.getName())) { - // If it's an ephemeral file, the bug will present itself; check that it's a remote file AND that we've uploaded it this session + Version rpcVersion = new Version(internalInterface.getSdlMsgVersion()); + if (new Version(4, 4, 0).isNewerThan(rpcVersion) == 1) { + if (file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName())) { + // HAX: If it's a persistent file, the bug won't present itself; just check if it's on the remote system + return true; + } else if (!file.isPersistent() && mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName()) && uploadedEphemeralFileNames.contains(file.getName())) { + // HAX: If it's an ephemeral file, the bug will present itself; check that it's a remote file AND that we've uploaded it this session + return true; + } + } else if (mutableRemoteFileNames != null && mutableRemoteFileNames.contains(file.getName())) { + // If not connected to a system where the bug presents itself, we can trust the `remoteFileNames` return true; } return false; 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 820311a58..4a9b785c6 100644 --- a/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java +++ b/base/src/main/java/com/smartdevicelink/managers/file/UploadFileOperation.java @@ -43,6 +43,7 @@ import com.smartdevicelink.proxy.RPCResponse; 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 java.io.IOException; import java.io.InputStream; @@ -221,7 +222,7 @@ class UploadFileOperation extends Task { try { this.inputStream.close(); } catch (IOException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to close input stream", e); } } @@ -330,7 +331,7 @@ class UploadFileOperation extends Task { try { bytesRead = inputStream.read(buffer, 0, size); } catch (IOException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to read from input stream", e); } if (bytesRead > 0) { @@ -365,7 +366,7 @@ class UploadFileOperation extends Task { try { size = inputStream.available(); } catch (IOException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error trying to get input stream size", e); } } return size; 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 57cf59ff8..9cf72f7cd 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java @@ -52,6 +52,7 @@ import com.smartdevicelink.proxy.RPCMessage; import com.smartdevicelink.proxy.RPCNotification; import com.smartdevicelink.proxy.RPCRequest; import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.rpc.DisplayCapabilities; import com.smartdevicelink.proxy.rpc.GenericResponse; import com.smartdevicelink.proxy.rpc.OnAppInterfaceUnregistered; import com.smartdevicelink.proxy.rpc.OnButtonEvent; @@ -61,6 +62,8 @@ import com.smartdevicelink.proxy.rpc.OnSystemRequest; import com.smartdevicelink.proxy.rpc.RegisterAppInterface; import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; import com.smartdevicelink.proxy.rpc.SdlMsgVersion; +import com.smartdevicelink.proxy.rpc.SetDisplayLayout; +import com.smartdevicelink.proxy.rpc.SetDisplayLayoutResponse; import com.smartdevicelink.proxy.rpc.SubscribeButton; import com.smartdevicelink.proxy.rpc.SystemRequest; import com.smartdevicelink.proxy.rpc.TTSChunk; @@ -73,6 +76,7 @@ import com.smartdevicelink.proxy.rpc.enums.ButtonName; import com.smartdevicelink.proxy.rpc.enums.FileType; import com.smartdevicelink.proxy.rpc.enums.HMILevel; import com.smartdevicelink.proxy.rpc.enums.Language; +import com.smartdevicelink.proxy.rpc.enums.PredefinedLayout; import com.smartdevicelink.proxy.rpc.enums.RequestType; import com.smartdevicelink.proxy.rpc.enums.Result; import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; @@ -132,6 +136,8 @@ abstract class BaseLifecycleManager { BaseTransportConfig _transportConfig; private Taskmaster taskmaster; private boolean didCheckSystemInfo = false; + String lastDisplayLayoutRequestTemplate; + DisplayCapabilities initialMediaCapabilities; BaseLifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener) { this.appConfig = appConfig; @@ -146,7 +152,7 @@ abstract class BaseLifecycleManager { try { session.startSession(); } catch (SdlException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to start session", e); } } @@ -384,6 +390,7 @@ abstract class BaseLifecycleManager { msg.setCorrelationID(UNREGISTER_APP_INTERFACE_CORRELATION_ID); sendRPCMessagePrivate(msg, true); clean(); + onClose("RPC spec version not supported: " + rpcSpecVersion.toString(), null, SdlDisconnectedReason.MINIMUM_RPC_VERSION_HIGHER_THAN_SUPPORTED); return; } if (!didCheckSystemInfo && lifecycleListener != null) { @@ -399,12 +406,17 @@ abstract class BaseLifecycleManager { msg.setCorrelationID(UNREGISTER_APP_INTERFACE_CORRELATION_ID); sendRPCMessagePrivate(msg, true); clean(); + onClose("System not supported", null, SdlDisconnectedReason.DEFAULT); return; } } //If the vehicle is acceptable and this is the first check, init security lib setSecurityLibraryIfAvailable(vehicleType); } + // HAX: Issue #1690, Ford Sync bug returning incorrect display capabilities (https://github.com/smartdevicelink/sdl_java_suite/issues/1690). Store the initial capabilities if we are a media app so that we can use them in the future. + if (appConfig.appType.contains(AppHMIType.MEDIA)) { + initialMediaCapabilities = raiResponse.getDisplayCapabilities(); + } systemCapabilityManager.parseRAIResponse(raiResponse); break; case ON_HMI_STATUS: @@ -481,6 +493,7 @@ abstract class BaseLifecycleManager { if (!onAppInterfaceUnregistered.getReason().equals(AppInterfaceUnregisteredReason.LANGUAGE_CHANGE)) { DebugTool.logInfo(TAG, "on app interface unregistered"); clean(); + onClose("OnAppInterfaceUnregistered received from head unit", null, SdlDisconnectedReason.APP_INTERFACE_UNREG); } else { DebugTool.logInfo(TAG, "re-registering for language change"); cycle(SdlDisconnectedReason.LANGUAGE_CHANGE); @@ -489,6 +502,7 @@ abstract class BaseLifecycleManager { case UNREGISTER_APP_INTERFACE: DebugTool.logInfo(TAG, "unregister app interface"); clean(); + onClose("UnregisterAppInterface response received from head unit", null, SdlDisconnectedReason.APP_INTERFACE_UNREG); break; } } @@ -825,6 +839,10 @@ abstract class BaseLifecycleManager { addOnRPCResponseListener(listener, corrId); } } + // HAX: Issue #1690, Ford Sync bug returning incorrect display capabilities (https://github.com/smartdevicelink/sdl_java_suite/issues/1690). Save the next desired layout type to the update capabilities when the SetDisplayLayout response is received + if (FunctionID.SET_DISPLAY_LAYOUT.toString().equals(message.getFunctionName())) { + lastDisplayLayoutRequestTemplate = ((SetDisplayLayout)message).getDisplayLayout(); + } } else if (RPCMessage.KEY_RESPONSE.equals(message.getMessageType())) { // Response Specifics RPCResponse response = (RPCResponse) message; pm.setRPCType((byte) 0x01); @@ -851,7 +869,17 @@ abstract class BaseLifecycleManager { session.sendMessage(pm); } catch (OutOfMemoryError e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to send RPC message.", e); + } + } + + // HAX: Issue #1690, Ford Sync bug returning incorrect display capabilities (https://github.com/smartdevicelink/sdl_java_suite/issues/1690). Use the initial capabilities from RAIR instead of the incorrect ones that are included in SetDisplayLayoutResponse. + void fixIncorrectDisplayCapabilities(RPCMessage rpc) { + if (RPCMessage.KEY_RESPONSE.equals(rpc.getMessageType()) && rpc.getFunctionName().equals(FunctionID.SET_DISPLAY_LAYOUT.toString()) && + initialMediaCapabilities != null && PredefinedLayout.MEDIA.toString().equals(lastDisplayLayoutRequestTemplate)) { + + SetDisplayLayoutResponse setDisplayLayoutResponse = (SetDisplayLayoutResponse) rpc; + setDisplayLayoutResponse.setDisplayCapabilities(initialMediaCapabilities); } } @@ -875,6 +903,8 @@ abstract class BaseLifecycleManager { rpc.format(rpcSpecVersion, true); + fixIncorrectDisplayCapabilities(rpc); + BaseLifecycleManager.this.onRPCReceived(rpc); if (RPCMessage.KEY_RESPONSE.equals(messageType)) { @@ -910,6 +940,7 @@ abstract class BaseLifecycleManager { DebugTool.logWarning(TAG, String.format("Disconnecting from head unit, the configured minimum protocol version %s is greater than the supported protocol version %s", minimumProtocolVersion, getProtocolVersion())); session.endService(SessionType.RPC); clean(); + onClose("Protocol version not supported: " + version, null, SdlDisconnectedReason.MINIMUM_PROTOCOL_VERSION_HIGHER_THAN_SUPPORTED); return; } @@ -920,6 +951,7 @@ abstract class BaseLifecycleManager { DebugTool.logWarning(TAG, "Disconnecting from head unit, the system info was not accepted."); session.endService(SessionType.RPC); clean(); + onClose("System not supported", null, SdlDisconnectedReason.DEFAULT); return; } //If the vehicle is acceptable, init security lib @@ -1197,6 +1229,8 @@ abstract class BaseLifecycleManager { void clean() { firstTimeFull = true; currentHMIStatus = null; + lastDisplayLayoutRequestTemplate = null; + initialMediaCapabilities = null; if (rpcListeners != null) { rpcListeners.clear(); } 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 cfb2176df..54a343043 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseSystemCapabilityManager.java @@ -133,8 +133,16 @@ abstract class BaseSystemCapabilityManager { return Collections.singletonList(displayCapability); } + // 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; + } + } // copy all available display capabilities - defaultWindowCapability.setTemplatesAvailable(display.getTemplatesAvailable()); + defaultWindowCapability.setTemplatesAvailable(templatesAvailable); defaultWindowCapability.setNumCustomPresetsAvailable(display.getNumCustomPresetsAvailable()); defaultWindowCapability.setTextFields(display.getTextFields()); defaultWindowCapability.setImageFields(display.getImageFields()); 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 746d49865..893909718 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,6 +42,7 @@ 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; @@ -54,6 +55,7 @@ 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; @@ -61,11 +63,13 @@ 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; @@ -540,7 +544,7 @@ abstract class BaseChoiceSetManager extends BaseSubManager { * 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(LinkedHashSet<ChoiceCell> choices) { + void addUniqueNamesToCells(List<ChoiceCell> choices) { HashMap<String, Integer> dictCounter = new HashMap<>(); for (ChoiceCell cell : choices) { @@ -556,16 +560,80 @@ abstract class BaseChoiceSetManager extends BaseSubManager { } } + 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; + } + + List<ChoiceCell> clone = new ArrayList<>(); + for (ChoiceCell choiceCell : originalList) { + clone.add(choiceCell.clone()); + } + return clone; + } + private LinkedHashSet<ChoiceCell> getChoicesToBeUploadedWithArray(List<ChoiceCell> choices) { - LinkedHashSet<ChoiceCell> choiceSet = new LinkedHashSet<>(choices); - // 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. + // 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))) { - addUniqueNamesToCells(choiceSet); + // 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); } - choiceSet.removeAll(preloadedChoices); - return choiceSet; + 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) { @@ -672,6 +740,14 @@ 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(); @@ -695,9 +771,8 @@ abstract class BaseChoiceSetManager extends BaseSubManager { int choiceCellWithVoiceCommandCount = 0; for (ChoiceCell cell : choices) { - uniqueChoiceCells.add(cell); - + // Not using cloned cell here because we set the clone's VoiceCommands to null for visual check only if (cell.getVoiceCommands() != null) { uniqueVoiceCommands.addAll(cell.getVoiceCommands()); choiceCellWithVoiceCommandCount += 1; 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 f39423b1a..d19bae619 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 @@ -176,11 +176,9 @@ abstract class BaseMenuManager extends BaseSubManager { // Create a deep copy of the list so future changes by developers don't affect the algorithm logic List<MenuCell> clonedCells = cloneMenuCellsList(cells); - // 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))) { - addUniqueNamesToCells(clonedCells); + // 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)) { @@ -194,6 +192,23 @@ abstract class BaseMenuManager extends BaseSubManager { } 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) { @@ -205,11 +220,6 @@ abstract class BaseMenuManager extends BaseSubManager { menuCells.addAll(clonedCells); } - // Check for cell lists with completely duplicate information, or any duplicate voiceCommands and return if it fails (logs are in the called method). - if (!menuCellsAreUnique(menuCells, new ArrayList<String>())) { - return; - } - // Upload the Artworks List<SdlArtwork> artworksToBeUploaded = findAllArtworksToBeUploadedFromCells(menuCells); if (artworksToBeUploaded.size() > 0 && fileManager.get() != null) { @@ -1041,7 +1051,7 @@ abstract class BaseMenuManager extends BaseSubManager { command.setVrCommands(null); } command.setCmdIcon((cell.getIcon() != null && shouldHaveArtwork) ? cell.getIcon().getImageRPC() : null); - command.setSecondaryImage((cell.getSecondaryArtwork() != null && shouldHaveArtwork && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork()))) ? cell.getSecondaryArtwork().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; } @@ -1065,7 +1075,7 @@ abstract class BaseMenuManager extends BaseSubManager { subMenu.setMenuLayout(menuConfiguration.getSubMenuLayout()); } subMenu.setMenuIcon((shouldHaveArtwork && (cell.getIcon() != null && cell.getIcon().getImageRPC() != null)) ? cell.getIcon().getImageRPC() : null); - subMenu.setSecondaryImage((shouldHaveArtwork && !(fileManager.get() != null && fileManager.get().fileNeedsUpload(cell.getSecondaryArtwork())) && (cell.getSecondaryArtwork() != null && cell.getSecondaryArtwork().getImageRPC() != null)) ? cell.getSecondaryArtwork().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; } @@ -1275,7 +1285,7 @@ abstract class BaseMenuManager extends BaseSubManager { try { DebugTool.logInfo(TAG, "Main Menu response: " + response.serializeJSON().toString()); } catch (JSONException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e); } } else { DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo()); @@ -1313,7 +1323,7 @@ abstract class BaseMenuManager extends BaseSubManager { try { DebugTool.logInfo(TAG, "Sub Menu response: " + response.serializeJSON().toString()); } catch (JSONException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e); } } else { DebugTool.logError(TAG, "Failed to send sub menu commands: " + response.getInfo()); @@ -1365,7 +1375,7 @@ abstract class BaseMenuManager extends BaseSubManager { try { DebugTool.logInfo(TAG, "Dynamic Sub Menu response: " + response.serializeJSON().toString()); } catch (JSONException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error attempting to serialize JSON of RPC response", e); } } else { DebugTool.logError(TAG, "Result: " + response.getResultCode() + " Info: " + response.getInfo()); @@ -1441,7 +1451,7 @@ abstract class BaseMenuManager extends BaseSubManager { return clone; } - private void addUniqueNamesToCells(List<MenuCell> cells) { + private void addUniqueNamesToCellsWithDuplicatePrimaryText(List<MenuCell> cells) { HashMap<String, Integer> dictCounter = new HashMap<>(); for (MenuCell cell : cells) { @@ -1456,11 +1466,80 @@ abstract class BaseMenuManager extends BaseSubManager { } if (cell.getSubCells() != null && cell.getSubCells().size() > 0) { - addUniqueNamesToCells(cell.getSubCells()); + addUniqueNamesToCellsWithDuplicatePrimaryText(cell.getSubCells()); } } } + 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()); + } + + } + + + } + + List<MenuCell> removeUnusedProperties(List<MenuCell> menuCells) { + if (menuCells == null) { + return null; + } + 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; + } /** * Check for cell lists with completely duplicate information, or any duplicate voiceCommands diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java index 3e5fa6dbe..9c5c39c21 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/BaseVoiceCommandManager.java @@ -51,6 +51,7 @@ import com.smartdevicelink.util.DebugTool; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; abstract class BaseVoiceCommandManager extends BaseSubManager { @@ -131,16 +132,40 @@ abstract class BaseVoiceCommandManager extends BaseSubManager { public void setVoiceCommands(List<VoiceCommand> voiceCommands) { // we actually need voice commands to set. - if (voiceCommands == null || voiceCommands.equals(this.voiceCommands)) { - DebugTool.logInfo(TAG, "Voice commands list was null or matches the current voice commands"); + if (voiceCommands == null) { + DebugTool.logInfo(TAG, "Voice commands list was null"); return; } - updateIdsOnVoiceCommands(voiceCommands); - this.voiceCommands = new ArrayList<>(voiceCommands); + // Clone voice commands + this.voiceCommands = new ArrayList<>(); + for (VoiceCommand voiceCommand : voiceCommands) { + if (voiceCommand == null) { + continue; + } + this.voiceCommands.add(voiceCommand.clone()); + } + + List<VoiceCommand> validatedVoiceCommands = removeEmptyVoiceCommands(this.voiceCommands); + + if (validatedVoiceCommands.size() == 0 && voiceCommands.size() > 0) { + DebugTool.logError(TAG, "New voice commands are invalid, skipping..."); + this.voiceCommands = null; + return; + } + + if (!isVoiceCommandsUnique(validatedVoiceCommands)) { + DebugTool.logError(TAG, "Not all voice command strings are unique across all voice commands. Voice commands will not be set."); + this.voiceCommands = null; + return; + } + + this.voiceCommands = validatedVoiceCommands; + + updateIdsOnVoiceCommands(this.voiceCommands); cleanTransactionQueue(); - updateOperation = new VoiceCommandUpdateOperation(internalInterface, currentVoiceCommands, voiceCommands, new VoiceCommandUpdateOperation.VoiceCommandChangesListener() { + updateOperation = new VoiceCommandUpdateOperation(internalInterface, currentVoiceCommands, this.voiceCommands, new VoiceCommandUpdateOperation.VoiceCommandChangesListener() { @Override public void updateVoiceCommands(List<VoiceCommand> newCurrentVoiceCommands, HashMap<RPCRequest, String> errorObject) { DebugTool.logInfo(TAG, "The updated list of VoiceCommands: " + newCurrentVoiceCommands); @@ -176,7 +201,7 @@ abstract class BaseVoiceCommandManager extends BaseSubManager { continue; } VoiceCommandUpdateOperation vcOperation = (VoiceCommandUpdateOperation) operation; - vcOperation.oldVoiceCommands = newCurrentVoiceCommands; + vcOperation.setOldVoiceCommands(newCurrentVoiceCommands); } } @@ -188,6 +213,30 @@ abstract class BaseVoiceCommandManager extends BaseSubManager { } } + List<VoiceCommand> removeEmptyVoiceCommands(List<VoiceCommand> voiceCommands) { + List<VoiceCommand> validatedVoiceCommands = new ArrayList<>(); + for (VoiceCommand voiceCommand : voiceCommands) { + if (voiceCommand == null) { + continue; + } + List<String> voiceCommandStrings = new ArrayList<>(); + for (String voiceCommandString : voiceCommand.getVoiceCommands()) { + if (voiceCommandString == null) { + continue; + } + String trimmedString = voiceCommandString.trim(); + if (trimmedString.length() > 0) { + voiceCommandStrings.add(trimmedString); + } + } + if (voiceCommandStrings.size() > 0) { + voiceCommand.setVoiceCommands(voiceCommandStrings); + validatedVoiceCommands.add(voiceCommand); + } + } + return validatedVoiceCommands; + } + // LISTENERS private void addListeners() { @@ -211,8 +260,8 @@ abstract class BaseVoiceCommandManager extends BaseSubManager { @Override public void onNotified(RPCNotification notification) { OnCommand onCommand = (OnCommand) notification; - if (voiceCommands != null && voiceCommands.size() > 0) { - for (VoiceCommand command : voiceCommands) { + if (currentVoiceCommands != null && currentVoiceCommands.size() > 0) { + for (VoiceCommand command : currentVoiceCommands) { if (onCommand.getCmdID() == command.getCommandId()) { if (command.getVoiceCommandSelectionListener() != null) { command.getVoiceCommandSelectionListener().onVoiceCommandSelected(); @@ -225,4 +274,24 @@ abstract class BaseVoiceCommandManager extends BaseSubManager { }; internalInterface.addOnRPCNotificationListener(FunctionID.ON_COMMAND, commandListener); } + + /** + * Boolean method that checks to see if all VoiceCommands in a given list are unique + * + * @param voiceCommands - list of VoiceCommands + * @return - true if VoiceCommands are unique, false if not + */ + private boolean isVoiceCommandsUnique(List<VoiceCommand> voiceCommands) { + HashSet<String> voiceCommandHashSet = new HashSet<>(); + int voiceCommandCount = 0; + + for (VoiceCommand voiceCommand : voiceCommands) { + if (voiceCommand == null) { + continue; + } + voiceCommandHashSet.addAll(voiceCommand.getVoiceCommands()); + voiceCommandCount += voiceCommand.getVoiceCommands().size(); + } + return (voiceCommandHashSet.size() == voiceCommandCount); + } } diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java index 15d66b09e..b171a702d 100644 --- a/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java +++ b/base/src/main/java/com/smartdevicelink/managers/screen/menu/VoiceCommand.java @@ -36,9 +36,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.smartdevicelink.util.DebugTool; + +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -public class VoiceCommand { +public class VoiceCommand implements Cloneable { /** * The strings the user can say to activate this voice command @@ -77,7 +81,7 @@ public class VoiceCommand { * @param voiceCommands - the list of commands to send to the head unit */ public void setVoiceCommands(@NonNull List<String> voiceCommands) { - this.voiceCommands = voiceCommands; + this.voiceCommands = new ArrayList<>(removeDuplicateStrings(voiceCommands)); } /** @@ -126,6 +130,10 @@ public class VoiceCommand { return commandId; } + private HashSet<String> removeDuplicateStrings(List<String> voiceCommands) { + return new HashSet<>(voiceCommands); + } + /** * Used to compile hashcode for VoiceCommand for use to compare in equals method * @@ -134,9 +142,8 @@ public class VoiceCommand { @Override public int hashCode() { int result = 1; - result += Integer.rotateLeft(getCommandId(), 1); for (int i = 0; i < this.getVoiceCommands().size(); i++) { - result += ((getVoiceCommands().get(i) == null) ? 0 : Integer.rotateLeft(getVoiceCommands().get(i).hashCode(), i + 2)); + result += ((getVoiceCommands().get(i) == null) ? 0 : Integer.rotateLeft(getVoiceCommands().get(i).hashCode(), i + 1)); } return result; } @@ -152,9 +159,27 @@ public class VoiceCommand { if (o == null) return false; // if this is the same memory address, it's the same if (this == o) return true; - // if this is not an instance of SoftButtonObject, not the same + // if this is not an instance of VoiceCommand, not the same if (!(o instanceof VoiceCommand)) return false; // 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 VoiceCommand clone() { + try { + VoiceCommand clone = (VoiceCommand) super.clone(); + return clone; + } catch (CloneNotSupportedException e) { + if (DebugTool.isDebugEnabled()) { + throw new RuntimeException("Clone not supported by super class"); + } + } + return null; + } } 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 b755abd7d..59caffbdb 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 @@ -22,7 +22,7 @@ class VoiceCommandUpdateOperation extends Task { private List<VoiceCommand> pendingVoiceCommands; private List<DeleteCommand> deleteVoiceCommands; private List<AddCommand> addCommandsToSend; - private VoiceCommandChangesListener voiceCommandListener; + VoiceCommandChangesListener voiceCommandListener; private List<VoiceCommand> currentVoiceCommands; private HashMap<RPCRequest, String> errorObject; @@ -53,6 +53,15 @@ class VoiceCommandUpdateOperation extends Task { onFinished(); return; } + // Check if a voiceCommand has already been uploaded and update its VoiceCommandSelectionListener to + // prevent calling the wrong listener in a case where a voice command was uploaded and then its voiceCommandSelectionListener was updated in another upload. + if (pendingVoiceCommands != null && pendingVoiceCommands.size() > 0) { + for (VoiceCommand voiceCommand : pendingVoiceCommands) { + if (currentVoiceCommands.contains(voiceCommand)) { + currentVoiceCommands.get(currentVoiceCommands.indexOf(voiceCommand)).setVoiceCommandSelectionListener(voiceCommand.getVoiceCommandSelectionListener()); + } + } + } sendDeleteCurrentVoiceCommands(new CompletionListener() { @Override @@ -82,14 +91,23 @@ class VoiceCommandUpdateOperation extends Task { private void sendDeleteCurrentVoiceCommands(final CompletionListener listener) { - if (oldVoiceCommands == null || oldVoiceCommands.isEmpty()) { + if (oldVoiceCommands == null || oldVoiceCommands.size() == 0) { if (listener != null) { listener.onComplete(true); } return; } - deleteVoiceCommands = deleteCommandsForVoiceCommands(oldVoiceCommands); + List<VoiceCommand> voiceCommandsToDelete = voiceCommandsInListNotInSecondList(oldVoiceCommands, pendingVoiceCommands); + + if (voiceCommandsToDelete.size() == 0) { + if (listener != null) { + listener.onComplete(true); + } + return; + } + + deleteVoiceCommands = deleteCommandsForVoiceCommands(voiceCommandsToDelete); internalInterface.get().sendRPCs(deleteVoiceCommands, new OnMultipleRequestListener() { @Override @@ -156,14 +174,16 @@ class VoiceCommandUpdateOperation extends Task { private void sendCurrentVoiceCommands(final CompletionListener listener) { - if (pendingVoiceCommands == null || pendingVoiceCommands.size() == 0) { + List<VoiceCommand> voiceCommandsToAdd = voiceCommandsInListNotInSecondList(pendingVoiceCommands, oldVoiceCommands); + + if (voiceCommandsToAdd.size() == 0) { if (listener != null) { - listener.onComplete(true); // no voice commands to send doesnt mean that its an error + listener.onComplete(true); // no voice commands to send doesn't mean that its an error } return; } - addCommandsToSend = addCommandsForVoiceCommands(pendingVoiceCommands); + addCommandsToSend = addCommandsForVoiceCommands(voiceCommandsToAdd); internalInterface.get().sendRPCs(addCommandsToSend, new OnMultipleRequestListener() { @Override @@ -207,6 +227,20 @@ class VoiceCommandUpdateOperation extends Task { }); } + List<VoiceCommand> voiceCommandsInListNotInSecondList(List<VoiceCommand> firstList, List<VoiceCommand> secondList) { + if (secondList == null || secondList.size() == 0) { + return firstList; + } + List<VoiceCommand> differencesList = new ArrayList<>(firstList); + differencesList.removeAll(secondList); + return differencesList; + } + + public void setOldVoiceCommands(List<VoiceCommand> oldVoiceCommands) { + this.oldVoiceCommands = oldVoiceCommands; + this.currentVoiceCommands = new ArrayList<>(oldVoiceCommands); + } + // Create AddCommand List List<AddCommand> addCommandsForVoiceCommands(List<VoiceCommand> voiceCommands) { diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java index c0dd495dd..1db39f2e9 100644 --- a/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java +++ b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java @@ -50,6 +50,8 @@ import java.util.List; import java.util.Set;
public class RPCStruct implements Cloneable {
+ private static final String TAG = "RPCStruct";
+
public static final String KEY_BULK_DATA = "bulkData";
public static final String KEY_PROTECTED = "protected";
@@ -268,7 +270,7 @@ public class RPCStruct implements Cloneable { return customObject;
} catch (Exception e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Error attempting to format an object from a Hashtable", e);
}
} else if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
@@ -300,7 +302,7 @@ public class RPCStruct implements Cloneable { }
newList.add(customObject);
} catch (Exception e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Error attempting to format object from list of Hashtables", e);
return null;
}
}
@@ -334,15 +336,15 @@ public class RPCStruct implements Cloneable { try {
valueForString = tClass.getDeclaredMethod("valueForString", String.class);
} catch (NoSuchMethodException e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Error attempting to find valueForString method in class", e);
}
if (valueForString != null) {
try {
return valueForString.invoke(null, (String) s);
} catch (IllegalAccessException e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Illegal access while using reflection to get enum from string", e);
} catch (InvocationTargetException e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Error attempting to use method from reflection to get enum from string", e);
}
}
return null;
diff --git a/base/src/main/java/com/smartdevicelink/proxy/rpc/OnSystemRequest.java b/base/src/main/java/com/smartdevicelink/proxy/rpc/OnSystemRequest.java index c07c4e2e0..ebe24268a 100644 --- a/base/src/main/java/com/smartdevicelink/proxy/rpc/OnSystemRequest.java +++ b/base/src/main/java/com/smartdevicelink/proxy/rpc/OnSystemRequest.java @@ -179,11 +179,9 @@ public class OnSystemRequest extends RPCNotification { tempBody = getBody(httpJson);
tempHeaders = getHeaders(httpJson);
} catch (JSONException e) {
- DebugTool.logError(TAG, "HTTPRequest in bulk data was malformed.");
- e.printStackTrace();
+ DebugTool.logError(TAG, "HTTPRequest in bulk data was malformed.", e);
} catch (NullPointerException e) {
- DebugTool.logError(TAG, "Invalid HTTPRequest object in bulk data.");
- e.printStackTrace();
+ DebugTool.logError(TAG, "Invalid HTTPRequest object in bulk data.", e);
}
} else if (RequestType.HTTP.equals(this.getRequestType())) {
tempHeaders = new Headers();
@@ -209,8 +207,7 @@ public class OnSystemRequest extends RPCNotification { try {
result = httpJson.getString(KEY_BODY);
} catch (JSONException e) {
- DebugTool.logError(TAG, KEY_BODY + " key doesn't exist in bulk data.");
- e.printStackTrace();
+ DebugTool.logError(TAG, KEY_BODY + " key doesn't exist in bulk data.", e);
}
return result;
@@ -224,8 +221,7 @@ public class OnSystemRequest extends RPCNotification { Hashtable<String, Object> httpHeadersHash = JsonRPCMarshaller.deserializeJSONObject(httpHeadersJson);
result = new Headers(httpHeadersHash);
} catch (JSONException e) {
- DebugTool.logError(TAG, KEY_HEADERS + " key doesn't exist in bulk data.");
- e.printStackTrace();
+ DebugTool.logError(TAG, KEY_HEADERS + " key doesn't exist in bulk data.", e);
}
return result;
diff --git a/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java b/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java index ce8d900ca..4e46b3632 100644 --- a/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java +++ b/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java @@ -124,7 +124,7 @@ public class RTPH264Packetizer extends AbstractPacketizer implements IVideoStrea bufferSize = MAX_DATA_SIZE_FOR_ENCRYPTED_SERVICE; } - mOutputQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE / bufferSize); + mOutputQueue = new LinkedBlockingQueue<>(Math.max(MAX_QUEUE_SIZE / bufferSize, 1)); mNALUnitReader = new NALUnitReader(); mPayloadType = DEFAULT_RTP_PAYLOAD_TYPE; diff --git a/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java b/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java index 99510b061..dc8fd3bbc 100644 --- a/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java +++ b/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java @@ -32,15 +32,18 @@ package com.smartdevicelink.transport; import com.smartdevicelink.protocol.SdlPacket; +import com.smartdevicelink.util.DebugTool; import static com.smartdevicelink.protocol.SdlProtocol.V1_HEADER_SIZE; import static com.smartdevicelink.protocol.SdlProtocol.V1_V2_MTU_SIZE; +import static com.smartdevicelink.protocol.SdlProtocol.V2_HEADER_SIZE; +import static com.smartdevicelink.protocol.SdlProtocol.V3_V4_MTU_SIZE; public class SdlPsm { - //private static final String TAG = "Sdl PSM"; - //Each state represents the byte that should be incoming + private static final String TAG = "Sdl PSM"; + //Each state represents the byte that should be incoming public static final int START_STATE = 0x0; public static final int SERVICE_TYPE_STATE = 0x02; public static final int CONTROL_FRAME_INFO_STATE = 0x03; @@ -83,8 +86,13 @@ public class SdlPsm { } public boolean handleByte(byte data) { - //Log.trace(TAG, data + " = incoming"); - state = transitionOnInput(data, state); + try { + state = transitionOnInput(data, state); + } catch (Exception e) { + DebugTool.logError(TAG, "Exception thrown while parsing byte - " + data, e); + state = ERROR_STATE; + return false; + } return state != ERROR_STATE; } @@ -93,18 +101,11 @@ public class SdlPsm { switch (state) { case START_STATE: version = (rawByte & (byte) VERSION_MASK) >> 4; - //Log.trace(TAG, "Version: " + version); - if (version == 0) { //It should never be 0 - return ERROR_STATE; - } encrypted = (1 == ((rawByte & (byte) ENCRYPTION_MASK) >> 3)); - - frameType = rawByte & (byte) FRAME_TYPE_MASK; - //Log.trace(TAG, rawByte + " = Frame Type: " + frameType); - if ((version < 1 || version > 5) //These are known versions supported by this library. - && frameType != SdlPacket.FRAME_TYPE_CONTROL) { + if ((version < 1 || version > 5)) { + //These are known versions supported by this library. return ERROR_STATE; } @@ -116,7 +117,16 @@ public class SdlPsm { case SERVICE_TYPE_STATE: serviceType = (int) (rawByte & 0xFF); - return CONTROL_FRAME_INFO_STATE; + switch (serviceType) { + case 0x00: //SessionType.CONTROL: + case 0x07: //SessionType.RPC: + case 0x0A: //SessionType.PCM (Audio): + case 0x0B: //SessionType.NAV (Video): + case 0x0F: //SessionType.BULK (Hybrid): + return CONTROL_FRAME_INFO_STATE; + default: + return ERROR_STATE; + } case CONTROL_FRAME_INFO_STATE: controlFrameInfo = (int) (rawByte & 0xFF); @@ -203,19 +213,34 @@ public class SdlPsm { default: return ERROR_STATE; } - if (version == 1) { //Version 1 packets will not have message id's - if (dataLength == 0) { - return FINISHED_STATE; //We are done if we don't have any payload - } - if (dataLength <= V1_V2_MTU_SIZE - V1_HEADER_SIZE) { // sizes from protocol/WiProProtocol.java - payload = new byte[dataLength]; - } else { - return ERROR_STATE; - } - dumpSize = dataLength; - return DATA_PUMP_STATE; - } else { - return MESSAGE_1_STATE; + switch (version) { + case 1: + //Version 1 packets will not have message id's + if (dataLength == 0) { + return FINISHED_STATE; //We are done if we don't have any payload + } + if (dataLength <= V1_V2_MTU_SIZE - V1_HEADER_SIZE) { // sizes from SDL protocol + payload = new byte[dataLength]; + } else { + return ERROR_STATE; + } + dumpSize = dataLength; + return DATA_PUMP_STATE; + case 2: + if (dataLength <= V1_V2_MTU_SIZE - V2_HEADER_SIZE) { + return MESSAGE_1_STATE; + } else { + return ERROR_STATE; + } + case 3: + case 4: + if (dataLength <= V3_V4_MTU_SIZE - V2_HEADER_SIZE) { + return MESSAGE_1_STATE; + } else { + return ERROR_STATE; + } + default: + return MESSAGE_1_STATE; } case MESSAGE_1_STATE: diff --git a/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java b/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java index cd22abc04..39871b777 100644 --- a/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java +++ b/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java @@ -107,7 +107,7 @@ public class SiphonServer { try {
SiphonServer.closeServer();
} catch (IOException e) {
- e.printStackTrace();
+ DebugTool.logError(TAG,"Error while trying to close siphon server", e);
}
return m_listenPort;
diff --git a/base/src/main/java/com/smartdevicelink/util/FileUtls.java b/base/src/main/java/com/smartdevicelink/util/FileUtls.java index 449fc6ea9..747052415 100644 --- a/base/src/main/java/com/smartdevicelink/util/FileUtls.java +++ b/base/src/main/java/com/smartdevicelink/util/FileUtls.java @@ -74,7 +74,7 @@ public class FileUtls { try { return Files.readAllBytes(file.toPath()); } catch (IOException e) { - e.printStackTrace(); + DebugTool.logError(TAG,"Error trying to get file data", e); } } } diff --git a/javaEE/javaEE/gradle.properties b/javaEE/javaEE/gradle.properties index deba1f427..9dbf6adf2 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.1.1 +VERSION_NAME=5.2.0 POM_NAME=sdl_java_ee POM_PACKAGING=jar diff --git a/javaSE/javaSE/gradle.properties b/javaSE/javaSE/gradle.properties index 7b3651b0c..b8f580351 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.1.1 +VERSION_NAME=5.2.0 POM_NAME=sdl_java_se POM_PACKAGING=jar diff --git a/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java b/javaSE/javaSE/src/main/java/com/smartdevicelink/BuildConfig.java index 1e3915205..30aaeaed4 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.1.1"; + public static final String VERSION_NAME = "5.2.0"; }
\ No newline at end of file |