diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2018-05-01 16:05:19 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-01 16:05:19 -0400 |
commit | 2a51aa3b92dc8404bda2d3d62c7ba0ab5acc1466 (patch) | |
tree | ab07f4d9d64934611812424211875ba37475468c | |
parent | d3b7508d80c2edfdddbda4d3004ad64bda1b87bd (diff) | |
parent | 1f132906f6e172b01dd4d55d3404c1b837a57cb4 (diff) | |
download | sdl_ios-2a51aa3b92dc8404bda2d3d62c7ba0ab5acc1466.tar.gz |
Merge pull request #930 from smartdevicelink/feature/issue_927_mobile_menu_manager
Menu Manager
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 Binary files differnew file mode 100644 index 000000000..c95eaa035 --- /dev/null +++ b/SmartDeviceLink_Example/Images.xcassets/car.imageset/iconmonstr-car-1-64.png 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 Binary files differnew file mode 100644 index 000000000..23a0aa481 --- /dev/null +++ b/SmartDeviceLink_Example/Images.xcassets/choice_set.imageset/iconmonstr-text-23-64.png 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 Binary files differnew file mode 100644 index 000000000..9404acfe5 --- /dev/null +++ b/SmartDeviceLink_Example/Images.xcassets/speak.imageset/iconmonstr-speech-bubble-5-64.png |