summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2018-05-01 16:05:19 -0400
committerGitHub <noreply@github.com>2018-05-01 16:05:19 -0400
commit2a51aa3b92dc8404bda2d3d62c7ba0ab5acc1466 (patch)
treeab07f4d9d64934611812424211875ba37475468c
parentd3b7508d80c2edfdddbda4d3004ad64bda1b87bd (diff)
parent1f132906f6e172b01dd4d55d3404c1b837a57cb4 (diff)
downloadsdl_ios-2a51aa3b92dc8404bda2d3d62c7ba0ab5acc1466.tar.gz
Merge pull request #930 from smartdevicelink/feature/issue_927_mobile_menu_manager
Menu Manager
-rw-r--r--SmartDeviceLink-iOS.podspec3
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj78
-rw-r--r--SmartDeviceLink.podspec3
-rw-r--r--SmartDeviceLink/SDLArtwork.m24
-rw-r--r--SmartDeviceLink/SDLAsynchronousRPCRequestOperation.h2
-rw-r--r--SmartDeviceLink/SDLAsynchronousRPCRequestOperation.m9
-rw-r--r--SmartDeviceLink/SDLConnectionManagerType.h4
-rw-r--r--SmartDeviceLink/SDLDisplayCapabilities+ShowManagerExtensions.m4
-rw-r--r--SmartDeviceLink/SDLError.h8
-rw-r--r--SmartDeviceLink/SDLError.m9
-rw-r--r--SmartDeviceLink/SDLErrorConstants.h4
-rw-r--r--SmartDeviceLink/SDLFile.m24
-rw-r--r--SmartDeviceLink/SDLLifecycleManager.m10
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m7
-rw-r--r--SmartDeviceLink/SDLManager.h28
-rw-r--r--SmartDeviceLink/SDLMenuCell.h51
-rw-r--r--SmartDeviceLink/SDLMenuCell.m58
-rw-r--r--SmartDeviceLink/SDLMenuManager.h34
-rw-r--r--SmartDeviceLink/SDLMenuManager.m429
-rw-r--r--SmartDeviceLink/SDLNotificationConstants.h27
-rw-r--r--SmartDeviceLink/SDLScreenManager.h5
-rw-r--r--SmartDeviceLink/SDLScreenManager.m22
-rw-r--r--SmartDeviceLink/SDLSoftButtonManager.m1
-rw-r--r--SmartDeviceLink/SDLVoiceCommand.h31
-rw-r--r--SmartDeviceLink/SDLVoiceCommand.m37
-rw-r--r--SmartDeviceLink/SDLVoiceCommandManager.h33
-rw-r--r--SmartDeviceLink/SDLVoiceCommandManager.m249
-rw-r--r--SmartDeviceLink/SmartDeviceLink.h6
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m47
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m314
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m44
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m123
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandSpec.m26
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m26
-rw-r--r--SmartDeviceLink_Example/Classes/ProxyManager.m124
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/car.imageset/Contents.json21
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/car.imageset/iconmonstr-car-1-64.pngbin0 -> 1381 bytes
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/Contents.json21
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/iconmonstr-text-23-64.pngbin0 -> 224 bytes
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/speak.imageset/Contents.json21
-rw-r--r--SmartDeviceLink_Example/Images.xcassets/speak.imageset/iconmonstr-speech-bubble-5-64.pngbin0 -> 1502 bytes
41 files changed, 1814 insertions, 153 deletions
diff --git a/SmartDeviceLink-iOS.podspec b/SmartDeviceLink-iOS.podspec
index d33e80b1d..0c6790dbb 100644
--- a/SmartDeviceLink-iOS.podspec
+++ b/SmartDeviceLink-iOS.podspec
@@ -157,6 +157,8 @@ ss.public_header_files = [
'SmartDeviceLink/SDLManager.h',
'SmartDeviceLink/SDLManagerDelegate.h',
'SmartDeviceLink/SDLMediaClockFormat.h',
+'SmartDeviceLink/SDLMenuCell.h',
+'SmartDeviceLink/SDLMenuManager.h',
'SmartDeviceLink/SDLMenuParams.h',
'SmartDeviceLink/SDLMetadataTags.h',
'SmartDeviceLink/SDLMetadataType.h',
@@ -326,6 +328,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLVideoStreamingCodec.h',
'SmartDeviceLink/SDLVideoStreamingFormat.h',
'SmartDeviceLink/SDLVideoStreamingProtocol.h',
+'SmartDeviceLink/SDLVoiceCommand.h',
'SmartDeviceLink/SDLVrCapabilities.h',
'SmartDeviceLink/SDLVrHelpItem.h',
'SmartDeviceLink/SDLWarningLightStatus.h',
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index b20d68c77..f971257d7 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -348,6 +348,12 @@
5D293AFE1FE078A9000CBD7E /* SDLCarWindowViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D293AFC1FE078A9000CBD7E /* SDLCarWindowViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
5D293AFF1FE078A9000CBD7E /* SDLCarWindowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D293AFD1FE078A9000CBD7E /* SDLCarWindowViewController.m */; };
5D2F58081D0717D5001085CE /* SDLManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D2F58071D0717D5001085CE /* SDLManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5D339CEA207C066E000CC364 /* SDLMenuCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D339CE8207C066E000CC364 /* SDLMenuCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5D339CEB207C066E000CC364 /* SDLMenuCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D339CE9207C066E000CC364 /* SDLMenuCell.m */; };
+ 5D339CEF207C08BA000CC364 /* SDLVoiceCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D339CED207C08BA000CC364 /* SDLVoiceCommand.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5D339CF0207C08BA000CC364 /* SDLVoiceCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D339CEE207C08BA000CC364 /* SDLVoiceCommand.m */; };
+ 5D339CF3207C0ACE000CC364 /* SDLMenuManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D339CF1207C0ACE000CC364 /* SDLMenuManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 5D339CF4207C0ACE000CC364 /* SDLMenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D339CF2207C0ACE000CC364 /* SDLMenuManager.m */; };
5D3E48751D6F3B330000BFEF /* SDLAsynchronousOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D3E48731D6F3B330000BFEF /* SDLAsynchronousOperation.h */; };
5D3E48761D6F3B330000BFEF /* SDLAsynchronousOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3E48741D6F3B330000BFEF /* SDLAsynchronousOperation.m */; };
5D3E487B1D6F888E0000BFEF /* SDLRPCResponseNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D3E48791D6F888E0000BFEF /* SDLRPCResponseNotification.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -968,6 +974,8 @@
5DA49CE61F1EA83300E65FC5 /* SDLControlFramePayloadRPCStartService.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA49CE41F1EA83300E65FC5 /* SDLControlFramePayloadRPCStartService.m */; };
5DA8A0E91E955F710039C50D /* SDLStreamingMediaManagerConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8966F31E56977C00413EAB /* SDLStreamingMediaManagerConstants.m */; };
5DA8A0EA1E955FE00039C50D /* SDLLogModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DBF06301E64A9C600A5CF03 /* SDLLogModel.m */; };
+ 5DAB5F512098994C00A020C8 /* SDLMenuCellSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DAB5F502098994C00A020C8 /* SDLMenuCellSpec.m */; };
+ 5DAB5F5320989A8300A020C8 /* SDLVoiceCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DAB5F5220989A8300A020C8 /* SDLVoiceCommandSpec.m */; };
5DAD5F7F204DEDEB0025624C /* SDLScreenManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DAD5F7D204DEDEB0025624C /* SDLScreenManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
5DAD5F80204DEDEB0025624C /* SDLScreenManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DAD5F7E204DEDEB0025624C /* SDLScreenManager.m */; };
5DAD5F8520507E1F0025624C /* SDLScreenManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DAD5F8420507E1F0025624C /* SDLScreenManagerSpec.m */; };
@@ -1051,6 +1059,10 @@
5DEF695D1FD6FA01004B8C2F /* testAudio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 5DEF695C1FD6FA01004B8C2F /* testAudio.mp3 */; };
5DEF69611FD6FB75004B8C2F /* SDLAudioStreamManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DEF69601FD6FB75004B8C2F /* SDLAudioStreamManagerSpec.m */; };
5DEF69661FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DEF69651FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m */; };
+ 5DF40B22208E761A00DD6FDA /* SDLVoiceCommandManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */; };
+ 5DF40B23208E761A00DD6FDA /* SDLVoiceCommandManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */; };
+ 5DF40B26208FA7DE00DD6FDA /* SDLMenuManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF40B25208FA7DE00DD6FDA /* SDLMenuManagerSpec.m */; };
+ 5DF40B28208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */; };
5DFFB9151BD7C89700DB3F04 /* SDLConnectionManagerType.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DFFB9141BD7C89700DB3F04 /* SDLConnectionManagerType.h */; };
880E8C2920697FEE00CF86C2 /* SDLSystemCapabilityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 880E8C2720697FEE00CF86C2 /* SDLSystemCapabilityManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
880E8C2A20697FEE00CF86C2 /* SDLSystemCapabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 880E8C2820697FEE00CF86C2 /* SDLSystemCapabilityManager.m */; };
@@ -1591,6 +1603,12 @@
5D293AFC1FE078A9000CBD7E /* SDLCarWindowViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLCarWindowViewController.h; sourceTree = "<group>"; };
5D293AFD1FE078A9000CBD7E /* SDLCarWindowViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLCarWindowViewController.m; sourceTree = "<group>"; };
5D2F58071D0717D5001085CE /* SDLManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLManagerDelegate.h; sourceTree = "<group>"; };
+ 5D339CE8207C066E000CC364 /* SDLMenuCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLMenuCell.h; sourceTree = "<group>"; };
+ 5D339CE9207C066E000CC364 /* SDLMenuCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLMenuCell.m; sourceTree = "<group>"; };
+ 5D339CED207C08BA000CC364 /* SDLVoiceCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLVoiceCommand.h; sourceTree = "<group>"; };
+ 5D339CEE207C08BA000CC364 /* SDLVoiceCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLVoiceCommand.m; sourceTree = "<group>"; };
+ 5D339CF1207C0ACE000CC364 /* SDLMenuManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLMenuManager.h; sourceTree = "<group>"; };
+ 5D339CF2207C0ACE000CC364 /* SDLMenuManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLMenuManager.m; sourceTree = "<group>"; };
5D3E48731D6F3B330000BFEF /* SDLAsynchronousOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLAsynchronousOperation.h; sourceTree = "<group>"; };
5D3E48741D6F3B330000BFEF /* SDLAsynchronousOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLAsynchronousOperation.m; sourceTree = "<group>"; };
5D3E48791D6F888E0000BFEF /* SDLRPCResponseNotification.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLRPCResponseNotification.h; sourceTree = "<group>"; };
@@ -2227,6 +2245,8 @@
5DA3F36F1BC4489A0026F2D0 /* SDLManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLManager.m; sourceTree = "<group>"; };
5DA49CE31F1EA83300E65FC5 /* SDLControlFramePayloadRPCStartService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLControlFramePayloadRPCStartService.h; sourceTree = "<group>"; };
5DA49CE41F1EA83300E65FC5 /* SDLControlFramePayloadRPCStartService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLControlFramePayloadRPCStartService.m; sourceTree = "<group>"; };
+ 5DAB5F502098994C00A020C8 /* SDLMenuCellSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuCellSpec.m; path = DevAPISpecs/SDLMenuCellSpec.m; sourceTree = "<group>"; };
+ 5DAB5F5220989A8300A020C8 /* SDLVoiceCommandSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandSpec.m; path = DevAPISpecs/SDLVoiceCommandSpec.m; sourceTree = "<group>"; };
5DAD5F7D204DEDEB0025624C /* SDLScreenManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLScreenManager.h; sourceTree = "<group>"; };
5DAD5F7E204DEDEB0025624C /* SDLScreenManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLScreenManager.m; sourceTree = "<group>"; };
5DAD5F8420507E1F0025624C /* SDLScreenManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLScreenManagerSpec.m; sourceTree = "<group>"; };
@@ -2320,6 +2340,10 @@
5DEF69641FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLStreamingAudioManagerMock.h; sourceTree = "<group>"; };
5DEF69651FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLStreamingAudioManagerMock.m; sourceTree = "<group>"; };
5DF2BB9C1B94E38A00CE5994 /* SDLURLSessionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLURLSessionSpec.m; path = "UtilitiesSpecs/HTTP Connection/SDLURLSessionSpec.m"; sourceTree = "<group>"; };
+ 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLVoiceCommandManager.h; sourceTree = "<group>"; };
+ 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLVoiceCommandManager.m; sourceTree = "<group>"; };
+ 5DF40B25208FA7DE00DD6FDA /* SDLMenuManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuManagerSpec.m; path = DevAPISpecs/SDLMenuManagerSpec.m; sourceTree = "<group>"; };
+ 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandManagerSpec.m; path = DevAPISpecs/SDLVoiceCommandManagerSpec.m; sourceTree = "<group>"; };
5DFFB9141BD7C89700DB3F04 /* SDLConnectionManagerType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLConnectionManagerType.h; sourceTree = "<group>"; };
880E8C2720697FEE00CF86C2 /* SDLSystemCapabilityManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSystemCapabilityManager.h; sourceTree = "<group>"; };
880E8C2820697FEE00CF86C2 /* SDLSystemCapabilityManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSystemCapabilityManager.m; sourceTree = "<group>"; };
@@ -2923,6 +2947,7 @@
5D0A736F203F0C450001595D /* Screen */ = {
isa = PBXGroup;
children = (
+ 5D339CE5207C0651000CC364 /* Menu */,
5D0A737F203F23D10001595D /* Soft Button */,
5D0A737D203F23B30001595D /* Text and Graphic */,
5DAD5F7D204DEDEB0025624C /* SDLScreenManager.h */,
@@ -3035,6 +3060,29 @@
path = Utilities;
sourceTree = "<group>";
};
+ 5D339CE5207C0651000CC364 /* Menu */ = {
+ isa = PBXGroup;
+ children = (
+ 5D339CEC207C08AB000CC364 /* Cells */,
+ 5D339CF1207C0ACE000CC364 /* SDLMenuManager.h */,
+ 5D339CF2207C0ACE000CC364 /* SDLMenuManager.m */,
+ 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */,
+ 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */,
+ );
+ name = Menu;
+ sourceTree = "<group>";
+ };
+ 5D339CEC207C08AB000CC364 /* Cells */ = {
+ isa = PBXGroup;
+ children = (
+ 5D339CE8207C066E000CC364 /* SDLMenuCell.h */,
+ 5D339CE9207C066E000CC364 /* SDLMenuCell.m */,
+ 5D339CED207C08BA000CC364 /* SDLVoiceCommand.h */,
+ 5D339CEE207C08BA000CC364 /* SDLVoiceCommand.m */,
+ );
+ name = Cells;
+ sourceTree = "<group>";
+ };
5D3E48771D6F3DA40000BFEF /* Superclass Operation */ = {
isa = PBXGroup;
children = (
@@ -4356,14 +4404,15 @@
name = "Focus / Haptic";
sourceTree = "<group>";
};
- 5DAD5F8120507DE40025624C /* Show */ = {
+ 5DAD5F8120507DE40025624C /* Screen */ = {
isa = PBXGroup;
children = (
+ 5DF40B24208FA7C500DD6FDA /* Menu */,
5DAD5F8220507DED0025624C /* Soft Button */,
5DAD5F8320507DF30025624C /* Text and Graphic */,
5DAD5F8420507E1F0025624C /* SDLScreenManagerSpec.m */,
);
- name = Show;
+ name = Screen;
sourceTree = "<group>";
};
5DAD5F8220507DED0025624C /* Soft Button */ = {
@@ -4531,7 +4580,7 @@
5DBAE0A81D35886E00CE00BF /* Managers */ = {
isa = PBXGroup;
children = (
- 5DAD5F8120507DE40025624C /* Show */,
+ 5DAD5F8120507DE40025624C /* Screen */,
DA8966ED1E5693D100413EAB /* Streaming */,
5D1654541D3E753100554D93 /* Lifecycle */,
5D76E31A1D3805E600647CFA /* LockScreen */,
@@ -4681,6 +4730,17 @@
name = Mocks;
sourceTree = "<group>";
};
+ 5DF40B24208FA7C500DD6FDA /* Menu */ = {
+ isa = PBXGroup;
+ children = (
+ 5DF40B25208FA7DE00DD6FDA /* SDLMenuManagerSpec.m */,
+ 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */,
+ 5DAB5F502098994C00A020C8 /* SDLMenuCellSpec.m */,
+ 5DAB5F5220989A8300A020C8 /* SDLVoiceCommandSpec.m */,
+ );
+ name = Menu;
+ sourceTree = "<group>";
+ };
886D313C206A94BA001185B4 /* System Capabilities */ = {
isa = PBXGroup;
children = (
@@ -4924,6 +4984,7 @@
5D61FD091A84238C00846EE7 /* SDLOnDriverDistraction.h in Headers */,
E9C32B9E1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h in Headers */,
5DAD5F7F204DEDEB0025624C /* SDLScreenManager.h in Headers */,
+ 5D339CEF207C08BA000CC364 /* SDLVoiceCommand.h in Headers */,
5D61FC4B1A84238C00846EE7 /* SDLBeltStatus.h in Headers */,
DAC5726B1D10D5FC0004288B /* dispatch_timer.h in Headers */,
DA9F7E991DCC052C00ACAE48 /* SDLLocationCoordinate.h in Headers */,
@@ -5196,6 +5257,7 @@
5D1665CB1CF8CA6700CC4CA1 /* NSNumber+NumberType.h in Headers */,
5D9FDA941F2A7D3400A495C8 /* bson_util.h in Headers */,
5D61FD771A84238C00846EE7 /* SDLSamplingRate.h in Headers */,
+ 5DF40B22208E761A00DD6FDA /* SDLVoiceCommandManager.h in Headers */,
5D61FCBB1A84238C00846EE7 /* SDLGPSData.h in Headers */,
5D61FDA31A84238C00846EE7 /* SDLSoftButtonType.h in Headers */,
5D61FC431A84238C00846EE7 /* SDLAppInterfaceUnregisteredReason.h in Headers */,
@@ -5223,6 +5285,7 @@
97E26DEC1E807AD70074A3C7 /* SDLMutableDataQueue.h in Headers */,
5D61FDF71A84238C00846EE7 /* SDLV1ProtocolMessage.h in Headers */,
5D61FDFB1A84238C00846EE7 /* SDLV2ProtocolMessage.h in Headers */,
+ 5D339CEA207C066E000CC364 /* SDLMenuCell.h in Headers */,
5DBF06311E64A9C600A5CF03 /* SDLLogModel.h in Headers */,
5D7F87EB1CE3C1A1002DD7C4 /* SDLDeleteFileOperation.h in Headers */,
5D61FCFC1A84238C00846EE7 /* SDLNames.h in Headers */,
@@ -5236,6 +5299,7 @@
5D00AC6B1F141339004000D9 /* SDLSystemCapability.h in Headers */,
5DCD7AE01FCCA8D200A0FC7F /* SDLCarWindow.h in Headers */,
5D61FD6F1A84238C00846EE7 /* SDLRPCPayload.h in Headers */,
+ 5D339CF3207C0ACE000CC364 /* SDLMenuManager.h in Headers */,
5D61FCF01A84238C00846EE7 /* SDLLockScreenStatusManager.h in Headers */,
5D61FD311A84238C00846EE7 /* SDLPolicyDataParser.h in Headers */,
);
@@ -5701,6 +5765,7 @@
5D61FDC41A84238C00846EE7 /* SDLTBTState.m in Sources */,
5D61FDA61A84238C00846EE7 /* SDLSpeak.m in Sources */,
5D4631111F2135850092EFDC /* SDLControlFramePayloadConstants.m in Sources */,
+ 5D339CF0207C08BA000CC364 /* SDLVoiceCommand.m in Sources */,
DA9F7E881DCC049900ACAE48 /* SDLSubscribeWayPoints.m in Sources */,
5D61FDDE1A84238C00846EE7 /* SDLTTSChunk.m in Sources */,
5D61FD9E1A84238C00846EE7 /* SDLSliderResponse.m in Sources */,
@@ -5717,6 +5782,7 @@
5D61FC9F1A84238C00846EE7 /* SDLEncodedSyncPData.m in Sources */,
5D61FE061A84238C00846EE7 /* SDLVehicleDataResultCode.m in Sources */,
5D61FCA41A84238C00846EE7 /* SDLEndAudioPassThru.m in Sources */,
+ 5D339CEB207C066E000CC364 /* SDLMenuCell.m in Sources */,
5D8B17541AC9E11B006A6E1C /* SDLDialNumberResponse.m in Sources */,
DA6223BE1E7B088200878689 /* CVPixelBufferRef+SDLUtil.m in Sources */,
1FF7DABC1F75B2BF00B46C30 /* SDLFocusableItemLocator.m in Sources */,
@@ -5729,6 +5795,7 @@
5D61FD581A84238C00846EE7 /* SDLPutFileResponse.m in Sources */,
5D61FCB21A84238C00846EE7 /* SDLGetDTCs.m in Sources */,
5D61FD441A84238C00846EE7 /* SDLProtocol.m in Sources */,
+ 5DF40B23208E761A00DD6FDA /* SDLVoiceCommandManager.m in Sources */,
5D61FC341A84238C00846EE7 /* SDLAddSubMenuResponse.m in Sources */,
5DA240011F325621009C0313 /* SDLStreamingMediaConfiguration.m in Sources */,
5D6F7A2F1BC5650B0070BF37 /* SDLLifecycleConfiguration.m in Sources */,
@@ -5809,6 +5876,7 @@
5DCD7AF71FCCA8E400A0FC7F /* SDLScreenshotViewController.m in Sources */,
5D61FD081A84238C00846EE7 /* SDLOnCommand.m in Sources */,
5D53C46E1B7A99B9003526EA /* SDLStreamingMediaManager.m in Sources */,
+ 5D339CF4207C0ACE000CC364 /* SDLMenuManager.m in Sources */,
5D61FD6A1A84238C00846EE7 /* SDLRPCMessage.m in Sources */,
1E5AD0391F1F4E390029B8AF /* SDLClimateControlCapabilities.m in Sources */,
5D61FDA21A84238C00846EE7 /* SDLSoftButtonCapabilities.m in Sources */,
@@ -5883,6 +5951,7 @@
5DBEFA581F436132009EE295 /* SDLFakeSecurityManager.m in Sources */,
162E82D91A9BDE8A00906325 /* SDLDisplayTypeSpec.m in Sources */,
162E83871A9BDE8B00906325 /* SDLPermissionItemSpec.m in Sources */,
+ 5DAB5F5320989A8300A020C8 /* SDLVoiceCommandSpec.m in Sources */,
162E82E31A9BDE8B00906325 /* SDLIgnitionStatusSpec.m in Sources */,
162E83511A9BDE8B00906325 /* SDLDeleteInteractionChoiceSetResponseSpec.m in Sources */,
DA9F7EB41DCC086400ACAE48 /* SDLDateTimeSpec.m in Sources */,
@@ -5907,6 +5976,7 @@
162E83681A9BDE8B00906325 /* SDLSpeakResponseSpec.m in Sources */,
162E83661A9BDE8B00906325 /* SDLShowResponseSpec.m in Sources */,
5D9F50831BEA5C6100FEF399 /* SDLFileManagerSpec.m in Sources */,
+ 5DAB5F512098994C00A020C8 /* SDLMenuCellSpec.m in Sources */,
1EE8C4481F38430900FDC2CF /* SDLRadioControlCapabilitiesSpec.m in Sources */,
162E83221A9BDE8B00906325 /* SDLAddCommandSpec.m in Sources */,
162E83121A9BDE8B00906325 /* SDLOnButtonPressSpec.m in Sources */,
@@ -6131,6 +6201,7 @@
162E83971A9BDE8B00906325 /* SDLVehicleTypeSpec.m in Sources */,
1680B1131A9CD7AD00DBD79E /* SDLProtocolHeaderSpec.m in Sources */,
162E82D01A9BDE8A00906325 /* SDLButtonEventModeSpec.m in Sources */,
+ 5DF40B26208FA7DE00DD6FDA /* SDLMenuManagerSpec.m in Sources */,
162E83781A9BDE8B00906325 /* SDLDeviceInfoSpec.m in Sources */,
162E83391A9BDE8B00906325 /* SDLSetAppIconSpec.m in Sources */,
162E83011A9BDE8B00906325 /* SDLTimerModeSpec.m in Sources */,
@@ -6169,6 +6240,7 @@
162E82CF1A9BDE8A00906325 /* SDLBitsPerSampleSpec.m in Sources */,
162E831E1A9BDE8B00906325 /* SDLOnTBTClientStateSpec.m in Sources */,
162E83351A9BDE8B00906325 /* SDLReadDIDSpec.m in Sources */,
+ 5DF40B28208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m in Sources */,
162E836F1A9BDE8B00906325 /* SDLUnsubscribeVehicleDataResponseSpec.m in Sources */,
162E82DB1A9BDE8B00906325 /* SDLECallConfirmationStatusSpec.m in Sources */,
162E82D81A9BDE8A00906325 /* SDLDimensionSpec.m in Sources */,
diff --git a/SmartDeviceLink.podspec b/SmartDeviceLink.podspec
index 86dd5156a..09d6d5b03 100644
--- a/SmartDeviceLink.podspec
+++ b/SmartDeviceLink.podspec
@@ -157,6 +157,8 @@ ss.public_header_files = [
'SmartDeviceLink/SDLManager.h',
'SmartDeviceLink/SDLManagerDelegate.h',
'SmartDeviceLink/SDLMediaClockFormat.h',
+'SmartDeviceLink/SDLMenuCell.h',
+'SmartDeviceLink/SDLMenuManager.h',
'SmartDeviceLink/SDLMenuParams.h',
'SmartDeviceLink/SDLMetadataTags.h',
'SmartDeviceLink/SDLMetadataType.h',
@@ -326,6 +328,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLVideoStreamingCodec.h',
'SmartDeviceLink/SDLVideoStreamingFormat.h',
'SmartDeviceLink/SDLVideoStreamingProtocol.h',
+'SmartDeviceLink/SDLVoiceCommand.h',
'SmartDeviceLink/SDLVrCapabilities.h',
'SmartDeviceLink/SDLVrHelpItem.h',
'SmartDeviceLink/SDLWarningLightStatus.h',
diff --git a/SmartDeviceLink/SDLArtwork.m b/SmartDeviceLink/SDLArtwork.m
index 062374858..bf2861b67 100644
--- a/SmartDeviceLink/SDLArtwork.m
+++ b/SmartDeviceLink/SDLArtwork.m
@@ -111,6 +111,30 @@ NS_ASSUME_NONNULL_BEGIN
return formattedHash;
}
+#pragma mark - NSObject overrides
+
+- (NSUInteger)hash {
+ return self.name.hash ^ self.data.hash;
+}
+
+- (BOOL)isEqual:(id)object {
+ if (self == object) { return YES; }
+
+ if (![object isKindOfClass:[SDLArtwork class]]) { return NO; }
+
+ return [self isEqualToArtwork:(SDLArtwork *)object];
+}
+
+- (BOOL)isEqualToArtwork:(SDLArtwork *)artwork {
+ if (!artwork) { return NO; }
+
+ BOOL haveEqualNames = [self.name isEqualToString:artwork.name];
+ BOOL haveEqualData = [self.data isEqualToData:artwork.data];
+ BOOL haveEqualFormats = [self.fileType isEqualToEnum:artwork.fileType];
+
+ return haveEqualNames && haveEqualData && haveEqualFormats;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.h b/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.h
index b08c700a2..a24165dad 100644
--- a/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.h
+++ b/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.h
@@ -17,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLAsynchronousRPCRequestOperation : SDLAsynchronousOperation
+@property (copy, nonatomic) NSArray<SDLRPCRequest *> *requests;
+
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager requests:(NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleAsyncRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler;
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager request:(SDLRPCRequest *)request responseHandler:(nullable SDLResponseHandler)responseHandler;
diff --git a/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.m b/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.m
index a79b30b09..c92a23c74 100644
--- a/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.m
+++ b/SmartDeviceLink/SDLAsynchronousRPCRequestOperation.m
@@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLAsynchronousRPCRequestOperation ()
-@property (copy, nonatomic) NSArray<SDLRPCRequest *> *requests;
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (copy, nonatomic, nullable) SDLMultipleAsyncRequestProgressHandler progressHandler;
@property (copy, nonatomic, nullable) SDLMultipleRequestCompletionHandler completionHandler;
@@ -160,6 +159,14 @@ NS_ASSUME_NONNULL_BEGIN
return NSOperationQueuePriorityNormal;
}
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@, request count=%lu, requests started=%lu, finished=%lu, failed=%@", self.name, self.requests.count, self.requestsStarted, self.requestsComplete, (self.requestFailed ? @"YES": @"NO")];
+}
+
+- (NSString *)debugDescription {
+ return [NSString stringWithFormat:@"%@, request count=%lu, requests started=%lu, finished=%lu, failed=%@, requests=%@", self.name, self.requests.count, self.requestsStarted, self.requestsComplete, (self.requestFailed ? @"YES": @"NO"), self.requests];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLConnectionManagerType.h b/SmartDeviceLink/SDLConnectionManagerType.h
index d1b316343..f7052f67d 100644
--- a/SmartDeviceLink/SDLConnectionManagerType.h
+++ b/SmartDeviceLink/SDLConnectionManagerType.h
@@ -33,6 +33,10 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)sendConnectionRequest:(__kindof SDLRPCRequest *)request withResponseHandler:(nullable SDLResponseHandler)handler;
+- (void)sendRequests:(NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleAsyncRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler;
+
+- (void)sendSequentialRequests:(NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleSequentialRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLDisplayCapabilities+ShowManagerExtensions.m b/SmartDeviceLink/SDLDisplayCapabilities+ShowManagerExtensions.m
index 351c62532..188b10729 100644
--- a/SmartDeviceLink/SDLDisplayCapabilities+ShowManagerExtensions.m
+++ b/SmartDeviceLink/SDLDisplayCapabilities+ShowManagerExtensions.m
@@ -40,6 +40,10 @@
}
- (BOOL)hasImageFieldOfName:(SDLImageFieldName)name {
+ if (!self.graphicSupported.boolValue) {
+ return NO;
+ }
+
for (SDLImageField *imageField in self.imageFields) {
if ([imageField.name isEqualToString:name]) {
return YES;
diff --git a/SmartDeviceLink/SDLError.h b/SmartDeviceLink/SDLError.h
index 3ae76736a..4a6ccf21b 100644
--- a/SmartDeviceLink/SDLError.h
+++ b/SmartDeviceLink/SDLError.h
@@ -19,6 +19,9 @@ NS_ASSUME_NONNULL_BEGIN
typedef NSString SDLErrorDomain;
extern SDLErrorDomain *const SDLErrorDomainLifecycleManager;
extern SDLErrorDomain *const SDLErrorDomainFileManager;
+extern SDLErrorDomain *const SDLErrorDomainTextAndGraphicManager;
+extern SDLErrorDomain *const SDLErrorDomainSoftButtonManager;
+extern SDLErrorDomain *const SDLErrorDomainMenuManager;
@interface NSError (SDLErrors)
@@ -47,9 +50,14 @@ extern SDLErrorDomain *const SDLErrorDomainFileManager;
+ (NSError *)sdl_fileManager_dataMissingError;
#pragma mark Show Managers
+
+ (NSError *)sdl_softButtonManager_pendingUpdateSuperseded;
+ (NSError *)sdl_textAndGraphicManager_pendingUpdateSuperseded;
+#pragma mark Menu Manager
+
++ (NSError *)sdl_menuManager_failedToUpdateWithDictionary:(NSDictionary *)userInfo;
+
@end
@interface NSException (SDLExceptions)
diff --git a/SmartDeviceLink/SDLError.m b/SmartDeviceLink/SDLError.m
index c5bda35f0..722eb3534 100644
--- a/SmartDeviceLink/SDLError.m
+++ b/SmartDeviceLink/SDLError.m
@@ -16,6 +16,7 @@ SDLErrorDomain *const SDLErrorDomainLifecycleManager = @"com.sdl.lifecyclemanage
SDLErrorDomain *const SDLErrorDomainFileManager = @"com.sdl.filemanager.error";
SDLErrorDomain *const SDLErrorDomainTextAndGraphicManager = @"com.sdl.textandgraphicmanager.error";
SDLErrorDomain *const SDLErrorDomainSoftButtonManager = @"com.sdl.softbuttonmanager.error";
+SDLErrorDomain *const SDLErrorDomainMenuManager = @"com.sdl.menumanager.error";
@implementation NSError (SDLErrors)
@@ -185,7 +186,7 @@ SDLErrorDomain *const SDLErrorDomainSoftButtonManager = @"com.sdl.softbuttonmana
return [NSError errorWithDomain:SDLErrorDomainFileManager code:SDLFileManagerErrorFileDoesNotExist userInfo:userInfo];
}
-#pragma mark Show Managers
+#pragma mark Screen Managers
+ (NSError *)sdl_textAndGraphicManager_pendingUpdateSuperseded {
return [NSError errorWithDomain:SDLErrorDomainTextAndGraphicManager code:SDLTextAndGraphicManagerErrorPendingUpdateSuperseded userInfo:nil];
@@ -195,6 +196,12 @@ SDLErrorDomain *const SDLErrorDomainSoftButtonManager = @"com.sdl.softbuttonmana
return [NSError errorWithDomain:SDLErrorDomainSoftButtonManager code:SDLSoftButtonManagerErrorPendingUpdateSuperseded userInfo:nil];
}
+#pragma mark Menu Manager
+
++ (NSError *)sdl_menuManager_failedToUpdateWithDictionary:(NSDictionary *)userInfo {
+ return [NSError errorWithDomain:SDLErrorDomainMenuManager code:SDLMenuManagerErrorRPCsFailed userInfo:userInfo];
+}
+
@end
diff --git a/SmartDeviceLink/SDLErrorConstants.h b/SmartDeviceLink/SDLErrorConstants.h
index 5eefd10f4..7cd7eba44 100644
--- a/SmartDeviceLink/SDLErrorConstants.h
+++ b/SmartDeviceLink/SDLErrorConstants.h
@@ -95,3 +95,7 @@ typedef NS_ENUM(NSInteger, SDLTextAndGraphicManagerError) {
typedef NS_ENUM(NSInteger, SDLSoftButtonManagerError) {
SDLSoftButtonManagerErrorPendingUpdateSuperseded = -1
};
+
+typedef NS_ENUM(NSInteger, SDLMenuManagerError) {
+ SDLMenuManagerErrorRPCsFailed = -1
+};
diff --git a/SmartDeviceLink/SDLFile.m b/SmartDeviceLink/SDLFile.m
index 9fc4d5423..522171f24 100644
--- a/SmartDeviceLink/SDLFile.m
+++ b/SmartDeviceLink/SDLFile.m
@@ -161,6 +161,30 @@ NS_ASSUME_NONNULL_BEGIN
return [[self.class allocWithZone:zone] initWithFileURL:_fileURL name:_name persistent:_persistent];
}
+#pragma mark - NSObject overrides
+
+- (NSUInteger)hash {
+ return self.name.hash ^ self.data.hash;
+}
+
+- (BOOL)isEqual:(id)object {
+ if (self == object) { return YES; }
+
+ if (![object isKindOfClass:[SDLFile class]]) { return NO; }
+
+ return [self isEqualToFile:(SDLFile *)object];
+}
+
+- (BOOL)isEqualToFile:(SDLFile *)file {
+ if (!file) { return NO; }
+
+ BOOL haveEqualNames = [self.name isEqualToString:file.name];
+ BOOL haveEqualData = [self.data isEqualToData:file.data];
+ BOOL haveEqualFormats = [self.fileType isEqualToEnum:file.fileType];
+
+ return haveEqualNames && haveEqualData && haveEqualFormats;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLLifecycleManager.m b/SmartDeviceLink/SDLLifecycleManager.m
index ccc95293c..854e27848 100644
--- a/SmartDeviceLink/SDLLifecycleManager.m
+++ b/SmartDeviceLink/SDLLifecycleManager.m
@@ -488,11 +488,21 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
}
- (void)sendRequests:(NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleAsyncRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler {
+ if (requests.count == 0) {
+ completionHandler(YES);
+ return;
+ }
+
SDLAsynchronousRPCRequestOperation *op = [[SDLAsynchronousRPCRequestOperation alloc] initWithConnectionManager:self requests:requests progressHandler:progressHandler completionHandler:completionHandler];
[self.rpcOperationQueue addOperation:op];
}
- (void)sendSequentialRequests:(NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleSequentialRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler {
+ if (requests.count == 0) {
+ completionHandler(YES);
+ return;
+ }
+
SDLSequentialRPCRequestOperation *op = [[SDLSequentialRPCRequestOperation alloc] initWithConnectionManager:self requests:requests progressHandler:progressHandler completionHandler:completionHandler];
[self.rpcOperationQueue addOperation:op];
}
diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m
index cdbeccc71..812c75313 100644
--- a/SmartDeviceLink/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/SDLLogFileModuleMap.m
@@ -21,7 +21,8 @@
[self sdl_fileManagerModule],
[self sdl_lifecycleManagerModule],
[self sdl_lockscreenManagerModule],
- [self sdl_streamingMediaManagerModule]]];
+ [self sdl_streamingMediaManagerModule],
+ [self sdl_screenManagerModule]]];
}
+ (SDLLogFileModule *)sdl_transportModule {
@@ -56,7 +57,7 @@
}
+ (SDLLogFileModule *)sdl_lockscreenManagerModule {
- return [SDLLogFileModule moduleWithName:@"Lockscreen" files:[NSSet setWithArray:@[@"SDLLockScreenManager", @"SDLLockScreenViewController"]]];
+ return [SDLLogFileModule moduleWithName:@"Lockscreen" files:[NSSet setWithArray:@[@"SDLLockScreenManager", @"SDLLockScreenViewController", @"SDLLockScreenPresenter"]]];
}
+ (SDLLogFileModule *)sdl_streamingMediaManagerModule {
@@ -64,7 +65,7 @@
}
+ (SDLLogFileModule *)sdl_screenManagerModule {
- return [SDLLogFileModule moduleWithName:@"Screen" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager", @"SDLSoftButtonManager", @"SDLScreenManager", @"SDLSoftButtonObject", @"SDLSoftButtonState"]]];
+ return [SDLLogFileModule moduleWithName:@"Screen" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager", @"SDLSoftButtonManager", @"SDLScreenManager", @"SDLSoftButtonObject", @"SDLSoftButtonState", @"SDLMenuManager", @"SDLVoiceCommandManager"]]];
}
diff --git a/SmartDeviceLink/SDLManager.h b/SmartDeviceLink/SDLManager.h
index 332e153b8..b984a58a5 100644
--- a/SmartDeviceLink/SDLManager.h
+++ b/SmartDeviceLink/SDLManager.h
@@ -27,34 +27,6 @@
NS_ASSUME_NONNULL_BEGIN
-/**
- A completion handler called after a sequential or simultaneous set of requests have completed sending.
-
- @param success True if every request succeeded, false if any failed. See the progress handler for more details on failures.
- */
-typedef void (^SDLMultipleRequestCompletionHandler)(BOOL success);
-
-/**
- A handler called after each response to a request comes in in a multiple request send.
-
- @param request The request that received a response
- @param response The response received
- @param error The error that occurred during the request if any occurred.
- @param percentComplete The percentage of requests that have received a response
- @return continueSendingRequests NO to cancel any requests that have not yet been sent. This is really only useful for a sequential send (sendSequentialRequests:progressHandler:completionHandler:). Return YES to continue sending requests.
- */
-typedef BOOL (^SDLMultipleSequentialRequestProgressHandler)(__kindof SDLRPCRequest *request, __kindof SDLRPCResponse *__nullable response, NSError *__nullable error, float percentComplete);
-
-/**
- A handler called after each response to a request comes in in a multiple request send.
-
- @param request The request that received a response
- @param response The response received
- @param error The error that occurred during the request if any occurred.
- @param percentComplete The percentage of requests that have received a response
- */
-typedef void (^SDLMultipleAsyncRequestProgressHandler)(__kindof SDLRPCRequest *request, __kindof SDLRPCResponse *__nullable response, NSError *__nullable error, float percentComplete);
-
typedef void (^SDLManagerReadyBlock)(BOOL success, NSError *_Nullable error);
diff --git a/SmartDeviceLink/SDLMenuCell.h b/SmartDeviceLink/SDLMenuCell.h
new file mode 100644
index 000000000..80edc70b1
--- /dev/null
+++ b/SmartDeviceLink/SDLMenuCell.h
@@ -0,0 +1,51 @@
+//
+// SDLMenuCell.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLTriggerSource.h"
+
+@class SDLArtwork;
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^SDLMenuCellSelectionHandler)(SDLTriggerSource triggerSource);
+
+@interface SDLMenuCell : NSObject
+
+/**
+ The cell's text to be displayed
+ */
+@property (copy, nonatomic, readonly) NSString *title;
+
+/**
+ The cell's icon to be displayed
+ */
+@property (strong, nonatomic, readonly, nullable) SDLArtwork *icon;
+
+/**
+ The strings the user can say to activate this voice command
+ */
+@property (copy, nonatomic, readonly, nullable) NSArray<NSString *> *voiceCommands;
+
+/**
+ The handler that will be called when the command is activated
+ */
+@property (copy, nonatomic, readonly, nullable) SDLMenuCellSelectionHandler handler;
+
+/**
+ If this is non-nil, this cell will be a sub-menu button, displaying the subcells in a menu when pressed.
+ */
+@property (copy, nonatomic, readonly, nullable) NSArray<SDLMenuCell *> *subCells;
+
+- (instancetype)initWithTitle:(NSString *)title icon:(nullable SDLArtwork *)icon voiceCommands:(nullable NSArray<NSString *> *)voiceCommands handler:(SDLMenuCellSelectionHandler)handler;
+- (instancetype)initWithTitle:(NSString *)title subCells:(NSArray<SDLMenuCell *> *)subCells;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLMenuCell.m b/SmartDeviceLink/SDLMenuCell.m
new file mode 100644
index 000000000..b5a259544
--- /dev/null
+++ b/SmartDeviceLink/SDLMenuCell.m
@@ -0,0 +1,58 @@
+//
+// SDLMenuCell.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import "SDLMenuCell.h"
+
+#import "SDLArtwork.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLMenuCell()
+
+@property (assign, nonatomic) UInt32 parentCellId;
+@property (assign, nonatomic) UInt32 cellId;
+
+@end
+
+@implementation SDLMenuCell
+
+- (instancetype)initWithTitle:(NSString *)title icon:(nullable SDLArtwork *)icon voiceCommands:(nullable NSArray<NSString *> *)voiceCommands handler:(SDLMenuCellSelectionHandler)handler {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _title = title;
+ _icon = icon;
+ _voiceCommands = voiceCommands;
+ _handler = handler;
+
+ _cellId = UINT32_MAX;
+ _parentCellId = UINT32_MAX;
+
+ return self;
+}
+
+- (instancetype)initWithTitle:(NSString *)title subCells:(NSArray<SDLMenuCell *> *)subCells {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _title = title;
+ _subCells = subCells;
+
+ _cellId = UINT32_MAX;
+ _parentCellId = UINT32_MAX;
+
+ return self;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"SDLMenuCell: %u-\"%@\", artworkName: %@, voice commands: %lu, isSubcell: %@, hasSubcells: %@", (unsigned int)_cellId, _title, _icon.name, _voiceCommands.count, (_parentCellId != UINT32_MAX ? @"YES" : @"NO"), (_subCells.count > 0 ? @"YES" : @"NO")];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLMenuManager.h b/SmartDeviceLink/SDLMenuManager.h
new file mode 100644
index 000000000..acf1aff99
--- /dev/null
+++ b/SmartDeviceLink/SDLMenuManager.h
@@ -0,0 +1,34 @@
+//
+// SDLMenuManager.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLFileManager;
+@class SDLMenuCell;
+@class SDLVoiceCommand;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The handler run when the update has completed
+
+ @param error An error if the update failed and an error occurred
+ */
+typedef void(^SDLMenuUpdateCompletionHandler)(NSError *__nullable error);
+
+@interface SDLMenuManager : NSObject
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager;
+
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *menuCells;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLMenuManager.m b/SmartDeviceLink/SDLMenuManager.m
new file mode 100644
index 000000000..39b6bc91c
--- /dev/null
+++ b/SmartDeviceLink/SDLMenuManager.m
@@ -0,0 +1,429 @@
+//
+// SDLMenuManager.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import "SDLMenuManager.h"
+
+#import "SDLAddCommand.h"
+#import "SDLAddSubMenu.h"
+#import "SDLArtwork.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDeleteCommand.h"
+#import "SDLDeleteSubMenu.h"
+#import "SDLDisplayCapabilities.h"
+#import "SDLDisplayCapabilities+ShowManagerExtensions.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLImage.h"
+#import "SDLLogMacros.h"
+#import "SDLMenuCell.h"
+#import "SDLMenuParams.h"
+#import "SDLOnCommand.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLRegisterAppInterfaceResponse.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLRPCResponseNotification.h"
+#import "SDLSetDisplayLayoutResponse.h"
+#import "SDLVoiceCommand.h"
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLMenuCell()
+
+@property (assign, nonatomic) UInt32 parentCellId;
+@property (assign, nonatomic) UInt32 cellId;
+
+@end
+
+@interface SDLMenuManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+
+@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
+@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext;
+@property (strong, nonatomic, nullable) SDLDisplayCapabilities *displayCapabilities;
+
+@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
+@property (assign, nonatomic) BOOL hasQueuedUpdate;
+@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *waitingUpdateMenuCells;
+
+@property (assign, nonatomic) UInt32 lastMenuId;
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *oldMenuCells;
+
+@end
+
+UInt32 const ParentIdNotFound = UINT32_MAX;
+UInt32 const MenuCellIdMin = 1;
+
+@implementation SDLMenuManager
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _lastMenuId = MenuCellIdMin;
+ _menuCells = @[];
+ _oldMenuCells = @[];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_registerResponse:) name:SDLDidReceiveRegisterAppInterfaceResponse object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_displayLayoutResponse:) name:SDLDidReceiveSetDisplayLayoutResponse object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusNotification:) name:SDLDidChangeHMIStatusNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_commandNotification:) name:SDLDidReceiveCommandNotification object:nil];
+
+ return self;
+}
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager {
+ self = [self init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+
+ return self;
+}
+
+#pragma mark - Setters
+
+- (void)setMenuCells:(NSArray<SDLMenuCell *> *)menuCells {
+ if (self.currentHMILevel == nil
+ || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]
+ || [self.currentSystemContext isEqualToEnum:SDLSystemContextMenu]) {
+ self.waitingOnHMIUpdate = YES;
+ self.waitingUpdateMenuCells = menuCells;
+ return;
+ }
+
+ self.waitingOnHMIUpdate = NO;
+
+ // Check for duplicate titles
+ NSMutableSet *titleCheckSet = [NSMutableSet set];
+ for (SDLMenuCell *cell in menuCells) {
+ [titleCheckSet addObject:cell.title];
+ }
+ if (titleCheckSet.count != menuCells.count) {
+ SDLLogE(@"Not all cell titles are unique. The menu will not be set.");
+ return;
+ }
+
+ // Set the ids
+ self.lastMenuId = MenuCellIdMin;
+ [self sdl_updateIdsOnMenuCells:menuCells parentId:ParentIdNotFound];
+
+ _oldMenuCells = _menuCells;
+ _menuCells = menuCells;
+
+ // Upload the artworks
+ NSArray<SDLArtwork *> *artworksToBeUploaded = [self sdl_findAllArtworksToBeUploadedFromCells:self.menuCells];
+ if (artworksToBeUploaded.count > 0) {
+ [self.fileManager uploadArtworks:artworksToBeUploaded completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading menu artworks: %@", error);
+ }
+
+ SDLLogD(@"Menu artworks uploaded");
+ [self sdl_updateWithCompletionHandler:nil];
+ }];
+ }
+
+ [self sdl_updateWithCompletionHandler:nil];
+}
+
+#pragma mark - Updating System
+
+- (void)sdl_updateWithCompletionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.currentHMILevel == nil
+ || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]
+ || [self.currentSystemContext isEqualToEnum:SDLSystemContextMenu]) {
+ self.waitingOnHMIUpdate = YES;
+ self.waitingUpdateMenuCells = self.menuCells;
+ return;
+ }
+
+ if (self.inProgressUpdate != nil) {
+ // There's an in progress update, we need to put this on hold
+ self.hasQueuedUpdate = YES;
+ return;
+ }
+
+ __weak typeof(self) weakself = self;
+ [self sdl_sendDeleteCurrentMenu:^(NSError * _Nullable error) {
+ [weakself sdl_sendCurrentMenu:^(NSError * _Nullable error) {
+ weakself.inProgressUpdate = nil;
+
+ if (completionHandler != nil) {
+ completionHandler(error);
+ }
+
+ if (weakself.hasQueuedUpdate) {
+ [weakself sdl_updateWithCompletionHandler:nil];
+ weakself.hasQueuedUpdate = NO;
+ }
+ }];
+ }];
+}
+
+#pragma mark Delete Old Menu Items
+
+- (void)sdl_sendDeleteCurrentMenu:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.oldMenuCells.count == 0) {
+ completionHandler(nil);
+ return;
+ }
+
+ NSArray<SDLRPCRequest *> *deleteMenuCommands = [self sdl_deleteCommandsForCells:self.oldMenuCells];
+ self.oldMenuCells = @[];
+ [self.connectionManager sendRequests:deleteMenuCommands progressHandler:nil completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogW(@"Unable to delete all old menu commands");
+ } else {
+ SDLLogD(@"Finished deleting old menu");
+ }
+
+ completionHandler(nil);
+ }];
+}
+
+#pragma mark Send New Menu Items
+
+- (void)sdl_sendCurrentMenu:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.menuCells.count == 0) {
+ SDLLogD(@"No main menu to send");
+ completionHandler(nil);
+
+ return;
+ }
+
+ NSArray<SDLRPCRequest *> *mainMenuCommands = nil;
+ NSArray<SDLRPCRequest *> *subMenuCommands = nil;
+ if ([self sdl_findAllArtworksToBeUploadedFromCells:self.menuCells].count > 0 || ![self.displayCapabilities hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
+ // Send artwork-less menu
+ mainMenuCommands = [self sdl_mainMenuCommandsForCells:self.menuCells withArtwork:NO];
+ subMenuCommands = [self sdl_subMenuCommandsForCells:self.menuCells withArtwork:NO];
+ } else {
+ // Send full artwork menu
+ mainMenuCommands = [self sdl_mainMenuCommandsForCells:self.menuCells withArtwork:YES];
+ subMenuCommands = [self sdl_subMenuCommandsForCells:self.menuCells withArtwork:YES];
+ }
+
+ self.inProgressUpdate = [mainMenuCommands arrayByAddingObjectsFromArray:subMenuCommands];
+
+ __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendRequests:mainMenuCommands progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ if (error != nil) {
+ errors[request] = error;
+ }
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send main menu commands: %@", errors);
+ completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
+ return;
+ }
+
+ weakSelf.oldMenuCells = weakSelf.menuCells;
+ [weakSelf.connectionManager sendRequests:subMenuCommands progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ if (error != nil) {
+ errors[request] = error;
+ }
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send sub menu commands: %@", errors);
+ completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
+ return;
+ }
+
+ SDLLogD(@"Finished updating menu");
+ completionHandler(nil);
+ }];
+ }];
+}
+
+#pragma mark - Helpers
+
+#pragma mark Artworks
+
+- (NSArray<SDLArtwork *> *)sdl_findAllArtworksToBeUploadedFromCells:(NSArray<SDLMenuCell *> *)cells {
+ if (![self.displayCapabilities hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
+ return @[];
+ }
+
+ NSMutableSet<SDLArtwork *> *mutableArtworks = [NSMutableSet set];
+ for (SDLMenuCell *cell in cells) {
+ if (cell.icon != nil && ![self.fileManager hasUploadedFile:cell.icon]) {
+ [mutableArtworks addObject:cell.icon];
+ }
+
+ if (cell.subCells.count > 0) {
+ [mutableArtworks addObjectsFromArray:[self sdl_findAllArtworksToBeUploadedFromCells:cell.subCells]];
+ }
+ }
+
+ return [mutableArtworks allObjects];
+}
+
+#pragma mark IDs
+
+- (void)sdl_updateIdsOnMenuCells:(NSArray<SDLMenuCell *> *)menuCells parentId:(UInt32)parentId {
+ for (SDLMenuCell *cell in menuCells) {
+ cell.cellId = self.lastMenuId++;
+ cell.parentCellId = parentId;
+ if (cell.subCells.count > 0) {
+ [self sdl_updateIdsOnMenuCells:cell.subCells parentId:cell.cellId];
+ }
+ }
+}
+
+#pragma mark Deletes
+
+- (NSArray<SDLRPCRequest *> *)sdl_deleteCommandsForCells:(NSArray<SDLMenuCell *> *)cells {
+ NSMutableArray<SDLRPCRequest *> *mutableDeletes = [NSMutableArray array];
+ for (SDLMenuCell *cell in cells) {
+ if (cell.subCells == nil) {
+ SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:cell.cellId];
+ [mutableDeletes addObject:delete];
+ } else {
+ SDLDeleteSubMenu *delete = [[SDLDeleteSubMenu alloc] initWithId:cell.cellId];
+ [mutableDeletes addObject:delete];
+ }
+ }
+
+ return [mutableDeletes copy];
+}
+
+#pragma mark Commands / SubMenu RPCs
+
+- (NSArray<SDLRPCRequest *> *)sdl_mainMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+ [cells enumerateObjectsUsingBlock:^(SDLMenuCell * _Nonnull cell, NSUInteger index, BOOL * _Nonnull stop) {
+ if (cell.subCells.count > 0) {
+ [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cell position:(UInt16)index]];
+ } else {
+ [mutableCommands addObject:[self sdl_commandForMenuCell:cell withArtwork:shouldHaveArtwork position:(UInt16)index]];
+ }
+ }];
+
+ return [mutableCommands copy];
+}
+
+- (NSArray<SDLRPCRequest *> *)sdl_subMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+ for (SDLMenuCell *cell in cells) {
+ if (cell.subCells.count > 0) {
+ [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cell.subCells withArtwork:shouldHaveArtwork]];
+ }
+ }
+
+ return [mutableCommands copy];
+}
+
+- (NSArray<SDLRPCRequest *> *)sdl_allCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+ [cells enumerateObjectsUsingBlock:^(SDLMenuCell * _Nonnull cell, NSUInteger index, BOOL * _Nonnull stop) {
+ if (cell.subCells.count > 0) {
+ [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cell position:(UInt16)index]];
+ [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cell.subCells withArtwork:shouldHaveArtwork]];
+ } else {
+ [mutableCommands addObject:[self sdl_commandForMenuCell:cell withArtwork:shouldHaveArtwork position:(UInt16)index]];
+ }
+ }];
+
+ return [mutableCommands copy];
+}
+
+- (SDLAddCommand *)sdl_commandForMenuCell:(SDLMenuCell *)cell withArtwork:(BOOL)shouldHaveArtwork position:(UInt16)position {
+ SDLAddCommand *command = [[SDLAddCommand alloc] init];
+
+ SDLMenuParams *params = [[SDLMenuParams alloc] init];
+ params.menuName = cell.title;
+ params.parentID = cell.parentCellId != UINT32_MAX ? @(cell.parentCellId) : nil;
+ params.position = @(position);
+
+ command.menuParams = params;
+ command.vrCommands = cell.voiceCommands;
+ command.cmdIcon = (cell.icon && shouldHaveArtwork) ? [[SDLImage alloc] initWithName:cell.icon.name] : nil;
+ command.cmdID = @(cell.cellId);
+
+ return command;
+}
+
+- (SDLAddSubMenu *)sdl_subMenuCommandForMenuCell:(SDLMenuCell *)cell position:(UInt16)position {
+ SDLAddSubMenu *submenu = [[SDLAddSubMenu alloc] initWithId:cell.cellId menuName:cell.title];
+ submenu.position = @(position);
+
+ return submenu;
+}
+
+#pragma mark - Calling handlers
+
+- (BOOL)sdl_callHandlerForCells:(NSArray<SDLMenuCell *> *)cells command:(SDLOnCommand *)onCommand {
+ for (SDLMenuCell *cell in cells) {
+ if (cell.cellId == onCommand.cmdID.unsignedIntegerValue) {
+ cell.handler(onCommand.triggerSource);
+ return YES;
+ }
+
+ if (cell.subCells.count > 0) {
+ BOOL succeeded = [self sdl_callHandlerForCells:cell.subCells command:onCommand];
+ if (succeeded) { return YES; }
+ }
+ }
+
+ return NO;
+}
+
+#pragma mark - Observers
+
+- (void)sdl_commandNotification:(SDLRPCNotificationNotification *)notification {
+ SDLOnCommand *onCommand = (SDLOnCommand *)notification.notification;
+
+ [self sdl_callHandlerForCells:self.menuCells command:onCommand];
+}
+
+- (void)sdl_registerResponse:(SDLRPCResponseNotification *)notification {
+ SDLRegisterAppInterfaceResponse *response = (SDLRegisterAppInterfaceResponse *)notification.response;
+ self.displayCapabilities = response.displayCapabilities;
+}
+
+- (void)sdl_displayLayoutResponse:(SDLRPCResponseNotification *)notification {
+ SDLSetDisplayLayoutResponse *response = (SDLSetDisplayLayoutResponse *)notification.response;
+ self.displayCapabilities = response.displayCapabilities;
+}
+
+- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification {
+ SDLOnHMIStatus *hmiStatus = (SDLOnHMIStatus *)notification.notification;
+ SDLHMILevel oldHMILevel = self.currentHMILevel;
+ self.currentHMILevel = hmiStatus.hmiLevel;
+
+ // Auto-send an updated menu if we were in NONE and now we are not, and we need an update
+ if ([oldHMILevel isEqualToString:SDLHMILevelNone] && ![self.currentHMILevel isEqualToString:SDLHMILevelNone] &&
+ ![self.currentSystemContext isEqualToEnum:SDLSystemContextMenu]) {
+ if (self.waitingOnHMIUpdate) {
+ [self setMenuCells:self.waitingUpdateMenuCells];
+ self.waitingUpdateMenuCells = @[];
+ return;
+ }
+ }
+
+ // If we don't check for this and only update when not in the menu, there can be IN_USE errors, especially with submenus. We also don't want to encourage changing out the menu while the user is using it for usability reasons.
+ SDLSystemContext oldSystemContext = self.currentSystemContext;
+ self.currentSystemContext = hmiStatus.systemContext;
+
+ if ([oldSystemContext isEqualToEnum:SDLSystemContextMenu] && ![self.currentSystemContext isEqualToEnum:SDLSystemContextMenu] && ![self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
+ if (self.waitingOnHMIUpdate) {
+ [self setMenuCells:self.waitingUpdateMenuCells];
+ self.waitingUpdateMenuCells = @[];
+ }
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLNotificationConstants.h b/SmartDeviceLink/SDLNotificationConstants.h
index be7c0b2aa..52b025303 100644
--- a/SmartDeviceLink/SDLNotificationConstants.h
+++ b/SmartDeviceLink/SDLNotificationConstants.h
@@ -47,6 +47,33 @@ typedef void (^SDLAudioPassThruHandler)(NSData *__nullable audioData);
*/
typedef void (^SDLResponseHandler)(__kindof SDLRPCRequest *__nullable request, __kindof SDLRPCResponse *__nullable response, NSError *__nullable error);
+/**
+ A completion handler called after a sequential or simultaneous set of requests have completed sending.
+
+ @param success True if every request succeeded, false if any failed. See the progress handler for more details on failures.
+ */
+typedef void (^SDLMultipleRequestCompletionHandler)(BOOL success);
+
+/**
+ A handler called after each response to a request comes in in a multiple request send.
+
+ @param request The request that received a response
+ @param response The response received
+ @param error The error that occurred during the request if any occurred.
+ @param percentComplete The percentage of requests that have received a response
+ @return continueSendingRequests NO to cancel any requests that have not yet been sent. This is really only useful for a sequential send (sendSequentialRequests:progressHandler:completionHandler:). Return YES to continue sending requests.
+ */
+typedef BOOL (^SDLMultipleSequentialRequestProgressHandler)(__kindof SDLRPCRequest *request, __kindof SDLRPCResponse *__nullable response, NSError *__nullable error, float percentComplete);
+
+/**
+ A handler called after each response to a request comes in in a multiple request send.
+
+ @param request The request that received a response
+ @param response The response received
+ @param error The error that occurred during the request if any occurred.
+ @param percentComplete The percentage of requests that have received a response
+ */
+typedef void (^SDLMultipleAsyncRequestProgressHandler)(__kindof SDLRPCRequest *request, __kindof SDLRPCResponse *__nullable response, NSError *__nullable error, float percentComplete);
/**
A handler that may optionally be run when an SDLSubscribeButton or SDLSoftButton has a corresponding notification occur.
diff --git a/SmartDeviceLink/SDLScreenManager.h b/SmartDeviceLink/SDLScreenManager.h
index 716a0eae9..70d57ccf4 100644
--- a/SmartDeviceLink/SDLScreenManager.h
+++ b/SmartDeviceLink/SDLScreenManager.h
@@ -13,6 +13,8 @@
@class SDLArtwork;
@class SDLFileManager;
+@class SDLMenuCell;
+@class SDLVoiceCommand;
@class SDLSoftButtonObject;
@protocol SDLConnectionManagerType;
@@ -44,6 +46,9 @@ typedef void(^SDLScreenManagerUpdateCompletionHandler)(NSError *__nullable error
@property (copy, nonatomic) NSArray<SDLSoftButtonObject *> *softButtonObjects;
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *menu;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *voiceCommands;
+
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager;
/**
diff --git a/SmartDeviceLink/SDLScreenManager.m b/SmartDeviceLink/SDLScreenManager.m
index 85c5793e5..f0de151d2 100644
--- a/SmartDeviceLink/SDLScreenManager.m
+++ b/SmartDeviceLink/SDLScreenManager.m
@@ -9,8 +9,10 @@
#import "SDLScreenManager.h"
#import "SDLArtwork.h"
+#import "SDLMenuManager.h"
#import "SDLSoftButtonManager.h"
#import "SDLTextAndGraphicManager.h"
+#import "SDLVoiceCommandManager.h"
NS_ASSUME_NONNULL_BEGIN
@@ -18,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) SDLTextAndGraphicManager *textAndGraphicManager;
@property (strong, nonatomic) SDLSoftButtonManager *softButtonManager;
+@property (strong, nonatomic) SDLMenuManager *menuManager;
+@property (strong, nonatomic) SDLVoiceCommandManager *voiceCommandMenuManager;
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (weak, nonatomic) SDLFileManager *fileManager;
@@ -35,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
_textAndGraphicManager = [[SDLTextAndGraphicManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager];
_softButtonManager = [[SDLSoftButtonManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager];
+ _menuManager = [[SDLMenuManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager];
+ _voiceCommandMenuManager = [[SDLVoiceCommandManager alloc] initWithConnectionManager:connectionManager];
return self;
}
@@ -108,6 +114,14 @@ NS_ASSUME_NONNULL_BEGIN
self.softButtonManager.softButtonObjects = softButtonObjects;
}
+- (void)setMenu:(NSArray<SDLMenuCell *> *)menu {
+ self.menuManager.menuCells = menu;
+}
+
+- (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ self.voiceCommandMenuManager.voiceCommands = voiceCommands;
+}
+
#pragma mark - Getters
- (nullable NSString *)textField1 {
@@ -170,6 +184,14 @@ NS_ASSUME_NONNULL_BEGIN
return _softButtonManager.softButtonObjects;
}
+- (NSArray<SDLMenuCell *> *)menu {
+ return _menuManager.menuCells;
+}
+
+- (NSArray<SDLVoiceCommand *> *)voiceCommands {
+ return _voiceCommandMenuManager.voiceCommands;
+}
+
#pragma mark - Begin / End Updates
- (void)beginUpdates {
diff --git a/SmartDeviceLink/SDLSoftButtonManager.m b/SmartDeviceLink/SDLSoftButtonManager.m
index 995c0c142..023dcdc85 100644
--- a/SmartDeviceLink/SDLSoftButtonManager.m
+++ b/SmartDeviceLink/SDLSoftButtonManager.m
@@ -43,7 +43,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate;
@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler inProgressHandler;
-@property (strong, nonatomic, nullable) SDLShow *queuedImageUpdate;
@property (assign, nonatomic) BOOL hasQueuedUpdate;
@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler queuedUpdateHandler;
diff --git a/SmartDeviceLink/SDLVoiceCommand.h b/SmartDeviceLink/SDLVoiceCommand.h
new file mode 100644
index 000000000..252fbaeae
--- /dev/null
+++ b/SmartDeviceLink/SDLVoiceCommand.h
@@ -0,0 +1,31 @@
+//
+// SDLVoiceCommand.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^SDLVoiceCommandSelectionHandler)(void);
+
+@interface SDLVoiceCommand : NSObject
+
+/**
+ The strings the user can say to activate this voice command
+ */
+@property (copy, nonatomic, readonly) NSArray<NSString *> *voiceCommands;
+
+/**
+ The handler that will be called when the command is activated
+ */
+@property (copy, nonatomic, readonly, nullable) SDLVoiceCommandSelectionHandler handler;
+
+- (instancetype)initWithVoiceCommands:(NSArray<NSString *> *)voiceCommands handler:(SDLVoiceCommandSelectionHandler)handler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLVoiceCommand.m b/SmartDeviceLink/SDLVoiceCommand.m
new file mode 100644
index 000000000..e9a619945
--- /dev/null
+++ b/SmartDeviceLink/SDLVoiceCommand.m
@@ -0,0 +1,37 @@
+//
+// SDLVoiceCommand.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/9/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import "SDLVoiceCommand.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLVoiceCommand()
+
+@property (assign, nonatomic) UInt32 commandId;
+
+@end
+
+@implementation SDLVoiceCommand
+
+- (instancetype)initWithVoiceCommands:(NSArray<NSString *> *)voiceCommands handler:(SDLVoiceCommandSelectionHandler)handler {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _voiceCommands = voiceCommands;
+ _handler = handler;
+
+ return self;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"SDLVoiceCommand: %u-\"%@\", voice commands: %lu", (unsigned int)_commandId, _voiceCommands.firstObject, _voiceCommands.count];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLVoiceCommandManager.h b/SmartDeviceLink/SDLVoiceCommandManager.h
new file mode 100644
index 000000000..bf8d13fb9
--- /dev/null
+++ b/SmartDeviceLink/SDLVoiceCommandManager.h
@@ -0,0 +1,33 @@
+//
+// SDLVoiceCommandManager.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/23/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLFileManager;
+@class SDLVoiceCommand;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The handler run when the update has completed
+
+ @param error An error if the update failed and an error occurred
+ */
+typedef void(^SDLMenuUpdateCompletionHandler)(NSError *__nullable error);
+
+@interface SDLVoiceCommandManager : NSObject
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager;
+
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *voiceCommands;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLVoiceCommandManager.m b/SmartDeviceLink/SDLVoiceCommandManager.m
new file mode 100644
index 000000000..884e5fbf6
--- /dev/null
+++ b/SmartDeviceLink/SDLVoiceCommandManager.m
@@ -0,0 +1,249 @@
+//
+// SDLVoiceCommandManager.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/23/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import "SDLVoiceCommandManager.h"
+
+#import "SDLAddCommand.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDeleteCommand.h"
+#import "SDLError.h"
+#import "SDLHMILevel.h"
+#import "SDLLogMacros.h"
+#import "SDLNotificationConstants.h"
+#import "SDLOnCommand.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLRPCRequest.h"
+#import "SDLVoiceCommand.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLVoiceCommand()
+
+@property (assign, nonatomic) UInt32 commandId;
+
+@end
+
+@interface SDLVoiceCommandManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+
+@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
+@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
+
+@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
+@property (assign, nonatomic) BOOL hasQueuedUpdate;
+
+@property (assign, nonatomic) UInt32 lastVoiceCommandId;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *oldVoiceCommands;
+
+@end
+
+UInt32 const VoiceCommandIdMin = 1900000000;
+
+@implementation SDLVoiceCommandManager
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _lastVoiceCommandId = VoiceCommandIdMin;
+ _voiceCommands = @[];
+ _oldVoiceCommands = @[];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusNotification:) name:SDLDidChangeHMIStatusNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_commandNotification:) name:SDLDidReceiveCommandNotification object:nil];
+
+ return self;
+}
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager {
+ self = [self init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+
+ return self;
+}
+
+#pragma mark - Setters
+
+- (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ if (self.currentHMILevel == nil || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
+ self.waitingOnHMIUpdate = YES;
+ return;
+ }
+
+ self.waitingOnHMIUpdate = NO;
+
+ // Set the ids
+ self.lastVoiceCommandId = VoiceCommandIdMin;
+ [self sdl_updateIdsOnVoiceCommands:voiceCommands];
+
+ _oldVoiceCommands = _voiceCommands;
+ _voiceCommands = voiceCommands;
+
+ [self sdl_updateWithCompletionHandler:nil];
+}
+
+#pragma mark - Updating System
+
+- (void)sdl_updateWithCompletionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.currentHMILevel == nil || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
+ self.waitingOnHMIUpdate = YES;
+ return;
+ }
+
+ if (self.inProgressUpdate != nil) {
+ // There's an in progress update, we need to put this on hold
+ self.hasQueuedUpdate = YES;
+ return;
+ }
+
+ __weak typeof(self) weakself = self;
+ [self sdl_sendDeleteCurrentVoiceCommands:^(NSError * _Nullable error) {
+ [weakself sdl_sendCurrentVoiceCommands:^(NSError * _Nullable error) {
+ weakself.inProgressUpdate = nil;
+
+ if (completionHandler != nil) {
+ completionHandler(error);
+ }
+
+ if (weakself.hasQueuedUpdate) {
+ [weakself sdl_updateWithCompletionHandler:nil];
+ weakself.hasQueuedUpdate = NO;
+ }
+ }];
+ }];
+}
+
+#pragma mark Delete Old Menu Items
+
+- (void)sdl_sendDeleteCurrentVoiceCommands:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.oldVoiceCommands.count == 0) {
+ completionHandler(nil);
+
+ return;
+ }
+
+ NSArray<SDLRPCRequest *> *deleteVoiceCommands = [self sdl_deleteCommandsForVoiceCommands:self.oldVoiceCommands];
+ self.oldVoiceCommands = @[];
+ [self.connectionManager sendRequests:deleteVoiceCommands progressHandler:nil completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Error deleting old voice commands");
+ } else {
+ SDLLogD(@"Finished deleting old voice commands");
+ }
+
+ completionHandler(nil);
+ }];
+}
+
+#pragma mark Send New Menu Items
+
+- (void)sdl_sendCurrentVoiceCommands:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.voiceCommands.count == 0) {
+ SDLLogD(@"No voice commands to send");
+ completionHandler(nil);
+
+ return;
+ }
+
+ self.inProgressUpdate = [self sdl_addCommandsForVoiceCommands:self.voiceCommands];
+
+ __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendRequests:self.inProgressUpdate progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ if (error != nil) {
+ errors[request] = error;
+ }
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send main menu commands: %@", errors);
+ completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
+ return;
+ }
+
+ SDLLogD(@"Finished updating voice commands");
+ weakSelf.oldVoiceCommands = weakSelf.voiceCommands;
+ completionHandler(nil);
+ }];
+}
+
+#pragma mark - Helpers
+
+#pragma mark IDs
+
+- (void)sdl_updateIdsOnVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ for (SDLVoiceCommand *voiceCommand in voiceCommands) {
+ voiceCommand.commandId = self.lastVoiceCommandId++;
+ }
+}
+
+#pragma mark Deletes
+
+- (NSArray<SDLDeleteCommand *> *)sdl_deleteCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ NSMutableArray<SDLDeleteCommand *> *mutableDeletes = [NSMutableArray array];
+ for (SDLVoiceCommand *command in voiceCommands) {
+ SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:command.commandId];
+ [mutableDeletes addObject:delete];
+ }
+
+ return [mutableDeletes copy];
+}
+
+#pragma mark Commands
+
+- (NSArray<SDLAddCommand *> *)sdl_addCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ NSMutableArray<SDLAddCommand *> *mutableCommands = [NSMutableArray array];
+ for (SDLVoiceCommand *command in voiceCommands) {
+ [mutableCommands addObject:[self sdl_commandForVoiceCommand:command]];
+ }
+
+ return [mutableCommands copy];
+}
+
+- (SDLAddCommand *)sdl_commandForVoiceCommand:(SDLVoiceCommand *)voiceCommand {
+ SDLAddCommand *command = [[SDLAddCommand alloc] init];
+ command.vrCommands = voiceCommand.voiceCommands;
+ command.cmdID = @(voiceCommand.commandId);
+
+ return command;
+}
+
+#pragma mark - Observers
+
+- (void)sdl_commandNotification:(SDLRPCNotificationNotification *)notification {
+ SDLOnCommand *onCommand = (SDLOnCommand *)notification.notification;
+
+ for (SDLVoiceCommand *voiceCommand in self.voiceCommands) {
+ if (onCommand.cmdID.unsignedIntegerValue != voiceCommand.commandId) { continue; }
+
+ voiceCommand.handler();
+ break;
+ }
+}
+
+- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification {
+ SDLOnHMIStatus *hmiStatus = (SDLOnHMIStatus *)notification.notification;
+ SDLHMILevel oldHMILevel = self.currentHMILevel;
+ self.currentHMILevel = hmiStatus.hmiLevel;
+
+ // Auto-send an updated show if we were in NONE and now we are not
+ if ([oldHMILevel isEqualToEnum:SDLHMILevelNone] && ![self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
+ if (self.waitingOnHMIUpdate) {
+ [self setVoiceCommands:_voiceCommands];
+ } else {
+ [self sdl_updateWithCompletionHandler:nil];
+ }
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SmartDeviceLink.h b/SmartDeviceLink/SmartDeviceLink.h
index d9f7aee10..a6a50289e 100644
--- a/SmartDeviceLink/SmartDeviceLink.h
+++ b/SmartDeviceLink/SmartDeviceLink.h
@@ -327,11 +327,15 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[];
#import "SDLPermissionConstants.h"
#import "SDLPermissionManager.h"
-// Show
+// Screen
#import "SDLScreenManager.h"
#import "SDLSoftButtonObject.h"
#import "SDLSoftButtonState.h"
+#import "SDLMenuManager.h"
+#import "SDLMenuCell.h"
+#import "SDLVoiceCommand.h"
+
// Touches
#import "SDLPinchGesture.h"
#import "SDLTouch.h"
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m
new file mode 100644
index 000000000..b6b52f0a0
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuCellSpec.m
@@ -0,0 +1,47 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLArtwork.h"
+#import "SDLMenuCell.h"
+
+QuickSpecBegin(SDLMenuCellSpec)
+
+describe(@"a menu cell", ^{
+ __block SDLMenuCell *testCell = nil;
+
+ describe(@"initializing", ^{
+ __block NSString *someTitle = nil;
+ __block SDLArtwork *someArtwork = nil;
+ __block NSArray<NSString *> *someVoiceCommands = nil;
+ __block NSArray<SDLMenuCell *> *someSubcells = nil;
+
+ beforeEach(^{
+ someTitle = @"Some Title";
+ someArtwork = [[SDLArtwork alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:@"data" options:kNilOptions] name:@"Some artwork" fileExtension:@"png" persistent:NO];
+ someVoiceCommands = @[@"some command"];
+
+ SDLMenuCell *subcell = [[SDLMenuCell alloc] initWithTitle:@"Hello" icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ someSubcells = @[subcell];
+ });
+
+ it(@"should initialize properly as a menu item", ^{
+ testCell = [[SDLMenuCell alloc] initWithTitle:someTitle icon:someArtwork voiceCommands:someVoiceCommands handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+
+ expect(testCell.title).to(equal(someTitle));
+ expect(testCell.icon).to(equal(someArtwork));
+ expect(testCell.voiceCommands).to(equal(someVoiceCommands));
+ expect(testCell.subCells).to(beNil());
+ });
+
+ it(@"should initialize properly as a submenu item", ^{
+ testCell = [[SDLMenuCell alloc] initWithTitle:someTitle subCells:someSubcells];
+
+ expect(testCell.title).to(equal(someTitle));
+ expect(testCell.icon).to(beNil());
+ expect(testCell.voiceCommands).to(beNil());
+ expect(testCell.subCells).to(equal(someSubcells));
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
new file mode 100644
index 000000000..68678e230
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
@@ -0,0 +1,314 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLAddCommand.h"
+#import "SDLAddSubMenu.h"
+#import "SDLDeleteCommand.h"
+#import "SDLDisplayCapabilities.h"
+#import "SDLFileManager.h"
+#import "SDLHMILevel.h"
+#import "SDLImage.h"
+#import "SDLImageField.h"
+#import "SDLImageFieldName.h"
+#import "SDLMenuCell.h"
+#import "SDLMenuManager.h"
+#import "SDLOnCommand.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLSystemContext.h"
+#import "TestConnectionManager.h"
+
+@interface SDLMenuCell()
+
+@property (assign, nonatomic) UInt32 parentCellId;
+@property (assign, nonatomic) UInt32 cellId;
+
+@end
+
+@interface SDLMenuManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+
+@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
+@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext;
+@property (strong, nonatomic, nullable) SDLDisplayCapabilities *displayCapabilities;
+
+@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
+@property (assign, nonatomic) BOOL hasQueuedUpdate;
+@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *waitingUpdateMenuCells;
+
+@property (assign, nonatomic) UInt32 lastMenuId;
+@property (copy, nonatomic) NSArray<SDLMenuCell *> *oldMenuCells;
+
+@end
+
+QuickSpecBegin(SDLMenuManagerSpec)
+
+describe(@"menu manager", ^{
+ __block SDLMenuManager *testManager = nil;
+ __block TestConnectionManager *mockConnectionManager = nil;
+ __block SDLFileManager *mockFileManager = nil;
+
+ __block SDLArtwork *testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO];
+
+ __block SDLMenuCell *textOnlyCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ __block SDLMenuCell *textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" icon:testArtwork voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ __block SDLMenuCell *submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Test 3" subCells:@[textOnlyCell, textAndImageCell]];
+
+ beforeEach(^{
+ mockConnectionManager = [[TestConnectionManager alloc] init];
+ mockFileManager = OCMClassMock([SDLFileManager class]);
+ testManager = [[SDLMenuManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager];
+ });
+
+ it(@"should instantiate correctly", ^{
+ expect(testManager.menuCells).to(beEmpty());
+ expect(testManager.connectionManager).to(equal(mockConnectionManager));
+ expect(testManager.fileManager).to(equal(mockFileManager));
+ expect(testManager.currentHMILevel).to(beNil());
+ expect(testManager.displayCapabilities).to(beNil());
+ expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.hasQueuedUpdate).to(beFalse());
+ expect(testManager.waitingOnHMIUpdate).to(beFalse());
+ expect(testManager.lastMenuId).to(equal(1));
+ expect(testManager.oldMenuCells).to(beEmpty());
+ });
+
+ describe(@"updating menu cells before HMI is ready", ^{
+ context(@"when in HMI NONE", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelNone;
+ testManager.menuCells = @[textOnlyCell];
+ });
+
+ it(@"should not update", ^{
+ expect(mockConnectionManager.receivedRequests).to(beEmpty());
+ });
+
+ describe(@"when entering the foreground", ^{
+ beforeEach(^{
+ SDLOnHMIStatus *onHMIStatus = [[SDLOnHMIStatus alloc] init];
+ onHMIStatus.hmiLevel = SDLHMILevelFull;
+ onHMIStatus.systemContext = SDLSystemContextMain;
+
+ SDLRPCNotificationNotification *testSystemContextNotification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:onHMIStatus];
+ [[NSNotificationCenter defaultCenter] postNotification:testSystemContextNotification];
+ });
+
+ it(@"should update", ^{
+ expect(mockConnectionManager.receivedRequests).toNot(beEmpty());
+ });
+ });
+ });
+
+ context(@"when no HMI level has been received", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = nil;
+ testManager.menuCells = @[textOnlyCell];
+ });
+
+ it(@"should not update", ^{
+ expect(mockConnectionManager.receivedRequests).to(beEmpty());
+ });
+ });
+
+ context(@"when in the menu", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelFull;
+ testManager.currentSystemContext = SDLSystemContextMenu;
+ testManager.menuCells = @[textOnlyCell];
+ });
+
+ it(@"should not update", ^{
+ expect(mockConnectionManager.receivedRequests).to(beEmpty());
+ });
+
+ describe(@"when exiting the menu", ^{
+ beforeEach(^{
+ SDLOnHMIStatus *onHMIStatus = [[SDLOnHMIStatus alloc] init];
+ onHMIStatus.hmiLevel = SDLHMILevelFull;
+ onHMIStatus.systemContext = SDLSystemContextMain;
+
+ SDLRPCNotificationNotification *testSystemContextNotification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:onHMIStatus];
+ [[NSNotificationCenter defaultCenter] postNotification:testSystemContextNotification];
+ });
+
+ it(@"should update", ^{
+ expect(mockConnectionManager.receivedRequests).toNot(beEmpty());
+ });
+ });
+ });
+ });
+
+ describe(@"updating menu cells", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelFull;
+ testManager.currentSystemContext = SDLSystemContextMain;
+
+ testManager.displayCapabilities = [[SDLDisplayCapabilities alloc] init];
+ SDLImageField *commandIconField = [[SDLImageField alloc] init];
+ commandIconField.name = SDLImageFieldNameCommandIcon;
+ testManager.displayCapabilities.imageFields = @[commandIconField];
+ testManager.displayCapabilities.graphicSupported = @YES;
+ });
+
+ it(@"should fail with a duplicate title", ^{
+ testManager.menuCells = @[textOnlyCell, textOnlyCell];
+
+ expect(testManager.menuCells).to(beEmpty());
+ });
+
+ it(@"should properly update a text cell", ^{
+ testManager.menuCells = @[textOnlyCell];
+
+ NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]];
+ NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
+ expect(deletes).to(beEmpty());
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+ expect(add).toNot(beEmpty());
+ });
+
+ describe(@"updating with an image", ^{
+ context(@"when the image is already on the head unit", ^{
+ beforeEach(^{
+ OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
+ });
+
+ it(@"should properly update an image cell", ^{
+ testManager.menuCells = @[textAndImageCell];
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+ SDLAddCommand *sentCommand = add.firstObject;
+
+ expect(add).to(haveCount(1));
+ expect(sentCommand.cmdIcon.value).to(equal(testArtwork.name));
+ });
+ });
+
+ context(@"when the image is not on the head unit", ^{
+ beforeEach(^{
+ OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
+ });
+
+ it(@"should immediately attempt to update without the image", ^{
+ testManager.menuCells = @[textAndImageCell];
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+ SDLAddCommand *sentCommand = add.firstObject;
+
+ expect(add).to(haveCount(1));
+ expect(sentCommand.cmdIcon.value).to(beNil());
+ });
+ });
+ });
+
+ it(@"should properly update with subcells", ^{
+ testManager.menuCells = @[submenuCell];
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+
+ NSPredicate *submenuCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddSubMenu class]];
+ NSArray *submenus = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:submenuCommandPredicate];
+
+ expect(adds).to(haveCount(2));
+ expect(submenus).to(haveCount(1));
+ });
+
+ context(@"when a menu already exists", ^{
+ beforeEach(^{
+ testManager.menuCells = @[textOnlyCell];
+ });
+
+ it(@"should send deletes first", ^{
+ testManager.menuCells = @[textAndImageCell];
+
+ NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]];
+ NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+
+ expect(deletes).to(haveCount(1));
+ expect(adds).to(haveCount(2));
+ });
+ });
+ });
+
+ describe(@"running menu cell handlers", ^{
+ __block SDLMenuCell *cellWithHandler = nil;
+ __block BOOL cellCalled = NO;
+ __block SDLTriggerSource testTriggerSource = nil;
+
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelFull;
+ testManager.currentSystemContext = SDLSystemContextMain;
+
+ testManager.displayCapabilities = [[SDLDisplayCapabilities alloc] init];
+ SDLImageField *commandIconField = [[SDLImageField alloc] init];
+ commandIconField.name = SDLImageFieldNameCommandIcon;
+ testManager.displayCapabilities.imageFields = @[commandIconField];
+ testManager.displayCapabilities.graphicSupported = @YES;
+
+ cellCalled = NO;
+ testTriggerSource = nil;
+ });
+
+ context(@"on a main menu cell", ^{
+ beforeEach(^{
+ cellWithHandler = [[SDLMenuCell alloc] initWithTitle:@"Hello" icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ cellCalled = YES;
+ testTriggerSource = triggerSource;
+ }];
+
+ testManager.menuCells = @[cellWithHandler];
+ });
+
+ it(@"should call the cell handler", ^{
+ SDLOnCommand *onCommand = [[SDLOnCommand alloc] init];
+ onCommand.cmdID = @1;
+ onCommand.triggerSource = SDLTriggerSourceMenu;
+
+ SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveCommandNotification object:nil rpcNotification:onCommand];
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ expect(cellCalled).to(beTrue());
+ expect(testTriggerSource).to(equal(SDLTriggerSourceMenu));
+ });
+ });
+
+ context(@"on a submenu menu cell", ^{
+ beforeEach(^{
+ cellWithHandler = [[SDLMenuCell alloc] initWithTitle:@"Hello" icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ cellCalled = YES;
+ testTriggerSource = triggerSource;
+ }];
+
+ SDLMenuCell *submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Submenu" subCells:@[cellWithHandler]];
+
+ testManager.menuCells = @[submenuCell];
+ });
+
+ it(@"should call the cell handler", ^{
+ SDLOnCommand *onCommand = [[SDLOnCommand alloc] init];
+ onCommand.cmdID = @2;
+ onCommand.triggerSource = SDLTriggerSourceMenu;
+
+ SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveCommandNotification object:nil rpcNotification:onCommand];
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ expect(cellCalled).to(beTrue());
+ expect(testTriggerSource).to(equal(SDLTriggerSourceMenu));
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
index cb29f3433..bc42d0866 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
@@ -66,37 +66,37 @@ describe(@"text and graphic manager", ^{
expect(testManager.textField4Type).to(beNil());
});
- context(@"when in HMI NONE", ^{
+ describe(@"setting setters", ^{
beforeEach(^{
- testManager.currentLevel = SDLHMILevelNone;
+ testManager.currentLevel = SDLHMILevelFull;
});
- it(@"should not set text field 1", ^{
- testManager.textField1 = testString;
+ context(@"when in HMI NONE", ^{
+ beforeEach(^{
+ testManager.currentLevel = SDLHMILevelNone;
+ });
- expect(testManager.textField1).to(equal(testString));
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.isDirty).to(beFalse());
- });
- });
+ it(@"should not set text field 1", ^{
+ testManager.textField1 = testString;
- context(@"when no HMI level has been received", ^{
- beforeEach(^{
- testManager.currentLevel = nil;
+ expect(testManager.textField1).to(equal(testString));
+ expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.isDirty).to(beFalse());
+ });
});
- it(@"should not set text field 1", ^{
- testManager.textField1 = testString;
+ context(@"when no HMI level has been received", ^{
+ beforeEach(^{
+ testManager.currentLevel = nil;
+ });
- expect(testManager.textField1).to(equal(testString));
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.isDirty).to(beFalse());
- });
- });
+ it(@"should not set text field 1", ^{
+ testManager.textField1 = testString;
- describe(@"setting setters", ^{
- beforeEach(^{
- testManager.currentLevel = SDLHMILevelFull;
+ expect(testManager.textField1).to(equal(testString));
+ expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.isDirty).to(beFalse());
+ });
});
context(@"while batching", ^{
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
new file mode 100644
index 000000000..d9cda0f5d
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
@@ -0,0 +1,123 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLAddCommand.h"
+#import "SDLDeleteCommand.h"
+#import "SDLFileManager.h"
+#import "SDLHMILevel.h"
+#import "SDLVoiceCommand.h"
+#import "SDLVoiceCommandManager.h"
+#import "TestConnectionManager.h"
+
+@interface SDLVoiceCommand()
+
+@property (assign, nonatomic) UInt32 commandId;
+
+@end
+
+@interface SDLVoiceCommandManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+
+@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
+@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
+
+@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
+@property (assign, nonatomic) BOOL hasQueuedUpdate;
+
+@property (assign, nonatomic) UInt32 lastVoiceCommandId;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *oldVoiceCommands;
+
+@end
+
+UInt32 const VoiceCommandIdMin = 1900000000;
+
+QuickSpecBegin(SDLVoiceCommandManagerSpec)
+
+describe(@"voice command manager", ^{
+ __block SDLVoiceCommandManager *testManager = nil;
+ __block TestConnectionManager *mockConnectionManager = nil;
+
+ __block SDLVoiceCommand *testVoiceCommand = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test 1"] handler:^{}];
+ __block SDLVoiceCommand *testVoiceCommand2 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test 2"] handler:^{}];
+
+ beforeEach(^{
+ mockConnectionManager = [[TestConnectionManager alloc] init];
+ testManager = [[SDLVoiceCommandManager alloc] initWithConnectionManager:mockConnectionManager];
+ });
+
+ it(@"should instantiate correctly", ^{
+ expect(testManager.voiceCommands).to(beEmpty());
+ expect(testManager.connectionManager).to(equal(mockConnectionManager));
+ expect(testManager.currentHMILevel).to(beNil());
+ expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.hasQueuedUpdate).to(beFalse());
+ expect(testManager.waitingOnHMIUpdate).to(beFalse());
+ expect(testManager.lastVoiceCommandId).to(equal(VoiceCommandIdMin));
+ expect(testManager.oldVoiceCommands).to(beEmpty());
+ });
+
+ describe(@"updating voice commands before HMI is ready", ^{
+ context(@"when in HMI NONE", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelNone;
+ });
+
+ it(@"should not update", ^{
+ testManager.voiceCommands = @[testVoiceCommand];
+ expect(testManager.inProgressUpdate).to(beNil());
+ });
+ });
+
+ context(@"when no HMI level has been received", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = nil;
+ });
+
+ it(@"should not update", ^{
+ testManager.voiceCommands = @[testVoiceCommand];
+ expect(testManager.inProgressUpdate).to(beNil());
+ });
+ });
+ });
+
+ describe(@"updating voice commands", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelFull;
+ });
+
+ it(@"should properly update a command", ^{
+ testManager.voiceCommands = @[testVoiceCommand];
+
+ NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]];
+ NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
+ expect(deletes).to(beEmpty());
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+ expect(add).toNot(beEmpty());
+ });
+
+ context(@"when a menu already exists", ^{
+ beforeEach(^{
+ testManager.voiceCommands = @[testVoiceCommand];
+ });
+
+ it(@"should send deletes first", ^{
+ testManager.voiceCommands = @[testVoiceCommand2];
+
+ NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]];
+ NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
+
+ NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
+ NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+
+ expect(deletes).to(haveCount(1));
+ expect(adds).to(haveCount(2));
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandSpec.m
new file mode 100644
index 000000000..8e8a2e7c3
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandSpec.m
@@ -0,0 +1,26 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLVoiceCommand.h"
+
+QuickSpecBegin(SDLVoiceCommandSpec)
+
+describe(@"a voice command", ^{
+ __block SDLVoiceCommand *testCommand = nil;
+
+ describe(@"initializing", ^{
+ __block NSArray<NSString *> *someVoiceCommands = nil;
+
+ beforeEach(^{
+ someVoiceCommands = @[@"some command"];
+ });
+
+ it(@"should initialize properly", ^{
+ testCommand = [[SDLVoiceCommand alloc] initWithVoiceCommands:someVoiceCommands handler:^{}];
+
+ expect(testCommand.voiceCommands).to(equal(someVoiceCommands));
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
index adb494fb6..ba0c29930 100644
--- a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
+++ b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
@@ -35,6 +35,32 @@ NS_ASSUME_NONNULL_BEGIN
[self sendConnectionRequest:request withResponseHandler:handler];
}
+- (void)sendRequests:(nonnull NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleAsyncRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler {
+ [requests enumerateObjectsUsingBlock:^(SDLRPCRequest * _Nonnull request, NSUInteger idx, BOOL * _Nonnull stop) {
+ [self sendConnectionRequest:request withResponseHandler:nil];
+
+ if (progressHandler != nil) {
+ progressHandler(request, nil, nil, (double)idx / (double)requests.count);
+ }
+ }];
+
+ if (completionHandler != nil) {
+ completionHandler(YES);
+ }
+}
+
+- (void)sendSequentialRequests:(nonnull NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleSequentialRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler {
+ [requests enumerateObjectsUsingBlock:^(SDLRPCRequest * _Nonnull request, NSUInteger idx, BOOL * _Nonnull stop) {
+ [self sendConnectionRequest:request withResponseHandler:nil];
+ progressHandler(request, nil, nil, (double)idx / (double)requests.count);
+ }];
+
+ if (completionHandler != nil) {
+ completionHandler(YES);
+ }
+}
+
+
- (void)respondToLastRequestWithResponse:(__kindof SDLRPCResponse *)response {
[self respondToLastRequestWithResponse:response error:nil];
}
diff --git a/SmartDeviceLink_Example/Classes/ProxyManager.m b/SmartDeviceLink_Example/Classes/ProxyManager.m
index 3d2216a68..adf083eef 100644
--- a/SmartDeviceLink_Example/Classes/ProxyManager.m
+++ b/SmartDeviceLink_Example/Classes/ProxyManager.m
@@ -202,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (SDLLogConfiguration *)sdlex_logConfiguration {
- SDLLogConfiguration *logConfig = [SDLLogConfiguration defaultConfiguration];
+ SDLLogConfiguration *logConfig = [SDLLogConfiguration debugConfiguration];
SDLLogFileModule *sdlExampleModule = [SDLLogFileModule moduleWithName:@"SDL Example" files:[NSSet setWithArray:@[@"ProxyManager"]]];
logConfig.modules = [logConfig.modules setByAddingObject:sdlExampleModule];
logConfig.targets = [logConfig.targets setByAddingObject:[SDLLogTargetFile logger]];
@@ -221,94 +221,6 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - RPC builders
-+ (SDLAddCommand *)sdlex_speakNameCommandWithManager:(SDLManager *)manager {
- NSString *commandName = @"Speak App Name";
-
- SDLMenuParams *commandMenuParams = [[SDLMenuParams alloc] init];
- commandMenuParams.menuName = commandName;
-
- SDLAddCommand *speakNameCommand = [[SDLAddCommand alloc] init];
- speakNameCommand.vrCommands = @[commandName];
- speakNameCommand.menuParams = commandMenuParams;
- speakNameCommand.cmdID = @0;
-
- speakNameCommand.handler = ^void(SDLOnCommand *notification) {
- [manager sendRequest:[self.class sdlex_appNameSpeak]];
- };
-
- return speakNameCommand;
-}
-
-+ (SDLAddCommand *)sdlex_interactionSetCommandWithManager:(SDLManager *)manager {
- NSString *commandName = @"Perform Interaction";
-
- SDLMenuParams *commandMenuParams = [[SDLMenuParams alloc] init];
- commandMenuParams.menuName = commandName;
-
- SDLAddCommand *performInteractionCommand = [[SDLAddCommand alloc] init];
- performInteractionCommand.vrCommands = @[commandName];
- performInteractionCommand.menuParams = commandMenuParams;
- performInteractionCommand.cmdID = @1;
-
- // NOTE: You may want to preload your interaction sets, because they can take a while for the remote system to process. We're going to ignore our own advice here.
- __weak typeof(self) weakSelf = self;
- performInteractionCommand.handler = ^void(SDLOnCommand *notification) {
- [weakSelf sdlex_sendPerformOnlyChoiceInteractionWithManager:manager];
- };
-
- return performInteractionCommand;
-}
-
-+ (SDLAddCommand *)sdlex_vehicleDataCommandWithManager:(SDLManager *)manager {
- SDLMenuParams *commandMenuParams = [[SDLMenuParams alloc] init];
- commandMenuParams.menuName = @"Get Vehicle Data";
-
- SDLAddCommand *getVehicleDataCommand = [[SDLAddCommand alloc] init];
- getVehicleDataCommand.vrCommands = [NSMutableArray arrayWithObject:@"Get Vehicle Data"];
- getVehicleDataCommand.menuParams = commandMenuParams;
- getVehicleDataCommand.cmdID = @2;
-
- getVehicleDataCommand.handler = ^void(SDLOnCommand *notification) {
- [ProxyManager sdlex_sendGetVehicleDataWithManager:manager];
- };
-
- return getVehicleDataCommand;
-}
-
-+ (SDLAddCommand *)sdlex_dialNumberCommandWithManager:(SDLManager *)manager {
- NSString *menuName = @"Dial Number";
- SDLMenuParams *commandMenuParams = [[SDLMenuParams alloc] initWithMenuName:menuName];
-
- SDLAddCommand *dialNumberCommand = [[SDLAddCommand alloc] init];
- dialNumberCommand.vrCommands = [NSMutableArray arrayWithObject:menuName];
- dialNumberCommand.menuParams = commandMenuParams;
- dialNumberCommand.cmdID = @3;
- dialNumberCommand.handler = ^(SDLOnCommand * _Nonnull command) {
- SDLLogD(@"Checking if app has permission to dial a number");
- if (![manager.permissionManager isRPCAllowed:@"DialNumber"]) {
- SDLAlert* alert = [[SDLAlert alloc] init];
- alert.alertText1 = @"This app does not have the required permissions to dial a number";
- [manager sendRequest:alert];
- return;
- }
-
- SDLLogD(@"Checking phone call capability");
- [manager.systemCapabilityManager updateCapabilityType:SDLSystemCapabilityTypePhoneCall completionHandler:^(NSError * _Nullable error, SDLSystemCapabilityManager *systemCapabilityManager) {
- SDLPhoneCapability *phoneCapability = systemCapabilityManager.phoneCapability;
- if (phoneCapability.dialNumberEnabled) {
- SDLLogD(@"Dialing number");
- [self.class sdlex_sendDialNumberWithManager:manager];
- } else {
- SDLAlert* alert = [[SDLAlert alloc] init];
- alert.alertText1 = @"The dial number feature is unavailable for this head unit";
- [manager sendRequest:alert];
- }
- }];
- };
-
- return dialNumberCommand;
-}
-
+ (SDLSpeak *)sdlex_appNameSpeak {
SDLSpeak *speak = [[SDLSpeak alloc] init];
speak.ttsChunks = [SDLTTSChunk textChunksFromString:@"S D L Example App"];
@@ -446,10 +358,36 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)sdlex_prepareRemoteSystem {
- [self.sdlManager sendRequests:@[[self.class sdlex_speakNameCommandWithManager:self.sdlManager], [self.class sdlex_interactionSetCommandWithManager:self.sdlManager], [self.class sdlex_vehicleDataCommandWithManager:self.sdlManager], [self.class sdlex_dialNumberCommandWithManager:self.sdlManager], [self.class sdlex_createOnlyChoiceInteractionSet]]
- progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- SDLLogD(@"Commands sent updated, percent complete %f%%", percentComplete * 100);
- } completionHandler:nil];
+ SDLCreateInteractionChoiceSet *choiceSet = [self.class sdlex_createOnlyChoiceInteractionSet];
+ [self.sdlManager sendRequest:choiceSet];
+
+ __weak typeof(self) weakself = self;
+ SDLMenuCell *speakCell = [[SDLMenuCell alloc] initWithTitle:@"Speak" icon:[SDLArtwork artworkWithImage:[UIImage imageNamed:@"speak"] asImageFormat:SDLArtworkImageFormatPNG] voiceCommands:@[@"Speak"] handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ [weakself.sdlManager sendRequest:[ProxyManager sdlex_appNameSpeak]];
+ }];
+
+ SDLMenuCell *interactionSetCell = [[SDLMenuCell alloc] initWithTitle:@"Perform Interaction" icon:[SDLArtwork artworkWithImage:[UIImage imageNamed:@"choice_set"] asImageFormat:SDLArtworkImageFormatPNG] voiceCommands:@[@"Perform Interaction"] handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ [ProxyManager sdlex_sendPerformOnlyChoiceInteractionWithManager:weakself.sdlManager];
+ }];
+
+ SDLMenuCell *getVehicleDataCell = [[SDLMenuCell alloc] initWithTitle:@"Get Vehicle Data" icon:[SDLArtwork artworkWithImage:[UIImage imageNamed:@"car"] asImageFormat:SDLArtworkImageFormatPNG] voiceCommands:@[@"Get Vehicle Data"] handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ [ProxyManager sdlex_sendGetVehicleDataWithManager:weakself.sdlManager];
+ }];
+
+ NSMutableArray *menuArray = [NSMutableArray array];
+ for (int i = 0; i < 75; i++) {
+ SDLMenuCell *cell = [[SDLMenuCell alloc] initWithTitle:[NSString stringWithFormat:@"%i", i] icon:[SDLArtwork artworkWithImage:[UIImage imageNamed:@"hexagon_on_softbutton_icon"] asImageFormat:SDLArtworkImageFormatPNG] voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource){}];
+ [menuArray addObject:cell];
+ }
+
+ SDLMenuCell *submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Submenu" subCells:[menuArray copy]];
+
+ SDLVoiceCommand *voiceCommand = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test"] handler:^{
+ [ProxyManager sdlex_sendPerformOnlyChoiceInteractionWithManager:weakself.sdlManager];
+ }];
+
+ self.sdlManager.screenManager.menu = @[speakCell, interactionSetCell, getVehicleDataCell, submenuCell];
+ self.sdlManager.screenManager.voiceCommands = @[voiceCommand];
}
diff --git a/SmartDeviceLink_Example/Images.xcassets/car.imageset/Contents.json b/SmartDeviceLink_Example/Images.xcassets/car.imageset/Contents.json
new file mode 100644
index 000000000..da6fe9437
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/car.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "iconmonstr-car-1-64.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/SmartDeviceLink_Example/Images.xcassets/car.imageset/iconmonstr-car-1-64.png b/SmartDeviceLink_Example/Images.xcassets/car.imageset/iconmonstr-car-1-64.png
new file mode 100644
index 000000000..c95eaa035
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/car.imageset/iconmonstr-car-1-64.png
Binary files differ
diff --git a/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/Contents.json b/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/Contents.json
new file mode 100644
index 000000000..e784269b3
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "iconmonstr-text-23-64.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/iconmonstr-text-23-64.png b/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/iconmonstr-text-23-64.png
new file mode 100644
index 000000000..23a0aa481
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/iconmonstr-text-23-64.png
Binary files differ
diff --git a/SmartDeviceLink_Example/Images.xcassets/speak.imageset/Contents.json b/SmartDeviceLink_Example/Images.xcassets/speak.imageset/Contents.json
new file mode 100644
index 000000000..38dbeb7ed
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/speak.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "iconmonstr-speech-bubble-5-64.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/SmartDeviceLink_Example/Images.xcassets/speak.imageset/iconmonstr-speech-bubble-5-64.png b/SmartDeviceLink_Example/Images.xcassets/speak.imageset/iconmonstr-speech-bubble-5-64.png
new file mode 100644
index 000000000..9404acfe5
--- /dev/null
+++ b/SmartDeviceLink_Example/Images.xcassets/speak.imageset/iconmonstr-speech-bubble-5-64.png
Binary files differ