summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2021-09-24 08:53:59 -0400
committerJoel Fischer <joeljfischer@gmail.com>2021-09-24 08:53:59 -0400
commit60104522aad1c45cdd55fa0c5b73e4b369e79582 (patch)
treeef051bd14ff9f9c6a10c12378fffb7df19c0002e
parent7ef29ccad916ad794aa7983817a4aa3a9fc59a5e (diff)
parent4c1fe3f7b15c11763adc88b2078b609df068e031 (diff)
downloadsdl_ios-60104522aad1c45cdd55fa0c5b73e4b369e79582.tar.gz
Merge branch 'develop' into feature/issue-1898-menu-manager-refactor
# Conflicts: # SmartDeviceLink-iOS.xcodeproj/project.pbxproj # SmartDeviceLink-iOS.xcodeproj/xcshareddata/xcschemes/SmartDeviceLink-Example-Swift.xcscheme
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj86
-rw-r--r--SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.h4
-rw-r--r--SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.m11
-rw-r--r--SmartDeviceLink/private/SDLChoiceSetManager.m447
-rw-r--r--SmartDeviceLink/private/SDLDeleteChoicesOperation.h7
-rw-r--r--SmartDeviceLink/private/SDLDeleteChoicesOperation.m56
-rw-r--r--SmartDeviceLink/private/SDLError.h5
-rw-r--r--SmartDeviceLink/private/SDLError.m27
-rw-r--r--SmartDeviceLink/private/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/private/SDLPreloadChoicesOperation.h43
-rw-r--r--SmartDeviceLink/private/SDLPreloadChoicesOperation.m243
-rw-r--r--SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.h41
-rw-r--r--SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.m632
-rw-r--r--SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.h32
-rw-r--r--SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.m233
-rw-r--r--SmartDeviceLink/private/SDLPresentChoiceSetOperation.h62
-rw-r--r--SmartDeviceLink/private/SDLPresentChoiceSetOperation.m348
-rw-r--r--SmartDeviceLink/private/SDLProtocol.m56
-rw-r--r--SmartDeviceLink/private/SDLSecurityQueryErrorCode.h63
-rw-r--r--SmartDeviceLink/private/SDLSecurityQueryErrorCode.m48
-rw-r--r--SmartDeviceLink/private/SDLSecurityQueryPayload.h83
-rw-r--r--SmartDeviceLink/private/SDLSecurityQueryPayload.m166
-rw-r--r--SmartDeviceLink/public/SDLChoiceCell.m23
-rw-r--r--SmartDeviceLink/public/SDLChoiceSet.m2
-rw-r--r--SmartDeviceLink/public/SDLErrorConstants.h12
-rw-r--r--SmartDeviceLink/public/SDLMenuCell.m1
-rw-r--r--SmartDeviceLink/public/SDLSeatControlCapabilities.h2
-rw-r--r--SmartDeviceLink/public/SDLSystemCapabilityManager.m92
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m18
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m733
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLDeleteChoicesOperationSpec.m25
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m417
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m1070
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationUtilitiesSpec.m504
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLPresentChoiceSetOperationSpec.m549
-rw-r--r--SmartDeviceLinkTests/RPCSpecs/PayloadSpecs/SDLSecurityQueryPayloadSpec.m80
36 files changed, 3399 insertions, 2824 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 8406bd793..4fe6cebb4 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -612,13 +612,11 @@
4ABB256324F7E5AA0061BF55 /* SDLPermissionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256124F7E5AA0061BF55 /* SDLPermissionElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
4ABB256624F7E5B80061BF55 /* SDLRPCPermissionStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256424F7E5B70061BF55 /* SDLRPCPermissionStatus.m */; };
4ABB256724F7E5B80061BF55 /* SDLRPCPermissionStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256524F7E5B80061BF55 /* SDLRPCPermissionStatus.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 4ABB257024F7E5E80061BF55 /* SDLPreloadChoicesOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256824F7E5E70061BF55 /* SDLPreloadChoicesOperation.m */; };
- 4ABB257124F7E5E80061BF55 /* SDLPresentChoiceSetOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256924F7E5E70061BF55 /* SDLPresentChoiceSetOperation.m */; };
+ 4ABB257024F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256824F7E5E70061BF55 /* SDLPreloadPresentChoicesOperation.m */; };
4ABB257224F7E5E80061BF55 /* SDLCheckChoiceVROptionalOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256A24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.m */; };
4ABB257324F7E5E80061BF55 /* SDLCheckChoiceVROptionalOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256B24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.h */; };
- 4ABB257424F7E5E80061BF55 /* SDLPresentChoiceSetOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256C24F7E5E70061BF55 /* SDLPresentChoiceSetOperation.h */; };
4ABB257524F7E5E80061BF55 /* SDLDeleteChoicesOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB256D24F7E5E70061BF55 /* SDLDeleteChoicesOperation.m */; };
- 4ABB257624F7E5E80061BF55 /* SDLPreloadChoicesOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256E24F7E5E80061BF55 /* SDLPreloadChoicesOperation.h */; };
+ 4ABB257624F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256E24F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.h */; };
4ABB257724F7E5E80061BF55 /* SDLDeleteChoicesOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB256F24F7E5E80061BF55 /* SDLDeleteChoicesOperation.h */; };
4ABB257A24F7E5FF0061BF55 /* SDLPresentKeyboardOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB257824F7E5FF0061BF55 /* SDLPresentKeyboardOperation.m */; };
4ABB257B24F7E5FF0061BF55 /* SDLPresentKeyboardOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB257924F7E5FF0061BF55 /* SDLPresentKeyboardOperation.h */; };
@@ -1407,6 +1405,9 @@
4ABC1CB125DC520300545AC6 /* SDLMenuShowOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABC1CB025DC520300545AC6 /* SDLMenuShowOperationSpec.m */; };
4ABED25B257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABED259257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m */; };
4ABED25C257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABED25A257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h */; };
+ 4AC0128026D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AC0127E26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.h */; };
+ 4AC0128126D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC0127F26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.m */; };
+ 4AC0128326D9686F00537E31 /* SDLPreloadPresentChoicesOperationUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC0128226D9686F00537E31 /* SDLPreloadPresentChoicesOperationUtilitiesSpec.m */; };
4AD1F1742559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD1F1732559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m */; };
4AE8A7022537796E000666C0 /* SmartDeviceLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AE8A7012537796E000666C0 /* SmartDeviceLink.h */; settings = {ATTRIBUTES = (Public, ); }; };
5D0A9F911F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D0A9F901F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m */; };
@@ -1562,8 +1563,7 @@
5DE35E4A20CB1BF70034BE5A /* SDLChoiceSetManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E4920CB1BF70034BE5A /* SDLChoiceSetManagerSpec.m */; };
5DE35E4C20CB1C1C0034BE5A /* SDLCheckChoiceVROptionalOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E4B20CB1C1C0034BE5A /* SDLCheckChoiceVROptionalOperationSpec.m */; };
5DE35E4E20CB1C2C0034BE5A /* SDLDeleteChoicesOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E4D20CB1C2C0034BE5A /* SDLDeleteChoicesOperationSpec.m */; };
- 5DE35E5220CB1C490034BE5A /* SDLPreloadChoicesOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E5120CB1C490034BE5A /* SDLPreloadChoicesOperationSpec.m */; };
- 5DE35E5420CB1C590034BE5A /* SDLPresentChoiceSetOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E5320CB1C590034BE5A /* SDLPresentChoiceSetOperationSpec.m */; };
+ 5DE35E5220CB1C490034BE5A /* SDLPreloadPresentChoicesOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E5120CB1C490034BE5A /* SDLPreloadPresentChoicesOperationSpec.m */; };
5DE35E5620CB1C680034BE5A /* SDLPresentKeyboardOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE35E5520CB1C680034BE5A /* SDLPresentKeyboardOperationSpec.m */; };
5DE372A41ACB336600849FAA /* SDLHMICapabilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE372A31ACB336600849FAA /* SDLHMICapabilitiesSpec.m */; };
5DEF695D1FD6FA01004B8C2F /* testAudio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 5DEF695C1FD6FA01004B8C2F /* testAudio.mp3 */; };
@@ -1752,6 +1752,8 @@
B3DF19F3251225AA0090D7BA /* TestSmartConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B3DF19F1251225A90090D7BA /* TestSmartConnection.m */; };
B3EC9E6E2579AA010039F3AA /* SDLClimateDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = B3EC9E6D2579AA010039F3AA /* SDLClimateDataSpec.m */; };
B3F7918324E062C200DB5CAF /* SDLGetVehicleDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 162E824C1A9BDE8A00906325 /* SDLGetVehicleDataSpec.m */; };
+ C93193DC26B1B57C008203EC /* SDLSecurityQueryPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = C93193DA26B1B57B008203EC /* SDLSecurityQueryPayload.h */; };
+ C93193DD26B1B57C008203EC /* SDLSecurityQueryPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = C93193DB26B1B57B008203EC /* SDLSecurityQueryPayload.m */; };
C9707D1825DEE786009D00F2 /* NSArray+Extensions.h in Headers */ = {isa = PBXBuildFile; fileRef = C9707D1625DEE786009D00F2 /* NSArray+Extensions.h */; };
C9707D1925DEE786009D00F2 /* NSArray+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = C9707D1725DEE786009D00F2 /* NSArray+Extensions.m */; };
C9707D3125E0444D009D00F2 /* SDLMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = C9707D2F25E0444D009D00F2 /* SDLMacros.m */; };
@@ -1760,6 +1762,9 @@
C971E3F02649D12D00FC24D6 /* NSMutableDictionary+StoreSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = BB3C600D221AEF37007DD4CA /* NSMutableDictionary+StoreSpec.m */; };
C975877F257AEFDB0066F271 /* SDLSeekIndicatorTypeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = C975877E257AEFDB0066F271 /* SDLSeekIndicatorTypeSpec.m */; };
C9758785257F4C570066F271 /* SDLSeekStreamingIndicatorSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = C9758784257F4C570066F271 /* SDLSeekStreamingIndicatorSpec.m */; };
+ C99BE00926C53E7F00DB0B54 /* SDLSecurityQueryErrorCode.h in Headers */ = {isa = PBXBuildFile; fileRef = C99BE00726C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.h */; };
+ C99BE00A26C53E7F00DB0B54 /* SDLSecurityQueryErrorCode.m in Sources */ = {isa = PBXBuildFile; fileRef = C99BE00826C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.m */; };
+ C99BE00D26C5B23000DB0B54 /* SDLSecurityQueryPayloadSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = C99BE00C26C5B23000DB0B54 /* SDLSecurityQueryPayloadSpec.m */; };
C9AA62C4261E5035000F49BC /* SDLVoiceCommandManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */; };
C9DFFE78257ACE0000F7D57A /* SDLSeekStreamingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = C9DFFE76257ACE0000F7D57A /* SDLSeekStreamingIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
C9DFFE79257ACE0000F7D57A /* SDLSeekStreamingIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C9DFFE77257ACE0000F7D57A /* SDLSeekStreamingIndicator.m */; };
@@ -2493,13 +2498,11 @@
4ABB256124F7E5AA0061BF55 /* SDLPermissionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLPermissionElement.h; path = public/SDLPermissionElement.h; sourceTree = "<group>"; };
4ABB256424F7E5B70061BF55 /* SDLRPCPermissionStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLRPCPermissionStatus.m; path = public/SDLRPCPermissionStatus.m; sourceTree = "<group>"; };
4ABB256524F7E5B80061BF55 /* SDLRPCPermissionStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLRPCPermissionStatus.h; path = public/SDLRPCPermissionStatus.h; sourceTree = "<group>"; };
- 4ABB256824F7E5E70061BF55 /* SDLPreloadChoicesOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadChoicesOperation.m; path = private/SDLPreloadChoicesOperation.m; sourceTree = "<group>"; };
- 4ABB256924F7E5E70061BF55 /* SDLPresentChoiceSetOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLPresentChoiceSetOperation.m; path = private/SDLPresentChoiceSetOperation.m; sourceTree = "<group>"; };
+ 4ABB256824F7E5E70061BF55 /* SDLPreloadPresentChoicesOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadPresentChoicesOperation.m; path = private/SDLPreloadPresentChoicesOperation.m; sourceTree = "<group>"; };
4ABB256A24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLCheckChoiceVROptionalOperation.m; path = private/SDLCheckChoiceVROptionalOperation.m; sourceTree = "<group>"; };
4ABB256B24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLCheckChoiceVROptionalOperation.h; path = private/SDLCheckChoiceVROptionalOperation.h; sourceTree = "<group>"; };
- 4ABB256C24F7E5E70061BF55 /* SDLPresentChoiceSetOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLPresentChoiceSetOperation.h; path = private/SDLPresentChoiceSetOperation.h; sourceTree = "<group>"; };
4ABB256D24F7E5E70061BF55 /* SDLDeleteChoicesOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLDeleteChoicesOperation.m; path = private/SDLDeleteChoicesOperation.m; sourceTree = "<group>"; };
- 4ABB256E24F7E5E80061BF55 /* SDLPreloadChoicesOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLPreloadChoicesOperation.h; path = private/SDLPreloadChoicesOperation.h; sourceTree = "<group>"; };
+ 4ABB256E24F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLPreloadPresentChoicesOperation.h; path = private/SDLPreloadPresentChoicesOperation.h; sourceTree = "<group>"; };
4ABB256F24F7E5E80061BF55 /* SDLDeleteChoicesOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLDeleteChoicesOperation.h; path = private/SDLDeleteChoicesOperation.h; sourceTree = "<group>"; };
4ABB257824F7E5FF0061BF55 /* SDLPresentKeyboardOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLPresentKeyboardOperation.m; path = private/SDLPresentKeyboardOperation.m; sourceTree = "<group>"; };
4ABB257924F7E5FF0061BF55 /* SDLPresentKeyboardOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLPresentKeyboardOperation.h; path = private/SDLPresentKeyboardOperation.h; sourceTree = "<group>"; };
@@ -3290,6 +3293,9 @@
4ABC1CB025DC520300545AC6 /* SDLMenuShowOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuShowOperationSpec.m; path = DevAPISpecs/SDLMenuShowOperationSpec.m; sourceTree = "<group>"; };
4ABED259257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandUpdateOperation.m; path = private/SDLVoiceCommandUpdateOperation.m; sourceTree = "<group>"; };
4ABED25A257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLVoiceCommandUpdateOperation.h; path = private/SDLVoiceCommandUpdateOperation.h; sourceTree = "<group>"; };
+ 4AC0127E26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLPreloadPresentChoicesOperationUtilities.h; path = private/SDLPreloadPresentChoicesOperationUtilities.h; sourceTree = "<group>"; };
+ 4AC0127F26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadPresentChoicesOperationUtilities.m; path = private/SDLPreloadPresentChoicesOperationUtilities.m; sourceTree = "<group>"; };
+ 4AC0128226D9686F00537E31 /* SDLPreloadPresentChoicesOperationUtilitiesSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadPresentChoicesOperationUtilitiesSpec.m; path = DevAPISpecs/SDLPreloadPresentChoicesOperationUtilitiesSpec.m; sourceTree = "<group>"; };
4AD1F1732559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandUpdateOperationSpec.m; path = DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m; sourceTree = "<group>"; };
4AE8A7012537796E000666C0 /* SmartDeviceLink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SmartDeviceLink.h; path = public/SmartDeviceLink.h; sourceTree = "<group>"; };
4AE8A707253779F9000666C0 /* EAAccessory+OCMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "EAAccessory+OCMock.h"; sourceTree = "<group>"; };
@@ -3489,8 +3495,7 @@
5DE35E4920CB1BF70034BE5A /* SDLChoiceSetManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLChoiceSetManagerSpec.m; path = DevAPISpecs/SDLChoiceSetManagerSpec.m; sourceTree = "<group>"; };
5DE35E4B20CB1C1C0034BE5A /* SDLCheckChoiceVROptionalOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLCheckChoiceVROptionalOperationSpec.m; path = DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m; sourceTree = "<group>"; };
5DE35E4D20CB1C2C0034BE5A /* SDLDeleteChoicesOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLDeleteChoicesOperationSpec.m; path = DevAPISpecs/SDLDeleteChoicesOperationSpec.m; sourceTree = "<group>"; };
- 5DE35E5120CB1C490034BE5A /* SDLPreloadChoicesOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadChoicesOperationSpec.m; path = DevAPISpecs/SDLPreloadChoicesOperationSpec.m; sourceTree = "<group>"; };
- 5DE35E5320CB1C590034BE5A /* SDLPresentChoiceSetOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPresentChoiceSetOperationSpec.m; path = DevAPISpecs/SDLPresentChoiceSetOperationSpec.m; sourceTree = "<group>"; };
+ 5DE35E5120CB1C490034BE5A /* SDLPreloadPresentChoicesOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPreloadPresentChoicesOperationSpec.m; path = DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m; sourceTree = "<group>"; };
5DE35E5520CB1C680034BE5A /* SDLPresentKeyboardOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPresentKeyboardOperationSpec.m; path = DevAPISpecs/SDLPresentKeyboardOperationSpec.m; sourceTree = "<group>"; };
5DE372A31ACB336600849FAA /* SDLHMICapabilitiesSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLHMICapabilitiesSpec.m; sourceTree = "<group>"; };
5DEE55BF1B8509CB004F0D0F /* SDLURLRequestTaskSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLURLRequestTaskSpec.m; path = "UtilitiesSpecs/HTTP Connection/SDLURLRequestTaskSpec.m"; sourceTree = "<group>"; };
@@ -3682,12 +3687,17 @@
B3DF19F2251225A90090D7BA /* TestSmartConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestSmartConnection.h; sourceTree = "<group>"; };
B3EC9E6D2579AA010039F3AA /* SDLClimateDataSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLClimateDataSpec.m; sourceTree = "<group>"; };
BB3C600D221AEF37007DD4CA /* NSMutableDictionary+StoreSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "NSMutableDictionary+StoreSpec.m"; path = "DevAPISpecs/NSMutableDictionary+StoreSpec.m"; sourceTree = "<group>"; };
+ C93193DA26B1B57B008203EC /* SDLSecurityQueryPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLSecurityQueryPayload.h; path = private/SDLSecurityQueryPayload.h; sourceTree = "<group>"; };
+ C93193DB26B1B57B008203EC /* SDLSecurityQueryPayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLSecurityQueryPayload.m; path = private/SDLSecurityQueryPayload.m; sourceTree = "<group>"; };
C9707D1625DEE786009D00F2 /* NSArray+Extensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSArray+Extensions.h"; path = "private/NSArray+Extensions.h"; sourceTree = "<group>"; };
C9707D1725DEE786009D00F2 /* NSArray+Extensions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "NSArray+Extensions.m"; path = "private/NSArray+Extensions.m"; sourceTree = "<group>"; };
C9707D2E25E0444D009D00F2 /* SDLMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLMacros.h; path = private/SDLMacros.h; sourceTree = "<group>"; };
C9707D2F25E0444D009D00F2 /* SDLMacros.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMacros.m; path = private/SDLMacros.m; sourceTree = "<group>"; };
C975877E257AEFDB0066F271 /* SDLSeekIndicatorTypeSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSeekIndicatorTypeSpec.m; sourceTree = "<group>"; };
C9758784257F4C570066F271 /* SDLSeekStreamingIndicatorSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSeekStreamingIndicatorSpec.m; sourceTree = "<group>"; };
+ C99BE00726C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLSecurityQueryErrorCode.h; path = private/SDLSecurityQueryErrorCode.h; sourceTree = "<group>"; };
+ C99BE00826C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSecurityQueryErrorCode.m; path = private/SDLSecurityQueryErrorCode.m; sourceTree = "<group>"; };
+ C99BE00C26C5B23000DB0B54 /* SDLSecurityQueryPayloadSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSecurityQueryPayloadSpec.m; sourceTree = "<group>"; };
C9DFFE76257ACE0000F7D57A /* SDLSeekStreamingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLSeekStreamingIndicator.h; path = public/SDLSeekStreamingIndicator.h; sourceTree = "<group>"; };
C9DFFE77257ACE0000F7D57A /* SDLSeekStreamingIndicator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSeekStreamingIndicator.m; path = public/SDLSeekStreamingIndicator.m; sourceTree = "<group>"; };
C9DFFE7C257AD07E00F7D57A /* SDLSeekIndicatorType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLSeekIndicatorType.h; path = public/SDLSeekIndicatorType.h; sourceTree = "<group>"; };
@@ -4005,6 +4015,7 @@
isa = PBXGroup;
children = (
162E823C1A9BDE8A00906325 /* SDLRPCPayloadSpec.m */,
+ C99BE00C26C5B23000DB0B54 /* SDLSecurityQueryPayloadSpec.m */,
);
path = PayloadSpecs;
sourceTree = "<group>";
@@ -4518,6 +4529,15 @@
4ABC1CB025DC520300545AC6 /* SDLMenuShowOperationSpec.m */,
);
name = Operations;
+ sourceTree = "<group>";
+ };
+ 4AC0127B26D960E900537E31 /* Utilities */ = {
+ isa = PBXGroup;
+ children = (
+ 4AC0127E26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.h */,
+ 4AC0127F26D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.m */,
+ );
+ name = Utilities;
sourceTree = "<group>";
};
4AD1F16A2559952D00637FE1 /* Voice Command */ = {
@@ -5881,6 +5901,7 @@
5D5935011A851D7E00687FB9 /* Header */ = {
isa = PBXGroup;
children = (
+ C904F1DD26D919EF00E073DA /* SecurityQuery */,
4A8BD32824F9431B000945E3 /* SDLProtocolHeader.h */,
4A8BD32924F9431B000945E3 /* SDLProtocolHeader.m */,
4A8BD32724F9431B000945E3 /* SDLV1ProtocolHeader.h */,
@@ -6199,14 +6220,13 @@
5D92936D20B5E07000FCC775 /* Operations */ = {
isa = PBXGroup;
children = (
+ 4AC0127B26D960E900537E31 /* Utilities */,
4ABB256B24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.h */,
4ABB256A24F7E5E70061BF55 /* SDLCheckChoiceVROptionalOperation.m */,
4ABB256F24F7E5E80061BF55 /* SDLDeleteChoicesOperation.h */,
4ABB256D24F7E5E70061BF55 /* SDLDeleteChoicesOperation.m */,
- 4ABB256E24F7E5E80061BF55 /* SDLPreloadChoicesOperation.h */,
- 4ABB256C24F7E5E70061BF55 /* SDLPresentChoiceSetOperation.h */,
- 4ABB256824F7E5E70061BF55 /* SDLPreloadChoicesOperation.m */,
- 4ABB256924F7E5E70061BF55 /* SDLPresentChoiceSetOperation.m */,
+ 4ABB256E24F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.h */,
+ 4ABB256824F7E5E70061BF55 /* SDLPreloadPresentChoicesOperation.m */,
4ABB257924F7E5FF0061BF55 /* SDLPresentKeyboardOperation.h */,
4ABB257824F7E5FF0061BF55 /* SDLPresentKeyboardOperation.m */,
);
@@ -6764,9 +6784,9 @@
children = (
5DE35E4B20CB1C1C0034BE5A /* SDLCheckChoiceVROptionalOperationSpec.m */,
5DE35E4D20CB1C2C0034BE5A /* SDLDeleteChoicesOperationSpec.m */,
- 5DE35E5120CB1C490034BE5A /* SDLPreloadChoicesOperationSpec.m */,
- 5DE35E5320CB1C590034BE5A /* SDLPresentChoiceSetOperationSpec.m */,
5DE35E5520CB1C680034BE5A /* SDLPresentKeyboardOperationSpec.m */,
+ 5DE35E5120CB1C490034BE5A /* SDLPreloadPresentChoicesOperationSpec.m */,
+ 4AC0128226D9686F00537E31 /* SDLPreloadPresentChoicesOperationUtilitiesSpec.m */,
);
name = Operations;
sourceTree = "<group>";
@@ -7125,6 +7145,17 @@
name = Frameworks;
sourceTree = "<group>";
};
+ C904F1DD26D919EF00E073DA /* SecurityQuery */ = {
+ isa = PBXGroup;
+ children = (
+ C93193DA26B1B57B008203EC /* SDLSecurityQueryPayload.h */,
+ C93193DB26B1B57B008203EC /* SDLSecurityQueryPayload.m */,
+ C99BE00726C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.h */,
+ C99BE00826C53E7E00DB0B54 /* SDLSecurityQueryErrorCode.m */,
+ );
+ name = SecurityQuery;
+ sourceTree = "<group>";
+ };
DA1166D71D14601C00438CEA /* Touches */ = {
isa = PBXGroup;
children = (
@@ -7340,6 +7371,7 @@
4ABB28D524F82A6A0061BF55 /* SDLOnButtonEvent.h in Headers */,
4ABB2B7024F84FE50061BF55 /* SDLFuelRange.h in Headers */,
4ABB25D624F7E7630061BF55 /* SDLImageField+ScreenManagerExtensions.h in Headers */,
+ C99BE00926C53E7F00DB0B54 /* SDLSecurityQueryErrorCode.h in Headers */,
4ABB299324F845440061BF55 /* SDLSetAppIcon.h in Headers */,
4ABB2A3B24F847980061BF55 /* SDLDeleteInteractionChoiceSetResponse.h in Headers */,
4ABB275324F7FD9C0061BF55 /* SDLECallConfirmationStatus.h in Headers */,
@@ -7404,6 +7436,7 @@
4ABB270324F7FB8F0061BF55 /* SDLButtonName.h in Headers */,
881BBF50255AC27000761B7E /* SDLAlertView.h in Headers */,
4ABB25B324F7E6F60061BF55 /* SDLSoftButtonTransitionOperation.h in Headers */,
+ 4AC0128026D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.h in Headers */,
4ABB24C624F592900061BF55 /* SDLError.h in Headers */,
5D9FDA901F2A7D3400A495C8 /* bson_array.h in Headers */,
4ABB2B8924F8504A0061BF55 /* SDLGrid.h in Headers */,
@@ -7466,7 +7499,6 @@
4ABB25CC24F7E74F0061BF55 /* SDLTextAndGraphicManager.h in Headers */,
4A8BD3B824F98F64000945E3 /* SDLOnUpdateSubMenu.h in Headers */,
4ABB291324F842160061BF55 /* SDLCreateInteractionChoiceSet.h in Headers */,
- 4ABB257424F7E5E80061BF55 /* SDLPresentChoiceSetOperation.h in Headers */,
4ABB2A3024F847980061BF55 /* SDLDiagnosticMessageResponse.h in Headers */,
4ABB2A7224F847D40061BF55 /* SDLPutFileResponse.h in Headers */,
4ABB24E224F5948D0061BF55 /* SDLEncryptionConfiguration.h in Headers */,
@@ -7650,7 +7682,7 @@
B3A9DB2825D4C3B000CDFD21 /* SDLOnAppCapabilityUpdated.h in Headers */,
4ABB275424F7FD9C0061BF55 /* SDLElectronicParkBrakeStatus.h in Headers */,
4ABB2B5524F84EF50061BF55 /* SDLDriverDistractionCapability.h in Headers */,
- 4ABB257624F7E5E80061BF55 /* SDLPreloadChoicesOperation.h in Headers */,
+ 4ABB257624F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.h in Headers */,
4ABB28E724F82A6A0061BF55 /* SDLOnAudioPassThru.h in Headers */,
4ABB25AD24F7E6E10061BF55 /* SDLMenuManager.h in Headers */,
88EE1A452502DB3100FFEBA9 /* SDLLockScreenConstants.h in Headers */,
@@ -7750,6 +7782,7 @@
4ABB2B5424F84EF50061BF55 /* SDLClimateControlData.h in Headers */,
4A8BD2B624F935BC000945E3 /* SDLSystemCapability.h in Headers */,
4ABB2AA624F847F40061BF55 /* SDLSetInteriorVehicleDataResponse.h in Headers */,
+ C93193DC26B1B57C008203EC /* SDLSecurityQueryPayload.h in Headers */,
4ABB264F24F7F5720061BF55 /* SDLConfiguration.h in Headers */,
4ABB252224F7E3FC0061BF55 /* SDLLifecycleSyncPDataHandler.h in Headers */,
4ABB29FD24F8477F0061BF55 /* SDLAlertResponse.h in Headers */,
@@ -8003,7 +8036,7 @@
};
5D61FA1B1A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
- LastSwiftMigration = 1210;
+ LastSwiftMigration = 1250;
ProvisioningStyle = Automatic;
};
5D61FA251A84237100846EE7 = {
@@ -8311,6 +8344,7 @@
4A8BD24824F93135000945E3 /* SDLMassageCushionFirmness.m in Sources */,
4ABB24DC24F594560061BF55 /* SDLEncryptionLifecycleManager.m in Sources */,
4ABB275524F7FD9C0061BF55 /* SDLDriverDistractionState.m in Sources */,
+ 4AC0128126D9612E00537E31 /* SDLPreloadPresentChoicesOperationUtilities.m in Sources */,
4ABB264124F7F45B0061BF55 /* SDLSystemCapabilityObserver.m in Sources */,
4ABB25F924F7E7EF0061BF55 /* SDLTouchManager.m in Sources */,
4ABB286024F828E00061BF55 /* SDLUpdateMode.m in Sources */,
@@ -8388,7 +8422,7 @@
4ABB258224F7E61E0061BF55 /* SDLChoiceCell.m in Sources */,
4ABB288D24F82A340061BF55 /* SDLVideoStreamingProtocol.m in Sources */,
B3A9DAFD25D4943E00CDFD21 /* SDLAppCapability.m in Sources */,
- 4ABB257024F7E5E80061BF55 /* SDLPreloadChoicesOperation.m in Sources */,
+ 4ABB257024F7E5E80061BF55 /* SDLPreloadPresentChoicesOperation.m in Sources */,
4ABB2B3F24F84EF50061BF55 /* SDLCloudAppProperties.m in Sources */,
4A8BD27C24F9343F000945E3 /* SDLPhoneCapability.m in Sources */,
4ABB269724F7F9400061BF55 /* SDLRPCFunctionNames.m in Sources */,
@@ -8446,6 +8480,7 @@
4ABB2BA424F850AE0061BF55 /* SDLLightCapabilities.m in Sources */,
B3A9DB0625D497FB00CDFD21 /* SDLAppCapabilityType.m in Sources */,
4ABB272224F7FCAE0061BF55 /* SDLDefrostZone.m in Sources */,
+ C93193DD26B1B57C008203EC /* SDLSecurityQueryPayload.m in Sources */,
4ABB24BB24F592620061BF55 /* NSMutableArray+Safe.m in Sources */,
4ABB25AA24F7E6E10061BF55 /* SDLMenuManager.m in Sources */,
4ABB25DD24F7E77C0061BF55 /* SDLScreenManager.m in Sources */,
@@ -8515,7 +8550,6 @@
4ABB29B424F845DB0061BF55 /* SDLSpeak.m in Sources */,
4A8BD3AF24F98ACE000945E3 /* SDLLogManager.m in Sources */,
4A39C6FA25E84C87005C8943 /* SDLKeyboardCapabilities.m in Sources */,
- 4ABB257124F7E5E80061BF55 /* SDLPresentChoiceSetOperation.m in Sources */,
4ABB2A5624F847B10061BF55 /* SDLGetWayPointsResponse.m in Sources */,
4ABB24F524F595120061BF55 /* SDLArtwork.m in Sources */,
4ABB27B124F7FFDA0061BF55 /* SDLMassageCushion.m in Sources */,
@@ -8525,6 +8559,7 @@
4ABB282F24F824E70061BF55 /* SDLTextAlignment.m in Sources */,
4A8BD31124F938D6000945E3 /* SDLWeatherServiceManifest.m in Sources */,
4ABB271524F7FC4E0061BF55 /* SDLCompassDirection.m in Sources */,
+ C99BE00A26C53E7F00DB0B54 /* SDLSecurityQueryErrorCode.m in Sources */,
4ABB254424F7E48D0061BF55 /* SDLLockScreenRootViewController.m in Sources */,
4A93895A25B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.m in Sources */,
4ABB265F24F7F5F20061BF55 /* SDLNotificationDispatcher.m in Sources */,
@@ -8935,6 +8970,7 @@
1680B11D1A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m in Sources */,
8BBEA6091F324832003EEA26 /* SDLMetadataTypeSpec.m in Sources */,
5DAD5F8B20508F140025624C /* SDLSoftButtonObjectSpec.m in Sources */,
+ C99BE00D26C5B23000DB0B54 /* SDLSecurityQueryPayloadSpec.m in Sources */,
DA9F7E9E1DCC05B900ACAE48 /* SDLWaypointTypeSpec.m in Sources */,
5D76E31C1D3805FF00647CFA /* SDLLockScreenManagerSpec.m in Sources */,
162E82DA1A9BDE8B00906325 /* SDLDriverDistractionStateSpec.m in Sources */,
@@ -8984,6 +9020,7 @@
5DA23FF61F2FAA31009C0313 /* SDLControlFramePayloadRPCStartServiceSpec.m in Sources */,
162E83201A9BDE8B00906325 /* SDLOnVehicleDataSpec.m in Sources */,
1EAA47762036B847000FE74B /* SDLEqualizerSettingsSpec.m in Sources */,
+ 4AC0128326D9686F00537E31 /* SDLPreloadPresentChoicesOperationUtilitiesSpec.m in Sources */,
752ECDB9228C42E100D945F4 /* SDLMenuRunScoreSpec.m in Sources */,
162E83141A9BDE8B00906325 /* SDLOnDriverDistractionSpec.m in Sources */,
B3838A09257C4EB400420C11 /* SDLDoorStatusSpec.m in Sources */,
@@ -9049,7 +9086,6 @@
162E83131A9BDE8B00906325 /* SDLOnCommandSpec.m in Sources */,
162E833A1A9BDE8B00906325 /* SDLSetDisplayLayoutSpec.m in Sources */,
1EE8C4401F348D3200FDC2CF /* SDLClimateControlCapabilitiesSpec.m in Sources */,
- 5DE35E5420CB1C590034BE5A /* SDLPresentChoiceSetOperationSpec.m in Sources */,
162E838F1A9BDE8B00906325 /* SDLTextFieldSpec.m in Sources */,
8818ADDD2100FE0C007D6F19 /* SDLTurnSignalSpec.m in Sources */,
162E82CA1A9BDE8A00906325 /* SDLAmbientLightStatusSpec.m in Sources */,
@@ -9108,7 +9144,7 @@
5DA23FF81F2FAF2D009C0313 /* SDLControlFramePayloadRPCStartServiceAckSpec.m in Sources */,
162E83191A9BDE8B00906325 /* SDLOnLanguageChangeSpec.m in Sources */,
5DE35E4E20CB1C2C0034BE5A /* SDLDeleteChoicesOperationSpec.m in Sources */,
- 5DE35E5220CB1C490034BE5A /* SDLPreloadChoicesOperationSpec.m in Sources */,
+ 5DE35E5220CB1C490034BE5A /* SDLPreloadPresentChoicesOperationSpec.m in Sources */,
5DADA7781F4E059E0084D17D /* SDLRectangleSpec.m in Sources */,
4A457DC324A2933E00386CBA /* SDLLifecycleRPCAdapterSpec.m in Sources */,
5DB1BCDD1D243DC3002FFC37 /* SDLLifecycleConfigurationSpec.m in Sources */,
diff --git a/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.h b/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.h
index a7a9f14d0..cf83e3ad9 100644
--- a/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.h
+++ b/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.h
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLCheckChoiceVROptionalOperation : SDLAsynchronousOperation
-@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
+typedef void(^SDLCheckChoiceVROptionalCompletionHandler)(BOOL isVROptional, NSError *_Nullable error);
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager;
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager completionHandler:(SDLCheckChoiceVROptionalCompletionHandler)completionHandler;
@end
diff --git a/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.m b/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.m
index 0615cb01a..f9ee3168b 100644
--- a/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.m
+++ b/SmartDeviceLink/private/SDLCheckChoiceVROptionalOperation.m
@@ -20,18 +20,21 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) NSUUID *operationId;
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (copy, nonatomic) SDLCheckChoiceVROptionalCompletionHandler vrOptionalCompletionHandler;
@end
@implementation SDLCheckChoiceVROptionalOperation
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager {
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager completionHandler:(nonnull SDLCheckChoiceVROptionalCompletionHandler)completionHandler {
self = [super init];
if (!self) { return nil; }
_connectionManager = connectionManager;
_operationId = [NSUUID UUID];
+ _vrOptionalCompletionHandler = completionHandler;
return self;
}
@@ -96,6 +99,12 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Property Overrides
+- (void)finishOperation {
+ self.vrOptionalCompletionHandler(self.isVROptional, self.internalError);
+
+ [super finishOperation];
+}
+
- (nullable NSString *)name {
return [NSString stringWithFormat:@"%@ - %@", self.class, self.operationId];
}
diff --git a/SmartDeviceLink/private/SDLChoiceSetManager.m b/SmartDeviceLink/private/SDLChoiceSetManager.m
index 68f77cdd6..006cff639 100644
--- a/SmartDeviceLink/private/SDLChoiceSetManager.m
+++ b/SmartDeviceLink/private/SDLChoiceSetManager.m
@@ -14,8 +14,7 @@
#import "SDLDeleteChoicesOperation.h"
#import "SDLError.h"
#import "SDLGlobals.h"
-#import "SDLPreloadChoicesOperation.h"
-#import "SDLPresentChoiceSetOperation.h"
+#import "SDLPreloadPresentChoicesOperation.h"
#import "SDLPresentKeyboardOperation.h"
#import "SDLRPCNotificationNotification.h"
#import "SDLRPCResponseNotification.h"
@@ -57,20 +56,12 @@ typedef NSNumber * SDLChoiceId;
@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
@property (copy, nonatomic, nullable) SDLWindowCapability *currentWindowCapability;
-@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *preloadedMutableChoices;
-@property (strong, nonatomic, readonly) NSSet<SDLChoiceCell *> *pendingPreloadChoices;
-@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *pendingMutablePreloadChoices;
-@property (strong, nonatomic, nullable) SDLChoiceSet *pendingPresentationSet;
-@property (strong, nonatomic, nullable) SDLAsynchronousOperation *pendingPresentOperation;
-
-@property (assign, nonatomic) UInt16 nextChoiceId;
@property (assign, nonatomic) UInt16 nextCancelId;
@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
+@property (copy, nonatomic, readwrite) NSSet<SDLChoiceCell *> *preloadedChoices;
@end
-UInt16 const ChoiceCellIdMin = 1;
-
// Assigns a set range of unique cancel ids in order to prevent overlap with other sub-screen managers that use cancel ids. If the max cancel id is reached, generation starts over from the cancel id min value.
UInt16 const ChoiceCellCancelIdMin = 101;
UInt16 const ChoiceCellCancelIdMax = 200;
@@ -91,10 +82,8 @@ UInt16 const ChoiceCellCancelIdMax = 200;
_readWriteQueue = dispatch_queue_create_with_target("com.sdl.screenManager.choiceSetManager.readWriteQueue", DISPATCH_QUEUE_SERIAL, [SDLGlobals sharedGlobals].sdlProcessingQueue);
- _preloadedMutableChoices = [NSMutableSet set];
- _pendingMutablePreloadChoices = [NSMutableSet set];
+ _preloadedChoices = [NSSet set];
- _nextChoiceId = ChoiceCellIdMin;
_nextCancelId = ChoiceCellCancelIdMin;
_vrOptional = YES;
_keyboardConfiguration = [self sdl_defaultKeyboardConfiguration];
@@ -169,31 +158,26 @@ UInt16 const ChoiceCellCancelIdMax = 200;
[self.transactionQueue cancelAllOperations];
self.transactionQueue = [self sdl_newTransactionQueue];
- _preloadedMutableChoices = [NSMutableSet set];
- _pendingMutablePreloadChoices = [NSMutableSet set];
- _pendingPresentationSet = nil;
+ _preloadedChoices = [NSMutableSet set];
_vrOptional = YES;
- _nextChoiceId = ChoiceCellIdMin;
_nextCancelId = ChoiceCellCancelIdMin;
}
- (void)didEnterStateCheckingVoiceOptional {
// Setup by sending a Choice Set without VR, seeing if there's an error. If there is, send one with VR. This choice set will be used for `presentKeyboard` interactions.
- SDLCheckChoiceVROptionalOperation *checkOp = [[SDLCheckChoiceVROptionalOperation alloc] initWithConnectionManager:self.connectionManager];
-
- __weak typeof(self) weakSelf = self;
- __weak typeof(checkOp) weakOp = checkOp;
- checkOp.completionBlock = ^{
+ __weak typeof(self) weakself = self;
+ SDLCheckChoiceVROptionalOperation *checkOp = [[SDLCheckChoiceVROptionalOperation alloc] initWithConnectionManager:self.connectionManager completionHandler:^(BOOL isVROptional, NSError * _Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
if ([self.currentState isEqualToString:SDLChoiceManagerStateShutdown]) { return; }
- weakSelf.vrOptional = weakOp.isVROptional;
- if (weakOp.error != nil) {
- [weakSelf.stateMachine transitionToState:SDLChoiceManagerStateStartupError];
+ strongself.vrOptional = isVROptional;
+ if (error != nil) {
+ [strongself.stateMachine transitionToState:SDLChoiceManagerStateStartupError];
} else {
- [weakSelf.stateMachine transitionToState:SDLChoiceManagerStateReady];
+ [strongself.stateMachine transitionToState:SDLChoiceManagerStateReady];
}
- };
+ }];
[self.transactionQueue addOperation:checkOp];
}
@@ -204,164 +188,78 @@ UInt16 const ChoiceCellCancelIdMax = 200;
#pragma mark - Choice Management
-#pragma mark Upload / Delete
+#pragma mark Delete
-- (void)preloadChoices:(NSArray<SDLChoiceCell *> *)choices withCompletionHandler:(nullable SDLPreloadChoiceCompletionHandler)handler {
- SDLLogV(@"Request to preload choices: %@", choices);
- if ([self.currentState isEqualToString:SDLChoiceManagerStateShutdown]) {
- NSError *error = [NSError sdl_choiceSetManager_incorrectState:self.currentState];
- SDLLogE(@"Attempted to preload choices but the choice set manager is shut down: %@", error);
- if (handler != nil) {
- handler(error);
- }
+- (void)deleteChoices:(NSArray<SDLChoiceCell *> *)choices {
+ SDLLogV(@"Request to delete choices: %@", choices);
+ if (![self.currentState isEqualToString:SDLChoiceManagerStateReady]) {
+ SDLLogE(@"Attempted to delete choices in an incorrect state: %@, they will not be deleted", self.currentState);
return;
}
- NSMutableOrderedSet<SDLChoiceCell *> *mutableChoicesToUpload = [self sdl_choicesToBeUploadedWithArray:choices];
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- [mutableChoicesToUpload minusSet:self.preloadedMutableChoices];
- [mutableChoicesToUpload minusSet:self.pendingMutablePreloadChoices];
- }];
-
- NSOrderedSet<SDLChoiceCell *> *choicesToUpload = [mutableChoicesToUpload copy];
- if (choicesToUpload.count == 0) {
- SDLLogD(@"All choices already preloaded. No need to perform a preload");
- if (handler != nil) {
- handler(nil);
+ __weak typeof(self) weakself = self;
+ SDLDeleteChoicesOperation *deleteOp = [[SDLDeleteChoicesOperation alloc] initWithConnectionManager:self.connectionManager cellsToDelete:[NSSet setWithArray:choices] loadedCells:self.preloadedChoices completionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError *_Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
+ if ([strongself.currentState isEqualToEnum:SDLChoiceManagerStateShutdown]) {
+ SDLLogD(@"Cancelling deleting choices because the manager is shut down");
+ return;
}
- return;
- }
+ SDLLogD(@"Finished deleting choices");
- [self sdl_updateIdsOnChoices:choicesToUpload];
+ strongself.preloadedChoices = updatedLoadedCells;
+ [strongself sdl_updatePendingTasksWithCurrentPreloads];
- // Add the preload cells to the pending preloads
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- [self.pendingMutablePreloadChoices unionSet:choicesToUpload.set];
+ if (error != nil) {
+ SDLLogE(@"Failed to delete choices with error: %@", error);
+ }
}];
+ [self.transactionQueue addOperation:deleteOp];
+}
+
+#pragma mark Upload / Present
+
+- (void)preloadChoices:(NSArray<SDLChoiceCell *> *)choices withCompletionHandler:(nullable SDLPreloadChoiceCompletionHandler)handler {
+ SDLLogV(@"Request to preload choices: %@", choices);
+ if (choices.count == 0) { return; }
+ if (![self.currentState isEqualToString:SDLChoiceManagerStateReady]) {
+ NSError *error = [NSError sdl_choiceSetManager_incorrectState:self.currentState];
+ SDLLogE(@"Cannot preload choices when the manager isn't in the ready state: %@", error);
+ if (handler != nil) { handler(error); }
+ return;
+ }
+
// Upload pending preloads
// For backward compatibility with Gen38Inch display type head units
- SDLLogD(@"Preloading choices");
- SDLLogV(@"Choices to be uploaded: %@", choicesToUpload);
+ SDLLogD(@"Starting preload choices");
+ SDLLogV(@"Choices to be uploaded: %@", choices);
NSString *displayName = self.systemCapabilityManager.displays.firstObject.displayName;
__weak typeof(self) weakSelf = self;
- SDLPreloadChoicesOperation *preloadOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager displayName:displayName windowCapability:self.systemCapabilityManager.defaultMainWindowCapability isVROptional:self.isVROptional cellsToPreload:choicesToUpload updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {
+ SDLPreloadPresentChoicesOperation *preloadOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager displayName:displayName windowCapability:self.currentWindowCapability isVROptional:self.isVROptional cellsToPreload:choices loadedCells:self.preloadedChoices preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
- // Find the `SDLChoiceCell`s that failed to upload using the `choiceId`s
- NSMutableSet<SDLChoiceCell *> *failedChoiceUploadSet = [NSMutableSet set];
- for (NSNumber *failedChoiceUploadID in failedChoiceUploadIDs) {
- NSUInteger failedChoiceUploadIndex = [choicesToUpload indexOfObjectPassingTest:^BOOL(SDLChoiceCell * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- return obj.choiceId == failedChoiceUploadID.intValue;
- }];
- if (failedChoiceUploadIndex == NSNotFound) { continue; }
- [failedChoiceUploadSet addObject:choicesToUpload[failedChoiceUploadIndex]];
- }
-
// Check if the manager has shutdown because the list of uploaded and pending choices should not be updated
if ([strongSelf.currentState isEqualToString:SDLChoiceManagerStateShutdown]) {
SDLLogD(@"Cancelling preloading choices because the manager is shut down");
- return;
- }
-
- // Update the list of `preloadedMutableChoices` and `pendingMutablePreloadChoices` with the successful choice uploads
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (failedChoiceUploadSet.count == 0) {
- [strongSelf.preloadedMutableChoices unionSet:choicesToUpload.set];
- [strongSelf.pendingMutablePreloadChoices minusSet:choicesToUpload.set];
- } else {
- // If some choices failed, remove the failed choices from the successful ones, then update the preloaded choices and pending choices
- NSMutableSet<SDLChoiceCell *> *successfulChoiceUploads = [NSMutableSet setWithSet:choicesToUpload.set];
- [successfulChoiceUploads minusSet:failedChoiceUploadSet];
-
- [strongSelf.preloadedMutableChoices unionSet:successfulChoiceUploads];
- [strongSelf.pendingMutablePreloadChoices minusSet:choicesToUpload.set];
- }
- }];
- }];
-
- __weak typeof(preloadOp) weakPreloadOp = preloadOp;
- preloadOp.completionBlock = ^{
- SDLLogD(@"Choices finished preloading");
- if (handler != nil) {
- handler(weakPreloadOp.error);
+ if (handler != nil) { handler([NSError sdl_choiceSetManager_incorrectState:SDLChoiceManagerStateShutdown]); }
+ BLOCK_RETURN;
}
- };
- [self.transactionQueue addOperation:preloadOp];
-}
+ // Update the list of `preloadedChoices`
+ strongSelf.preloadedChoices = updatedLoadedCells;
+ [strongSelf sdl_updatePendingTasksWithCurrentPreloads];
-- (void)deleteChoices:(NSArray<SDLChoiceCell *> *)choices {
- SDLLogV(@"Request to delete choices: %@", choices);
- if (![self.currentState isEqualToString:SDLChoiceManagerStateReady]) {
- SDLLogE(@"Attempted to delete choices in an incorrect state: %@, they will not be deleted", self.currentState);
- return;
- }
-
- // Find cells to be deleted that are already uploaded or are pending upload
- NSSet<SDLChoiceCell *> *cellsToBeDeleted = [self sdl_choicesToBeDeletedWithArray:choices];
- NSSet<SDLChoiceCell *> *cellsToBeRemovedFromPending = [self sdl_choicesToBeRemovedFromPendingWithArray:choices];
-
- // If choices are deleted that are already uploaded or pending and are used by a pending presentation, cancel it and send an error
- NSSet<SDLChoiceCell *> *pendingPresentationChoices = [NSSet setWithArray:self.pendingPresentationSet.choices];
- if ((!self.pendingPresentOperation.isCancelled && !self.pendingPresentOperation.isFinished)
- && ([cellsToBeDeleted intersectsSet:pendingPresentationChoices] || [cellsToBeRemovedFromPending intersectsSet:pendingPresentationChoices])) {
- [self.pendingPresentOperation cancel];
- if (self.pendingPresentationSet.delegate != nil) {
- [self.pendingPresentationSet.delegate choiceSet:self.pendingPresentationSet didReceiveError:[NSError sdl_choiceSetManager_choicesDeletedBeforePresentation:@{@"deletedChoices": choices}]];
- }
- self.pendingPresentationSet = nil;
- }
+ SDLLogD(@"Choices finished preloading");
- // Remove the cells from pending and delete choices
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- [self.pendingMutablePreloadChoices minusSet:cellsToBeRemovedFromPending];
+ if (handler != nil) { handler(error); }
}];
- for (SDLAsynchronousOperation *op in self.transactionQueue.operations) {
- if (![op isMemberOfClass:[SDLPreloadChoicesOperation class]]) { continue; }
-
- SDLPreloadChoicesOperation *preloadOp = (SDLPreloadChoicesOperation *)op;
- [preloadOp removeChoicesFromUpload:cellsToBeRemovedFromPending];
- }
-
- // Find choices to delete
- if (cellsToBeDeleted.count == 0) { return; }
-
- [self sdl_findIdsOnChoices:cellsToBeDeleted];
- SDLDeleteChoicesOperation *deleteOp = [[SDLDeleteChoicesOperation alloc] initWithConnectionManager:self.connectionManager cellsToDelete:cellsToBeDeleted];
-
- __weak typeof(self) weakSelf = self;
- __weak typeof(deleteOp) weakOp = deleteOp;
- deleteOp.completionBlock = ^{
- SDLLogD(@"Finished deleting choices");
-
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (weakOp.error != nil) {
- SDLLogE(@"Failed to delete choices: %@", weakOp.error);
- return;
- }
-
- // Check if the manager has shutdown because the list of uploaded choices should not be updated
- if ([strongSelf.currentState isEqualToString:SDLChoiceManagerStateShutdown]) {
- SDLLogD(@"Cancelling deleting choices because manager is shut down");
- return;
- }
-
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- __strong typeof(weakSelf) strongSelf = weakSelf;
- [strongSelf.preloadedMutableChoices minusSet:cellsToBeDeleted];
- }];
- };
- [self.transactionQueue addOperation:deleteOp];
+ [self.transactionQueue addOperation:preloadOp];
}
-#pragma mark Present
-
- (void)presentChoiceSet:(SDLChoiceSet *)choiceSet mode:(SDLInteractionMode)mode withKeyboardDelegate:(nullable id<SDLKeyboardDelegate>)delegate {
if (![self.currentState isEqualToString:SDLChoiceManagerStateReady]) {
SDLLogE(@"Attempted to present choices in an incorrect state: %@, it will not be presented", self.currentState);
@@ -373,67 +271,26 @@ UInt16 const ChoiceCellCancelIdMax = 200;
return;
}
- if (self.pendingPresentationSet != nil && !self.pendingPresentOperation.isFinished) {
- SDLLogW(@"A choice set is pending: %@. We will try to cancel it in favor of presenting a different choice set: %@. If it's already on screen it cannot be cancelled", self.pendingPresentationSet, choiceSet);
- [self.pendingPresentOperation cancel];
- }
-
SDLLogD(@"Preloading and presenting choice set: %@", choiceSet);
- self.pendingPresentationSet = choiceSet;
+ // Add an operation to present it once the preload is complete
__weak typeof(self) weakSelf = self;
- [self preloadChoices:self.pendingPresentationSet.choices withCompletionHandler:^(NSError * _Nullable error) {
+ NSString *displayName = self.systemCapabilityManager.displays.firstObject.displayName;
+ SDLPreloadPresentChoicesOperation *presentOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager choiceSet:choiceSet mode:mode keyboardProperties:self.keyboardConfiguration keyboardDelegate:delegate cancelID:self.nextCancelId displayName:displayName windowCapability:self.currentWindowCapability isVROptional:self.isVROptional loadedCells:self.preloadedChoices preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
- if (error != nil) {
- SDLLogE(@"Error preloading choice cells for choice set presentation; aborting. Error: %@", error);
- [choiceSet.delegate choiceSet:choiceSet didReceiveError:error];
- return;
- }
+ // Check if the manager has shutdown because the list of uploaded and pending choices should not be updated
if ([strongSelf.currentState isEqualToString:SDLChoiceManagerStateShutdown]) {
- SDLLogD(@"Cancelling presenting choices because the manager has shut down");
- strongSelf.pendingPresentOperation = nil;
- strongSelf.pendingPresentationSet = nil;
- return;
+ SDLLogD(@"Cancelling preloading choices because the manager is shut down");
+ BLOCK_RETURN;
}
- // The cells necessary for this presentation are now preloaded, so we will enqueue a presentation
- [strongSelf sdl_presentChoiceSetWithMode:mode keyboardDelegate:delegate];
- }];
-}
-
-/// Helper method for presenting a choice set.
-/// @param mode If the set should be presented for the user to interact via voice, touch, or both
-/// @param delegate The keyboard delegate called when the user interacts with the search field of the choice set, if not set, a non-searchable choice set will be used
-- (void)sdl_presentChoiceSetWithMode:(SDLInteractionMode)mode keyboardDelegate:(nullable id<SDLKeyboardDelegate>)delegate {
- [self sdl_findIdsOnChoiceSet:self.pendingPresentationSet];
-
- SDLPresentChoiceSetOperation *presentOp = nil;
- if (delegate == nil) {
- // Non-searchable choice set
- presentOp = [[SDLPresentChoiceSetOperation alloc] initWithConnectionManager:self.connectionManager choiceSet:self.pendingPresentationSet mode:mode keyboardProperties:nil keyboardDelegate:nil cancelID:self.nextCancelId windowCapability:self.currentWindowCapability];
- } else {
- // Searchable choice set
- presentOp = [[SDLPresentChoiceSetOperation alloc] initWithConnectionManager:self.connectionManager choiceSet:self.pendingPresentationSet mode:mode keyboardProperties:self.keyboardConfiguration keyboardDelegate:delegate cancelID:self.nextCancelId windowCapability:self.currentWindowCapability];
- }
- self.pendingPresentOperation = presentOp;
-
- __weak typeof(self) weakSelf = self;
- __weak typeof(presentOp) weakOp = presentOp;
- self.pendingPresentOperation.completionBlock = ^{
- __strong typeof(weakSelf) strongSelf = weakSelf;
- __strong typeof(weakOp) strongOp = weakOp;
-
- SDLLogD(@"Finished presenting choice set: %@", strongOp.choiceSet);
- if (strongOp.error != nil && strongOp.choiceSet.delegate != nil) {
- [strongOp.choiceSet.delegate choiceSet:strongOp.choiceSet didReceiveError:strongOp.error];
- } else if (strongOp.selectedCell != nil && strongOp.choiceSet.delegate != nil) {
- [strongOp.choiceSet.delegate choiceSet:strongOp.choiceSet didSelectChoice:strongOp.selectedCell withSource:strongOp.selectedTriggerSource atRowIndex:strongOp.selectedCellRow];
- }
+ // Update the list of `preloadedChoices`
+ strongSelf.preloadedChoices = updatedLoadedCells;
+ [strongSelf sdl_updatePendingTasksWithCurrentPreloads];
- strongSelf.pendingPresentOperation = nil;
- strongSelf.pendingPresentationSet = nil;
- };
+ SDLLogD(@"Choices finished preloading");
+ }];
[self.transactionQueue addOperation:presentOp];
}
@@ -444,17 +301,11 @@ UInt16 const ChoiceCellCancelIdMax = 200;
return nil;
}
- if (self.pendingPresentationSet != nil) {
- SDLLogW(@"There's already a pending presentation set, cancelling it in favor of a keyboard");
- [self.pendingPresentOperation cancel];
- self.pendingPresentationSet = nil;
- }
-
SDLLogD(@"Presenting keyboard with initial text: %@", initialText);
// Present a keyboard with the choice set that we used to test VR's optional state
UInt16 keyboardCancelId = self.nextCancelId;
- self.pendingPresentOperation = [[SDLPresentKeyboardOperation alloc] initWithConnectionManager:self.connectionManager keyboardProperties:self.keyboardConfiguration initialText:initialText keyboardDelegate:delegate cancelID:keyboardCancelId windowCapability:self.currentWindowCapability];
- [self.transactionQueue addOperation:self.pendingPresentOperation];
+ SDLPresentKeyboardOperation *keyboardOperation = [[SDLPresentKeyboardOperation alloc] initWithConnectionManager:self.connectionManager keyboardProperties:self.keyboardConfiguration initialText:initialText keyboardDelegate:delegate cancelID:keyboardCancelId windowCapability:self.currentWindowCapability];
+ [self.transactionQueue addOperation:keyboardOperation];
return @(keyboardCancelId);
}
@@ -473,133 +324,17 @@ UInt16 const ChoiceCellCancelIdMax = 200;
#pragma mark - Choice Management Helpers
-/// Checks the passed list of choices to be uploaded and returns the items that have not yet been uploaded to the module.
-/// @param choices The choices to be uploaded
-/// @return The choices that have not yet been uploaded to the module
-- (NSMutableOrderedSet<SDLChoiceCell *> *)sdl_choicesToBeUploadedWithArray:(NSArray<SDLChoiceCell *> *)choices {
- NSMutableOrderedSet<SDLChoiceCell *> *choicesCopy = [[NSMutableOrderedSet alloc] initWithArray:choices copyItems:YES];
-
- SDLVersion *choiceUniquenessSupportedVersion = [[SDLVersion alloc] initWithMajor:7 minor:1 patch:0];
- if ([[SDLGlobals sharedGlobals].rpcVersion isLessThanVersion:choiceUniquenessSupportedVersion]) {
- // If we're on < RPC 7.1, all primary texts need to be unique, so we don't need to check removed properties and duplicate cells
- [self sdl_addUniqueNamesToCells:choicesCopy];
- } else {
- // On > RPC 7.1, at this point all cells are unique when considering all properties, but we also need to check if any cells will _appear_ as duplicates when displayed on the screen. To check that, we'll remove properties from the set cells based on the system capabilities (we probably don't need to consider them changing between now and when they're actually sent to the HU) and check for uniqueness again. Then we'll add unique identifiers to primary text if there are duplicates. Then we transfer the primary text identifiers back to the main cells and add those to an operation to be sent.
- NSMutableOrderedSet<SDLChoiceCell *> *strippedCellsCopy = [self sdl_removeUnusedProperties:choicesCopy];
- [self sdl_addUniqueNamesBasedOnStrippedCells:strippedCellsCopy toUnstrippedCells:choicesCopy];
- }
- [choicesCopy minusSet:self.preloadedChoices];
-
- return choicesCopy;
-}
-
-- (NSMutableOrderedSet<SDLChoiceCell *> *)sdl_removeUnusedProperties:(NSMutableOrderedSet<SDLChoiceCell *> *)choiceCells {
- NSMutableOrderedSet<SDLChoiceCell *> *strippedCellsCopy = [[NSMutableOrderedSet alloc] initWithOrderedSet:choiceCells copyItems:YES];
- for (SDLChoiceCell *cell in strippedCellsCopy) {
- // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
- cell.voiceCommands = nil;
-
- // Don't check SDLImageFieldNameSubMenuIcon because it was added in 7.0 when the feature was added in 5.0. Just assume that if CommandIcon is not available, the submenu icon is not either.
- if (![self.currentWindowCapability hasImageFieldOfName:SDLImageFieldNameChoiceImage]) {
- cell.artwork = nil;
- }
- if (![self.currentWindowCapability hasTextFieldOfName:SDLTextFieldNameSecondaryText]) {
- cell.secondaryText = nil;
- }
- if (![self.currentWindowCapability hasTextFieldOfName:SDLTextFieldNameTertiaryText]) {
- cell.tertiaryText = nil;
- }
- if (![self.currentWindowCapability hasImageFieldOfName:SDLImageFieldNameChoiceSecondaryImage]) {
- cell.secondaryArtwork = nil;
- }
- }
-
- return strippedCellsCopy;
-}
-
-- (void)sdl_addUniqueNamesBasedOnStrippedCells:(NSMutableOrderedSet<SDLChoiceCell *> *)strippedCells toUnstrippedCells:(NSMutableOrderedSet<SDLChoiceCell *> *)unstrippedCells {
- NSParameterAssert(strippedCells.count == unstrippedCells.count);
- // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
- NSMutableDictionary<SDLChoiceCell *, NSNumber *> *dictCounter = [[NSMutableDictionary alloc] init];
- for (NSUInteger i = 0; i < strippedCells.count; i++) {
- SDLChoiceCell *cell = strippedCells[i];
- NSNumber *counter = dictCounter[cell];
- if (counter != nil) {
- counter = @(counter.intValue + 1);
- dictCounter[cell] = counter;
- } else {
- dictCounter[cell] = @1;
- }
-
- counter = dictCounter[cell];
- if (counter.intValue > 1) {
- unstrippedCells[i].uniqueText = [NSString stringWithFormat: @"%@ (%d)", unstrippedCells[i].text, counter.intValue];
- }
- }
-}
+- (void)sdl_updatePendingTasksWithCurrentPreloads {
+ for (NSOperation *op in self.transactionQueue.operations) {
+ if (op.isExecuting || op.isCancelled) { continue; }
-/// Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
-/// E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
-/// @param choices The choices to be uploaded.
-- (void)sdl_addUniqueNamesToCells:(NSOrderedSet<SDLChoiceCell *> *)choices {
- // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
- NSMutableDictionary<NSString *, NSNumber *> *dictCounter = [[NSMutableDictionary alloc] init];
- for (SDLChoiceCell *cell in choices) {
- NSString *cellName = cell.text;
- NSNumber *counter = dictCounter[cellName];
- if (counter != nil) {
- counter = @(counter.intValue + 1);
- dictCounter[cellName] = counter;
- } else {
- dictCounter[cellName] = @1;
+ if ([op isMemberOfClass:SDLPreloadPresentChoicesOperation.class]) {
+ SDLPreloadPresentChoicesOperation *preloadPresentOp = (SDLPreloadPresentChoicesOperation *)op;
+ preloadPresentOp.loadedCells = self.preloadedChoices;
+ } else if ([op isMemberOfClass:SDLDeleteChoicesOperation.class]) {
+ SDLDeleteChoicesOperation *deleteOp = (SDLDeleteChoicesOperation *)op;
+ deleteOp.loadedCells = self.preloadedChoices;
}
- if (counter.intValue > 1) {
- cell.uniqueText = [NSString stringWithFormat: @"%@ (%d)", cell.text, counter.intValue];
- }
- }
-}
-
-/// Checks the passed list of choices to be deleted and returns the items that have been uploaded to the module.
-/// @param choices The choices to be deleted
-/// @return The choices that have been uploaded to the module
-- (NSSet<SDLChoiceCell *> *)sdl_choicesToBeDeletedWithArray:(NSArray<SDLChoiceCell *> *)choices {
- NSMutableSet<SDLChoiceCell *> *choicesSet = [NSMutableSet setWithArray:choices];
- [choicesSet intersectSet:self.preloadedChoices];
-
- return [choicesSet copy];
-}
-
-/// Checks the passed list of choices to be deleted and returns the items that are waiting to be uploaded to the module.
-/// @param choices The choices to be deleted
-/// @return The choices that are waiting to be uploaded to the module
-- (NSSet<SDLChoiceCell *> *)sdl_choicesToBeRemovedFromPendingWithArray:(NSArray<SDLChoiceCell *> *)choices {
- NSMutableSet<SDLChoiceCell *> *choicesSet = [NSMutableSet setWithArray:choices];
- [choicesSet intersectSet:self.pendingPreloadChoices];
-
- return [choicesSet copy];
-}
-
-/// Assigns a unique id to each choice item.
-/// @param choices An array of choices
-- (void)sdl_updateIdsOnChoices:(NSOrderedSet<SDLChoiceCell *> *)choices {
- for (SDLChoiceCell *cell in choices) {
- cell.choiceId = self.nextChoiceId;
- }
-}
-
-/// Checks each choice item to find out if it has already been uploaded or if it is the the process of being uploaded. If so, the choice item is assigned the unique id of the uploaded item.
-/// @param choiceSet A set of choice items
-- (void)sdl_findIdsOnChoiceSet:(SDLChoiceSet *)choiceSet {
- [self sdl_findIdsOnChoices:[NSSet setWithArray:choiceSet.choices]];
-}
-
-/// Checks each choice item to find out if it has already been uploaded or if it is the the process of being uploaded. If so, the choice item is assigned the unique id of the uploaded item.
-/// @param choices An array of choice items
-- (void)sdl_findIdsOnChoices:(NSSet<SDLChoiceCell *> *)choices {
- for (SDLChoiceCell *cell in choices) {
- SDLChoiceCell *uploadCell = [self.pendingPreloadChoices member:cell] ?: [self.preloadedChoices member:cell];
- if (uploadCell == nil) { continue; }
- cell.choiceId = uploadCell.choiceId;
}
}
@@ -628,34 +363,6 @@ UInt16 const ChoiceCellCancelIdMax = 200;
#pragma mark - Getters
-- (NSSet<SDLChoiceCell *> *)preloadedChoices {
- __block NSSet<SDLChoiceCell *> *set = nil;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- set = [self->_preloadedMutableChoices copy];
- }];
-
- return set;
-}
-
-- (NSSet<SDLChoiceCell *> *)pendingPreloadChoices {
- __block NSSet<SDLChoiceCell *> *set = nil;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- set = [self->_pendingMutablePreloadChoices copy];
- }];
-
- return set;
-}
-
-- (UInt16)nextChoiceId {
- __block UInt16 choiceId = 0;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- choiceId = self->_nextChoiceId;
- self->_nextChoiceId = choiceId + 1;
- }];
-
- return choiceId;
-}
-
- (UInt16)nextCancelId {
__block UInt16 cancelId = 0;
[SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
diff --git a/SmartDeviceLink/private/SDLDeleteChoicesOperation.h b/SmartDeviceLink/private/SDLDeleteChoicesOperation.h
index b712759cb..21d4cdbed 100644
--- a/SmartDeviceLink/private/SDLDeleteChoicesOperation.h
+++ b/SmartDeviceLink/private/SDLDeleteChoicesOperation.h
@@ -18,7 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLDeleteChoicesOperation : SDLAsynchronousOperation
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager cellsToDelete:(NSSet<SDLChoiceCell *> *)cells;
+typedef void(^SDLDeleteChoicesCompletionHandler)(NSSet<SDLChoiceCell *> *updatedLoadedCells, NSError *_Nullable error);
+
+/// The cells that are loaded on the head unit
+@property (strong, nonatomic) NSSet<SDLChoiceCell *> *loadedCells;
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager cellsToDelete:(NSSet<SDLChoiceCell *> *)cellsToDelete loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells completionHandler:(SDLDeleteChoicesCompletionHandler)completionHandler;
@end
diff --git a/SmartDeviceLink/private/SDLDeleteChoicesOperation.m b/SmartDeviceLink/private/SDLDeleteChoicesOperation.m
index 027c549dd..189460b34 100644
--- a/SmartDeviceLink/private/SDLDeleteChoicesOperation.m
+++ b/SmartDeviceLink/private/SDLDeleteChoicesOperation.m
@@ -27,18 +27,23 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) NSSet<SDLChoiceCell *> *cellsToDelete;
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (copy, nonatomic) SDLDeleteChoicesCompletionHandler deleteCompletionHandler;
+
+@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *mutableLoadedCells;
@end
@implementation SDLDeleteChoicesOperation
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager cellsToDelete:(NSSet<SDLChoiceCell *> *)cells {
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager cellsToDelete:(NSSet<SDLChoiceCell *> *)cellsToDelete loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells completionHandler:(SDLDeleteChoicesCompletionHandler)completionHandler {
self = [super init];
if (!self) { return nil; }
_connectionManager = connectionManager;
- _cellsToDelete = cells;
+ _cellsToDelete = cellsToDelete;
+ _mutableLoadedCells = [loadedCells mutableCopy];
_operationId = [NSUUID UUID];
+ _deleteCompletionHandler = completionHandler;
return self;
}
@@ -47,6 +52,9 @@ NS_ASSUME_NONNULL_BEGIN
[super start];
if (self.isCancelled) { return; }
+ [self sdl_updateCellsToDelete];
+ if (self.cellsToDelete.count == 0) { [self finishOperation]; }
+
[self sdl_sendDeletions];
}
@@ -59,8 +67,11 @@ NS_ASSUME_NONNULL_BEGIN
__weak typeof(self) weakSelf = self;
__block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
[self.connectionManager sendRequests:deleteChoices progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ SDLDeleteInteractionChoiceSet *sentRequest = (SDLDeleteInteractionChoiceSet *)request;
if (error != nil) {
errors[request] = error;
+ } else {
+ [self.mutableLoadedCells removeObject:[self sdl_loadedCellFromChoiceId:(UInt16)sentRequest.interactionChoiceSetID.unsignedIntValue]];
}
} completionHandler:^(BOOL success) {
if (!success) {
@@ -71,8 +82,49 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
+#pragma mark - Getters / Setters
+
+- (void)setLoadedCells:(NSSet<SDLChoiceCell *> *)loadedCells {
+ _mutableLoadedCells = [loadedCells mutableCopy];
+}
+
+- (NSSet<SDLChoiceCell *> *)loadedCells {
+ return [_mutableLoadedCells copy];
+}
+
+#pragma mark - Helpers
+
+- (void)sdl_updateCellsToDelete {
+ // Remove cells that aren't loaded
+ NSMutableSet<SDLChoiceCell *> *updatedCellsToDelete = [self.cellsToDelete mutableCopy];
+ [updatedCellsToDelete intersectSet:self.loadedCells];
+
+ // Update the choice ids on the cells to be deleted
+ for (SDLChoiceCell *cell in updatedCellsToDelete) {
+ SDLChoiceCell *uploadCell = [self.loadedCells member:cell];
+ if (uploadCell == nil) { continue; }
+ cell.choiceId = uploadCell.choiceId;
+ }
+
+ // Update our cells to delete
+ self.cellsToDelete = [updatedCellsToDelete copy];
+}
+
+- (nullable SDLChoiceCell *)sdl_loadedCellFromChoiceId:(UInt16)choiceId {
+ for (SDLChoiceCell *cell in self.loadedCells) {
+ if (cell.choiceId == choiceId) { return cell; }
+ }
+
+ return nil;
+}
+
#pragma mark - Property Overrides
+- (void)finishOperation {
+ self.deleteCompletionHandler(self.loadedCells, self.internalError);
+ [super finishOperation];
+}
+
- (nullable NSString *)name {
return [NSString stringWithFormat:@"%@ - %@", self.class, self.operationId];
}
diff --git a/SmartDeviceLink/private/SDLError.h b/SmartDeviceLink/private/SDLError.h
index 34b96247c..7baabc7d1 100644
--- a/SmartDeviceLink/private/SDLError.h
+++ b/SmartDeviceLink/private/SDLError.h
@@ -8,6 +8,7 @@
#import <Foundation/Foundation.h>
+#import "SDLChoiceCell.h"
#import "SDLErrorConstants.h"
#import "SDLResult.h"
@@ -69,11 +70,13 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Choice Set Manager
-+ (NSError *)sdl_choiceSetManager_choicesDeletedBeforePresentation:(NSDictionary *)userInfo;
++ (NSError *)sdl_choiceSetManager_choicesNotAvailableForPresentation:(NSSet<SDLChoiceCell *> *)neededCells availableCells:(NSSet<SDLChoiceCell *> *)availableCells;
+ (NSError *)sdl_choiceSetManager_choiceDeletionFailed:(NSDictionary *)userInfo;
+ (NSError *)sdl_choiceSetManager_choiceUploadFailed:(NSDictionary *)userInfo;
+ (NSError *)sdl_choiceSetManager_failedToCreateMenuItems;
+ (NSError *)sdl_choiceSetManager_incorrectState:(NSString *)state;
++ (NSError *)sdl_choiceSetManager_cancelled;
++ (NSError *)sdl_choiceSetManager_noIdsAvailable;
#pragma mark Alert Manager
diff --git a/SmartDeviceLink/private/SDLError.m b/SmartDeviceLink/private/SDLError.m
index 765e16760..a1dc6b52d 100644
--- a/SmartDeviceLink/private/SDLError.m
+++ b/SmartDeviceLink/private/SDLError.m
@@ -332,8 +332,15 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Choice Set Manager
-+ (NSError *)sdl_choiceSetManager_choicesDeletedBeforePresentation:(NSDictionary *)userInfo {
- return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorPendingPresentationDeleted userInfo:userInfo];
++ (NSError *)sdl_choiceSetManager_choicesNotAvailableForPresentation:(NSSet<SDLChoiceCell *> *)neededCells availableCells:(NSSet<SDLChoiceCell *> *)availableCells {
+
+ return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorNeededChoicesUnavailable userInfo:@{
+ NSLocalizedDescriptionKey: @"Choice Set Manager error",
+ NSLocalizedFailureReasonErrorKey: @"Not all needed choices for presentation are available on the head unit. See key 'neededChoices' and 'availableChoices'",
+ NSLocalizedRecoverySuggestionErrorKey: @"Choices may have been deleted or were not all properly uploaded for presentation. You can attempt the presentation again to retry the upload.",
+ @"neededChoices": neededCells.description,
+ @"availableChoices": availableCells.description
+ }];
}
+ (NSError *)sdl_choiceSetManager_choiceDeletionFailed:(NSDictionary *)userInfo {
@@ -363,6 +370,22 @@ NS_ASSUME_NONNULL_BEGIN
return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorInvalidState userInfo:userInfo];
}
++ (NSError *)sdl_choiceSetManager_cancelled {
+ return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorCancelled userInfo:@{
+ NSLocalizedDescriptionKey: @"Choice set operation error cancelled",
+ NSLocalizedFailureReasonErrorKey: @"The choice operation was cancelled and may or may not have completed",
+ NSLocalizedRecoverySuggestionErrorKey: @"It may have been cancelled due to shutdown, or it may have been cancelled by the developer"
+ }];
+}
+
++ (NSError *)sdl_choiceSetManager_noIdsAvailable {
+ return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorNoIdsAvailable userInfo:@{
+ NSLocalizedDescriptionKey: @"Choice set operation failed because the maximum number of choices have been uploaded (65535)",
+ NSLocalizedFailureReasonErrorKey: @"65535 unique choices have been uploaded to the head unit in this session and no more are allowed",
+ NSLocalizedRecoverySuggestionErrorKey: @"Re-use or delete choices to free up space"
+ }];
+}
+
#pragma mark Alert Manager
+ (NSError *)sdl_alertManager_presentationFailedWithError:(NSError *)error tryAgainTime:(int)tryAgainTime {
diff --git a/SmartDeviceLink/private/SDLLogFileModuleMap.m b/SmartDeviceLink/private/SDLLogFileModuleMap.m
index 2ae0a3ab7..d8da4cdb2 100644
--- a/SmartDeviceLink/private/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/private/SDLLogFileModuleMap.m
@@ -139,7 +139,7 @@
}
+ (SDLLogFileModule *)sdl_screenManagerChoiceSetModule {
- return [SDLLogFileModule moduleWithName:@"Screen/ChoiceSet" files:[NSSet setWithArray:@[@"SDLChoiceSetManager", @"SDLCheckChoiceVROptionalOperation", @"SDLDeleteChoicesOperation", @"SDLPreloadChoicesOperation", @"SDLPresentChoiceSetOperation", @"SDLPresentKeyboardOperation", @"SDLChoiceSet"]]];
+ return [SDLLogFileModule moduleWithName:@"Screen/ChoiceSet" files:[NSSet setWithArray:@[@"SDLChoiceSetManager", @"SDLCheckChoiceVROptionalOperation", @"SDLDeleteChoicesOperation", @"SDLPreloadPresentChoicesOperation", @"SDLPresentKeyboardOperation", @"SDLChoiceSet"]]];
}
diff --git a/SmartDeviceLink/private/SDLPreloadChoicesOperation.h b/SmartDeviceLink/private/SDLPreloadChoicesOperation.h
deleted file mode 100644
index 5434e60dd..000000000
--- a/SmartDeviceLink/private/SDLPreloadChoicesOperation.h
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// SDLPreloadChoicesOperation.h
-// SmartDeviceLink
-//
-// Created by Joel Fischer on 5/23/18.
-// Copyright © 2018 smartdevicelink. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-#import "SDLAsynchronousOperation.h"
-
-@class SDLChoiceCell;
-@class SDLFileManager;
-@class SDLWindowCapability;
-
-@protocol SDLConnectionManagerType;
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// A handler run when the operation completes, containing the failed choice uploads.
-///
-/// @param failedChoiceUploadIDs The IDs of failed choice uploads
-typedef void(^SDLPreloadChoicesCompletionHandler)(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs);
-
-typedef NS_ENUM(NSUInteger, SDLPreloadChoicesOperationState) {
- SDLPreloadChoicesOperationStateWaitingToStart,
- SDLPreloadChoicesOperationStateUploadingArtworks,
- SDLPreloadChoicesOperationStatePreloadingChoices,
- SDLPreloadChoicesOperationStateFinished
-};
-
-@interface SDLPreloadChoicesOperation : SDLAsynchronousOperation
-
-@property (assign, nonatomic) SDLPreloadChoicesOperationState currentState;
-
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)defaultMainWindowCapability isVROptional:(BOOL)isVROptional cellsToPreload:(NSOrderedSet<SDLChoiceCell *> *)cells updateCompletionHandler:(SDLPreloadChoicesCompletionHandler)completionHandler;
-
-- (BOOL)removeChoicesFromUpload:(NSSet<SDLChoiceCell *> *)choices;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPreloadChoicesOperation.m b/SmartDeviceLink/private/SDLPreloadChoicesOperation.m
deleted file mode 100644
index a9fd94f6d..000000000
--- a/SmartDeviceLink/private/SDLPreloadChoicesOperation.m
+++ /dev/null
@@ -1,243 +0,0 @@
-//
-// SDLPreloadChoicesOperation.m
-// SmartDeviceLink
-//
-// Created by Joel Fischer on 5/23/18.
-// Copyright © 2018 smartdevicelink. All rights reserved.
-//
-
-#import "SDLPreloadChoicesOperation.h"
-
-#import "SDLChoice.h"
-#import "SDLChoiceCell.h"
-#import "SDLConnectionManagerType.h"
-#import "SDLCreateInteractionChoiceSet.h"
-#import "SDLCreateInteractionChoiceSetResponse.h"
-#import "SDLDisplayType.h"
-#import "SDLError.h"
-#import "SDLFileManager.h"
-#import "SDLImage.h"
-#import "SDLLogMacros.h"
-#import "SDLWindowCapability.h"
-#import "SDLWindowCapability+ScreenManagerExtensions.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface SDLChoiceCell()
-
-@property (assign, nonatomic) UInt16 choiceId;
-
-@end
-
-@interface SDLPreloadChoicesOperation()
-
-@property (strong, nonatomic) NSUUID *operationId;
-@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *cellsToUpload;
-@property (strong, nonatomic) SDLWindowCapability *windowCapability;
-@property (strong, nonatomic) NSString *displayName;
-@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
-@property (copy, nonatomic) SDLPreloadChoicesCompletionHandler completionHandler;
-
-@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
-@property (weak, nonatomic) SDLFileManager *fileManager;
-@property (copy, nonatomic, nullable) NSError *internalError;
-@property (strong, nonatomic) NSMutableArray<NSNumber *> *failedChoiceUploadIDs;
-
-@end
-
-@implementation SDLPreloadChoicesOperation
-
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)defaultMainWindowCapability isVROptional:(BOOL)isVROptional cellsToPreload:(NSOrderedSet<SDLChoiceCell *> *)cells updateCompletionHandler:(SDLPreloadChoicesCompletionHandler)completionHandler {
- self = [super init];
- if (!self) { return nil; }
-
- _connectionManager = connectionManager;
- _fileManager = fileManager;
- _displayName = displayName;
- _windowCapability = defaultMainWindowCapability;
- _vrOptional = isVROptional;
- _cellsToUpload = [cells mutableCopy];
- _completionHandler = completionHandler;
- _operationId = [NSUUID UUID];
-
- _currentState = SDLPreloadChoicesOperationStateWaitingToStart;
- _failedChoiceUploadIDs = [NSMutableArray array];
-
- return self;
-}
-
-- (void)start {
- [super start];
- if (self.isCancelled) { return; }
-
- [self sdl_preloadCellArtworksWithCompletionHandler:^(NSError * _Nullable error) {
- self.internalError = error;
-
- [self sdl_preloadCells];
- }];
-}
-
-- (BOOL)removeChoicesFromUpload:(NSSet<SDLChoiceCell *> *)choices {
- if (self.isExecuting) { return NO; }
-
- [self.cellsToUpload minusSet:choices];
- return YES;
-}
-
-#pragma mark - Sending Choice Data
-
-- (void)sdl_preloadCellArtworksWithCompletionHandler:(void(^)(NSError *_Nullable))completionHandler {
- _currentState = SDLPreloadChoicesOperationStateUploadingArtworks;
-
- NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray arrayWithCapacity:self.cellsToUpload.count];
- for (SDLChoiceCell *cell in self.cellsToUpload) {
- if ([self sdl_shouldSendChoicePrimaryImage] && [self.fileManager fileNeedsUpload:cell.artwork]) {
- [artworksToUpload addObject:cell.artwork];
- }
- if ([self sdl_shouldSendChoiceSecondaryImage] && [self.fileManager fileNeedsUpload:cell.secondaryArtwork]) {
- [artworksToUpload addObject:cell.secondaryArtwork];
- }
- }
-
- if (artworksToUpload.count == 0) {
- SDLLogD(@"No choice artworks to be uploaded");
- completionHandler(nil);
- return;
- }
-
- [self.fileManager uploadArtworks:[artworksToUpload copy] completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error uploading choice artworks: %@", error);
- } else {
- SDLLogD(@"Finished uploading choice artworks");
- SDLLogV(@"%@", artworkNames);
- }
-
- completionHandler(error);
- }];
-}
-
-- (void)sdl_preloadCells {
- _currentState = SDLPreloadChoicesOperationStatePreloadingChoices;
-
- NSMutableArray<SDLCreateInteractionChoiceSet *> *choiceRPCs = [NSMutableArray arrayWithCapacity:self.cellsToUpload.count];
- for (SDLChoiceCell *cell in self.cellsToUpload) {
- SDLCreateInteractionChoiceSet *csCell = [self sdl_choiceFromCell:cell];
- if (csCell != nil) {
- [choiceRPCs addObject:csCell];
- }
- }
- if (choiceRPCs.count == 0) {
- SDLLogE(@"All choice cells to send are nil, so the choice set will not be shown");
- self.internalError = [NSError sdl_choiceSetManager_failedToCreateMenuItems];
- [self finishOperation];
- return;
- }
-
- __weak typeof(self) weakSelf = self;
- __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
- [self.connectionManager sendRequests:[choiceRPCs copy] progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- if (error != nil) {
- errors[request] = error;
- SDLCreateInteractionChoiceSet *sentRequest = (SDLCreateInteractionChoiceSet *)request;
- [self.failedChoiceUploadIDs addObject:sentRequest.choiceSet.firstObject.choiceID];
- }
- } completionHandler:^(BOOL success) {
- if (!success) {
- SDLLogE(@"Error preloading choice cells: %@", errors);
- weakSelf.internalError = [NSError sdl_choiceSetManager_choiceUploadFailed:errors];
- }
-
- SDLLogD(@"Finished preloading choice cells");
-
- [weakSelf finishOperation];
- }];
-}
-
-#pragma mark - Assembling Choice Data
-
-- (nullable SDLCreateInteractionChoiceSet *)sdl_choiceFromCell:(SDLChoiceCell *)cell {
- NSArray<NSString *> *vrCommands = nil;
- if (cell.voiceCommands == nil) {
- vrCommands = self.isVROptional ? nil : @[[NSString stringWithFormat:@"%hu", cell.choiceId]];
- } else {
- vrCommands = cell.voiceCommands;
- }
-
- NSString *menuName = nil;
- if ([self sdl_shouldSendChoiceText]) {
- menuName = cell.uniqueText;
- }
-
- if(!menuName) {
- SDLLogE(@"Could not convert SDLChoiceCell to SDLCreateInteractionChoiceSet. It will not be shown. Cell: %@", cell);
- return nil;
- }
-
- NSString *secondaryText = [self sdl_shouldSendChoiceSecondaryText] ? cell.secondaryText : nil;
- NSString *tertiaryText = [self sdl_shouldSendChoiceTertiaryText] ? cell.tertiaryText : nil;
-
- SDLImage *image = [self sdl_shouldSendChoicePrimaryImage] ? cell.artwork.imageRPC : nil;
- SDLImage *secondaryImage = [self sdl_shouldSendChoiceSecondaryImage] ? cell.secondaryArtwork.imageRPC : nil;
-
- SDLChoice *choice = [[SDLChoice alloc] initWithId:cell.choiceId menuName:(NSString *_Nonnull)menuName vrCommands:(NSArray<NSString *> * _Nonnull)vrCommands image:image secondaryText:secondaryText secondaryImage:secondaryImage tertiaryText:tertiaryText];
-
- return [[SDLCreateInteractionChoiceSet alloc] initWithId:(UInt32)choice.choiceID.unsignedIntValue choiceSet:@[choice]];
-}
-
-/// Determine if we should send primary text. If textFields is nil, we don't know the capabilities and we will send everything.
-- (BOOL)sdl_shouldSendChoiceText {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if ([self.displayName isEqualToString:SDLDisplayTypeGen38Inch]) {
- return YES;
- }
-#pragma clang diagnostic pop
-
- return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameMenuName];
-}
-
-/// Determine if we should send secondary text. If textFields is nil, we don't know the capabilities and we will send everything.
-- (BOOL)sdl_shouldSendChoiceSecondaryText {
- return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameSecondaryText];
-}
-
-/// Determine if we should send teriary text. If textFields is nil, we don't know the capabilities and we will send everything.
-- (BOOL)sdl_shouldSendChoiceTertiaryText {
- return [self.windowCapability hasTextFieldOfName:SDLTextFieldNameTertiaryText];
-}
-
-/// Determine if we should send the primary image. If imageFields is nil, we don't know the capabilities and we will send everything.
-- (BOOL)sdl_shouldSendChoicePrimaryImage {
- return [self.windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceImage];
-}
-
-/// Determine if we should send the secondary image. If imageFields is nil, we don't know the capabilities and we will send everything.
-- (BOOL)sdl_shouldSendChoiceSecondaryImage {
- return [self.windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceSecondaryImage];
-}
-
-#pragma mark - Property Overrides
-
-- (void)finishOperation {
- _currentState = SDLPreloadChoicesOperationStateFinished;
- self.completionHandler(self.failedChoiceUploadIDs);
-
- [super finishOperation];
-}
-
-- (nullable NSString *)name {
- return [NSString stringWithFormat:@"%@ - %@", self.class, self.operationId];
-}
-
-- (NSOperationQueuePriority)queuePriority {
- return NSOperationQueuePriorityNormal;
-}
-
-- (nullable NSError *)error {
- return self.internalError;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.h b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.h
new file mode 100644
index 000000000..45922dbc0
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.h
@@ -0,0 +1,41 @@
+//
+// SDLPreloadChoicesOperation.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 5/23/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLAsynchronousOperation.h"
+#import "SDLInteractionMode.h"
+#import "SDLKeyboardDelegate.h"
+#import "SDLTriggerSource.h"
+
+@class SDLChoiceCell;
+@class SDLChoiceSet;
+@class SDLFileManager;
+@class SDLKeyboardProperties;
+@class SDLWindowCapability;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^SDLUploadChoicesCompletionHandler)(NSSet<SDLChoiceCell *> *updatedLoadedCells, NSError *_Nullable error);
+
+@interface SDLPreloadPresentChoicesOperation : SDLAsynchronousOperation
+
+/// The cells that are loaded on the head unit
+@property (strong, nonatomic) NSSet<SDLChoiceCell *> *loadedCells;
+
+// Preload only init
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)defaultMainWindowCapability isVROptional:(BOOL)isVROptional cellsToPreload:(NSArray<SDLChoiceCell *> *)cellsToPreload loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells preloadCompletionHandler:(SDLUploadChoicesCompletionHandler)preloadCompletionHandler;
+
+/// Preload and Present Init
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager choiceSet:(SDLChoiceSet *)choiceSet mode:(SDLInteractionMode)mode keyboardProperties:(nullable SDLKeyboardProperties *)originalKeyboardProperties keyboardDelegate:(nullable id<SDLKeyboardDelegate>)keyboardDelegate cancelID:(UInt16)cancelID displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)windowCapability isVROptional:(BOOL)isVROptional loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells preloadCompletionHandler:(SDLUploadChoicesCompletionHandler)preloadCompletionHandler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.m b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.m
new file mode 100644
index 000000000..a2d42c333
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperation.m
@@ -0,0 +1,632 @@
+//
+// SDLPreloadChoicesOperation.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 5/23/18.
+// Copyright © 2018 smartdevicelink. All rights reserved.
+//
+
+#import "SDLPreloadPresentChoicesOperation.h"
+
+#import "SDLCancelInteraction.h"
+#import "SDLChoice.h"
+#import "SDLChoiceCell.h"
+#import "SDLChoiceSet.h"
+#import "SDLChoiceSetDelegate.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLCreateInteractionChoiceSet.h"
+#import "SDLCreateInteractionChoiceSetResponse.h"
+#import "SDLDisplayType.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLGlobals.h"
+#import "SDLImage.h"
+#import "SDLKeyboardProperties.h"
+#import "SDLLogMacros.h"
+#import "SDLOnKeyboardInput.h"
+#import "SDLPreloadPresentChoicesOperationUtilities.h"
+#import "SDLPerformInteraction.h"
+#import "SDLPerformInteractionResponse.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLSetGlobalProperties.h"
+#import "SDLVersion.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSUInteger, SDLPreloadPresentChoicesOperationState) {
+ SDLPreloadPresentChoicesOperationStateNotStarted,
+ SDLPreloadPresentChoicesOperationStateUploadingImages,
+ SDLPreloadPresentChoicesOperationStateUploadingChoices,
+ SDLPreloadPresentChoicesOperationStateUpdatingKeyboardProperties,
+ SDLPreloadPresentChoicesOperationStatePresentingChoices,
+ SDLPreloadPresentChoicesOperationStateCancellingPresentChoices,
+ SDLPreloadPresentChoicesOperationStateResettingKeyboardProperties,
+ SDLPreloadPresentChoicesOperationStateFinishing
+};
+
+@interface SDLChoiceCell()
+
+@property (assign, nonatomic) UInt16 choiceId;
+@property (copy, nonatomic, readwrite, nullable) NSString *secondaryText;
+@property (copy, nonatomic, readwrite, nullable) NSString *tertiaryText;
+@property (copy, nonatomic, readwrite, nullable) NSArray<NSString *> *voiceCommands;
+@property (strong, nonatomic, readwrite, nullable) SDLArtwork *artwork;
+@property (strong, nonatomic, readwrite, nullable) SDLArtwork *secondaryArtwork;
+
+@property (assign, nonatomic) NSUInteger uniqueTextId;
+
+@end
+
+@interface SDLChoiceSet()
+
+@property (copy, nonatomic) SDLChoiceSetCanceledHandler canceledHandler;
+
+@end
+
+@interface SDLPreloadPresentChoicesOperation()
+
+// Dependencies
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (strong, nonatomic) SDLWindowCapability *windowCapability;
+
+// Preload Dependencies
+@property (strong, nonatomic) NSMutableOrderedSet<SDLChoiceCell *> *cellsToUpload;
+@property (strong, nonatomic) NSString *displayName;
+@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
+@property (copy, nonatomic) SDLUploadChoicesCompletionHandler preloadCompletionHandler;
+
+// Present Dependencies
+@property (strong, nonatomic) SDLChoiceSet *choiceSet;
+@property (strong, nonatomic, nullable) SDLInteractionMode presentationMode;
+@property (strong, nonatomic, nullable) SDLKeyboardProperties *originalKeyboardProperties;
+@property (strong, nonatomic, nullable) SDLKeyboardProperties *customKeyboardProperties;
+@property (weak, nonatomic, nullable) id<SDLKeyboardDelegate> keyboardDelegate;
+@property (assign, nonatomic) UInt16 cancelId;
+
+// Internal operation properties
+@property (assign, nonatomic) SDLPreloadPresentChoicesOperationState currentState;
+@property (strong, nonatomic) NSUUID *operationId;
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+// Mutable state
+@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *mutableLoadedCells;
+
+// Present completion handler properties
+@property (strong, nonatomic, nullable) SDLChoiceCell *selectedCell;
+@property (strong, nonatomic, nullable) SDLTriggerSource selectedTriggerSource;
+@property (assign, nonatomic) NSUInteger selectedCellRow;
+
+@end
+
+@implementation SDLPreloadPresentChoicesOperation
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)windowCapability isVROptional:(BOOL)isVROptional cellsToPreload:(NSArray<SDLChoiceCell *> *)cellsToPreload loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells preloadCompletionHandler:(SDLUploadChoicesCompletionHandler)preloadCompletionHandler {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _currentState = SDLPreloadPresentChoicesOperationStateNotStarted;
+ _operationId = [NSUUID UUID];
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _cancelId = UINT16_MAX;
+ _displayName = displayName;
+ _windowCapability = windowCapability;
+ _vrOptional = isVROptional;
+
+ _cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:cellsToPreload];
+ _mutableLoadedCells = [loadedCells mutableCopy];
+ _preloadCompletionHandler = preloadCompletionHandler;
+
+ return self;
+}
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager choiceSet:(SDLChoiceSet *)choiceSet mode:(SDLInteractionMode)mode keyboardProperties:(nullable SDLKeyboardProperties *)originalKeyboardProperties keyboardDelegate:(nullable id<SDLKeyboardDelegate>)keyboardDelegate cancelID:(UInt16)cancelID displayName:(NSString *)displayName windowCapability:(SDLWindowCapability *)windowCapability isVROptional:(BOOL)isVROptional loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells preloadCompletionHandler:(SDLUploadChoicesCompletionHandler)preloadCompletionHandler {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _currentState = SDLPreloadPresentChoicesOperationStateNotStarted;
+ _operationId = [NSUUID UUID];
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _choiceSet = choiceSet;
+ _presentationMode = mode;
+
+ __weak typeof(self) weakSelf = self;
+ _choiceSet.canceledHandler = ^{
+ [weakSelf sdl_cancelInteraction];
+ };
+
+ _originalKeyboardProperties = originalKeyboardProperties;
+ _customKeyboardProperties = originalKeyboardProperties;
+ _keyboardDelegate = keyboardDelegate;
+ _cancelId = cancelID;
+
+ _displayName = displayName;
+ _windowCapability = windowCapability;
+ _vrOptional = isVROptional;
+ _mutableLoadedCells = [loadedCells mutableCopy];
+ _cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:choiceSet.choices];
+ _preloadCompletionHandler = preloadCompletionHandler;
+
+ _selectedCellRow = NSNotFound;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+ if (self.isCancelled) { return; }
+
+ // If we have no loaded cells, reset choice ids to ensure reconnections restart numbering
+ if (self.loadedCells.count == 0) {
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 0;
+ SDLPreloadPresentChoicesOperationUtilities.reachedMaxId = NO;
+ }
+
+ // Remove cells that are already loaded so that we don't try to re-upload them
+ [self.cellsToUpload minusSet:self.loadedCells];
+
+ // If loaded cells is full and we need to upload cells, just fail the operation since we can't successfully upload or present
+ if ((self.loadedCells.count == UINT16_MAX) && (self.cellsToUpload.count > 0)) {
+ return [self finishOperation:[NSError sdl_choiceSetManager_noIdsAvailable]];
+ }
+
+ // Assign Ids, then make cells to upload unique so that they upload properly (if necessary)
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:self.cellsToUpload loadedCells:self.loadedCells];
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:self.cellsToUpload basedOnLoadedCells:self.mutableLoadedCells windowCapability:self.windowCapability];
+
+ // If we have a choice set, we need to replace the choices with the cells that we're uploading (with new ids and unique text) and the cells that are already on the head unit (with the correct cell ids and unique text)
+ if (self.choiceSet != nil) {
+ [SDLPreloadPresentChoicesOperationUtilities updateChoiceSet:self.choiceSet withLoadedCells:self.loadedCells cellsToUpload:self.cellsToUpload.set];
+ }
+
+ // Start uploading cell artworks, then cells themselves, then determine if we want to present, then update keyboard properties if necessary, then present the choice set, then revert keyboard properties if necessary
+ [self sdl_uploadCellArtworksWithCompletionHandler:^(NSError * _Nullable uploadArtError) {
+ // If some artworks failed to upload, we are still going to try to load the cells
+ if (self.isCancelled || uploadArtError != nil) { return [self finishOperation:uploadArtError]; }
+
+ [self sdl_uploadCellsWithCompletionHandler:^(NSError * _Nullable uploadCellsError) {
+ // If this operation has been cancelled or if there was an error with loading the cells, we don't want to present, so we'll end the operation
+ if (self.isCancelled || uploadCellsError != nil) { return [self finishOperation:uploadCellsError]; }
+
+ // If necessary, present the choice set
+ if (self.choiceSet == nil) { return [self finishOperation]; }
+ [self sdl_updateKeyboardPropertiesWithCompletionHandler:^(NSError * _Nullable updateKeyboardPropertiesError) {
+ if (self.isCancelled || updateKeyboardPropertiesError != nil) { return [self finishOperation]; }
+
+ [self sdl_presentChoiceSetWithCompletionHandler:^(NSError * _Nullable presentError) {
+ [self sdl_resetKeyboardPropertiesWithCompletionHandler:^(NSError * _Nullable resetKeyboardPropertiesError) {
+ if (presentError != nil) { return [self finishOperation:presentError]; }
+ return [self finishOperation:resetKeyboardPropertiesError];
+ }];
+ }];
+ }];
+ }];
+ }];
+}
+
+#pragma mark - Getters / Setters
+
+- (void)setLoadedCells:(NSSet<SDLChoiceCell *> *)loadedCells {
+ _mutableLoadedCells = [loadedCells mutableCopy];
+}
+
+- (NSSet<SDLChoiceCell *> *)loadedCells {
+ return [_mutableLoadedCells copy];
+}
+
+#pragma mark - Uploading Choice Data
+
+- (void)sdl_uploadCellArtworksWithCompletionHandler:(void(^)(NSError *_Nullable error))completionHandler {
+ self.currentState = SDLPreloadPresentChoicesOperationStateUploadingImages;
+
+ NSArray<SDLArtwork *> *artworksToUpload = [self.class sdl_findAllArtworksToBeUploadedFromCells:self.cellsToUpload.array fileManager:self.fileManager windowCapability:self.windowCapability];
+ if (artworksToUpload.count == 0) {
+ SDLLogD(@"No choice artworks to be uploaded");
+ return completionHandler(nil);
+ }
+
+ [self.fileManager uploadArtworks:[artworksToUpload copy] completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading choice artworks: %@", error);
+ } else {
+ SDLLogD(@"Finished uploading choice artworks");
+ SDLLogV(@"%@", artworkNames);
+ }
+
+ completionHandler(error);
+ }];
+}
+
+- (void)sdl_uploadCellsWithCompletionHandler:(void(^)(NSError *_Nullable error))completionHandler {
+ self.currentState = SDLPreloadPresentChoicesOperationStateUploadingChoices;
+ if (self.cellsToUpload.count == 0) { return completionHandler(nil); }
+
+ NSMutableArray<SDLCreateInteractionChoiceSet *> *choiceRPCs = [NSMutableArray arrayWithCapacity:self.cellsToUpload.count];
+ for (SDLChoiceCell *cell in self.cellsToUpload) {
+ SDLCreateInteractionChoiceSet *csCell = [self.class sdl_choiceFromCell:cell windowCapability:self.windowCapability displayName:self.displayName isVROptional:self.isVROptional];
+ if (csCell != nil) {
+ [choiceRPCs addObject:csCell];
+ }
+ }
+ if (choiceRPCs.count == 0) {
+ SDLLogE(@"All choice cells to send are nil, so the choice set will not be shown");
+ return completionHandler([NSError sdl_choiceSetManager_failedToCreateMenuItems]);
+ }
+
+ __weak typeof(self) weakSelf = self;
+ __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
+ [self.connectionManager sendRequests:[choiceRPCs copy] progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ SDLCreateInteractionChoiceSet *sentRequest = (SDLCreateInteractionChoiceSet *)request;
+ if (error != nil) {
+ errors[request] = error;
+ } else {
+ [weakSelf.mutableLoadedCells addObject:[self sdl_cellFromChoiceId:(UInt16)sentRequest.interactionChoiceSetID.unsignedIntValue]];
+ }
+ } completionHandler:^(BOOL success) {
+ NSError *preloadError = nil;
+ if (!success) {
+ SDLLogE(@"Error preloading choice cells: %@", errors);
+ preloadError = [NSError sdl_choiceSetManager_choiceUploadFailed:errors];
+ }
+
+ SDLLogD(@"Finished preloading choice cells");
+
+ return completionHandler(preloadError);
+ }];
+}
+
+#pragma mark - Artwork
+
+/// Get an array of artwork that needs to be uploaded form a list of menu cells
+/// @param cells The menu cells to get artwork from
+/// @param fileManager The file manager to use for checking artwork availability
+/// @param windowCapability The window capability to use to check if artwork fields are supported
+/// @returns The array of artwork that needs to be uploaded
++ (NSArray<SDLArtwork *> *)sdl_findAllArtworksToBeUploadedFromCells:(NSArray<SDLChoiceCell *> *)cells fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability {
+ NSMutableSet<SDLArtwork *> *mutableArtworks = [NSMutableSet set];
+ for (SDLChoiceCell *cell in cells) {
+ if ([windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceImage] && [fileManager fileNeedsUpload:cell.artwork]) {
+ [mutableArtworks addObject:cell.artwork];
+ }
+
+ if ([windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceSecondaryImage] && [fileManager fileNeedsUpload:cell.secondaryArtwork]) {
+ [mutableArtworks addObject:cell.secondaryArtwork];
+ }
+ }
+
+ return [mutableArtworks allObjects];
+}
+
+#pragma mark - Presenting Choice Set
+
+#pragma mark Updating Keyboard Properties
+
+- (void)sdl_updateKeyboardPropertiesWithCompletionHandler:(void(^)(NSError *_Nullable))completionHandler {
+ self.currentState = SDLPreloadPresentChoicesOperationStateUpdatingKeyboardProperties;
+ if (self.keyboardDelegate == nil) { return completionHandler(nil); }
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_keyboardInputNotification:) name:SDLDidReceiveKeyboardInputNotification object:nil];
+
+ // Check if we're using a keyboard (searchable) choice set and setup keyboard properties if we need to
+ if (self.keyboardDelegate != nil && [self.keyboardDelegate respondsToSelector:@selector(customKeyboardConfiguration)]) {
+ SDLKeyboardProperties *customProperties = self.keyboardDelegate.customKeyboardConfiguration;
+ if (customProperties != nil) {
+ self.customKeyboardProperties = customProperties;
+ }
+ }
+
+ // Create the keyboard configuration based on the window capability's keyboard capabilities
+ SDLKeyboardProperties *modifiedKeyboardConfig = [self.windowCapability createValidKeyboardConfigurationBasedOnKeyboardCapabilitiesFromConfiguration:self.customKeyboardProperties];
+ if (modifiedKeyboardConfig == nil) { return completionHandler(nil); }
+
+ SDLSetGlobalProperties *setProperties = [[SDLSetGlobalProperties alloc] init];
+ setProperties.keyboardProperties = modifiedKeyboardConfig;
+
+ [self.connectionManager sendConnectionRequest:setProperties withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error setting keyboard properties to new value: %@, with error: %@", request, error);
+ }
+
+ return completionHandler(error);
+ }];
+}
+
+- (void)sdl_resetKeyboardPropertiesWithCompletionHandler:(void(^)(NSError *_Nullable))completionHandler {
+ self.currentState = SDLPreloadPresentChoicesOperationStateResettingKeyboardProperties;
+ if (self.keyboardDelegate == nil || self.originalKeyboardProperties == nil) { return completionHandler(nil); }
+
+ SDLSetGlobalProperties *setProperties = [[SDLSetGlobalProperties alloc] init];
+ setProperties.keyboardProperties = self.originalKeyboardProperties;
+
+ [self.connectionManager sendConnectionRequest:setProperties withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error resetting keyboard properties to values: %@, with error: %@", request, error);
+ }
+
+ completionHandler(error);
+ }];
+}
+
+#pragma mark Present
+
+- (void)sdl_presentChoiceSetWithCompletionHandler:(void(^)(NSError *_Nullable error))completionHandler {
+ self.currentState = SDLPreloadPresentChoicesOperationStatePresentingChoices;
+
+ __weak typeof(self) weakself = self;
+ [self.connectionManager sendConnectionRequest:[self sdl_performInteractionFromChoiceSet] withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Presenting choice set request: %@, failed with response: %@, error: %@", request, response, error);
+ return completionHandler(error);
+ }
+
+ SDLPerformInteractionResponse *performResponse = response;
+ [weakself sdl_setSelectedCellWithId:performResponse.choiceID];
+ weakself.selectedTriggerSource = performResponse.triggerSource;
+
+ return completionHandler(error);
+ }];
+}
+
+#pragma mark Cancel
+
+/**
+ * Cancels the choice set. If the choice set has not yet been sent to Core, it will not be sent. If the choice set is already presented on Core, the choice set will be immediately dismissed. Canceling an already presented choice set will only work if connected to Core versions 6.0+. On older versions of Core, the choice set will not be dismissed.
+ */
+- (void)sdl_cancelInteraction {
+ if (self.isFinished) {
+ SDLLogW(@"This operation has already finished so it can not be canceled.");
+ return;
+ } else if (self.isCancelled) {
+ SDLLogW(@"This operation has already been canceled. It will be finished at some point during the operation.");
+ return;
+ } else if (self.isExecuting) {
+ if (self.currentState != SDLPreloadPresentChoicesOperationStatePresentingChoices) {
+ SDLLogD(@"Canceling the operation before a present.");
+ return [self cancel];
+ } else if ([SDLGlobals.sharedGlobals.rpcVersion isLessThanVersion:[[SDLVersion alloc] initWithMajor:6 minor:0 patch:0]]) {
+ SDLLogW(@"Canceling a currently displaying choice set is not supported on this head unit. Trying to cancel the operation.");
+ return [self cancel];
+ }
+
+ self.currentState = SDLPreloadPresentChoicesOperationStateCancellingPresentChoices;
+ SDLLogD(@"Canceling the presented choice set interaction");
+
+ __weak typeof(self) weakSelf = self;
+ SDLCancelInteraction *cancelInteraction = [[SDLCancelInteraction alloc] initWithPerformInteractionCancelID:self.cancelId];
+ [self.connectionManager sendConnectionRequest:cancelInteraction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ weakSelf.internalError = error;
+ SDLLogE(@"Error canceling the presented choice set: %@, with error: %@", request, error);
+ return;
+ }
+ SDLLogD(@"The presented choice set was canceled successfully");
+ }];
+ } else {
+ SDLLogD(@"Canceling a choice set that has not yet been sent to Core");
+ [self cancel];
+ }
+}
+
+#pragma mark Present Helpers
+
+- (void)sdl_setSelectedCellWithId:(NSNumber<SDLInt> *)cellId {
+ for (NSUInteger i = 0; i < self.choiceSet.choices.count; i++) {
+ SDLChoiceCell *thisCell = self.choiceSet.choices[i];
+ if (thisCell.choiceId == cellId.unsignedIntValue) {
+ self.selectedCell = thisCell;
+ self.selectedCellRow = i;
+ break;
+ }
+ }
+}
+
+- (SDLPerformInteraction *)sdl_performInteractionFromChoiceSet {
+ NSParameterAssert(self.choiceSet != nil);
+
+ SDLLayoutMode layoutMode = nil;
+ switch (self.choiceSet.layout) {
+ case SDLChoiceSetLayoutList:
+ layoutMode = self.keyboardDelegate ? SDLLayoutModeListWithSearch : SDLLayoutModeListOnly;
+ break;
+ case SDLChoiceSetLayoutTiles:
+ layoutMode = self.keyboardDelegate ? SDLLayoutModeIconWithSearch : SDLLayoutModeIconOnly;
+ break;
+ }
+
+ NSMutableArray<NSNumber<SDLInt> *> *choiceIds = [NSMutableArray arrayWithCapacity:self.choiceSet.choices.count];
+ for (SDLChoiceCell *cell in self.choiceSet.choices) {
+ [choiceIds addObject:@(cell.choiceId)];
+ }
+
+ SDLPerformInteraction *performInteraction = [[SDLPerformInteraction alloc] init];
+ performInteraction.interactionMode = self.presentationMode;
+ performInteraction.initialText = self.choiceSet.title;
+ performInteraction.initialPrompt = self.choiceSet.initialPrompt;
+ performInteraction.helpPrompt = self.choiceSet.helpPrompt;
+ performInteraction.timeoutPrompt = self.choiceSet.timeoutPrompt;
+ performInteraction.vrHelp = self.choiceSet.helpList;
+ performInteraction.timeout = @((NSUInteger)(self.choiceSet.timeout * 1000));
+ performInteraction.interactionLayout = layoutMode;
+ performInteraction.interactionChoiceSetIDList = [choiceIds copy];
+ performInteraction.cancelID = @(self.cancelId);
+
+ return performInteraction;
+}
+
+#pragma mark Finding Cells
+
+- (nullable SDLChoiceCell *)sdl_cellFromChoiceId:(UInt16)choiceId {
+ for (SDLChoiceCell *cell in self.cellsToUpload) {
+ if (cell.choiceId == choiceId) { return cell; }
+ }
+
+ return nil;
+}
+
+#pragma mark - Assembling Choice RPCs
+
++ (nullable SDLCreateInteractionChoiceSet *)sdl_choiceFromCell:(SDLChoiceCell *)cell windowCapability:(SDLWindowCapability *)windowCapability displayName:(NSString *)displayName isVROptional:(BOOL)isVROptional {
+ NSArray<NSString *> *vrCommands = nil;
+ if (cell.voiceCommands == nil) {
+ vrCommands = isVROptional ? nil : @[[NSString stringWithFormat:@"%hu", cell.choiceId]];
+ } else {
+ vrCommands = cell.voiceCommands;
+ }
+
+ NSString *menuName = nil;
+ if ([self sdl_shouldSendChoiceTextBasedOnWindowCapability:windowCapability displayName:displayName]) {
+ menuName = cell.uniqueText;
+ }
+
+ if (menuName == nil) {
+ SDLLogE(@"Could not convert SDLChoiceCell to SDLCreateInteractionChoiceSet because there was no menu name. This could be because the head unit does not support text field 'menuName', which means it does not support Choice Sets. It will not be shown. Cell: %@", cell);
+ return nil;
+ }
+
+ NSString *secondaryText = [self sdl_shouldSendChoiceSecondaryTextBasedOnWindowCapability:windowCapability] ? cell.secondaryText : nil;
+ NSString *tertiaryText = [self sdl_shouldSendChoiceTertiaryTextBasedOnWindowCapability:windowCapability] ? cell.tertiaryText : nil;
+
+ SDLImage *image = [self sdl_shouldSendChoicePrimaryImageBasedOnWindowCapability:windowCapability] ? cell.artwork.imageRPC : nil;
+ SDLImage *secondaryImage = [self sdl_shouldSendChoiceSecondaryImageBasedOnWindowCapability:windowCapability] ? cell.secondaryArtwork.imageRPC : nil;
+
+ SDLChoice *choice = [[SDLChoice alloc] initWithId:cell.choiceId menuName:menuName vrCommands:vrCommands image:image secondaryText:secondaryText secondaryImage:secondaryImage tertiaryText:tertiaryText];
+
+ return [[SDLCreateInteractionChoiceSet alloc] initWithId:(UInt32)choice.choiceID.unsignedIntValue choiceSet:@[choice]];
+}
+
+/// Determine if we should send primary text. If textFields is nil, we don't know the capabilities and we will send everything.
++ (BOOL)sdl_shouldSendChoiceTextBasedOnWindowCapability:(SDLWindowCapability *)windowCapability displayName:(NSString *)displayName {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ if ([displayName isEqualToString:SDLDisplayTypeGen38Inch]) {
+ return YES;
+ }
+#pragma clang diagnostic pop
+
+ return [windowCapability hasTextFieldOfName:SDLTextFieldNameMenuName];
+}
+
+/// Determine if we should send secondary text. If textFields is nil, we don't know the capabilities and we will send everything.
++ (BOOL)sdl_shouldSendChoiceSecondaryTextBasedOnWindowCapability:(SDLWindowCapability *)windowCapability {
+ return [windowCapability hasTextFieldOfName:SDLTextFieldNameSecondaryText];
+}
+
+/// Determine if we should send tertiary text. If textFields is nil, we don't know the capabilities and we will send everything.
++ (BOOL)sdl_shouldSendChoiceTertiaryTextBasedOnWindowCapability:(SDLWindowCapability *)windowCapability {
+ return [windowCapability hasTextFieldOfName:SDLTextFieldNameTertiaryText];
+}
+
+/// Determine if we should send the primary image. If imageFields is nil, we don't know the capabilities and we will send everything.
++ (BOOL)sdl_shouldSendChoicePrimaryImageBasedOnWindowCapability:(SDLWindowCapability *)windowCapability {
+ return [windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceImage];
+}
+
+/// Determine if we should send the secondary image. If imageFields is nil, we don't know the capabilities and we will send everything.
++ (BOOL)sdl_shouldSendChoiceSecondaryImageBasedOnWindowCapability:(SDLWindowCapability *)windowCapability {
+ return [windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceSecondaryImage];
+}
+
+#pragma mark - SDL Notifications
+
+- (void)sdl_keyboardInputNotification:(SDLRPCNotificationNotification *)notification {
+ if (self.isCancelled) { return [self finishOperation]; }
+
+ if (self.keyboardDelegate == nil) { return; }
+ SDLOnKeyboardInput *onKeyboard = notification.notification;
+
+ if ([self.keyboardDelegate respondsToSelector:@selector(keyboardDidSendEvent:text:)]) {
+ [self.keyboardDelegate keyboardDidSendEvent:onKeyboard.event text:onKeyboard.data];
+ }
+
+ __weak typeof(self) weakself = self;
+ if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventVoice] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventSubmitted]) {
+ // Submit voice or text
+ [self.keyboardDelegate userDidSubmitInput:onKeyboard.data withEvent:onKeyboard.event];
+ } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventKeypress]) {
+ // Notify of keypress
+ if ([self.keyboardDelegate respondsToSelector:@selector(updateAutocompleteWithInput:autoCompleteResultsHandler:)]) {
+ [self.keyboardDelegate updateAutocompleteWithInput:onKeyboard.data autoCompleteResultsHandler:^(NSArray<NSString *> * _Nullable updatedAutoCompleteList) {
+ __strong typeof(self) strongself = weakself;
+ NSArray<NSString *> *newList = nil;
+ if (updatedAutoCompleteList.count > 100) {
+ newList = [updatedAutoCompleteList subarrayWithRange:NSMakeRange(0, 100)];
+ } else {
+ newList = updatedAutoCompleteList;
+ }
+
+ strongself.customKeyboardProperties.autoCompleteList = (newList.count > 0) ? newList : @[];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ strongself.customKeyboardProperties.autoCompleteText = (newList.count > 0) ? newList.firstObject : nil;
+#pragma clang diagnostic pop
+ [strongself sdl_updateKeyboardPropertiesWithCompletionHandler:^(NSError * _Nullable error) {}];
+ }];
+ }
+
+ if ([self.keyboardDelegate respondsToSelector:@selector(updateCharacterSetWithInput:completionHandler:)]) {
+ [self.keyboardDelegate updateCharacterSetWithInput:onKeyboard.data completionHandler:^(NSArray<NSString *> *updatedCharacterSet) {
+ __strong typeof(self) strongself = weakself;
+ strongself.customKeyboardProperties.limitedCharacterList = updatedCharacterSet;
+ [strongself sdl_updateKeyboardPropertiesWithCompletionHandler:^(NSError * _Nullable error) {}];
+ }];
+ }
+ } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventAborted] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventCancelled]) {
+ // Notify of abort / cancellation
+ [self.keyboardDelegate keyboardDidAbortWithReason:onKeyboard.event];
+ } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskDisabled]) {
+ // Notify of key mask change
+ if ([self.keyboardDelegate respondsToSelector:@selector(keyboardDidUpdateInputMask:)]) {
+ BOOL isEnabled = [onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
+ [self.keyboardDelegate keyboardDidUpdateInputMask:isEnabled];
+ }
+ }
+}
+
+#pragma mark - Property Overrides
+
+- (void)finishOperation {
+ [self finishOperation:nil];
+}
+
+- (void)finishOperation:(nullable NSError *)error {
+ self.currentState = SDLPreloadPresentChoicesOperationStateFinishing;
+
+ self.internalError = error;
+ self.preloadCompletionHandler(self.loadedCells, self.internalError);
+
+ if (self.choiceSet.delegate == nil) {
+ SDLLogD(@"Preload finished, no choice set delegate was set, so no present will occur.");
+ } else if (error != nil) {
+ SDLLogW(@"Choice set did error: %@", self.internalError);
+ [self.choiceSet.delegate choiceSet:self.choiceSet didReceiveError:self.internalError];
+ } else if (self.selectedCell != nil) {
+ SDLLogD(@"Choice set did present successfully: %@, selected choice: %@, trigger source: %@, row index: %ld", self.choiceSet, self.selectedCell, self.selectedTriggerSource, self.selectedCellRow);
+ [self.choiceSet.delegate choiceSet:self.choiceSet didSelectChoice:self.selectedCell withSource:self.selectedTriggerSource atRowIndex:self.selectedCellRow];
+ } else {
+ SDLLogE(@"Present finished, but an unhandled state occurred and callback failed");
+ }
+
+ [super finishOperation];
+}
+
+- (nullable NSString *)name {
+ return [NSString stringWithFormat:@"%@ - %@", self.class, self.operationId];
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.h b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.h
new file mode 100644
index 000000000..72be60d9a
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.h
@@ -0,0 +1,32 @@
+//
+// SDLPreloadPresentChoiceSetOperationUtilities.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 8/27/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLChoiceCell;
+@class SDLChoiceSet;
+@class SDLWindowCapability;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLPreloadPresentChoicesOperationUtilities : NSObject
+
+@property (class, assign, nonatomic) UInt16 choiceId;
+@property (class, assign, nonatomic) BOOL reachedMaxId;
+
+/// Assigns a unique id to each choice item.
+/// @param cells An array of choices
+/// @param loadedCells The already loaded cells with ids to avoid
++ (void)assignIdsToCells:(NSOrderedSet<SDLChoiceCell *> *)cells loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells;
+
++ (void)makeCellsToUploadUnique:(NSMutableOrderedSet<SDLChoiceCell *> *)cellsToUpload basedOnLoadedCells:(NSMutableSet<SDLChoiceCell *> *)loadedCells windowCapability:(SDLWindowCapability *)windowCapability;
++ (void)updateChoiceSet:(SDLChoiceSet *)choiceSet withLoadedCells:(NSSet<SDLChoiceCell *> *)loadedCells cellsToUpload:(NSSet<SDLChoiceCell *> *)cellsToUpload;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.m b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.m
new file mode 100644
index 000000000..a3a3a3f68
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPreloadPresentChoicesOperationUtilities.m
@@ -0,0 +1,233 @@
+//
+// SDLPreloadPresentChoiceSetOperationUtilities.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 8/27/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import "SDLPreloadPresentChoicesOperationUtilities.h"
+
+#import "SDLChoiceCell.h"
+#import "SDLChoiceSet.h"
+#import "SDLGlobals.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+#import "SDLVersion.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLChoiceCell()
+
+@property (assign, nonatomic) UInt16 choiceId;
+@property (copy, nonatomic, readwrite, nullable) NSString *secondaryText;
+@property (copy, nonatomic, readwrite, nullable) NSString *tertiaryText;
+@property (copy, nonatomic, readwrite, nullable) NSArray<NSString *> *voiceCommands;
+@property (strong, nonatomic, readwrite, nullable) SDLArtwork *artwork;
+@property (strong, nonatomic, readwrite, nullable) SDLArtwork *secondaryArtwork;
+
+@property (assign, nonatomic) NSUInteger uniqueTextId;
+
+@end
+
+@implementation SDLPreloadPresentChoicesOperationUtilities
+
+// HAX: Choice ID starts at 1 instead of 0 because of an HMI bug that existed until 2021 (https://github.com/smartdevicelink/generic_hmi/commit/b292fbbec095b9ce11b520d47ec95b6fcff8e247)
+static UInt16 _choiceId = 1;
+static BOOL _reachedMaxId = NO;
+
+#pragma mark Getters / Setters
+
++ (UInt16)choiceId {
+ return _choiceId;
+}
+
++ (void)setChoiceId:(UInt16)choiceId {
+ _choiceId = choiceId;
+}
+
++ (BOOL)reachedMaxId {
+ return _reachedMaxId;
+}
+
++ (void)setReachedMaxId:(BOOL)reachedMaxId {
+ _reachedMaxId = reachedMaxId;
+}
+
+#pragma mark - Cell Ids
+
++ (void)assignIdsToCells:(NSOrderedSet<SDLChoiceCell *> *)cells loadedCells:(NSSet<SDLChoiceCell *> *)loadedCells {
+ NSMutableArray<NSNumber *> *usedIds = [NSMutableArray array];
+ for (SDLChoiceCell *loadedCell in loadedCells) { [usedIds addObject:@(loadedCell.choiceId)]; }
+ NSMutableArray<NSNumber *> *sortedUsedIds = [usedIds sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]].mutableCopy;
+
+ // Loop through the cells we need ids for. Get and assign those ids.
+ for (UInt16 i = 0; i < cells.count; i++) {
+ UInt16 cellId = [self sdl_nextChoiceIdBasedOnUsedIds:sortedUsedIds];
+ cells[i].choiceId = cellId;
+
+ // Insert the ids into the usedIds sorted array in the correct position
+ for (NSUInteger j = 0; j < sortedUsedIds.count; j++) {
+ if (sortedUsedIds[j].unsignedShortValue > cellId) {
+ [sortedUsedIds insertObject:@(cellId) atIndex:j];
+ break;
+ } else if (j == (sortedUsedIds.count - 1)) {
+ [sortedUsedIds addObject:@(cellId)];
+ break;
+ }
+ }
+ }
+}
+
+/// Find the next available choice id. Takes into account the loaded cells to ensure there are not duplicates
+/// @param usedIds The already loaded cell ids
+/// @return The choice id between 0 - 65535, or NSNotFound if no cell ids were available
++ (UInt16)sdl_nextChoiceIdBasedOnUsedIds:(NSArray<NSNumber *> *)usedIds {
+ // Check if we are entirely full, or if we've advanced beyond the max value, loop back
+ if (_choiceId == UINT16_MAX) {
+ _choiceId = 1;
+ _reachedMaxId = YES;
+ }
+
+ if (_reachedMaxId) {
+ // We've looped all the way around, so we need to check loaded cells
+ // Sort the set of cells by the choice id so that we can more easily check which slots are available
+ if (usedIds.count >= (UINT16_MAX + 1)) {
+ // If we've maxed out our slots, return the max value
+ _choiceId = UINT16_MAX;
+ return _choiceId;
+ }
+
+ // If the last value isn't the max value, just keep grabbing towards the max value
+ UInt16 lastUsedId = usedIds.lastObject.unsignedShortValue;
+ if (lastUsedId < UINT16_MAX) {
+ _choiceId = lastUsedId + 1;
+ return _choiceId;
+ }
+
+ // All our easy options are gone. Find and grab an empty slot from within the sorted list
+ for (UInt16 i = 0; i < usedIds.count; i++) {
+ UInt16 loopId = usedIds[i].unsignedShortValue;
+ if (i != loopId) {
+ // This slot is open because the cell id does not match an open sorted slot
+ _choiceId = i;
+ return _choiceId;
+ }
+ }
+
+ // This *shouldn't* be possible
+ _choiceId = UINT16_MAX;
+ return _choiceId;
+ } else {
+ // We haven't looped all the way around yet, so we'll just take the current number, then advance the item
+ return _choiceId++;
+ }
+}
+
+#pragma mark - Choice Uniqueness
+
++ (void)makeCellsToUploadUnique:(NSMutableOrderedSet<SDLChoiceCell *> *)cellsToUpload basedOnLoadedCells:(NSMutableSet<SDLChoiceCell *> *)loadedCells windowCapability:(SDLWindowCapability *)windowCapability {
+ if (cellsToUpload.count == 0) { return; }
+
+ // If we're on < RPC 7.1, all primary texts need to be unique, so we don't need to check removed properties and duplicate cells
+ // On > RPC 7.1, at this point all cells are unique when considering all properties, but we also need to check if any cells will _appear_ as duplicates when displayed on the screen. To check that, we'll remove properties from the set cells based on the system capabilities (we probably don't need to consider them changing between now and when they're actually sent to the HU) and check for uniqueness again. Then we'll add unique identifiers to primary text if there are duplicates. Then we transfer the primary text identifiers back to the main cells and add those to an operation to be sent.
+ NSArray<SDLChoiceCell *> *strippedCellsToUpload = [[NSArray alloc] initWithArray:cellsToUpload.array copyItems:YES];
+ NSArray<SDLChoiceCell *> *strippedLoadedCells = [[NSArray alloc] initWithArray:loadedCells.allObjects copyItems:YES];
+ BOOL supportsChoiceUniqueness = [[SDLGlobals sharedGlobals].rpcVersion isGreaterThanOrEqualToVersion:[SDLVersion versionWithMajor:7 minor:1 patch:0]];
+ if (supportsChoiceUniqueness) {
+ [self sdl_removeUnusedProperties:strippedCellsToUpload basedOnWindowCapability:windowCapability];
+ [self sdl_removeUnusedProperties:strippedLoadedCells basedOnWindowCapability:windowCapability];
+ }
+
+ // Now remove duplicate cells that are already on the head unit, then add unique names to the ones to upload
+ [self sdl_addUniqueNamesToCells:strippedCellsToUpload loadedCells:strippedLoadedCells supportsChoiceUniqueness:supportsChoiceUniqueness];
+ [self sdl_transferUniqueNamesFromCells:strippedCellsToUpload toCells:cellsToUpload];
+}
+
++ (void)updateChoiceSet:(SDLChoiceSet *)choiceSet withLoadedCells:(NSSet<SDLChoiceCell *> *)loadedCells cellsToUpload:(NSSet<SDLChoiceCell *> *)cellsToUpload {
+ NSMutableArray<SDLChoiceCell *> *choiceSetCells = [NSMutableArray array];
+ for (SDLChoiceCell *cell in choiceSet.choices) {
+ [choiceSetCells addObject:([loadedCells member:cell] ?: [cellsToUpload member:cell])];
+ }
+
+ choiceSet.choices = [choiceSetCells copy];
+}
+
++ (void)sdl_removeUnusedProperties:(NSArray<SDLChoiceCell *> *)choiceCells basedOnWindowCapability:(SDLWindowCapability *)windowCapability {
+ for (SDLChoiceCell *cell in choiceCells) {
+ // Strip away fields that cannot be used to determine uniqueness visually including fields not supported by the HMI
+ cell.voiceCommands = nil;
+
+ if (![windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceImage]) {
+ cell.artwork = nil;
+ }
+ if (![windowCapability hasTextFieldOfName:SDLTextFieldNameSecondaryText]) {
+ cell.secondaryText = nil;
+ }
+ if (![windowCapability hasTextFieldOfName:SDLTextFieldNameTertiaryText]) {
+ cell.tertiaryText = nil;
+ }
+ if (![windowCapability hasImageFieldOfName:SDLImageFieldNameChoiceSecondaryImage]) {
+ cell.secondaryArtwork = nil;
+ }
+ }
+}
+
+/// Checks if 2 or more cells have the same text/title. In case this condition is true, this function will handle the presented issue by adding "(count)".
+/// E.g. Choices param contains 2 cells with text/title "Address" will be handled by updating the uniqueText/uniqueTitle of the second cell to "Address (2)".
+/// @param cellsToUpload The choices to be uploaded.
+/// @param loadedCells The cells already on the head unit
++ (void)sdl_addUniqueNamesToCells:(NSArray<SDLChoiceCell *> *)cellsToUpload loadedCells:(NSArray<SDLChoiceCell *> *)loadedCells supportsChoiceUniqueness:(BOOL)supportsChoiceUniqueness {
+ // Tracks how many of each cell primary text there are so that we can append numbers to make each unique as necessary
+ NSMutableDictionary<id<NSCopying>, NSMutableArray<NSNumber *> *> *dictCounter = [[NSMutableDictionary alloc] init];
+
+ // Include cells from loaded cells to ensure that new cells get the correct title
+ for (SDLChoiceCell *loadedCell in loadedCells) {
+ id<NSCopying> cellKey = supportsChoiceUniqueness ? loadedCell : loadedCell.text;
+ NSNumber *cellUniqueId = @(loadedCell.uniqueTextId);
+ if (dictCounter[cellKey] == nil) {
+ dictCounter[cellKey] = [NSMutableArray arrayWithObject:cellUniqueId];
+ } else {
+ [dictCounter[cellKey] addObject:cellUniqueId];
+ }
+ }
+
+ // Sort the arrays so that the numbers are in order
+ for (id<NSCopying> cellKey in dictCounter.keyEnumerator) {
+ [dictCounter[cellKey] sortUsingComparator:^NSComparisonResult(NSNumber *_Nonnull obj1, NSNumber *_Nonnull obj2) {
+ if (obj1.unsignedIntegerValue > obj2.unsignedIntegerValue) { return NSOrderedDescending; }
+ if (obj1.unsignedIntegerValue < obj2.unsignedIntegerValue) { return NSOrderedAscending; }
+
+ return NSOrderedSame;
+ }];
+ }
+
+ // Run through cellsToUpload and add unique text as needed
+ for (SDLChoiceCell *cell in cellsToUpload) {
+ id<NSCopying> cellKey = supportsChoiceUniqueness ? cell : cell.text;
+ if (dictCounter[cellKey] == nil) {
+ dictCounter[cellKey] = [NSMutableArray arrayWithObject:@(cell.uniqueTextId)];
+ } else {
+ // There are already some duplicates, loop through to find the lowest unused duplicate number
+ NSUInteger lowestMissingUniqueId = dictCounter[cellKey].lastObject.unsignedIntegerValue + 1;
+ for (NSUInteger i = 1; i < dictCounter[cellKey].count + 1; i++) {
+ if (i != dictCounter[cellKey][i - 1].unsignedIntegerValue) {
+ lowestMissingUniqueId = i;
+ break;
+ }
+ }
+
+ cell.uniqueTextId = lowestMissingUniqueId;
+ [dictCounter[cellKey] insertObject:@(cell.uniqueTextId) atIndex:(cell.uniqueTextId - 1)];
+ }
+ }
+}
+
++ (void)sdl_transferUniqueNamesFromCells:(NSArray<SDLChoiceCell *> *)fromCells toCells:(NSOrderedSet<SDLChoiceCell *> *)toCells {
+ for (NSUInteger i = 0; i < fromCells.count; i++) {
+ toCells[i].uniqueTextId = fromCells[i].uniqueTextId;
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPresentChoiceSetOperation.h b/SmartDeviceLink/private/SDLPresentChoiceSetOperation.h
deleted file mode 100644
index b2589a280..000000000
--- a/SmartDeviceLink/private/SDLPresentChoiceSetOperation.h
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// SDLPresentChoiceSetOperation.h
-// SmartDeviceLink
-//
-// Created by Joel Fischer on 5/24/18.
-// Copyright © 2018 smartdevicelink. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-#import "SDLAsynchronousOperation.h"
-#import "SDLInteractionMode.h"
-#import "SDLTriggerSource.h"
-
-@class SDLChoiceCell;
-@class SDLChoiceSet;
-@class SDLKeyboardProperties;
-@class SDLWindowCapability;
-
-@protocol SDLConnectionManagerType;
-@protocol SDLKeyboardDelegate;
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface SDLPresentChoiceSetOperation : SDLAsynchronousOperation
-
-/**
- The choice set to be displayed.
- */
-@property (strong, nonatomic, readonly) SDLChoiceSet *choiceSet;
-
-/**
- The choice set item the user selected.
- */
-@property (strong, nonatomic, readonly, nullable) SDLChoiceCell *selectedCell;
-
-/**
- How the user selected the choice set item: either from the menu or through voice-recognition.
- */
-@property (strong, nonatomic, readonly, nullable) SDLTriggerSource selectedTriggerSource;
-
-/**
- The row number of the choice set item the user selected.
- */
-@property (assign, nonatomic, readonly) NSUInteger selectedCellRow;
-
-/**
- An operation to present a choice set.
-
- @param connectionManager The connection manager
- @param choiceSet The choice set to be displayed
- @param mode If the set should be presented for the user to interact via voice, touch, or both
- @param originalKeyboardProperties The keyboard configuration
- @param keyboardDelegate The keyboard delegate called when the user interacts with the keyboard
- @param cancelID A unique ID for this specific choice set that allows cancellation through the `CancelInteraction` RPC.
- @return A SDLPresentChoiceSetOperation object
- */
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager choiceSet:(SDLChoiceSet *)choiceSet mode:(SDLInteractionMode)mode keyboardProperties:(nullable SDLKeyboardProperties *)originalKeyboardProperties keyboardDelegate:(nullable id<SDLKeyboardDelegate>)keyboardDelegate cancelID:(UInt16)cancelID windowCapability:(SDLWindowCapability *)windowCapability;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPresentChoiceSetOperation.m b/SmartDeviceLink/private/SDLPresentChoiceSetOperation.m
deleted file mode 100644
index 121383ce7..000000000
--- a/SmartDeviceLink/private/SDLPresentChoiceSetOperation.m
+++ /dev/null
@@ -1,348 +0,0 @@
-//
-// SDLPresentChoiceSetOperation.m
-// SmartDeviceLink
-//
-// Created by Joel Fischer on 5/24/18.
-// Copyright © 2018 smartdevicelink. All rights reserved.
-//
-
-#import "SDLPresentChoiceSetOperation.h"
-
-#import "SDLCancelInteraction.h"
-#import "SDLChoiceCell.h"
-#import "SDLChoiceSet.h"
-#import "SDLChoiceSetDelegate.h"
-#import "SDLConnectionManagerType.h"
-#import "SDLGlobals.h"
-#import "SDLKeyboardDelegate.h"
-#import "SDLKeyboardProperties.h"
-#import "SDLLogMacros.h"
-#import "SDLNotificationConstants.h"
-#import "SDLOnKeyboardInput.h"
-#import "SDLPerformInteraction.h"
-#import "SDLPerformInteractionResponse.h"
-#import "SDLRPCNotificationNotification.h"
-#import "SDLSetGlobalProperties.h"
-#import "SDLVersion.h"
-#import "SDLWindowCapability+ScreenManagerExtensions.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface SDLChoiceCell()
-
-@property (assign, nonatomic) UInt16 choiceId;
-
-@end
-
-@interface SDLChoiceSet()
-
-@property (copy, nonatomic) SDLChoiceSetCanceledHandler canceledHandler;
-
-
-@end
-
-@interface SDLPresentChoiceSetOperation()
-
-@property (strong, nonatomic) NSUUID *operationId;
-@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
-@property (strong, nonatomic, readwrite) SDLChoiceSet *choiceSet;
-@property (strong, nonatomic) SDLInteractionMode presentationMode;
-@property (strong, nonatomic, nullable) SDLKeyboardProperties *originalKeyboardProperties;
-@property (strong, nonatomic, nullable) SDLKeyboardProperties *keyboardProperties;
-@property (weak, nonatomic) id<SDLKeyboardDelegate> keyboardDelegate;
-
-@property (strong, nonatomic, readonly) SDLPerformInteraction *performInteraction;
-@property (strong, nonatomic, readonly) SDLLayoutMode layoutMode;
-@property (strong, nonatomic, readonly) NSArray<NSNumber<SDLInt> *> *choiceIds;
-@property (assign, nonatomic) UInt16 cancelId;
-@property (assign, nonatomic) BOOL updatedKeyboardProperties;
-
-@property (copy, nonatomic, nullable) NSError *internalError;
-@property (strong, nonatomic, readwrite, nullable) SDLChoiceCell *selectedCell;
-@property (strong, nonatomic, readwrite, nullable) SDLTriggerSource selectedTriggerSource;
-@property (assign, nonatomic, readwrite) NSUInteger selectedCellRow;
-@property (strong, nonatomic) SDLWindowCapability *windowCapability;
-
-@end
-
-@implementation SDLPresentChoiceSetOperation
-
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager choiceSet:(SDLChoiceSet *)choiceSet mode:(SDLInteractionMode)mode keyboardProperties:(nullable SDLKeyboardProperties *)originalKeyboardProperties keyboardDelegate:(nullable id<SDLKeyboardDelegate>)keyboardDelegate cancelID:(UInt16)cancelID windowCapability:(SDLWindowCapability *)windowCapability {
- self = [super init];
- if (!self) { return self; }
-
- _connectionManager = connectionManager;
- _choiceSet = choiceSet;
-
- __weak typeof(self) weakSelf = self;
- self.choiceSet.canceledHandler = ^{
- [weakSelf sdl_cancelInteraction];
- };
-
- _presentationMode = mode;
- _operationId = [NSUUID UUID];
-
- _originalKeyboardProperties = originalKeyboardProperties;
- _keyboardProperties = originalKeyboardProperties;
- _keyboardDelegate = keyboardDelegate;
- _cancelId = cancelID;
-
- _selectedCellRow = NSNotFound;
- _windowCapability = windowCapability;
-
- return self;
-}
-
-- (void)start {
- [super start];
- if (self.isCancelled) { return; }
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_keyboardInputNotification:) name:SDLDidReceiveKeyboardInputNotification object:nil];
-
- [self sdl_start];
-}
-
-- (void)sdl_start {
- // Check if we're using a keyboard (searchable) choice set and setup keyboard properties if we need to
- if (self.keyboardDelegate != nil && [self.keyboardDelegate respondsToSelector:@selector(customKeyboardConfiguration)]) {
- SDLKeyboardProperties *customProperties = self.keyboardDelegate.customKeyboardConfiguration;
- if (customProperties != nil) {
- self.keyboardProperties = customProperties;
- }
- }
-
- [self sdl_updateKeyboardPropertiesWithCompletionHandler:^{
- if (self.isCancelled) {
- [self finishOperation];
- return;
- }
-
- [self sdl_presentChoiceSet];
- }];
-}
-
-#pragma mark - Sending Requests
-
-- (void)sdl_updateKeyboardPropertiesWithCompletionHandler:(nullable void(^)(void))completionHandler {
- // Create the keyboard configuration based on the window capability's keyboard capabilities
- SDLKeyboardProperties *modifiedKeyboardConfig = [self.windowCapability createValidKeyboardConfigurationBasedOnKeyboardCapabilitiesFromConfiguration:self.keyboardProperties];
- if (modifiedKeyboardConfig == nil) {
- if (completionHandler != nil) {
- completionHandler();
- }
- return;
- }
- SDLSetGlobalProperties *setProperties = [[SDLSetGlobalProperties alloc] init];
- setProperties.keyboardProperties = modifiedKeyboardConfig;
-
- __weak typeof(self) weakself = self;
- [self.connectionManager sendConnectionRequest:setProperties withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error setting keyboard properties to new value: %@, with error: %@", request, error);
- }
-
- weakself.updatedKeyboardProperties = YES;
-
- if (completionHandler != nil) {
- completionHandler();
- }
- }];
-}
-
-- (void)sdl_presentChoiceSet {
- __weak typeof(self) weakself = self;
- [self.connectionManager sendConnectionRequest:self.performInteraction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Presenting choice set failed with response: %@, error: %@", response, error);
- weakself.internalError = error;
-
- [weakself finishOperation];
- return;
- }
-
- SDLPerformInteractionResponse *performResponse = (SDLPerformInteractionResponse *)response;
- [weakself sdl_setSelectedCellWithId:performResponse.choiceID];
- weakself.selectedTriggerSource = performResponse.triggerSource;
-
- [weakself finishOperation];
- }];
-}
-
-#pragma mark - Helpers
-
-- (void)sdl_setSelectedCellWithId:(NSNumber<SDLInt> *)cellId {
- __weak typeof(self) weakself = self;
- [self.choiceSet.choices enumerateObjectsUsingBlock:^(SDLChoiceCell * _Nonnull cell, NSUInteger i, BOOL * _Nonnull stop) {
- if (cell.choiceId == cellId.unsignedIntValue) {
- weakself.selectedCell = cell;
- weakself.selectedCellRow = i;
- }
- }];
-}
-
-/**
- * Cancels the choice set. If the choice set has not yet been sent to Core, it will not be sent. If the choice set is already presented on Core, the choice set will be immediately dismissed. Canceling an already presented choice set will only work if connected to Core versions 6.0+. On older versions of Core, the choice set will not be dismissed.
- */
-- (void)sdl_cancelInteraction {
- if (self.isFinished) {
- SDLLogW(@"This operation has already finished so it can not be canceled.");
- return;
- } else if (self.isCancelled) {
- SDLLogW(@"This operation has already been canceled. It will be finished at some point during the operation.");
- return;
- } else if (self.isExecuting) {
- if ([SDLGlobals.sharedGlobals.rpcVersion isLessThanVersion:[[SDLVersion alloc] initWithMajor:6 minor:0 patch:0]]) {
- SDLLogE(@"Canceling a choice set is not supported on this head unit");
- return;
- }
-
- SDLLogD(@"Canceling the presented choice set interaction");
-
- SDLCancelInteraction *cancelInteraction = [[SDLCancelInteraction alloc] initWithPerformInteractionCancelID:self.cancelId];
-
- __weak typeof(self) weakSelf = self;
- [self.connectionManager sendConnectionRequest:cancelInteraction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if (error != nil) {
- weakSelf.internalError = error;
- SDLLogE(@"Error canceling the presented choice set: %@, with error: %@", request, error);
- return;
- }
- SDLLogD(@"The presented choice set was canceled successfully");
- }];
- } else {
- SDLLogD(@"Canceling a choice set that has not yet been sent to Core");
- [self cancel];
- }
-}
-
-#pragma mark - Getters
-
-- (SDLPerformInteraction *)performInteraction {
- SDLPerformInteraction *performInteraction = [[SDLPerformInteraction alloc] init];
- performInteraction.interactionMode = self.presentationMode;
- performInteraction.initialText = self.choiceSet.title;
- performInteraction.initialPrompt = self.choiceSet.initialPrompt;
- performInteraction.helpPrompt = self.choiceSet.helpPrompt;
- performInteraction.timeoutPrompt = self.choiceSet.timeoutPrompt;
- performInteraction.vrHelp = self.choiceSet.helpList;
- performInteraction.timeout = @((NSUInteger)(self.choiceSet.timeout * 1000));
- performInteraction.interactionLayout = self.layoutMode;
- performInteraction.interactionChoiceSetIDList = self.choiceIds;
- performInteraction.cancelID = @(self.cancelId);
-
- return performInteraction;
-}
-
-- (SDLLayoutMode)layoutMode {
- switch (self.choiceSet.layout) {
- case SDLChoiceSetLayoutList:
- return self.keyboardDelegate ? SDLLayoutModeListWithSearch : SDLLayoutModeListOnly;
- case SDLChoiceSetLayoutTiles:
- return self.keyboardDelegate ? SDLLayoutModeIconWithSearch : SDLLayoutModeIconOnly;
- }
-}
-
-- (NSArray<NSNumber<SDLInt> *> *)choiceIds {
- NSMutableArray<NSNumber<SDLInt> *> *choiceIds = [NSMutableArray arrayWithCapacity:self.choiceSet.choices.count];
- for (SDLChoiceCell *cell in self.choiceSet.choices) {
- [choiceIds addObject:@(cell.choiceId)];
- }
-
- return [choiceIds copy];
-}
-
-#pragma mark - SDL Notifications
-
-- (void)sdl_keyboardInputNotification:(SDLRPCNotificationNotification *)notification {
- if (self.isCancelled) {
- [self finishOperation];
- return;
- }
-
- if (self.keyboardDelegate == nil) { return; }
- SDLOnKeyboardInput *onKeyboard = notification.notification;
-
- if ([self.keyboardDelegate respondsToSelector:@selector(keyboardDidSendEvent:text:)]) {
- [self.keyboardDelegate keyboardDidSendEvent:onKeyboard.event text:onKeyboard.data];
- }
-
- __weak typeof(self) weakself = self;
- if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventVoice] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventSubmitted]) {
- // Submit voice or text
- [self.keyboardDelegate userDidSubmitInput:onKeyboard.data withEvent:onKeyboard.event];
- } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventKeypress]) {
- // Notify of keypress
- if ([self.keyboardDelegate respondsToSelector:@selector(updateAutocompleteWithInput:autoCompleteResultsHandler:)]) {
- [self.keyboardDelegate updateAutocompleteWithInput:onKeyboard.data autoCompleteResultsHandler:^(NSArray<NSString *> * _Nullable updatedAutoCompleteList) {
- NSArray<NSString *> *newList = nil;
- if (updatedAutoCompleteList.count > 100) {
- newList = [updatedAutoCompleteList subarrayWithRange:NSMakeRange(0, 100)];
- } else {
- newList = updatedAutoCompleteList;
- }
-
- weakself.keyboardProperties.autoCompleteList = (newList.count > 0) ? newList : @[];
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- weakself.keyboardProperties.autoCompleteText = (newList.count > 0) ? newList.firstObject : nil;
-#pragma clang diagnostic pop
- [weakself sdl_updateKeyboardPropertiesWithCompletionHandler:nil];
- }];
- }
-
- if ([self.keyboardDelegate respondsToSelector:@selector(updateCharacterSetWithInput:completionHandler:)]) {
- [self.keyboardDelegate updateCharacterSetWithInput:onKeyboard.data completionHandler:^(NSArray<NSString *> *updatedCharacterSet) {
- weakself.keyboardProperties.limitedCharacterList = updatedCharacterSet;
- [self sdl_updateKeyboardPropertiesWithCompletionHandler:nil];
- }];
- }
- } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventAborted] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventCancelled]) {
- // Notify of abort / cancellation
- [self.keyboardDelegate keyboardDidAbortWithReason:onKeyboard.event];
- } else if ([onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled] || [onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskDisabled]) {
- // Notify of key mask change
- if ([self.keyboardDelegate respondsToSelector:@selector(keyboardDidUpdateInputMask:)]) {
- BOOL isEnabled = [onKeyboard.event isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
- [self.keyboardDelegate keyboardDidUpdateInputMask:isEnabled];
- }
- }
-}
-
-#pragma mark - Property Overrides
-
-- (void)finishOperation {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- if (self.keyboardProperties == nil) {
- [super finishOperation];
- return;
- }
-
- // We need to reset the keyboard properties
- SDLSetGlobalProperties *setProperties = [[SDLSetGlobalProperties alloc] init];
- setProperties.keyboardProperties = self.originalKeyboardProperties;
-
- [self.connectionManager sendConnectionRequest:setProperties withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error resetting keyboard properties to values: %@, with error: %@", request, error);
- }
-
- [super finishOperation];
- }];
-}
-
-- (nullable NSString *)name {
- return [NSString stringWithFormat:@"%@ - %@", self.class, self.operationId];
-}
-
-- (NSOperationQueuePriority)queuePriority {
- return NSOperationQueuePriorityNormal;
-}
-
-- (nullable NSError *)error {
- return self.internalError;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLProtocol.m b/SmartDeviceLink/private/SDLProtocol.m
index ea2cc84e0..f2ef868c5 100644
--- a/SmartDeviceLink/private/SDLProtocol.m
+++ b/SmartDeviceLink/private/SDLProtocol.m
@@ -28,6 +28,8 @@
#import "SDLRPCRequest.h"
#import "SDLRPCResponse.h"
#import "SDLSecurityType.h"
+#import "SDLSecurityQueryErrorCode.h"
+#import "SDLSecurityQueryPayload.h"
#import "SDLSystemInfo.h"
#import "SDLTimer.h"
#import "SDLVersion.h"
@@ -775,7 +777,42 @@ NS_ASSUME_NONNULL_BEGIN
// Misformatted handshake message, something went wrong
if (clientHandshakeMessage.payload.length <= 12) {
- SDLLogE(@"Security message is malformed, less than 12 bytes. It does not have a protocol header.");
+ SDLLogE(@"Security message is malformed, less than 12 bytes. It does not have a security payload header.");
+ }
+
+ // Check the client's message header for any internal errors
+ // NOTE: Before Core v8.0.0, all these messages will be notifications. In Core v8.0.0 and later, received messages will have the proper query type. Therefore, we cannot do things based only on the query type being request or response.
+ SDLSecurityQueryPayload *clientSecurityQueryPayload = [SDLSecurityQueryPayload securityPayloadWithData:clientHandshakeMessage.payload];
+ if (clientSecurityQueryPayload == nil) {
+ SDLLogE(@"Module Security Query could not convert to object.");
+ return;
+ }
+
+ // If the query is of type `Notification` and the id represents a client internal error, we abort the response message and the encryptionManager will not be in state ready.
+ if (clientSecurityQueryPayload.queryID == SDLSecurityQueryIdSendInternalError && clientSecurityQueryPayload.queryType == SDLSecurityQueryTypeNotification) {
+ NSError *jsonDecodeError = nil;
+ NSDictionary<NSString *, id> *securityQueryErrorDictionary = [NSJSONSerialization JSONObjectWithData:clientSecurityQueryPayload.jsonData options:kNilOptions error:&jsonDecodeError];
+ if (jsonDecodeError != nil) {
+ SDLLogE(@"Error decoding module security query response JSON: %@", jsonDecodeError);
+ } else {
+ if (securityQueryErrorDictionary[@"text"] != nil) {
+ SDLSecurityQueryErrorCode errorCodeString = [SDLSecurityQueryError convertErrorIdToStringEnum:securityQueryErrorDictionary[@"id"]];
+ SDLLogE(@"Security Query module internal error: %@, code: %@", securityQueryErrorDictionary[@"text"], errorCodeString);
+ } else {
+ SDLLogE(@"Security Query module error: No information provided");
+ }
+ }
+ return;
+ }
+
+ if (clientSecurityQueryPayload.queryID != SDLSecurityQueryIdSendHandshake) {
+ SDLLogE(@"Security Query module error: Message is not a SEND_HANDSHAKE_DATA REQUEST");
+ return;
+ }
+
+ if (clientSecurityQueryPayload.queryType == SDLSecurityQueryTypeResponse) {
+ SDLLogE(@"Security Query module error: Message is a response, which is not supported");
+ return;
}
// Tear off the binary header of the client protocol message to get at the actual TLS handshake
@@ -812,14 +849,10 @@ NS_ASSUME_NONNULL_BEGIN
serverMessageHeader.sessionID = clientHeader.sessionID;
serverMessageHeader.messageID = messageId;
- // For a control service packet, we need a binary header with a function ID corresponding to what type of packet we're sending.
- SDLRPCPayload *serverTLSPayload = [[SDLRPCPayload alloc] init];
- serverTLSPayload.functionID = 0x01; // TLS Handshake message
- serverTLSPayload.rpcType = 0x00;
- serverTLSPayload.correlationID = 0x00;
- serverTLSPayload.binaryData = data;
+ // Assemble a security query payload header for our response
+ SDLSecurityQueryPayload *serverTLSPayload = [[SDLSecurityQueryPayload alloc] initWithQueryType:SDLSecurityQueryTypeResponse queryID:SDLSecurityQueryIdSendHandshake sequenceNumber:0x00 jsonData:nil binaryData:data];
- NSData *binaryData = serverTLSPayload.data;
+ NSData *binaryData = [serverTLSPayload convertToData];
return [SDLProtocolMessage messageWithHeader:serverMessageHeader andPayload:binaryData];
}
@@ -835,12 +868,9 @@ NS_ASSUME_NONNULL_BEGIN
serverMessageHeader.messageID = messageId;
// For a control service packet, we need a binary header with a function ID corresponding to what type of packet we're sending.
- SDLRPCPayload *serverTLSPayload = [[SDLRPCPayload alloc] init];
- serverTLSPayload.functionID = 0x02; // TLS Error message
- serverTLSPayload.rpcType = 0x02;
- serverTLSPayload.correlationID = 0x00;
+ SDLSecurityQueryPayload *serverTLSPayload = [[SDLSecurityQueryPayload alloc] initWithQueryType:SDLSecurityQueryTypeNotification queryID:SDLSecurityQueryIdSendInternalError sequenceNumber:0x00 jsonData:nil binaryData:nil];
- NSData *binaryData = serverTLSPayload.data;
+ NSData *binaryData = [serverTLSPayload convertToData];
// TODO: (Joel F.)[2016-02-15] This is supposed to have some JSON data and json data size
return [SDLProtocolMessage messageWithHeader:serverMessageHeader andPayload:binaryData];
diff --git a/SmartDeviceLink/private/SDLSecurityQueryErrorCode.h b/SmartDeviceLink/private/SDLSecurityQueryErrorCode.h
new file mode 100644
index 000000000..7f516c7d3
--- /dev/null
+++ b/SmartDeviceLink/private/SDLSecurityQueryErrorCode.h
@@ -0,0 +1,63 @@
+//
+// SDLSecurityQueryErrorCode.h
+// SmartDeviceLink
+//
+// Created by Frank Elias on 8/12/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import "SDLEnum.h"
+
+typedef SDLEnum SDLSecurityQueryErrorCode NS_TYPED_ENUM;
+
+///Internal Security Manager value
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeSuccess;
+
+///Wrong size of query data
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidQuerySize;
+
+///Unknown Query ID
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidQueryID;
+
+///SDL does not support encryption
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeNotSupported;
+
+///Received request to protect a service that was protected before
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeServiceAlreadyProtected;
+
+///Received handshake or encrypted data for not protected service
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeServiceNotProtected;
+
+///Decryption failed
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeDecryptionFailed;
+
+///Encryption failed
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeEncryptionFailed;
+
+///SSL invalid data
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeSSLInvalidData;
+
+///In case of all other handshake errors
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeHandshakeFailed;
+
+///Handshake failed because certificate is invalid
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidCertificate;
+
+///Handshake failed because certificate is expired
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeExpiredCertificate;
+
+///Internal error
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInternal;
+
+///Error value for testing
+extern SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeUnknownInternalError;
+
+
+@interface SDLSecurityQueryError : NSObject
+
+/**
+ Compare the internal error ID with the App's security query error codes
+ */
++ (SDLSecurityQueryErrorCode)convertErrorIdToStringEnum:(NSNumber *)errorId;
+
+@end
diff --git a/SmartDeviceLink/private/SDLSecurityQueryErrorCode.m b/SmartDeviceLink/private/SDLSecurityQueryErrorCode.m
new file mode 100644
index 000000000..a1a55ea6c
--- /dev/null
+++ b/SmartDeviceLink/private/SDLSecurityQueryErrorCode.m
@@ -0,0 +1,48 @@
+//
+// SDLSecurityQueryErrorCode.m
+// SmartDeviceLink
+//
+// Created by Frank Elias on 8/12/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import "SDLSecurityQueryErrorCode.h"
+
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeSuccess = @"Success";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidQuerySize = @"Wrong size of query data";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidQueryID = @"Unknown Query ID";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeNotSupported = @"SDL does not support encryption";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeServiceAlreadyProtected = @"Received request to protect a service that was protected before";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeServiceNotProtected = @"Received handshake or encrypted data for not protected service";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeDecryptionFailed = @"Decryption failed";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeEncryptionFailed = @"Encryption failed";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeSSLInvalidData = @"SSL invalid data";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeHandshakeFailed = @"In case of all other handshake errors";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInvalidCertificate = @"Handshake failed because certificate is invalid";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeExpiredCertificate = @"Handshake failed because certificate is expired";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeInternal = @"Internal error";
+SDLSecurityQueryErrorCode const SDLSecurityQueryErrorCodeUnknownInternalError = @"Error value for testing";
+
+
+@implementation SDLSecurityQueryError
+
++ (SDLSecurityQueryErrorCode)convertErrorIdToStringEnum:(NSNumber *)errorId {
+ switch (errorId.unsignedIntegerValue) {
+ case 0x00: return SDLSecurityQueryErrorCodeSuccess;
+ case 0x01: return SDLSecurityQueryErrorCodeInvalidQuerySize;
+ case 0x02: return SDLSecurityQueryErrorCodeInvalidQueryID;
+ case 0x03: return SDLSecurityQueryErrorCodeNotSupported;
+ case 0x04: return SDLSecurityQueryErrorCodeServiceAlreadyProtected;
+ case 0x05: return SDLSecurityQueryErrorCodeServiceNotProtected;
+ case 0x06: return SDLSecurityQueryErrorCodeDecryptionFailed;
+ case 0x07: return SDLSecurityQueryErrorCodeEncryptionFailed;
+ case 0x08: return SDLSecurityQueryErrorCodeSSLInvalidData;
+ case 0x09: return SDLSecurityQueryErrorCodeHandshakeFailed;
+ case 0x0A: return SDLSecurityQueryErrorCodeInvalidCertificate;
+ case 0x0B: return SDLSecurityQueryErrorCodeExpiredCertificate;
+ case 0xFF: return SDLSecurityQueryErrorCodeInternal;
+ default: return SDLSecurityQueryErrorCodeUnknownInternalError;
+ }
+}
+
+@end
diff --git a/SmartDeviceLink/private/SDLSecurityQueryPayload.h b/SmartDeviceLink/private/SDLSecurityQueryPayload.h
new file mode 100644
index 000000000..27354a47c
--- /dev/null
+++ b/SmartDeviceLink/private/SDLSecurityQueryPayload.h
@@ -0,0 +1,83 @@
+//
+// SDLSecurityQueryPayload.h
+// SmartDeviceLink
+//
+// Created by Frank Elias on 7/28/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "SDLRPCMessageType.h"
+
+/// Enum for different SDL security query types
+typedef NS_ENUM(Byte, SDLSecurityQueryType) {
+ /// A request that will require a response
+ SDLSecurityQueryTypeRequest = 0x00,
+
+ /// A response to a request
+ SDLSecurityQueryTypeResponse = 0x10,
+
+ /// A message that does not have a response
+ SDLSecurityQueryTypeNotification = 0x20,
+
+ /// An invalid query Type
+ SDLSecurityQueryTypeInvalid = 0xFF
+};
+
+/// Enum for each type of SDL security query IDs
+typedef NS_ENUM(UInt32, SDLSecurityQueryId) {
+ /// Send handshake data
+ SDLSecurityQueryIdSendHandshake = 0x000001,
+
+ /// Send internal error
+ SDLSecurityQueryIdSendInternalError = 0x000002,
+
+ /// Invalid query id
+ SDLSecurityQueryIdInvalid = 0xFFFFFF
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLSecurityQueryPayload : NSObject
+
+/// The security query's type, could be of type request, response, or notification
+@property (assign, nonatomic) SDLSecurityQueryType queryType;
+
+/// The security query's ID.
+@property (assign, nonatomic) SDLSecurityQueryId queryID;
+
+/// The message ID is set by the Mobile libraries to track security messages.
+@property (assign, nonatomic) UInt32 sequenceNumber;
+
+/// The JSON data following the binary query header.
+@property (nullable, strong, nonatomic) NSData *jsonData;
+
+/// The binary data that is after the header (96 bits) and the JSON data.
+@property (nullable, strong, nonatomic) NSData *binaryData;
+
+/// Create a security query object from raw data
+/// @param data The data to convert into an SDLSecurityQueryPayload object
+/// @return The SDLSecurityQueryPayload object, or nil if the data is malformed
+- (nullable instancetype)initWithData:(NSData *)data;
+
+/// Create a security query object from security query properties
+/// @param queryType The security query type to be sent
+/// @param queryID The security query ID
+/// @param sequenceNumber The security query sequence number
+/// @param jsonData The JSON data to be set in the security query
+/// @param binaryData The binary data that's after the header and the JSON data
+/// @return The SDLSecurityQueryPayload non-nullable object
+- (instancetype)initWithQueryType:(SDLSecurityQueryType)queryType queryID:(SDLSecurityQueryId)queryID sequenceNumber:(UInt32)sequenceNumber jsonData:(nullable NSData *)jsonData binaryData:(nullable NSData *)binaryData;
+
+/// Create a security query object from raw data
+/// @param data The data to convert into an SDLSecurityQueryPayload object
+/// @return The SDLSecurityQueryPayload object, or nil if the data is malformed
++ (nullable id)securityPayloadWithData:(NSData *)data;
+
+/// Convert the object into raw NSData
+/// @return The raw NSData of the object
+- (NSData *)convertToData;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLSecurityQueryPayload.m b/SmartDeviceLink/private/SDLSecurityQueryPayload.m
new file mode 100644
index 000000000..744e1770a
--- /dev/null
+++ b/SmartDeviceLink/private/SDLSecurityQueryPayload.m
@@ -0,0 +1,166 @@
+//
+// SDLSecurityQueryPayload.m
+// SmartDeviceLink
+//
+// Created by Frank Elias on 7/28/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import "SDLSecurityQueryPayload.h"
+
+#import "SDLLogMacros.h"
+
+const NSUInteger SECURITY_QUERY_HEADER_SIZE = 12;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation SDLSecurityQueryPayload
+
+- (nullable instancetype)initWithData:(NSData *)data {
+ if (data == nil || data.length < SECURITY_QUERY_HEADER_SIZE) {
+ SDLLogE(@"Security Payload error: not enough data to form Security Query header. Data length: %lu", (unsigned long)data.length);
+ return nil;
+ }
+
+ self = [super init];
+ if (!self) { return nil; }
+
+ @try {
+ // Setup our pointers for data access
+ Byte *bytePointer = (UInt8 *)data.bytes;
+ UInt32 *ui32Pointer = (UInt32 *)data.bytes;
+
+ // Extract the parts
+ UInt8 queryType = bytePointer[0];
+
+ self.queryType = queryType;
+
+ // Extract the 24 bit query ID in the last 24 bits of the first 32 bits.
+ UInt32 queryID = ui32Pointer[0];
+ queryID = CFSwapInt32BigToHost(queryID) & 0x00FFFFFF;
+ self.queryID = queryID;
+
+ // Extract the 32 bit sequence number from the data after the first 32 bits.
+ UInt32 sequenceNumber = ui32Pointer[1];
+ sequenceNumber = CFSwapInt32BigToHost(sequenceNumber);
+ self.sequenceNumber = sequenceNumber;
+
+ // Extract the 32 bit json data size from the data after the first 64 bits
+ UInt32 jsonDataSize = ui32Pointer[2];
+ jsonDataSize = CFSwapInt32BigToHost(jsonDataSize);
+
+ // Extract the JSON data after the header (96 bits) based on the JSON data size
+ NSData *jsonData = nil;
+ NSUInteger offsetOfJSONData = SECURITY_QUERY_HEADER_SIZE;
+ if (jsonDataSize > 0 && jsonDataSize <= (data.length - SECURITY_QUERY_HEADER_SIZE)) {
+ jsonData = [data subdataWithRange:NSMakeRange(offsetOfJSONData, jsonDataSize)];
+ }
+ self.jsonData = jsonData;
+
+ // Extract the binary data after the header (96 bits) and the JSON data to the end
+ NSData *binaryData = nil;
+ NSUInteger offsetOfBinaryData = SECURITY_QUERY_HEADER_SIZE + jsonDataSize;
+ NSUInteger binaryDataSize = data.length - jsonDataSize - SECURITY_QUERY_HEADER_SIZE;
+ if (binaryDataSize > 0) {
+ binaryData = [data subdataWithRange:NSMakeRange(offsetOfBinaryData, binaryDataSize)];
+ }
+ self.binaryData = binaryData;
+
+ } @catch (NSException *e) {
+ SDLLogE(@"SDLSecurityQueryPayload init error: %@", e);
+ return nil;
+ }
+
+ return self;
+}
+
+- (instancetype)initWithQueryType:(SDLSecurityQueryType)queryType queryID:(SDLSecurityQueryId)queryID sequenceNumber:(UInt32)sequenceNumber jsonData:(nullable NSData *)jsonData binaryData:(nullable NSData *)binaryData {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _queryType = queryType;
+ _queryID = queryID;
+ _sequenceNumber = sequenceNumber;
+ _jsonData = jsonData;
+ _binaryData = binaryData;
+
+ return self;
+}
+
++ (nullable id)securityPayloadWithData:(NSData *)data {
+ return [[SDLSecurityQueryPayload alloc] initWithData:data];
+}
+
+- (NSData *)convertToData {
+ // From the properties, create a data buffer
+ // Query Type - first 8 bits
+ // Query ID - next 24 bits
+ // Sequence Number - next 32 bits
+ // JSON size - next 32 bits
+ UInt8 headerBuffer[SECURITY_QUERY_HEADER_SIZE];
+ *(UInt32 *)&headerBuffer[0] = CFSwapInt32HostToBig(self.queryID);
+ *(UInt32 *)&headerBuffer[4] = CFSwapInt32HostToBig(self.sequenceNumber);
+ *(UInt32 *)&headerBuffer[8] = CFSwapInt32HostToBig((UInt32)self.jsonData.length);
+ headerBuffer[0] &= 0xFF;
+ headerBuffer[0] |= self.queryType;
+
+ // Serialize the header. Append the json data, then the binary data
+ NSUInteger jsonDataSize = self.jsonData.length;
+ NSUInteger binaryDataSize = self.binaryData.length;
+ NSUInteger size = SECURITY_QUERY_HEADER_SIZE + jsonDataSize + binaryDataSize;
+ NSMutableData *dataOut = [NSMutableData dataWithCapacity:size];
+ [dataOut appendBytes:&headerBuffer length:12];
+ [dataOut appendData:self.jsonData];
+ [dataOut appendData:self.binaryData];
+
+ return dataOut;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"Security Query Header: %@, %@, sequenceNumber: %lu, json size: %lu bytes, binary size: %lu bytes", [self descriptionForQueryID], [self descriptionForQueryType], (unsigned long)self.sequenceNumber, (unsigned long)self.jsonData.length, (unsigned long)self.binaryData.length];
+}
+
+- (NSString *)descriptionForQueryID {
+ NSString *queryIdDescription;
+ switch (self.queryID) {
+ case SDLSecurityQueryIdSendHandshake:
+ queryIdDescription = @"Send Handshake Data";
+ break;
+ case SDLSecurityQueryIdSendInternalError:
+ queryIdDescription = @"Send Internal Error";
+ break;
+ case SDLSecurityQueryIdInvalid:
+ queryIdDescription = @"Invalid Query ID";
+ break;
+ default:
+ queryIdDescription = @"Unknown Query ID";
+ break;
+ }
+ return [NSString stringWithFormat:@"queryID: %lu - %@", (unsigned long)self.queryID, queryIdDescription];
+}
+
+- (NSString *)descriptionForQueryType {
+ NSString *queryTypeDescription;
+ switch (self.queryType) {
+ case SDLSecurityQueryTypeRequest:
+ queryTypeDescription = @"Request";
+ break;
+ case SDLSecurityQueryTypeResponse:
+ queryTypeDescription = @"Response";
+ break;
+ case SDLSecurityQueryTypeNotification:
+ queryTypeDescription = @"Notification";
+ break;
+ case SDLSecurityQueryTypeInvalid:
+ queryTypeDescription = @"Invalid Query Type";
+ break;
+ default:
+ queryTypeDescription = @"Unknown Query Type";
+ break;
+ }
+ return [NSString stringWithFormat:@"queryType: %lu - %@", (unsigned long)self.queryType, queryTypeDescription];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLChoiceCell.m b/SmartDeviceLink/public/SDLChoiceCell.m
index 8d2d767c8..afbe2e972 100644
--- a/SmartDeviceLink/public/SDLChoiceCell.m
+++ b/SmartDeviceLink/public/SDLChoiceCell.m
@@ -16,13 +16,14 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLChoiceCell()
@property (assign, nonatomic) UInt16 choiceId;
-@property (nonatomic, readwrite) NSString *uniqueText;
@property (copy, nonatomic, readwrite, nullable) NSString *secondaryText;
@property (copy, nonatomic, readwrite, nullable) NSString *tertiaryText;
@property (copy, nonatomic, readwrite, nullable) NSArray<NSString *> *voiceCommands;
@property (strong, nonatomic, readwrite, nullable) SDLArtwork *artwork;
@property (strong, nonatomic, readwrite, nullable) SDLArtwork *secondaryArtwork;
+@property (assign, nonatomic) NSUInteger uniqueTextId;
+
@end
@implementation SDLChoiceCell
@@ -47,13 +48,23 @@ NS_ASSUME_NONNULL_BEGIN
_voiceCommands = voiceCommands;
_artwork = artwork;
_secondaryArtwork = secondaryArtwork;
- _uniqueText = text;
+ _uniqueTextId = 1;
_choiceId = UINT16_MAX;
return self;
}
+#pragma mark - Getters
+
+- (NSString *)uniqueText {
+ if (self.uniqueTextId != 1) {
+ return [NSString stringWithFormat:@"%@ (%lu)", self.text, (unsigned long)self.uniqueTextId];
+ } else {
+ return self.text;
+ }
+}
+
#pragma mark - Object Equality
@@ -92,11 +103,15 @@ NSUInteger NSUIntRotate(NSUInteger val, NSUInteger howMuch) {
#pragma mark - Etc.
- (id)copyWithZone:(nullable NSZone *)zone {
- return [[SDLChoiceCell allocWithZone:zone] initWithText:_text secondaryText:_secondaryText tertiaryText:_tertiaryText voiceCommands:_voiceCommands artwork:_artwork secondaryArtwork:_secondaryArtwork];
+ SDLChoiceCell *newCell = [[SDLChoiceCell allocWithZone:zone] initWithText:_text secondaryText:_secondaryText tertiaryText:_tertiaryText voiceCommands:_voiceCommands artwork:_artwork secondaryArtwork:_secondaryArtwork];
+ newCell.choiceId = _choiceId;
+ newCell.uniqueTextId = _uniqueTextId;
+
+ return newCell;
}
- (NSString *)description {
- return [NSString stringWithFormat:@"SDLChoiceCell: %u-\"%@ - %@ - %@\", artworkNames: %@ - %@, voice commands: %lu, uniqueText: %@", _choiceId, _text, _secondaryText, _tertiaryText, _artwork.name, _secondaryArtwork.name, (unsigned long)_voiceCommands.count, ([_text isEqualToString:_uniqueText] ? @"NO" : _uniqueText)];
+ return [NSString stringWithFormat:@"SDLChoiceCell: %u-\"%@ - %@ - %@\", artworkNames: %@ - %@, voice commands: %lu, uniqueText: %@", _choiceId, _text, _secondaryText, _tertiaryText, _artwork.name, _secondaryArtwork.name, (unsigned long)_voiceCommands.count, ((_uniqueTextId == 1) ? @"NO" : self.uniqueText)];
}
@end
diff --git a/SmartDeviceLink/public/SDLChoiceSet.m b/SmartDeviceLink/public/SDLChoiceSet.m
index 8baf085a6..85c22bbcd 100644
--- a/SmartDeviceLink/public/SDLChoiceSet.m
+++ b/SmartDeviceLink/public/SDLChoiceSet.m
@@ -145,7 +145,7 @@ static SDLChoiceSetLayout _defaultLayout = SDLChoiceSetLayoutList;
@param choices The choices you will be adding
@return Boolean that indicates whether choices and voice commands are unique or not
*/
--(BOOL)sdl_choiceCellsAreUnique:(NSArray<SDLChoiceCell *> *)choices {
+- (BOOL)sdl_choiceCellsAreUnique:(NSArray<SDLChoiceCell *> *)choices {
NSMutableSet<SDLChoiceCell *> *identicalCellsCheckSet = [NSMutableSet setWithCapacity:choices.count];
NSMutableSet<NSString *> *identicalVoiceCommandsCheckSet = [NSMutableSet set];
NSUInteger allVoiceCommandsCount = 0;
diff --git a/SmartDeviceLink/public/SDLErrorConstants.h b/SmartDeviceLink/public/SDLErrorConstants.h
index 78b81425f..a148e9f57 100644
--- a/SmartDeviceLink/public/SDLErrorConstants.h
+++ b/SmartDeviceLink/public/SDLErrorConstants.h
@@ -202,8 +202,8 @@ typedef NS_ENUM(NSInteger, SDLMenuManagerError) {
/// Errors associated with Choice Set Manager class
typedef NS_ENUM(NSInteger, SDLChoiceSetManagerError) {
- /// The choice set has been deleted before it was presented
- SDLChoiceSetManagerErrorPendingPresentationDeleted = -1,
+ /// Some needed choices for presentation are not available
+ SDLChoiceSetManagerErrorNeededChoicesUnavailable = -1,
/// The choice set failed to delete
SDLChoiceSetManagerErrorDeletionFailed = -2,
@@ -215,7 +215,13 @@ typedef NS_ENUM(NSInteger, SDLChoiceSetManagerError) {
SDLChoiceSetManagerErrorFailedToCreateMenuItems = -4,
/// Invalid state
- SDLChoiceSetManagerErrorInvalidState = -5
+ SDLChoiceSetManagerErrorInvalidState = -5,
+
+ /// An operation was cancelled and may or may not have succeeded
+ SDLChoiceSetManagerErrorCancelled = -6,
+
+ /// No new choices could be loaded because the maximum number of choices are loaded (65535)
+ SDLChoiceSetManagerErrorNoIdsAvailable = -7,
};
/// Errors associated with Alert Manager class
diff --git a/SmartDeviceLink/public/SDLMenuCell.m b/SmartDeviceLink/public/SDLMenuCell.m
index fb07ef49f..bbc02f2b6 100644
--- a/SmartDeviceLink/public/SDLMenuCell.m
+++ b/SmartDeviceLink/public/SDLMenuCell.m
@@ -63,7 +63,6 @@ NS_ASSUME_NONNULL_BEGIN
}
- (instancetype)initWithTitle:(NSString *)title secondaryText:(nullable NSString *)secondaryText tertiaryText:(nullable NSString *)tertiaryText icon:(nullable SDLArtwork *)icon secondaryArtwork:(nullable SDLArtwork *)secondaryArtwork submenuLayout:(nullable SDLMenuLayout)layout subCells:(NSArray<SDLMenuCell *> *)subCells {
-
self = [super init];
if (!self) { return nil; }
diff --git a/SmartDeviceLink/public/SDLSeatControlCapabilities.h b/SmartDeviceLink/public/SDLSeatControlCapabilities.h
index c20f5db5f..3895a89e1 100644
--- a/SmartDeviceLink/public/SDLSeatControlCapabilities.h
+++ b/SmartDeviceLink/public/SDLSeatControlCapabilities.h
@@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
coolingEnabledAvailable:(BOOL)coolingEnabledAvail heatingLevelAvailable:(BOOL)heatingLevelAvail coolingLevelAvailable:(BOOL)coolingLevelAvail horizontalPositionAvailable:(BOOL)horizontalPositionAvail verticalPositionAvailable:(BOOL)verticalPositionAvail frontVerticalPositionAvailable:(BOOL)frontVerticalPositionAvail backVerticalPositionAvailable:(BOOL)backVerticalPositionAvail backTiltAngleAvailable:(BOOL)backTitlAngleAvail headSupportHorizontalPositionAvailable:(BOOL)headSupportHorizontalPositionAvail headSupportVerticalPositionAvailable:(BOOL)headSupportVerticalPositionAvail massageEnabledAvailable:(BOOL)massageEnabledAvail massageModeAvailable:(BOOL)massageModeAvail massageCushionFirmnessAvailable:(BOOL)massageCushionFirmnessAvail memoryAvailable:(BOOL)memoryAvail;
/**
- * @abstract The short friendly name of the light control module.
+ * @abstract The short friendly name of the seat control module.
* It should not be used to identify a module by mobile application.
*
* Required, Max length 100 chars
diff --git a/SmartDeviceLink/public/SDLSystemCapabilityManager.m b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
index e014bb4ca..9130cd673 100644
--- a/SmartDeviceLink/public/SDLSystemCapabilityManager.m
+++ b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
@@ -123,7 +123,7 @@ typedef NSString * SDLServiceID;
self.supportsSubscriptions = NO;
- self.appServicesCapabilitiesDictionary = [NSMutableDictionary dictionary];
+ [self.appServicesCapabilitiesDictionary removeAllObjects];
[self.capabilityObservers removeAllObjects];
[self.subscriptionStatus removeAllObjects];
@@ -386,7 +386,8 @@ typedef NSString * SDLServiceID;
}
- (void)sdl_notifyObserversOfCapabilityType:(SDLSystemCapabilityType)type capability:(nullable SDLSystemCapability *)capability error:(nullable NSError *)error {
- for (SDLSystemCapabilityObserver *observer in self.capabilityObservers[type]) {
+ NSDictionary<SDLSystemCapabilityType, NSMutableArray<SDLSystemCapabilityObserver *> *> *capabilityObservers = [self.capabilityObservers copy];
+ for (SDLSystemCapabilityObserver *observer in capabilityObservers[type]) {
[self sdl_invokeObserver:observer withCapabilityType:type capability:capability error:error];
}
}
@@ -584,28 +585,27 @@ typedef NSString * SDLServiceID;
return observerObject.observer;
}
-#pragma mark Unubscribing
+#pragma mark Unsubscribing
- (void)unsubscribeFromCapabilityType:(SDLSystemCapabilityType)type withObserver:(id)observer {
SDLLogD(@"Unsubscribing from capability type: %@", type);
- for (SDLSystemCapabilityObserver *capabilityObserver in self.capabilityObservers[type]) {
- if ([observer isEqual:capabilityObserver.observer] && self.capabilityObservers[type] != nil) {
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
+ [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
+ for (SDLSystemCapabilityObserver *capabilityObserver in self.capabilityObservers[type]) {
+ if ([observer isEqual:capabilityObserver.observer] && self.capabilityObservers[type] != nil) {
[self.capabilityObservers[type] removeObject:capabilityObserver];
- }];
-
- [self sdl_removeNilObserversAndUnsubscribeIfNecessary];
- break;
+ [self sdl_removeNilObserversAndUnsubscribeIfNecessary];
+ break;
+ }
}
- }
+ }];
}
- (void)sdl_removeNilObserversAndUnsubscribeIfNecessary {
SDLLogV(@"Checking for nil observers and removing them, then checking for subscriptions we don't need and unsubscribing.");
- // Loop through our observers
- for (SDLSystemCapabilityType key in self.capabilityObservers.allKeys) {
- for (SDLSystemCapabilityObserver *observer in self.capabilityObservers[key]) {
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
+
+ [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
+ for (SDLSystemCapabilityType key in self.capabilityObservers.allKeys) {
+ for (SDLSystemCapabilityObserver *observer in self.capabilityObservers[key]) {
// If an observer object is nil, remove it
if (observer.observer == nil) {
[self.capabilityObservers[key] removeObject:observer];
@@ -615,24 +615,24 @@ typedef NSString * SDLServiceID;
if (self.capabilityObservers[key].count == 0) {
[self.capabilityObservers removeObjectForKey:key];
}
- }];
+ }
}
- }
- // If we don't support subscriptions, we don't want to unsubscribe by sending an RPC below
- if (!self.supportsSubscriptions) {
- return;
- }
+ // If we don't support subscriptions, we don't want to unsubscribe by sending an RPC below
+ if (!self.supportsSubscriptions) {
+ return;
+ }
- // Loop through our subscription statuses, check if we're subscribed. If we are, and we do not have observers for that type, and that type is not DISPLAYS, then unsubscribe.
- for (SDLSystemCapabilityType type in self.subscriptionStatus.allKeys) {
- if ([self.subscriptionStatus[type] isEqualToNumber:@YES]
- && self.capabilityObservers[type] == nil
- && ![type isEqualToEnum:SDLSystemCapabilityTypeDisplays]) {
- SDLLogD(@"Removing the last subscription to type %@, sending a GetSystemCapability with subscribe false (will unsubscribe)", type);
- [self sdl_sendGetSystemCapabilityWithType:type subscribe:@NO completionHandler:nil];
+ // Loop through our subscription statuses, check if we're subscribed. If we are, and we do not have observers for that type, and that type is not DISPLAYS, then unsubscribe.
+ for (SDLSystemCapabilityType type in self.subscriptionStatus.allKeys) {
+ if ([self.subscriptionStatus[type] isEqualToNumber:@YES]
+ && self.capabilityObservers[type] == nil
+ && ![type isEqualToEnum:SDLSystemCapabilityTypeDisplays]) {
+ SDLLogD(@"Removing the last subscription to type %@, sending a GetSystemCapability with subscribe false (will unsubscribe)", type);
+ [self sdl_sendGetSystemCapabilityWithType:type subscribe:@NO completionHandler:nil];
+ }
}
- }
+ }];
}
#pragma mark Notifying Subscribers
@@ -736,9 +736,9 @@ typedef NSString * SDLServiceID;
}
/**
- * Called when a `SetDisplayLayoutResponse` response is received from Core. If the template was set successfully, the the new capabilities for the template are saved.
+ * Called when a `SetDisplayLayoutResponse` response is received from Core. If the template was set successfully, the the new capabilities for the template are saved.
*
- * @param notification The `SetDisplayLayoutResponse` response received from Core
+ * @param notification The `SetDisplayLayoutResponse` response received from Core
*/
- (void)sdl_displayLayoutResponse:(SDLRPCResponseNotification *)notification {
#pragma clang diagnostic push
@@ -786,36 +786,6 @@ typedef NSString * SDLServiceID;
self.currentHMILevel = onHMIStatus.hmiLevel;
}
-
-#pragma mark Getters
-
-- (NSMutableDictionary<SDLSystemCapabilityType, NSMutableArray<SDLSystemCapabilityObserver *> *> *)capabilityObservers {
- __block NSMutableDictionary<SDLSystemCapabilityType, NSMutableArray<SDLSystemCapabilityObserver *> *> *dict = nil;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- dict = self->_capabilityObservers;
- }];
-
- return dict;
-}
-
-- (NSMutableDictionary<SDLSystemCapabilityType, NSNumber<SDLBool> *> *)subscriptionStatus {
- __block NSMutableDictionary<SDLSystemCapabilityType, NSNumber<SDLBool> *> *dict = nil;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- dict = self->_subscriptionStatus;
- }];
-
- return dict;
-}
-
-- (nullable NSMutableDictionary<SDLServiceID, SDLAppServiceCapability *> *)appServicesCapabilitiesDictionary {
- __block NSMutableDictionary<SDLServiceID, SDLAppServiceCapability *> *dict = nil;
- [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
- dict = self->_appServicesCapabilitiesDictionary;
- }];
-
- return dict;
-}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m
index 265dad6e4..f811192a4 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLCheckChoiceVROptionalOperationSpec.m
@@ -17,20 +17,17 @@ describe(@"check choice VR optional operation", ^{
__block SDLCheckChoiceVROptionalOperation *testOp = nil;
__block BOOL resultVROptional = NO;
- __block BOOL hasCalledOperationCompletionHandler = NO;
__block NSError *resultError = nil;
beforeEach(^{
resultVROptional = NO;
- hasCalledOperationCompletionHandler = NO;
+ resultError = nil;
testConnectionManager = [[TestConnectionManager alloc] init];
- testOp = [[SDLCheckChoiceVROptionalOperation alloc] initWithConnectionManager:testConnectionManager];
- testOp.completionBlock = ^{
- hasCalledOperationCompletionHandler = YES;
- resultVROptional = testOp.vrOptional;
- resultError = testOp.error;
- };
+ testOp = [[SDLCheckChoiceVROptionalOperation alloc] initWithConnectionManager:testConnectionManager completionHandler:^(BOOL isVROptional, NSError * _Nullable error) {
+ resultVROptional = isVROptional;
+ resultError = error;
+ }];
});
it(@"should have priority of 'very high'", ^{
@@ -80,7 +77,6 @@ describe(@"check choice VR optional operation", ^{
});
it(@"should have called the completion handler with proper data and finish", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
expect(resultVROptional).to(beTrue());
expect(resultError).to(beNil());
expect(@(testOp.finished)).to(equal(@YES));
@@ -99,8 +95,6 @@ describe(@"check choice VR optional operation", ^{
});
it(@"should have sent out a new request", ^{
- expect(hasCalledOperationCompletionHandler).to(beFalse());
-
expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLCreateInteractionChoiceSet class]));
SDLCreateInteractionChoiceSet *receivedRequest = testConnectionManager.receivedRequests.lastObject;
@@ -132,7 +126,6 @@ describe(@"check choice VR optional operation", ^{
});
it(@"should have called the completion handler with proper data and finish", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
expect(resultVROptional).to(beFalse());
expect(resultError).to(beNil());
expect(@(testOp.finished)).to(equal(@YES));
@@ -151,7 +144,6 @@ describe(@"check choice VR optional operation", ^{
});
it(@"should return a failure", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
expect(resultVROptional).to(beFalse());
expect(resultError).toNot(beNil());
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
index 30c97f00a..815b1a0b9 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
@@ -9,8 +9,7 @@
#import "SDLDeleteChoicesOperation.h"
#import "SDLError.h"
#import "SDLGlobals.h"
-#import "SDLPreloadChoicesOperation.h"
-#import "SDLPresentChoiceSetOperation.h"
+#import "SDLPreloadPresentChoicesOperation.h"
#import "SDLPresentKeyboardOperation.h"
#import "SDLRPCNotificationNotification.h"
#import "SDLStateMachine.h"
@@ -19,22 +18,18 @@
#import "TestConnectionManager.h"
-@interface SDLPreloadChoicesOperation()
-
-@property (copy, nonatomic, nullable) NSError *internalError;
-@property (strong, nonatomic, nullable) NSMutableArray<NSNumber *> *failedChoiceUploadIDs;
-
-@end
-
@interface SDLChoiceCell()
@property (assign, nonatomic) UInt16 choiceId;
@end
-@interface SDLPresentChoiceSetOperation()
+@interface SDLPreloadPresentChoicesOperation()
+@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *cellsToUpload;
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (copy, nonatomic) SDLUploadChoicesCompletionHandler preloadCompletionHandler;
+
@property (assign, nonatomic) UInt16 cancelId;
@property (strong, nonatomic, readwrite, nullable) SDLChoiceCell *selectedCell;
@property (strong, nonatomic, readwrite, nullable) SDLTriggerSource selectedTriggerSource;
@@ -42,9 +37,17 @@
@end
+@interface SDLDeleteChoicesOperation()
+
+@property (copy, nonatomic) SDLDeleteChoicesCompletionHandler deleteCompletionHandler;
+
+@end
+
@interface SDLCheckChoiceVROptionalOperation()
+@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (copy, nonatomic) SDLCheckChoiceVROptionalCompletionHandler vrOptionalCompletionHandler;
@end
@@ -52,17 +55,14 @@
@property (strong, nonatomic, readonly) SDLStateMachine *stateMachine;
@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+@property (copy, nonatomic) dispatch_queue_t readWriteQueue;
@property (assign, nonatomic) UInt16 nextCancelId;
@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext;
@property (copy, nonatomic, nullable) SDLWindowCapability *currentWindowCapability;
-@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *preloadedMutableChoices;
-@property (strong, nonatomic, readonly) NSSet<SDLChoiceCell *> *pendingPreloadChoices;
-@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *pendingMutablePreloadChoices;
-@property (strong, nonatomic, nullable) SDLChoiceSet *pendingPresentationSet;
-@property (strong, nonatomic, nullable) SDLAsynchronousOperation *pendingPresentOperation;
+@property (copy, nonatomic, readwrite) NSSet<SDLChoiceCell *> *preloadedChoices;
@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
@@ -83,7 +83,13 @@ describe(@"choice set manager tests", ^{
__block SDLWindowCapability *enabledWindowCapability = nil;
__block SDLWindowCapability *disabledWindowCapability = nil;
- __block SDLWindowCapability *primaryTextOnlyCapability = nil;
+
+ __block SDLChoiceSet *testChoiceSet = nil;
+ __block SDLChoiceSet *testFailedChoiceSet = nil;
+ __block NSString *testTitle = @"test title";
+ __block id<SDLChoiceSetDelegate> choiceDelegate = nil;
+ __block id<SDLKeyboardDelegate> keyboardDelegate = nil;
+ __block SDLInteractionMode testMode = SDLInteractionModeBoth;
__block SDLChoiceCell *testCell1 = nil;
__block SDLChoiceCell *testCell2 = nil;
@@ -91,9 +97,13 @@ describe(@"choice set manager tests", ^{
__block SDLChoiceCell *testCell4 = nil;
__block SDLChoiceCell *testCell1Duplicate = nil;
__block SDLChoiceCell *testCell1Similar = nil;
- __block SDLVersion *choiceSetUniquenessActiveVersion = nil;
__block SDLArtwork *testArtwork = nil;
+ __block SDLTriggerSource resultTriggerSource = SDLTriggerSourceMenu;
+ __block SDLChoiceCell *resultChoiceCell = nil;
+ __block NSUInteger resultChoiceRow = NSUIntegerMax;
+ __block NSError *resultError = nil;
+
beforeEach(^{
testConnectionManager = [[TestConnectionManager alloc] init];
testFileManager = OCMClassMock([SDLFileManager class]);
@@ -121,11 +131,16 @@ describe(@"choice set manager tests", ^{
];
disabledWindowCapability = [[SDLWindowCapability alloc] init];
disabledWindowCapability.textFields = @[];
- primaryTextOnlyCapability = [[SDLWindowCapability alloc] init];
- primaryTextOnlyCapability.textFields = @[
- [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1],
- ];
- choiceSetUniquenessActiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:1 patch:0];
+
+ keyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate));
+ choiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
+ testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:testTitle delegate:choiceDelegate choices:@[testCell1, testCell2, testCell3]];
+ testFailedChoiceSet = [[SDLChoiceSet alloc] initWithTitle:testTitle delegate:choiceDelegate choices:@[testCell1, testCell2, testCell3, testCell4]];
+
+ resultTriggerSource = SDLTriggerSourceMenu;
+ resultChoiceCell = nil;
+ resultChoiceRow = NSUIntegerMax;
+ resultError = nil;
});
it(@"should be in the correct startup state", ^{
@@ -212,12 +227,10 @@ describe(@"choice set manager tests", ^{
describe(@"after the bad vr optional response", ^{
beforeEach(^{
SDLCheckChoiceVROptionalOperation *vrOptionalOp = testManager.transactionQueue.operations.lastObject;
- vrOptionalOp.vrOptional = NO;
- vrOptionalOp.internalError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
- vrOptionalOp.completionBlock();
+ vrOptionalOp.vrOptionalCompletionHandler(NO, [NSError errorWithDomain:@"test" code:0 userInfo:nil]);
});
- it(@"should be ready", ^{
+ it(@"should be in startup error", ^{
expect(testManager.currentState).to(equal(SDLChoiceManagerStateStartupError));
});
});
@@ -225,8 +238,7 @@ describe(@"choice set manager tests", ^{
describe(@"after the vr optional response", ^{
beforeEach(^{
SDLCheckChoiceVROptionalOperation *vrOptionalOp = testManager.transactionQueue.operations.lastObject;
- vrOptionalOp.vrOptional = YES;
- vrOptionalOp.completionBlock();
+ vrOptionalOp.vrOptionalCompletionHandler(YES, nil);
});
it(@"should be ready", ^{
@@ -243,9 +255,7 @@ describe(@"choice set manager tests", ^{
expect(testManager.currentState).to(equal(SDLChoiceManagerStateShutdown));
expect(testManager.vrOptional).to(beTrue());
expect(testManager.currentHMILevel).to(equal(SDLHMILevelNone));
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.preloadedMutableChoices).to(beEmpty());
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
+ expect(testManager.preloadedChoices).to(beEmpty());
});
});
});
@@ -258,664 +268,128 @@ describe(@"choice set manager tests", ^{
describe(@"preloading choices", ^{
context(@"when some choices are already uploaded", ^{
beforeEach(^{
- testManager.preloadedMutableChoices = [NSMutableSet setWithArray:@[testCell1]];
-
+ testManager.preloadedChoices = [NSSet setWithArray:@[testCell1]];
[testManager preloadChoices:@[testCell1, testCell2, testCell3] withCompletionHandler:^(NSError * _Nullable error) {
}];
});
it(@"should properly start the preload", ^{
- expect(testManager.pendingPreloadChoices).toNot(contain(testCell1));
- expect(testManager.pendingPreloadChoices).to(contain(testCell2));
- expect(testManager.pendingPreloadChoices).to(contain(testCell3));
- expect(testManager.transactionQueue.operations.firstObject).to(beAnInstanceOf([SDLPreloadChoicesOperation class]));
+ expect(testManager.transactionQueue.operations[0]).to(beAnInstanceOf([SDLPreloadPresentChoicesOperation class]));
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
- [testOp finishOperation];
+ SDLPreloadPresentChoicesOperation *testOp = testManager.transactionQueue.operations[0];
+ testOp.preloadCompletionHandler([NSSet setWithArray:@[testCell1, testCell2, testCell3]], nil);
expect(testManager.preloadedChoices).to(contain(testCell1));
expect(testManager.preloadedChoices).to(contain(testCell2));
expect(testManager.preloadedChoices).to(contain(testCell3));
- expect(testManager.pendingPreloadChoices).to(haveCount(0));
- });
- });
-
- context(@"when some choices are already uploaded with duplicate titles version >= 7.1.0", ^{
- beforeEach(^{
- [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessActiveVersion;
- });
-
- context(@"if there are duplicate cells once you strip unused cell properties", ^{
- beforeEach(^{
- testManager.currentWindowCapability = primaryTextOnlyCapability;
- [testManager preloadChoices:@[testCell1, testCell1Similar] withCompletionHandler:^(NSError * _Nullable error) { }];
- });
-
- it(@"should update the choiceCells' unique title", ^{
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
- [testOp finishOperation];
- NSArray <SDLChoiceCell *> *testArrays = testManager.preloadedChoices.allObjects;
- for (SDLChoiceCell *choiceCell in testArrays) {
- if (choiceCell.secondaryText) {
- expect(choiceCell.uniqueText).to(equal("test1 (2)"));
- } else {
- expect(choiceCell.uniqueText).to(equal("test1"));
- }
- }
- expect(testManager.preloadedChoices).to(haveCount(2));
- expect(testManager.preloadedChoices).to(contain(testCell1));
- expect(testManager.preloadedChoices).to(contain(testCell1Duplicate));
- });
- });
-
- context(@"if all cell properties are used", ^{
- beforeEach(^{
- testManager.currentWindowCapability = enabledWindowCapability;
- [testManager preloadChoices:@[testCell1, testCell1Similar] withCompletionHandler:^(NSError * _Nullable error) { }];
- });
-
- it(@"should not update the choiceCells' unique title", ^{
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
- [testOp finishOperation];
- NSArray <SDLChoiceCell *> *testArrays = testManager.preloadedChoices.allObjects;
- for (SDLChoiceCell *choiceCell in testArrays) {
- expect(choiceCell.uniqueText).to(equal("test1"));
- }
- expect(testManager.preloadedChoices).to(haveCount(2));
- expect(testManager.preloadedChoices).to(contain(testCell1));
- expect(testManager.preloadedChoices).to(contain(testCell1Duplicate));
- });
- });
- });
-
- context(@"when some choices are already uploaded with duplicate titles version <= 7.1.0", ^{
- beforeEach(^{
- [SDLGlobals sharedGlobals].rpcVersion = [[SDLVersion alloc] initWithMajor:7 minor:0 patch:0];
- [testManager preloadChoices:@[testCell1, testCell1Similar] withCompletionHandler:^(NSError * _Nullable error) { }];
- });
-
- it(@"append a number to the unique text for choice set cells", ^{
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
- [testOp finishOperation];
- NSArray <SDLChoiceCell *> *testArrays = testManager.preloadedChoices.allObjects;
- for (SDLChoiceCell *choiceCell in testArrays) {
- if (choiceCell.secondaryText) {
- expect(choiceCell.uniqueText).to(equal("test1 (2)"));
- } else {
- expect(choiceCell.uniqueText).to(equal("test1"));
- }
- }
- expect(testManager.preloadedChoices).to(haveCount(2));
- expect(testManager.preloadedChoices).to(contain(testCell1));
- expect(testManager.preloadedChoices).to(contain(testCell1Duplicate));
});
});
- context(@"when some choices are already pending", ^{
+ context(@"when the manager shuts down during preloading", ^{
beforeEach(^{
- testManager.pendingMutablePreloadChoices = [NSMutableSet setWithArray:@[testCell1]];
-
[testManager preloadChoices:@[testCell1, testCell2, testCell3] withCompletionHandler:^(NSError * _Nullable error) {
+ resultError = error;
}];
});
- it(@"should properly start the preload", ^{
- expect(testManager.pendingPreloadChoices).to(contain(testCell1));
- expect(testManager.pendingPreloadChoices).to(contain(testCell2));
- expect(testManager.pendingPreloadChoices).to(contain(testCell3));
- expect(testManager.transactionQueue.operations.firstObject).to(beAnInstanceOf([SDLPreloadChoicesOperation class]));
-
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
- [testOp finishOperation];
-
- expect(testManager.preloadedChoices).toNot(contain(testCell1));
- expect(testManager.preloadedChoices).to(contain(testCell2));
- expect(testManager.preloadedChoices).to(contain(testCell3));
- expect(testManager.pendingPreloadChoices).to(haveCount(1));
- });
- });
-
- context(@"when the manager shuts down during preloading", ^{
- beforeEach(^{
- testManager.pendingMutablePreloadChoices = [NSMutableSet setWithArray:@[testCell1]];
-
- [testManager preloadChoices:@[testCell1, testCell2, testCell3] withCompletionHandler:^(NSError * _Nullable error) {}];
- });
-
it(@"should leave the list of pending and uploaded choice items empty when the operation finishes", ^{
- expect(testManager.pendingPreloadChoices).to(contain(testCell1));
- expect(testManager.pendingPreloadChoices).to(contain(testCell2));
- expect(testManager.pendingPreloadChoices).to(contain(testCell3));
- expect(testManager.transactionQueue.operations.firstObject).to(beAnInstanceOf([SDLPreloadChoicesOperation class]));
+ expect(testManager.transactionQueue.operations.firstObject).to(beAnInstanceOf([SDLPreloadPresentChoicesOperation class]));
[testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:nil callEnterTransition:NO];
- testManager.pendingMutablePreloadChoices = [NSMutableSet set];
- testManager.preloadedMutableChoices = [NSMutableSet set];
+ testManager.preloadedChoices = [NSMutableSet set];
- SDLPreloadChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
+ SDLPreloadPresentChoicesOperation *testOp = testManager.transactionQueue.operations.firstObject;
[testOp finishOperation];
- expect(testManager.preloadedMutableChoices).to(beEmpty());
expect(testManager.preloadedChoices).to(beEmpty());
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- expect(testManager.pendingPreloadChoices).to(beEmpty());
});
});
});
describe(@"deleting choices", ^{
- context(@"used in a pending presentation", ^{
- __block SDLPresentChoiceSetOperation *pendingPresentOp = nil;
- __block id<SDLChoiceSetDelegate> choiceDelegate = nil;
-
- beforeEach(^{
- choiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
- pendingPresentOp = OCMClassMock([SDLPresentChoiceSetOperation class]);
- OCMStub(pendingPresentOp.choiceSet.choices).andReturn([NSSet setWithArray:@[testCell1]]);
- testManager.pendingPresentOperation = pendingPresentOp;
- testManager.pendingPresentationSet = [[SDLChoiceSet alloc] initWithTitle:@"Test" delegate:choiceDelegate choices:@[testCell1]];
-
- testManager.preloadedMutableChoices = [NSMutableSet setWithObject:testCell1];
-
- [testManager deleteChoices:@[testCell1, testCell2, testCell3]];
- });
-
- it(@"should properly start the deletion", ^{
- expect(testManager.transactionQueue.operations.lastObject).to(beAnInstanceOf([SDLDeleteChoicesOperation class]));
- expect(testManager.pendingPresentationSet).to(beNil());
- OCMVerify([pendingPresentOp cancel]);
- OCMVerify([choiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]);
-
- testManager.transactionQueue.operations.lastObject.completionBlock();
- expect(testManager.preloadedChoices).to(beEmpty());
- });
- });
-
context(@"used in pending preloads", ^{
- __block SDLPreloadChoicesOperation *pendingPreloadOp = nil;
-
beforeEach(^{
- pendingPreloadOp = [[SDLPreloadChoicesOperation alloc] init];
-
- [testManager.transactionQueue addOperation:pendingPreloadOp];
-
- testManager.pendingMutablePreloadChoices = [NSMutableSet setWithObject:testCell1];
+ [testManager preloadChoices:@[testCell1, testCell2, testCell3, testCell4] withCompletionHandler:^(NSError * _Nullable error) {}];
[testManager deleteChoices:@[testCell1, testCell2, testCell3]];
});
- it(@"should properly start the deletion", ^{
- expect(testManager.pendingPreloadChoices).to(beEmpty());
- expect(testManager.transactionQueue.operationCount).to(equal(1)); // No delete operation
+ it(@"should preload the choices, then delete them", ^{
+ expect(testManager.transactionQueue.operationCount).to(equal(2));
+ expect(testManager.transactionQueue.operations[0]).to(beAnInstanceOf(SDLPreloadPresentChoicesOperation.class));
+ expect(testManager.transactionQueue.operations[1]).to(beAnInstanceOf(SDLDeleteChoicesOperation.class));
});
});
context(@"when the manager shuts down during deletion", ^{
- __block SDLPresentChoiceSetOperation *pendingPresentOp = nil;
- __block id<SDLChoiceSetDelegate> choiceDelegate = nil;
-
beforeEach(^{
- choiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
- pendingPresentOp = OCMClassMock([SDLPresentChoiceSetOperation class]);
- OCMStub(pendingPresentOp.choiceSet.choices).andReturn([NSSet setWithArray:@[testCell1]]);
- testManager.pendingPresentOperation = pendingPresentOp;
- testManager.pendingPresentationSet = [[SDLChoiceSet alloc] initWithTitle:@"Test" delegate:choiceDelegate choices:@[testCell1]];
- testManager.preloadedMutableChoices = [NSMutableSet setWithObject:testCell1];
+ testManager.preloadedChoices = [NSSet setWithArray:@[testCell1, testCell2, testCell3]];
+ [testManager deleteChoices:@[testCell1, testCell2]];
- [testManager deleteChoices:@[testCell1, testCell2, testCell3]];
+ [SDLGlobals runSyncOnSerialSubQueue:testManager.readWriteQueue block:^{
+ [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:SDLChoiceManagerStateReady callEnterTransition:YES];
+ }];
});
it(@"should leave the list of pending and uploaded choice items empty when the operation finishes", ^{
- expect(testManager.transactionQueue.operations.lastObject).to(beAnInstanceOf([SDLDeleteChoicesOperation class]));
- expect(testManager.pendingPresentationSet).to(beNil());
- OCMVerify([pendingPresentOp cancel]);
- OCMVerify([choiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]);
-
- [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:nil callEnterTransition:NO];
- testManager.pendingMutablePreloadChoices = [NSMutableSet set];
- testManager.preloadedMutableChoices = [NSMutableSet set];
-
- testManager.transactionQueue.operations.lastObject.completionBlock();
-
- expect(testManager.preloadedMutableChoices).to(beEmpty());
+ expect(testManager.transactionQueue.operationCount).to(equal(0));
expect(testManager.preloadedChoices).to(beEmpty());
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- expect(testManager.pendingPreloadChoices).to(beEmpty());
});
});
});
describe(@"presenting a choice set", ^{
- __block SDLChoiceSet *testChoiceSet = nil;
- __block SDLChoiceSet *testFailedChoiceSet = nil;
- __block NSString *testTitle = @"test title";
- __block id<SDLChoiceSetDelegate> choiceDelegate = nil;
- __block id<SDLKeyboardDelegate> keyboardDelegate = nil;
- __block SDLInteractionMode testMode = SDLInteractionModeBoth;
- __block SDLPresentKeyboardOperation *pendingPresentOp = nil;
- __block id strickMockOperationQueue = nil;
- __block SDLChoiceCell *testSelectedCell = nil;
- __block NSError *testError = nil;
- NSUInteger testSelectedCellRow = 1;
-
- beforeEach(^{
- keyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate));
- choiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
- testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:testTitle delegate:choiceDelegate choices:@[testCell1, testCell2, testCell3]];
- testFailedChoiceSet = [[SDLChoiceSet alloc] initWithTitle:testTitle delegate:choiceDelegate choices:@[testCell1, testCell2, testCell3, testCell4]];
- testSelectedCell = testChoiceSet.choices[1];
- testError = [NSError sdl_choiceSetManager_failedToCreateMenuItems];
-
- pendingPresentOp = OCMClassMock([SDLPresentKeyboardOperation class]);
- testManager.pendingPresentOperation = pendingPresentOp;
- testManager.pendingPresentationSet = [[SDLChoiceSet alloc] init];
-
- strickMockOperationQueue = OCMStrictClassMock([NSOperationQueue class]);
- [strickMockOperationQueue setExpectationOrderMatters:YES];
- testManager.transactionQueue = strickMockOperationQueue;
- });
-
- context(@"searchable", ^{
- it(@"should notify the choice delegate when a choice item is selected", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.selectedCell = testSelectedCell;
- presentChoicesOperation.selectedTriggerSource = testMode;
- presentChoicesOperation.selectedCellRow = testSelectedCellRow;
- presentChoicesOperation.internalError = nil;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:testChoiceSet didSelectChoice:testSelectedCell withSource:testMode atRowIndex:testSelectedCellRow]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(3));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[0]));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[1]));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[2]));
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- });
-
- it(@"should notify the choice delegate if an error occured during presentation", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.internalError = testError;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:testError]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(3));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[0]));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[1]));
- expect(testManager.preloadedMutableChoices).to(contain(testChoiceSet.choices[2]));
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- });
-
- it(@"should not add a choice item that fails to the list of preloaded choices", ^{
- NSMutableDictionary<SDLRPCRequest *, NSError *> *testErrors = [NSMutableDictionary dictionary];
- SDLCreateInteractionChoiceSet *failedChoiceSet = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:1 menuName:@"1" vrCommands:nil]]];
- testErrors[failedChoiceSet] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- NSError *testInternalError = [NSError sdl_choiceSetManager_choiceUploadFailed:testErrors];
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingMutablePreloadChoices.count).to(equal(4));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[3]));
- preloadChoicesOperation.internalError = testInternalError;
- preloadChoicesOperation.failedChoiceUploadIDs = [[NSMutableArray alloc] initWithArray:(@[@1])];
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMReject([strickMockOperationQueue addOperation:[OCMArg isKindOfClass:SDLPresentChoiceSetOperation.class]]);
- OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:testInternalError]);
-
- [testManager presentChoiceSet:testFailedChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).toNot(beNil());
- expect(testManager.pendingPresentOperation).toNot(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(3));
- expect(testManager.preloadedMutableChoices).toNot(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[3]));
-
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- });
-
- it(@"should not add any of choice items if they all fail to upload to the list of preloaded choices", ^{
- NSMutableDictionary<SDLRPCRequest *, NSError *> *testErrors = [NSMutableDictionary dictionary];
- SDLCreateInteractionChoiceSet *failedChoiceSet1 = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:1 menuName:@"1" vrCommands:nil]]];
- SDLCreateInteractionChoiceSet *failedChoiceSet2 = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:2 menuName:@"2" vrCommands:nil]]];
- SDLCreateInteractionChoiceSet *failedChoiceSet3 = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:3 menuName:@"3" vrCommands:nil]]];
- SDLCreateInteractionChoiceSet *failedChoiceSet4 = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:4 menuName:@"4" vrCommands:nil]]];
- testErrors[failedChoiceSet1] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- testErrors[failedChoiceSet2] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- testErrors[failedChoiceSet3] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- testErrors[failedChoiceSet4] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- NSError *testInternalError = [NSError sdl_choiceSetManager_choiceUploadFailed:testErrors];
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingMutablePreloadChoices.count).to(equal(4));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[3]));
- preloadChoicesOperation.internalError = testInternalError;
- preloadChoicesOperation.failedChoiceUploadIDs = [[NSMutableArray alloc] initWithArray:(@[@1, @2, @3, @4])];
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMReject([strickMockOperationQueue addOperation:[OCMArg isKindOfClass:SDLPresentChoiceSetOperation.class]]);
- OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:testInternalError]);
-
- [testManager presentChoiceSet:testFailedChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 1.0);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).toNot(beNil());
- expect(testManager.pendingPresentOperation).toNot(beNil());
-
- expect(testManager.preloadedMutableChoices).to(beEmpty());
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- });
- });
-
- it(@"should skip preloading the choices if all choice items have already been uploaded", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingMutablePreloadChoices.count).to(equal(3));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testChoiceSet.choices[0]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testChoiceSet.choices[1]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testChoiceSet.choices[2]));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.selectedCell = testSelectedCell;
- presentChoicesOperation.selectedTriggerSource = testMode;
- presentChoicesOperation.selectedCellRow = testSelectedCellRow;
- presentChoicesOperation.internalError = nil;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:testChoiceSet didSelectChoice:testSelectedCell withSource:testMode atRowIndex:testSelectedCellRow]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(3));
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
-
- // Present the exact same choices again
- OCMReject([strickMockOperationQueue addOperation:[OCMArg isKindOfClass:SDLPreloadChoicesOperation.class]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.selectedCell = testSelectedCell;
- presentChoicesOperation.selectedTriggerSource = testMode;
- presentChoicesOperation.selectedCellRow = testSelectedCellRow;
- presentChoicesOperation.internalError = nil;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:testChoiceSet didSelectChoice:testSelectedCell withSource:testMode atRowIndex:testSelectedCellRow]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
- });
-
- it(@"should upload choices that failed to upload in previous presentations", ^{
- NSMutableDictionary<SDLRPCRequest *, NSError *> *testErrors = [NSMutableDictionary dictionary];
- SDLCreateInteractionChoiceSet *failedChoiceSet = [[SDLCreateInteractionChoiceSet alloc] initWithId:0 choiceSet:@[[[SDLChoice alloc] initWithId:1 menuName:@"1" vrCommands:nil]]];
- testErrors[failedChoiceSet] = [NSError sdl_choiceSetManager_choiceUploadFailed:[NSDictionary dictionary]];
- NSError *testInternalError = [NSError sdl_choiceSetManager_choiceUploadFailed:testErrors];
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingMutablePreloadChoices.count).to(equal(4));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[3]));
- expect(testManager.pendingPresentationSet).to(equal(testFailedChoiceSet));
- preloadChoicesOperation.internalError = testInternalError;
- preloadChoicesOperation.failedChoiceUploadIDs = [[NSMutableArray alloc] initWithArray:(@[@1])];
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:testInternalError]);
-
- [testManager presentChoiceSet:testFailedChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 1.0);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).toNot(beNil());
- expect(testManager.pendingPresentOperation).toNot(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(3));
- expect(testManager.preloadedMutableChoices).toNot(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[3]));
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
-
- // Present the exact same choices again
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingMutablePreloadChoices.count).to(equal(1));
- expect(testManager.pendingMutablePreloadChoices).to(contain(testFailedChoiceSet.choices[0]));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.selectedCell = testSelectedCell;
- presentChoicesOperation.selectedTriggerSource = testMode;
- presentChoicesOperation.selectedCellRow = testSelectedCellRow;
- presentChoicesOperation.internalError = nil;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:testChoiceSet didSelectChoice:testSelectedCell withSource:testMode atRowIndex:testSelectedCellRow]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
-
- expect(testManager.preloadedMutableChoices.count).to(equal(4));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[0]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[1]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[2]));
- expect(testManager.preloadedMutableChoices).to(contain(testFailedChoiceSet.choices[3]));
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- });
-
it(@"should not present choices if the manager shuts down after the choices are uploaded but before presentation", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:nil callEnterTransition:NO];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMReject([strickMockOperationQueue addOperation:[OCMArg isKindOfClass:SDLPresentChoiceSetOperation.class]]);
-
+ OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg isNotNil]]);
[testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:nil];
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
+ SDLPreloadPresentChoicesOperation *preload = (SDLPreloadPresentChoicesOperation *)testManager.transactionQueue.operations[0];
+ preload.loadedCells = [NSSet setWithArray:testChoiceSet.choices];
- expect(testManager.pendingPresentOperation).toEventually(beNil());
- expect(testManager.pendingPresentationSet).toEventually(beNil());
- });
-
- context(@"non-searchable", ^{
- it(@"should notify the choice delegate when a choice item is selected", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.selectedCell = testSelectedCell;
- presentChoicesOperation.selectedTriggerSource = testMode;
- presentChoicesOperation.selectedCellRow = testSelectedCellRow;
- presentChoicesOperation.internalError = nil;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:testChoiceSet didSelectChoice:testSelectedCell withSource:testMode atRowIndex:testSelectedCellRow]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:nil];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
- });
+ [SDLGlobals runSyncOnSerialSubQueue:testManager.readWriteQueue block:^{
+ [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:SDLChoiceManagerStateReady callEnterTransition:YES];
+ }];
+ [preload finishOperation];
- it(@"should notify the choice delegate if an error occured during presentation", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.internalError = testError;
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
- OCMExpect([choiceDelegate choiceSet:[OCMArg any] didReceiveError:testError]);
-
- [testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:nil];
-
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- OCMVerifyAllWithDelay(choiceDelegate, 0.5);
-
- expect(testManager.pendingPresentationSet).to(beNil());
- expect(testManager.pendingPresentOperation).to(beNil());
- });
+ expect(testManager.preloadedChoices).to(beEmpty());
+ expect(testManager.transactionQueue.operationCount).to(equal(0));
});
describe(@"when the manager shuts down during presentation", ^{
- __block SDLPresentChoiceSetOperation *presentChoicesOperation = nil;
-
it(@"should leave the list of pending and uploaded choice items empty when the operation finishes", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- expect(testManager.pendingPresentationSet).to(equal(testChoiceSet));
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- presentChoicesOperation.internalError = nil;
- testManager.pendingMutablePreloadChoices = [NSMutableSet set];
- testManager.preloadedMutableChoices = [NSMutableSet set];
-
- [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:nil callEnterTransition:NO];
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
-
[testManager presentChoiceSet:testChoiceSet mode:testMode withKeyboardDelegate:keyboardDelegate];
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- expect(testManager.preloadedMutableChoices).to(beEmpty());
+ SDLPreloadPresentChoicesOperation *preloadChoicesOperation = (SDLPreloadPresentChoicesOperation *)testManager.transactionQueue.operations.firstObject;
+ [testManager.stateMachine setToState:SDLChoiceManagerStateShutdown fromOldState:nil callEnterTransition:NO];
+ [preloadChoicesOperation finishOperation];
+
expect(testManager.preloadedChoices).to(beEmpty());
- expect(testManager.pendingMutablePreloadChoices).to(beEmpty());
- expect(testManager.pendingPreloadChoices).to(beEmpty());
});
});
-
- afterEach(^{
- [strickMockOperationQueue stopMocking];
- });
});
describe(@"presenting a keyboard", ^{
- __block SDLPresentChoiceSetOperation *pendingPresentOp = nil;
__block NSString *testInitialText = @"Test text";
__block id<SDLKeyboardDelegate> testKeyboardDelegate = nil;
beforeEach(^{
testKeyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate));
-
- pendingPresentOp = OCMClassMock([SDLPresentChoiceSetOperation class]);
- testManager.pendingPresentOperation = pendingPresentOp;
- testManager.pendingPresentationSet = [[SDLChoiceSet alloc] init];
});
it(@"should return a cancelID and should properly start the keyboard presentation with presentKeyboardWithInitialText:keyboardDelegate:", ^{
NSNumber *cancelID = [testManager presentKeyboardWithInitialText:testInitialText delegate:testKeyboardDelegate];
expect(cancelID).toNot(beNil());
- OCMVerify([pendingPresentOp cancel]);
expect(testManager.transactionQueue.operations).to(haveCount(1));
- expect(testManager.pendingPresentOperation).to(beAnInstanceOf([SDLPresentKeyboardOperation class]));
+ expect( testManager.transactionQueue.operations[0]).to(beAnInstanceOf([SDLPresentKeyboardOperation class]));
});
- it(@"should return nil and should not start the keyboard presentation if the the keyboard can not be sent to Core", ^{
+ it(@"should return nil and should not start the keyboard presentation if the keyboard can not be sent to Core", ^{
[testManager.stateMachine setToState:SDLChoiceManagerStateCheckingVoiceOptional fromOldState:SDLChoiceManagerStateShutdown callEnterTransition:NO];
NSNumber *cancelID = [testManager presentKeyboardWithInitialText:testInitialText delegate:testKeyboardDelegate];
expect(cancelID).to(beNil());
- OCMReject([pendingPresentOp cancel]);
expect(testManager.transactionQueue.operations).to(haveCount(0));
- expect(testManager.pendingPresentOperation).toNot(beAnInstanceOf([SDLPresentKeyboardOperation class]));
});
});
@@ -923,7 +397,6 @@ describe(@"choice set manager tests", ^{
__block SDLChoiceSet *testChoiceSet = nil;
__block SDLChoiceSet *testChoiceSet2 = nil;
__block id<SDLChoiceSetDelegate> testChoiceDelegate = nil;
- __block id strickMockOperationQueue = nil;
beforeEach(^{
testChoiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
@@ -931,67 +404,29 @@ describe(@"choice set manager tests", ^{
testChoiceSet2 = [[SDLChoiceSet alloc] initWithTitle:@"choice set 2" delegate:testChoiceDelegate choices:@[testCell2]];
testManager = [[SDLChoiceSetManager alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager systemCapabilityManager:testSystemCapabilityManager];
[testManager.stateMachine setToState:SDLChoiceManagerStateReady fromOldState:SDLChoiceManagerStateCheckingVoiceOptional callEnterTransition:NO];
- strickMockOperationQueue = OCMStrictClassMock([NSOperationQueue class]);
- testManager.transactionQueue = strickMockOperationQueue;
});
it(@"should set the first cancelID correctly", ^{
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- expect(@(presentChoicesOperation.cancelId)).to(equal(101));
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
-
[testManager presentChoiceSet:testChoiceSet mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
+ SDLPreloadPresentChoicesOperation *preloadChoicesOperation = (SDLPreloadPresentChoicesOperation *)testManager.transactionQueue.operations[0];
+ expect(@(preloadChoicesOperation.cancelId)).to(equal(101));
});
it(@"should reset the cancelID correctly once the max has been reached", ^{
- testManager.nextCancelId = 200; // set the max cancelID
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- expect(@(presentChoicesOperation.cancelId)).to(equal(200));
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
+ testManager.nextCancelId = 200;
[testManager presentChoiceSet:testChoiceSet mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
-
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPreloadChoicesOperation *preloadChoicesOperation = (SDLPreloadChoicesOperation *)value;
- [preloadChoicesOperation finishOperation];
- return [value isKindOfClass:[SDLPreloadChoicesOperation class]];
- }]]);
- OCMExpect([strickMockOperationQueue addOperation:[OCMArg checkWithBlock:^BOOL(id value) {
- SDLPresentChoiceSetOperation *presentChoicesOperation = (SDLPresentChoiceSetOperation *)value;
- expect(@(presentChoicesOperation.cancelId)).to(equal(101));
- presentChoicesOperation.completionBlock();
- return [value isKindOfClass:[SDLPresentChoiceSetOperation class]];
- }]]);
+ SDLPreloadPresentChoicesOperation *presentChoicesOperation = testManager.transactionQueue.operations[0];
+ expect(@(presentChoicesOperation.cancelId)).to(equal(200));
[testManager presentChoiceSet:testChoiceSet2 mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
- OCMVerifyAllWithDelay(strickMockOperationQueue, 0.5);
- });
+ [NSThread sleepForTimeInterval:0.5];
- afterEach(^{
- [strickMockOperationQueue stopMocking];
+ SDLPreloadPresentChoicesOperation *presentChoicesOperation2 = (SDLPreloadPresentChoicesOperation *)testManager.transactionQueue.operations[1];
+ expect(@(presentChoicesOperation2.cancelId)).to(equal(101));
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLDeleteChoicesOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLDeleteChoicesOperationSpec.m
index 2996da282..adf5632a6 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLDeleteChoicesOperationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLDeleteChoicesOperationSpec.m
@@ -14,20 +14,23 @@ describe(@"delete choices operation", ^{
__block TestConnectionManager *testConnectionManager = nil;
__block SDLDeleteChoicesOperation *testOp = nil;
__block NSSet<SDLChoiceCell *> *testCellsToDelete = nil;
+ __block NSSet<SDLChoiceCell *> *testLoadedCells = nil;
- __block BOOL hasCalledOperationCompletionHandler = NO;
__block NSError *resultError = nil;
+ __block NSSet<SDLChoiceCell *> *resultLoadedCells;
beforeEach(^{
- hasCalledOperationCompletionHandler = NO;
-
testConnectionManager = [[TestConnectionManager alloc] init];
testCellsToDelete = [NSSet setWithArray:@[[[SDLChoiceCell alloc] initWithText:@"Text"], [[SDLChoiceCell alloc] initWithText:@"Text 2"]]];
- testOp = [[SDLDeleteChoicesOperation alloc] initWithConnectionManager:testConnectionManager cellsToDelete:testCellsToDelete];
- testOp.completionBlock = ^{
- hasCalledOperationCompletionHandler = YES;
- resultError = testOp.error;
- };
+ testLoadedCells = testCellsToDelete;
+
+ resultError = nil;
+ resultLoadedCells = nil;
+
+ testOp = [[SDLDeleteChoicesOperation alloc] initWithConnectionManager:testConnectionManager cellsToDelete:testCellsToDelete loadedCells:testLoadedCells completionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultLoadedCells = updatedLoadedCells;
+ resultError = error;
+ }];
});
it(@"should have priority of 'normal'", ^{
@@ -56,7 +59,7 @@ describe(@"delete choices operation", ^{
});
it(@"should finish with success", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
+ expect(resultLoadedCells).toEventuallyNot(beNil());
expect(resultError).to(beNil());
});
});
@@ -66,8 +69,8 @@ describe(@"delete choices operation", ^{
[testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
});
- it(@"should finish with success", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
+ it(@"should finish with a failure", ^{
+ expect(resultLoadedCells).toEventuallyNot(beNil());
expect(resultError).toNot(beNil());
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m
deleted file mode 100644
index fc6fdfe00..000000000
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m
+++ /dev/null
@@ -1,417 +0,0 @@
-#import <Quick/Quick.h>
-#import <Nimble/Nimble.h>
-#import <OCMock/OCMock.h>
-
-#import "SDLPreloadChoicesOperation.h"
-
-#import "SDLChoice.h"
-#import "SDLChoiceCell.h"
-#import "SDLCreateInteractionChoiceSet.h"
-#import "SDLDisplayType.h"
-#import "SDLFileManager.h"
-#import "SDLImageField.h"
-#import "SDLImageFieldName.h"
-#import "SDLTextField.h"
-#import "SDLTextFieldName.h"
-#import "SDLWindowCapability.h"
-#import "TestConnectionManager.h"
-
-@interface SDLPreloadChoicesOperation()
-
-@property (strong, nonatomic, nullable) NSMutableArray<NSNumber *> *failedChoiceUploadIDs;
-
-@end
-
-@interface SDLChoiceCell()
-
-@property (assign, nonatomic) UInt16 choiceId;
-
-@end
-
-QuickSpecBegin(SDLPreloadChoicesOperationSpec)
-
-describe(@"a preload choices operation", ^{
- __block TestConnectionManager *testConnectionManager = nil;
- __block SDLFileManager *testFileManager = nil;
- __block SDLPreloadChoicesOperation *testOp = nil;
- __block NSString *testDisplayName = @"SDL_GENERIC";
-
- __block NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding];
- __block NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding];
-
- __block BOOL hasCalledOperationCompletionHandler = NO;
- __block NSError *resultError = nil;
-
- beforeEach(^{
- resultError = nil;
- hasCalledOperationCompletionHandler = NO;
-
- testConnectionManager = [[TestConnectionManager alloc] init];
- testFileManager = OCMClassMock([SDLFileManager class]);
- });
-
- it(@"should have a priority of 'normal'", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] init];
-
- expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
- });
-
- describe(@"running the operation", ^{
- __block SDLWindowCapability *windowCapability = nil;
- beforeEach(^{
- windowCapability = [[SDLWindowCapability alloc] init];
- windowCapability.imageTypeSupported = @[SDLImageTypeStatic, SDLImageTypeDynamic];
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- windowCapability.textFields = @[primaryTextField];
-
- OCMStub([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]);
- OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
- });
-
- context(@"with artworks", ^{
- __block NSOrderedSet<SDLChoiceCell *> *cellsWithArtwork = nil;
- __block NSOrderedSet<SDLChoiceCell *> *cellsWithStaticIcon = nil;
- __block NSString *art1Name = @"Art1Name";
- __block NSString *art2Name = @"Art2Name";
- __block SDLArtwork *cell1Art2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:art1Name fileExtension:@"png" persistent:NO];
-
- beforeEach(^{
- SDLArtwork *cell1Art = [[SDLArtwork alloc] initWithData:cellArtData name:art1Name fileExtension:@"png" persistent:NO];
- SDLChoiceCell *cell1WithArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art voiceCommands:nil];
-
- SDLArtwork *cell2Art = [[SDLArtwork alloc] initWithData:cellArtData name:art2Name fileExtension:@"png" persistent:NO];
- SDLChoiceCell *cell2WithArtAndSecondary = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:cell2Art secondaryArtwork:cell2Art];
-
- SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate];
- SDLChoiceCell *cellWithStaticIcon = [[SDLChoiceCell alloc] initWithText:@"Static Icon" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:staticIconArt secondaryArtwork:nil];
-
- cellsWithArtwork = [[NSOrderedSet alloc] initWithArray:@[cell1WithArt, cell2WithArtAndSecondary]];
- cellsWithStaticIcon = [[NSOrderedSet alloc] initWithArray:@[cellWithStaticIcon]];
- });
-
- context(@"if the menuName is not set", ^{
- it(@"should not send any requests", ^{
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- windowCapability.textFields = @[];
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(0));
- });
- });
-
- context(@"only main text capabilities", ^{
- it(@"should skip to preloading cells", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
- });
- });
-
- context(@"only main text and image capabilities", ^{
- beforeEach(^{
- SDLImageField *choiceField = [[SDLImageField alloc] init];
- choiceField.name = SDLImageFieldNameChoiceImage;
- windowCapability.imageFields = @[choiceField];
-
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- });
-
- it(@"should upload some artworks", ^{
- OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
- NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
- return (artworks.count == 2);
- }] completionHandler:[OCMArg any]]);
- expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
- });
- });
-
- context(@"main text and all image display capabilities", ^{
- beforeEach(^{
- SDLImageField *choiceField = [[SDLImageField alloc] init];
- choiceField.name = SDLImageFieldNameChoiceImage;
- SDLImageField *choiceSecondaryField = [[SDLImageField alloc] init];
- choiceSecondaryField.name = SDLImageFieldNameChoiceSecondaryImage;
-
- windowCapability.imageFields = @[choiceField, choiceSecondaryField];
- });
-
- context(@"when artworks are already on the system", ^{
- beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- });
-
- it(@"should not upload artworks", ^{
- OCMReject([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
- NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
- return (artworks.count == 2);
- }] completionHandler:[OCMArg any]]);
- expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
- });
-
- it(@"should properly overwrite artwork", ^{
- cell1Art2.overwrite = YES;
- SDLChoiceCell *cell1WithArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art2 voiceCommands:nil];
-
- SDLArtwork *cell2Art = [[SDLArtwork alloc] initWithData:cellArtData name:art2Name fileExtension:@"png" persistent:NO];
- SDLChoiceCell *cell2WithArtAndSecondary = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:cell2Art secondaryArtwork:cell2Art];
-
- SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate];
- SDLChoiceCell *cellWithStaticIcon = [[SDLChoiceCell alloc] initWithText:@"Static Icon" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:staticIconArt secondaryArtwork:nil];
-
- cellsWithArtwork = [[NSOrderedSet alloc] initWithArray:@[cell1WithArt, cell2WithArtAndSecondary]];
- cellsWithStaticIcon = [[NSOrderedSet alloc] initWithArray:@[cellWithStaticIcon]];
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- OCMExpect([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- OCMVerify([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- });
- });
-
- context(@"when artworks are static icons", ^{
- beforeEach(^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithStaticIcon updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- });
-
- it(@"should skip uploading artwork", ^{
- OCMReject([testFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]);
- });
- });
-
- context(@"when artwork are not already on the system", ^{
- beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- });
-
- it(@"should upload artworks", ^{
- OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
- NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
- return (artworks.count == 3);
- }] completionHandler:[OCMArg any]]);
- expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
- });
- });
- });
- });
-
- context(@"without artworks", ^{
- __block NSOrderedSet<SDLChoiceCell *> *cellsWithoutArtwork = nil;
- beforeEach(^{
- SDLChoiceCell *cellBasic = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil];
- SDLChoiceCell *cellWithVR = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:@[@"Cell2"] artwork:nil secondaryArtwork:nil];
- SDLChoiceCell *cellWithAllText = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:@"Cell2" tertiaryText:@"Cell2" voiceCommands:nil artwork:nil secondaryArtwork:nil];
- cellsWithoutArtwork = [[NSOrderedSet alloc] initWithArray:@[cellBasic, cellWithVR, cellWithAllText]];
- });
-
- it(@"should skip to preloading cells", ^{
- expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
- });
-
- describe(@"assembling choices", ^{
- it(@"should be correct with no text and VR required", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithoutArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(3));
- expect(receivedRequests.lastObject.choiceSet.firstObject.menuName).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.secondaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.tertiaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.vrCommands).toNot(beNil());
- });
-
- it(@"should be correct with only primary text", ^{
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- windowCapability.textFields = @[primaryTextField];
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithoutArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(3));
- expect(receivedRequests.lastObject.choiceSet.firstObject.menuName).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.secondaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.tertiaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.vrCommands).toNot(beNil());
- });
-
- it(@"should be correct with primary and secondary text", ^{
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- SDLTextField *secondaryTextField = [[SDLTextField alloc] init];
- secondaryTextField.name = SDLTextFieldNameSecondaryText;
- windowCapability.textFields = @[primaryTextField, secondaryTextField];
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithoutArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(3));
- expect(receivedRequests.lastObject.choiceSet.firstObject.menuName).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.secondaryText).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.tertiaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.vrCommands).toNot(beNil());
- });
-
- it(@"should be correct with all text", ^{
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- SDLTextField *secondaryTextField = [[SDLTextField alloc] init];
- secondaryTextField.name = SDLTextFieldNameSecondaryText;
- SDLTextField *tertiaryTextField = [[SDLTextField alloc] init];
- tertiaryTextField.name = SDLTextFieldNameTertiaryText;
- windowCapability.textFields = @[primaryTextField, secondaryTextField, tertiaryTextField];
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithoutArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(3));
- expect(receivedRequests.lastObject.choiceSet.firstObject.menuName).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.secondaryText).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.tertiaryText).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.vrCommands).toNot(beNil());
- });
-
- it(@"should be correct with VR optional", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(3));
- expect(receivedRequests.lastObject.choiceSet.firstObject.menuName).toNot(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.secondaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.tertiaryText).to(beNil());
- expect(receivedRequests.lastObject.choiceSet.firstObject.vrCommands).to(beNil());
- });
- });
- });
-
- context(@"updating choices", ^{
- __block SDLChoiceCell *testCell1 = nil;
- __block SDLChoiceCell *testCell2 = nil;
- __block NSOrderedSet<SDLChoiceCell *> *testCells = nil;
-
- beforeEach(^{
- testCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil];
- testCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:[SDLArtwork artworkWithStaticIcon:SDLStaticIconNameClock]];
- testCells = [[NSOrderedSet alloc] initWithArray:@[testCell1, testCell2]];
- });
-
- describe(@"if a choice item is removed", ^{
- it(@"should be removed if the removal is attempted while the operation is pending", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:testCells updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp removeChoicesFromUpload:[NSSet setWithArray:@[testCell1]]];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(1));
- expect(receivedRequests[0].choiceSet[0].menuName).to(equal(testCell2.text));
- });
-
- it(@"should not be removed if the removal is attempted while operation is executing", ^{
- SDLTextField *primaryTextField = [[SDLTextField alloc] init];
- primaryTextField.name = SDLTextFieldNameMenuName;
- windowCapability.textFields = @[primaryTextField];
-
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:testCells updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
- [testOp removeChoicesFromUpload:[NSSet setWithArray:@[testCell1]]];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(2));
- expect(receivedRequests[0].choiceSet[0].menuName).to(equal(testCell1.text));
- expect(receivedRequests[1].choiceSet[0].menuName).to(equal(testCell2.text));
- });
- });
- });
-
- describe(@"the module's response to choice uploads", ^{
- __block SDLChoiceCell *testCell1 = nil;
- __block SDLChoiceCell *testCell2 = nil;
- __block NSOrderedSet<SDLChoiceCell *> *testCells = nil;
- __block SDLCreateInteractionChoiceSetResponse *testBadResponse = nil;
- __block SDLCreateInteractionChoiceSetResponse *testGoodResponse = nil;
-
- beforeEach(^{
- testCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil];
- testCell1.choiceId = 55;
- testCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:[SDLArtwork artworkWithStaticIcon:SDLStaticIconNameClock]];
- testCell2.choiceId = 66;
- testCells = [[NSOrderedSet alloc] initWithArray:@[testCell1, testCell2]];
-
- testBadResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init];
- testBadResponse.success = @NO;
- testBadResponse.resultCode = SDLResultRejected;
-
- testGoodResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init];
- testGoodResponse.success = @YES;
- testGoodResponse.resultCode = SDLResultSuccess;
- });
-
- context(@"when a bad response comes back", ^{
- it(@"should add the choiceID of the failed choice item to the failedChoiceUploadIDs array", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:testCells updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(2));
- expect(receivedRequests[0].choiceSet[0].menuName).to(equal(testCell1.text));
- expect(receivedRequests[1].choiceSet[0].menuName).to(equal(testCell2.text));
-
- [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
- [testConnectionManager respondToRequestWithResponse:testBadResponse requestNumber:1 error:[NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorUploadFailed userInfo:nil]];
-
- expect(testOp.failedChoiceUploadIDs.count).to(equal(1));
- expect(testOp.failedChoiceUploadIDs).to(contain(@(testCell2.choiceId)));
- expect(testOp.failedChoiceUploadIDs).toNot(contain(@(testCell1.choiceId)));
- });
- });
-
- context(@"when only good responses comes back", ^{
- it(@"should leave the failedChoiceUploadIDs array empty", ^{
- testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:testCells updateCompletionHandler:^(NSArray<NSNumber *> * _Nullable failedChoiceUploadIDs) {}];
- [testOp start];
-
- NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
-
- expect(receivedRequests).to(haveCount(2));
- expect(receivedRequests[0].choiceSet[0].menuName).to(equal(testCell1.text));
- expect(receivedRequests[1].choiceSet[0].menuName).to(equal(testCell2.text));
-
- [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
- [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil];
-
- expect(testOp.failedChoiceUploadIDs).to(beEmpty());
- });
- });
- });
- });
-});
-
-QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m
new file mode 100644
index 000000000..45e4b7efc
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationSpec.m
@@ -0,0 +1,1070 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import <SmartDeviceLink/SmartDeviceLink.h>
+#import "SDLError.h"
+#import "SDLPreloadPresentChoicesOperation.h"
+
+#import "SDLGlobals.h"
+#import "TestConnectionManager.h"
+
+@interface SDLPreloadPresentChoicesOperation()
+
+// Dependencies
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (strong, nonatomic) SDLWindowCapability *windowCapability;
+
+// Preload Dependencies
+@property (strong, nonatomic) NSMutableOrderedSet<SDLChoiceCell *> *cellsToUpload;
+@property (strong, nonatomic) NSString *displayName;
+@property (assign, nonatomic, readwrite, getter=isVROptional) BOOL vrOptional;
+@property (copy, nonatomic) SDLUploadChoicesCompletionHandler preloadCompletionHandler;
+
+// Present Dependencies
+@property (strong, nonatomic) SDLChoiceSet *choiceSet;
+@property (strong, nonatomic, nullable) SDLInteractionMode presentationMode;
+@property (strong, nonatomic, nullable) SDLKeyboardProperties *originalKeyboardProperties;
+@property (strong, nonatomic, nullable) SDLKeyboardProperties *customKeyboardProperties;
+@property (weak, nonatomic, nullable) id<SDLKeyboardDelegate> keyboardDelegate;
+@property (assign, nonatomic) UInt16 cancelId;
+
+// Internal operation properties
+@property (strong, nonatomic) NSUUID *operationId;
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+// Mutable state
+@property (strong, nonatomic) NSMutableSet<SDLChoiceCell *> *mutableLoadedCells;
+
+@end
+
+@interface SDLChoiceCell()
+
+@property (assign, nonatomic) UInt16 choiceId;
+@property (assign, nonatomic) NSUInteger uniqueTextId;
+
+@end
+
+QuickSpecBegin(SDLPreloadPresentChoicesOperationSpec)
+
+describe(@"a preload choices operation", ^{
+ __block TestConnectionManager *testConnectionManager = nil;
+ __block SDLFileManager *testFileManager = nil;
+ __block SDLPreloadPresentChoicesOperation *testOp = nil;
+ __block NSString *testDisplayName = @"SDL_GENERIC";
+ __block SDLVersion *choiceSetUniquenessActiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:1 patch:0];
+ __block SDLVersion *choiceSetUniquenessInactiveVersion = [[SDLVersion alloc] initWithMajor:7 minor:0 patch:0];
+
+ __block SDLWindowCapability *enabledWindowCapability = nil;
+ __block SDLWindowCapability *disabledWindowCapability = nil;
+ __block SDLWindowCapability *primaryTextOnlyCapability = nil;
+
+ __block NSSet<SDLChoiceCell *> *emptyLoadedCells = [NSSet set];
+ __block NSArray<SDLChoiceCell *> *cellsWithArtwork = nil;
+ __block NSArray<SDLChoiceCell *> *cellsWithStaticIcon = nil;
+ __block NSArray<SDLChoiceCell *> *cellsWithoutArtwork = nil;
+
+ __block NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding];
+ __block NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding];
+ __block NSString *art1Name = @"Art1Name";
+ __block NSString *art2Name = @"Art2Name";
+ SDLArtwork *cell1Art = [[SDLArtwork alloc] initWithData:cellArtData name:art1Name fileExtension:@"png" persistent:NO];
+ SDLArtwork *cell1Art2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:art1Name fileExtension:@"png" persistent:NO];
+ SDLArtwork *cell2Art = [[SDLArtwork alloc] initWithData:cellArtData name:art2Name fileExtension:@"png" persistent:NO];
+
+ __block SDLChoiceCell *cellBasic = nil;
+ __block SDLChoiceCell *cellBasicDuplicate = nil;
+ __block SDLChoiceCell *cellWithVR = nil;
+ __block SDLChoiceCell *cellWithAllText = nil;
+
+ __block SDLCreateInteractionChoiceSetResponse *testBadResponse = nil;
+ __block SDLCreateInteractionChoiceSetResponse *testGoodResponse = nil;
+
+ __block NSSet<SDLChoiceCell *> *resultChoices = nil;
+ __block NSError *resultPreloadError = nil;
+
+ __block SDLChoiceSet *testChoiceSet = nil;
+ __block int testCancelID = 98;
+ __block SDLInteractionMode testInteractionMode = SDLInteractionModeBoth;
+ __block SDLKeyboardProperties *testKeyboardProperties = nil;
+ __block id<SDLKeyboardDelegate> testKeyboardDelegate = nil;
+ __block id<SDLChoiceSetDelegate> testChoiceDelegate = nil;
+
+ beforeEach(^{
+ resultPreloadError = nil;
+ resultChoices = nil;
+
+ testConnectionManager = [[TestConnectionManager alloc] init];
+ testFileManager = OCMClassMock([SDLFileManager class]);
+ OCMStub([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]);
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
+
+ enabledWindowCapability = [[SDLWindowCapability alloc] init];
+ enabledWindowCapability.textFields = @[
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameSecondaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameTertiaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1]
+ ];
+ enabledWindowCapability.imageFields = @[
+ [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil],
+ [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceSecondaryImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]
+ ];
+ disabledWindowCapability = [[SDLWindowCapability alloc] init];
+ disabledWindowCapability.textFields = @[];
+ primaryTextOnlyCapability = [[SDLWindowCapability alloc] init];
+ primaryTextOnlyCapability.textFields = @[
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ ];
+
+ SDLChoiceCell *cell1WithArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art voiceCommands:nil];
+ cell1WithArt.choiceId = 1;
+ SDLChoiceCell *cell2WithArtAndSecondary = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:cell2Art secondaryArtwork:cell2Art];
+ cell2WithArtAndSecondary.choiceId = 2;
+
+ SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate];
+ SDLChoiceCell *cellWithStaticIcon = [[SDLChoiceCell alloc] initWithText:@"Static Icon" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:staticIconArt secondaryArtwork:nil];
+ cellWithStaticIcon.choiceId = 3;
+
+ cellsWithArtwork = @[cell1WithArt, cell2WithArtAndSecondary];
+ cellsWithStaticIcon = @[cellWithStaticIcon];
+
+ cellBasic = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil];
+ cellBasic.choiceId = 4;
+ cellBasicDuplicate = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:nil voiceCommands:nil];
+ cellBasicDuplicate.choiceId = 5;
+ cellWithVR = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:@[@"Cell2"] artwork:nil secondaryArtwork:nil];
+ cellWithVR.choiceId = 6;
+ cellWithAllText = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:@"Cell2" tertiaryText:@"Cell2" voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ cellWithAllText.choiceId = 7;
+ cellsWithoutArtwork = @[cellBasic, cellWithVR, cellWithAllText];
+
+ testBadResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init];
+ testBadResponse.success = @NO;
+ testBadResponse.resultCode = SDLResultRejected;
+
+ testGoodResponse = [[SDLCreateInteractionChoiceSetResponse alloc] init];
+ testGoodResponse.success = @YES;
+ testGoodResponse.resultCode = SDLResultSuccess;
+
+ testChoiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
+ testKeyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate));
+ OCMStub([testKeyboardDelegate customKeyboardConfiguration]).andReturn(nil);
+ testKeyboardProperties = [[SDLKeyboardProperties alloc] initWithLanguage:SDLLanguageArSa keyboardLayout:SDLKeyboardLayoutAZERTY keypressMode:SDLKeypressModeResendCurrentEntry limitedCharacterList:nil autoCompleteList:nil maskInputCharacters:nil customKeys:nil];
+ testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:@"Choice Set" delegate:testChoiceDelegate layout:SDLChoiceSetLayoutTiles timeout:8.0 initialPromptString:@"Initial Prompt" timeoutPromptString:@"Timeout Prompt" helpPromptString:@"Help Prompt" vrHelpList:nil choices:cellsWithoutArtwork];
+ });
+
+ it(@"should have a priority of 'normal'", ^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] init];
+
+ expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
+ });
+
+ context(@"running a preload only operation", ^{
+ describe(@"updating cells for uniqueness", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:@[cellWithVR] loadedCells:[NSSet setWithArray:@[cellWithAllText]] preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {}];
+ });
+
+ context(@"when some choices are already uploaded with duplicate titles version >= 7.1.0", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessActiveVersion;
+ });
+
+ context(@"if there are duplicate cells once you strip unused cell properties", ^{
+ beforeEach(^{
+ SDLChoiceCell *loadedCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *loadedCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ loadedCell2.uniqueTextId = 3;
+ SDLChoiceCell *loadedCell3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ loadedCell3.uniqueTextId = 5;
+
+ SDLChoiceCell *cellToUpload1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload4 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 4" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+
+ testOp.windowCapability = primaryTextOnlyCapability;
+ testOp.loadedCells = [NSSet setWithArray:@[loadedCell1, loadedCell2, loadedCell3]];
+ testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4]];
+ [testOp start];
+ });
+
+ it(@"should properly assign unique text", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell 2 (2)"));
+ expect(testOp.cellsToUpload[1].uniqueText).to(equal(@"Cell 2 (4)"));
+ expect(testOp.cellsToUpload[2].uniqueText).to(equal(@"Cell 2 (6)"));
+ expect(testOp.cellsToUpload[3].uniqueText).to(equal(@"Cell 2 (7)"));
+ expect(testOp.cellsToUpload[0].secondaryText).toNot(beNil());
+ });
+ });
+
+ context(@"if all cell properties are used", ^{
+ beforeEach(^{
+ testOp.windowCapability = enabledWindowCapability;
+ [testOp start];
+ });
+
+ it(@"should not update the choiceCells' unique title", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2"));
+ expect(testOp.cellsToUpload.count).to(equal(1));
+ });
+ });
+ });
+
+ context(@"when some choices are already uploaded with duplicate titles version <= 7.1.0", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessInactiveVersion;
+ });
+
+ context(@"if all cell properties are used", ^{
+ beforeEach(^{
+ testOp.windowCapability = enabledWindowCapability;
+ [testOp start];
+ });
+
+ it(@"should update the choiceCells' unique title", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2 (2)"));
+ expect(testOp.cellsToUpload.count).to(equal(1));
+ });
+ });
+ });
+ });
+
+ context(@"with artworks", ^{
+ context(@"only primary text allowed", ^{
+ it(@"should skip loading artworks to preloading cells", ^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultPreloadError = error;
+ resultChoices = updatedLoadedCells;
+ }];
+ [testOp start];
+
+ for (SDLRPCRequest *request in testConnectionManager.receivedRequests) {
+ expect(request).toNot(beAnInstanceOf(SDLPutFile.class));
+ }
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ context(@"all text and image display capabilities", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultPreloadError = error;
+ resultChoices = updatedLoadedCells;
+ }];
+ });
+
+ context(@"when artworks are already on the system", ^{
+ beforeEach(^{
+ OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
+ });
+
+ it(@"should not upload artworks", ^{
+ OCMReject([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
+ NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
+ return (artworks.count == 2);
+ }] completionHandler:[OCMArg any]]);
+
+ [testOp start];
+
+ OCMVerifyAll(testFileManager);
+ });
+
+ it(@"should properly overwrite artwork", ^{
+ cell1Art2.overwrite = YES;
+ SDLChoiceCell *cellOverwriteArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art2 voiceCommands:nil];
+
+ testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[cellOverwriteArt]];
+ [testOp start];
+
+ OCMVerify([testFileManager uploadArtworks:[OCMArg isNotNil] completionHandler:[OCMArg any]]);
+ });
+ });
+
+ context(@"when artworks are static icons", ^{
+ beforeEach(^{
+ testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:cellsWithStaticIcon];
+ [testOp start];
+ });
+
+ it(@"should skip uploading artwork", ^{
+ OCMReject([testFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]);
+ });
+ });
+
+ fcontext(@"when artworks are not already on the system", ^{
+ beforeEach(^{
+ OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
+ });
+
+ context(@"when there's more than one of the same artwork", ^{
+ beforeEach(^{
+ testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" artwork:cell1Art voiceCommands:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" artwork:cell1Art voiceCommands:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3" artwork:cell1Art voiceCommands:nil],
+ ]];
+ testOp.loadedCells = [NSSet set];
+ });
+
+ it(@"should only attempt to upload one of each art", ^{
+ [testOp start];
+ OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
+ NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
+ return (artworks.count == 1);
+ }] completionHandler:[OCMArg any]]);
+ });
+ });
+
+ context(@"when uploading unique art", ^{
+ beforeEach(^{
+ testOp.cellsToUpload = [NSMutableOrderedSet orderedSetWithArray:cellsWithArtwork];
+ testOp.loadedCells = [NSSet set];
+ });
+
+ it(@"should upload artworks", ^{
+ [testOp start];
+ OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
+ NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
+ return (artworks.count == 2);
+ }] completionHandler:[OCMArg any]]);
+ });
+ });
+ });
+ });
+ });
+
+ context(@"without artworks", ^{
+ describe(@"only main text capabilities", ^{
+ it(@"should skip loading artworks to preloading cells", ^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultPreloadError = error;
+ resultChoices = updatedLoadedCells;
+ }];
+ [testOp start];
+
+ for (SDLRPCRequest *request in testConnectionManager.receivedRequests) {
+ expect(request).toNot(beAnInstanceOf(SDLPutFile.class));
+ }
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ describe(@"assembling choices", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultChoices = updatedLoadedCells;
+ resultPreloadError = error;
+ }];
+ });
+
+ it(@"should skip preloading the choices if all choice items have already been uploaded", ^{
+ testOp.loadedCells = [NSSet setWithArray:cellsWithoutArtwork];
+ [testOp start];
+
+ expect(testConnectionManager.receivedRequests).to(haveCount(0));
+ });
+
+ it(@"should not send any requests if all items are disabled", ^{
+ testOp.windowCapability = disabledWindowCapability;
+ [testOp start];
+
+ expect(testConnectionManager.receivedRequests).to(haveCount(0));
+ });
+
+ it(@"should be correct with only primary text", ^{
+ testOp.windowCapability = primaryTextOnlyCapability;
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+
+ SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject;
+ expect(representativeItem.menuName).toNot(beNil());
+ expect(representativeItem.secondaryText).to(beNil());
+ expect(representativeItem.tertiaryText).to(beNil());
+ });
+
+ it(@"should be correct with all text", ^{
+ SDLWindowCapability *allTextCapability = [enabledWindowCapability copy];
+ allTextCapability.imageFields = @[];
+ testOp.windowCapability = allTextCapability;
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+
+ SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject;
+ expect(representativeItem.menuName).toNot(beNil());
+ expect(representativeItem.secondaryText).toNot(beNil());
+ expect(representativeItem.tertiaryText).toNot(beNil());
+ });
+
+ it(@"should be correct with VR required", ^{
+ testOp.vrOptional = NO;
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+
+ // The last item has no VR
+ SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject;
+ expect(representativeItem.vrCommands).toNot(beNil());
+ });
+
+ it(@"should be correct with VR Optional", ^{
+ testOp.vrOptional = YES;
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+
+ // The middle item is the one with VR
+ SDLChoice *representativeItem = receivedRequests.lastObject.choiceSet.firstObject;
+ expect(representativeItem.vrCommands).to(beNil());
+ });
+ });
+ });
+
+ describe(@"the module's response to choice uploads", ^{
+ context(@"when a bad response comes back", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultChoices = updatedLoadedCells;
+ resultPreloadError = error;
+ }];
+ });
+
+ it(@"should not add the item to the list of loaded cells", ^{
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+ expect(receivedRequests[0].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[0].uniqueText));
+ expect(receivedRequests[1].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[1].uniqueText));
+ expect(receivedRequests[2].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[2].uniqueText));
+
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testBadResponse requestNumber:1 error:[NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorUploadFailed userInfo:nil]];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
+
+ expect(testOp.loadedCells).to(haveCount(1));
+ expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[0]));
+ expect(testOp.loadedCells).toNot(contain(cellsWithoutArtwork[1]));
+ expect(testOp.error).toNot(beNil());
+ expect(resultChoices).toNot(beNil());
+ expect(resultPreloadError).toNot(beNil());
+ });
+ });
+
+ context(@"when only good responses comes back", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:primaryTextOnlyCapability isVROptional:YES cellsToPreload:cellsWithoutArtwork loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultChoices = updatedLoadedCells;
+ resultPreloadError = error;
+ }];
+ });
+
+ it(@"should add all the items to the list of loaded cells", ^{
+ [testOp start];
+
+ NSArray<SDLCreateInteractionChoiceSet *> *receivedRequests = (NSArray<SDLCreateInteractionChoiceSet *> *)testConnectionManager.receivedRequests;
+
+ expect(receivedRequests).to(haveCount(3));
+ expect(receivedRequests[0].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[0].uniqueText));
+ expect(receivedRequests[1].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[1].uniqueText));
+ expect(receivedRequests[2].choiceSet[0].menuName).to(equal(cellsWithoutArtwork[2].uniqueText));
+
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+
+ expect(testOp.loadedCells).to(haveCount(3));
+ expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[0]));
+ expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[1]));
+ expect(testOp.loadedCells).to(contain(cellsWithoutArtwork[2]));
+ expect(resultPreloadError).to(beNil());
+ expect(resultChoices).to(haveCount(3));
+ });
+ });
+ });
+ });
+
+ context(@"running a preload and present operation", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager choiceSet:testChoiceSet mode:testInteractionMode keyboardProperties:testKeyboardProperties keyboardDelegate:testKeyboardDelegate cancelID:testCancelID displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES loadedCells:emptyLoadedCells preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultChoices = updatedLoadedCells;
+ resultPreloadError = error;
+ }];
+ });
+
+ describe(@"updating cells for uniqueness", ^{
+ beforeEach(^{
+ testOp = [[SDLPreloadPresentChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager choiceSet:[[SDLChoiceSet alloc] initWithTitle:@"Test Choice Set" delegate:testChoiceDelegate choices:@[cellWithVR]] mode:testInteractionMode keyboardProperties:testKeyboardProperties keyboardDelegate:testKeyboardDelegate cancelID:testCancelID displayName:testDisplayName windowCapability:enabledWindowCapability isVROptional:YES loadedCells:[NSSet setWithArray:@[cellWithAllText]] preloadCompletionHandler:^(NSSet<SDLChoiceCell *> * _Nonnull updatedLoadedCells, NSError * _Nullable error) {
+ resultChoices = updatedLoadedCells;
+ resultPreloadError = error;
+ }];
+ });
+
+ context(@"when some choices are already uploaded with duplicate titles version >= 7.1.0", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessActiveVersion;
+ });
+
+ context(@"if there are duplicate cells once you strip unused cell properties", ^{
+ beforeEach(^{
+ SDLChoiceCell *loadedCell1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *loadedCell2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ loadedCell2.uniqueTextId = 3;
+ SDLChoiceCell *loadedCell3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Loaded 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ loadedCell3.uniqueTextId = 5;
+
+ SDLChoiceCell *cellToUpload1 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload2 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload3 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+ SDLChoiceCell *cellToUpload4 = [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 4" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil];
+
+ testOp.windowCapability = primaryTextOnlyCapability;
+ testOp.loadedCells = [NSSet setWithArray:@[loadedCell1, loadedCell2, loadedCell3]];
+ testOp.choiceSet.choices = @[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4];
+ testOp.cellsToUpload = [[NSMutableOrderedSet alloc] initWithArray:@[cellToUpload1, cellToUpload2, cellToUpload3, cellToUpload4]];
+ [testOp start];
+ });
+
+ it(@"should properly assign unique text", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell 2 (2)"));
+ expect(testOp.cellsToUpload[1].uniqueText).to(equal(@"Cell 2 (4)"));
+ expect(testOp.cellsToUpload[2].uniqueText).to(equal(@"Cell 2 (6)"));
+ expect(testOp.cellsToUpload[3].uniqueText).to(equal(@"Cell 2 (7)"));
+ expect(testOp.cellsToUpload[0].secondaryText).toNot(beNil());
+ expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell 2 (2)"));
+ });
+ });
+
+ context(@"if all cell properties are used", ^{
+ beforeEach(^{
+ testOp.windowCapability = enabledWindowCapability;
+ [testOp start];
+ });
+
+ it(@"should not update the choiceCells' unique title", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal(@"Cell2"));
+ expect(testOp.cellsToUpload.count).to(equal(1));
+ expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell2"));
+ });
+ });
+ });
+
+ context(@"when some choices are already uploaded with duplicate titles version <= 7.1.0", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = choiceSetUniquenessInactiveVersion;
+ });
+
+ context(@"if all cell properties are used", ^{
+ beforeEach(^{
+ testOp.windowCapability = enabledWindowCapability;
+ [testOp start];
+ });
+
+ it(@"should update the choiceCells' unique title", ^{
+ expect(testOp.cellsToUpload[0].uniqueText).to(equal("Cell2 (2)"));
+ expect(testOp.cellsToUpload.count).to(equal(1));
+ expect(testOp.choiceSet.choices[0].uniqueText).to(equal(@"Cell2 (2)"));
+ });
+ });
+ });
+ });
+
+ describe(@"running a non-searchable choice set operation", ^{
+ beforeEach(^{
+ testOp.keyboardDelegate = nil;
+ [testOp start];
+
+ // Move us past the preload
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+ });
+
+ it(@"should not update global keyboard properties", ^{
+ for (SDLRPCRequest *req in testConnectionManager.receivedRequests) {
+ expect(req).toNot(beAnInstanceOf([SDLSetGlobalProperties class]));
+ }
+ });
+
+ it(@"should send the perform interaction", ^{
+ SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject;
+ expect(request).to(beAnInstanceOf([SDLPerformInteraction class]));
+
+ expect(request.initialText).to(equal(testChoiceSet.title));
+ expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt));
+ expect(request.interactionMode).to(equal(testInteractionMode));
+ expect(request.interactionLayout).to(equal(SDLLayoutModeIconOnly));
+ expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt));
+ expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt));
+ expect(request.timeout).to(equal(testChoiceSet.timeout * 1000));
+ expect(request.vrHelp).to(beNil());
+ expect(request.interactionChoiceSetIDList).to(equal(@[@(cellsWithoutArtwork[0].choiceId), @(cellsWithoutArtwork[1].choiceId), @(cellsWithoutArtwork[2].choiceId)]));
+ expect(request.cancelID).to(equal(testCancelID));
+ });
+
+ describe(@"after a perform interaction response", ^{
+ __block UInt16 responseChoiceId = UINT16_MAX;
+ __block SDLTriggerSource responseTriggerSource = SDLTriggerSourceMenu;
+
+ beforeEach(^{
+ SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init];
+ response.success = @YES;
+ response.choiceID = @(responseChoiceId);
+ response.triggerSource = responseTriggerSource;
+
+ [testConnectionManager respondToLastRequestWithResponse:response];
+ });
+
+ it(@"should not reset the keyboard properties and should be finished", ^{
+ expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLSetGlobalProperties class]));
+ expect(testOp.isFinished).to(beTrue());
+ });
+ });
+ });
+
+ describe(@"running a searchable choice set operation", ^{
+ beforeEach(^{
+ [testOp start];
+
+ // Move us past the preload
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+ });
+
+ it(@"should ask for custom properties", ^{
+ OCMVerify([testKeyboardDelegate customKeyboardConfiguration]);
+
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
+ });
+
+ describe(@"presenting the keyboard", ^{
+ beforeEach(^{
+ SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init];
+ response.success = @YES;
+ [testConnectionManager respondToLastRequestWithResponse:response];
+ });
+
+ it(@"should send the perform interaction", ^{
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLPerformInteraction class]));
+ SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject;
+ expect(request.initialText).to(equal(testChoiceSet.title));
+ expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt));
+ expect(request.interactionMode).to(equal(testInteractionMode));
+ expect(request.interactionLayout).to(equal(SDLLayoutModeIconWithSearch));
+ expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt));
+ expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt));
+ expect(request.timeout).to(equal(testChoiceSet.timeout * 1000));
+ expect(request.vrHelp).to(beNil());
+ expect(request.interactionChoiceSetIDList).to(equal(@[@(testChoiceSet.choices[0].choiceId), @(testChoiceSet.choices[1].choiceId), @(testChoiceSet.choices[2].choiceId)]));
+ expect(request.cancelID).to(equal(testCancelID));
+ });
+
+ it(@"should respond to submitted notifications", ^{
+ NSString *inputData = @"Test";
+ SDLRPCNotificationNotification *notification = nil;
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventSubmitted;
+ input.data = inputData;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted];
+ }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }]]);
+
+ OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted];
+ }]]);
+ });
+
+ it(@"should respond to voice request notifications", ^{
+ SDLRPCNotificationNotification *notification = nil;
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventVoice;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice];
+ }] text:[OCMArg isNil]]);
+
+ OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg isNil] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice];
+ }]]);
+ });
+
+ it(@"should respond to abort notifications", ^{
+ SDLRPCNotificationNotification *notification = nil;
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventAborted;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted];
+ }] text:[OCMArg isNil]]);
+
+ OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted];
+ }]]);
+ });
+
+ it(@"should respond to enabled keyboard event", ^{
+ SDLRPCNotificationNotification *notification = nil;
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventInputKeyMaskEnabled;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
+ }] text:[OCMArg isNil]]);
+
+ OCMVerify([testKeyboardDelegate keyboardDidUpdateInputMask:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
+ }]]);
+ });
+
+ it(@"should respond to cancellation notifications", ^{
+ SDLRPCNotificationNotification *notification = nil;
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventCancelled;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled];
+ }] text:[OCMArg isNil]]);
+
+ OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled];
+ }]]);
+ });
+
+ it(@"should respond to text input notification with autocomplete", ^{
+ NSString *inputData = @"Test";
+ SDLRPCNotificationNotification *notification = nil;
+
+ OCMStub([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg any] autoCompleteResultsHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]);
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventKeypress;
+ input.data = inputData;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress];
+ }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }]]);
+
+ OCMVerify([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }] autoCompleteResultsHandler:[OCMArg any]]);
+
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
+
+ SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ expect(setProperties.keyboardProperties.autoCompleteText).to(equal(inputData));
+#pragma clang diagnostic pop
+ });
+
+ it(@"should respond to text input notification with character set", ^{
+ NSString *inputData = @"Test";
+ SDLRPCNotificationNotification *notification = nil;
+
+ OCMStub([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg any] completionHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]);
+
+ // Submit notification
+ SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
+ input.event = SDLKeyboardEventKeypress;
+ input.data = inputData;
+ notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress];
+ }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }]]);
+
+ OCMVerify([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg checkWithBlock:^BOOL(id obj) {
+ return [(NSString *)obj isEqualToString:inputData];
+ }] completionHandler:[OCMArg any]]);
+
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
+
+ SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject;
+ expect(setProperties.keyboardProperties.limitedCharacterList).to(equal(@[inputData]));
+ });
+
+ describe(@"after a perform interaction response", ^{
+ beforeEach(^{
+ SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init];
+ response.success = @YES;
+ response.choiceID = @65535;
+ response.triggerSource = SDLTriggerSourceVoiceRecognition;
+
+ [testConnectionManager respondToLastRequestWithResponse:response];
+ });
+
+ it(@"should reset the keyboard properties", ^{
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
+ });
+
+ describe(@"after the reset response", ^{
+ __block SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init];
+ beforeEach(^{
+ response.success = @YES;
+ });
+
+ it(@"should be finished", ^{
+ OCMExpect([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg isEqual:SDLTriggerSourceVoiceRecognition] atRowIndex:0]);
+ OCMReject([testChoiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]);
+
+ [testConnectionManager respondToLastRequestWithResponse:response];
+
+ expect(testOp.isFinished).to(beTrue());
+ });
+ });
+ });
+ });
+ });
+
+ describe(@"canceling the choice set", ^{
+ context(@"if the head unit supports the `CancelInteraction` RPC", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0];
+ });
+
+ context(@"if the operation is executing", ^{
+ beforeEach(^{
+ [testOp start];
+ });
+
+ context(@"before the present is sent", ^{
+ it(@"should cancel without a CancelInteraction", ^{
+ expect(testOp.isExecuting).to(beTrue());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+
+ expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLCancelInteraction class]));
+
+ expect(testOp.isExecuting).to(beTrue());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beTrue());
+ });
+ });
+
+ context(@"if the present is in progress", ^{
+ beforeEach(^{
+ // Move us past the preload
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:1 error:nil];
+ [testConnectionManager respondToRequestWithResponse:testGoodResponse requestNumber:2 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+
+ // Move us past the SetGlobalProperties
+ SDLSetGlobalPropertiesResponse *sgpr = [[SDLSetGlobalPropertiesResponse alloc] init];
+ sgpr.success = @YES;
+ sgpr.resultCode = SDLResultSuccess;
+ [testConnectionManager respondToLastRequestWithResponse:sgpr];
+ });
+
+ it(@"should attempt to send a cancel interaction", ^{
+ expect(testOp.isExecuting).to(beTrue());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+
+ SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
+ expect(lastRequest).to(beAnInstanceOf([SDLCancelInteraction class]));
+ expect(lastRequest.cancelID).to(equal(testCancelID));
+ expect(lastRequest.functionID).to(equal([SDLFunctionID.sharedInstance functionIdForName:SDLRPCFunctionNamePerformInteraction]));
+ });
+
+ context(@"If the cancel interaction was successful", ^{
+ __block SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init];
+ beforeEach(^{
+ testCancelInteractionResponse.success = @YES;
+ testCancelInteractionResponse.resultCode = SDLResultSuccess;
+ [testChoiceSet cancel];
+ });
+
+ it(@"should finish with an error", ^{
+ // Respond to the cancel interaction, then the perform interaction
+ [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse];
+
+ SDLPerformInteractionResponse *pir = [[SDLPerformInteractionResponse alloc] init];
+ pir.success = @NO;
+ pir.resultCode = SDLResultAborted;
+ [testConnectionManager respondToRequestWithResponse:pir requestNumber:4 error:[NSError sdl_choiceSetManager_cancelled]];
+
+ // Try to reset the keyboard
+ expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
+
+ SDLSetGlobalPropertiesResponse *sgpr = [[SDLSetGlobalPropertiesResponse alloc] init];
+ sgpr.success = @YES;
+ sgpr.resultCode = SDLResultSuccess;
+ [testConnectionManager respondToLastRequestWithResponse:sgpr];
+
+ OCMReject([testChoiceDelegate choiceSet:[OCMArg isNotNil] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg any] atRowIndex:0]);
+ OCMVerify([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didReceiveError:[OCMArg isNotNil]]);
+ });
+ });
+
+ context(@"If the cancel interaction was not successful", ^{
+ __block NSError *testError = [NSError sdl_lifecycle_notConnectedError];
+ __block SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init];
+
+ beforeEach(^{
+ testCancelInteractionResponse.success = @NO;
+ });
+
+ it(@"should error", ^{
+ OCMExpect([testChoiceDelegate choiceSet:[OCMArg any] didReceiveError:[OCMArg any]]);
+ OCMReject([testChoiceDelegate choiceSet:[OCMArg isEqual:testChoiceSet] didSelectChoice:[OCMArg isNotNil] withSource:[OCMArg isEqual:SDLTriggerSourceVoiceRecognition] atRowIndex:0]);
+ [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse error:testError];
+ });
+ });
+ });
+ });
+
+ context(@"if the operation has already finished", ^{
+ it(@"should not attempt to send a cancel interaction", ^{
+ [testOp finishOperation];
+
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beTrue());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+
+ SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
+ expect(lastRequest).to(beNil());
+ });
+ });
+
+ context(@"if the operation has not started", ^{
+ beforeEach(^{
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+ });
+
+ it(@"should not attempt to send a cancel interaction", ^{
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beTrue());
+
+ SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
+ expect(lastRequest).to(beNil());
+ });
+
+ context(@"once the operation has started", ^{
+ beforeEach(^{
+ [testOp start];
+ });
+
+ it(@"immediately finish", ^{
+ expect(testConnectionManager.receivedRequests).to(haveCount(0));
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beTrue());
+ expect(testOp.isCancelled).to(beTrue());
+ });
+
+ it(@"should finish", ^{
+ expect(testOp.isExecuting).toEventually(beFalse());
+ expect(testOp.isFinished).toEventually(beTrue());
+ expect(testOp.isCancelled).toEventually(beTrue());
+ });
+ });
+ });
+ });
+
+ context(@"Head unit does not support the `CancelInteraction` RPC", ^{
+ beforeEach(^{
+ SDLVersion *unsupportedVersion = [SDLVersion versionWithMajor:5 minor:1 patch:0];
+ id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
+ OCMStub([globalMock rpcVersion]).andReturn(unsupportedVersion);
+ });
+
+ it(@"should not attempt to send a cancel interaction if the operation is executing", ^{
+ [testOp start];
+
+ expect(testOp.isExecuting).to(beTrue());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+
+ SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
+ expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
+ });
+
+ it(@"should cancel the operation if it has not yet been run", ^{
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beFalse());
+
+ [testChoiceSet cancel];
+
+ SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
+ expect(lastRequest).to(beNil());
+
+ expect(testOp.isExecuting).to(beFalse());
+ expect(testOp.isFinished).to(beFalse());
+ expect(testOp.isCancelled).to(beTrue());
+ });
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationUtilitiesSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationUtilitiesSpec.m
new file mode 100644
index 000000000..b09d7a3e2
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadPresentChoicesOperationUtilitiesSpec.m
@@ -0,0 +1,504 @@
+//
+// SDLPreloadPresentChoiceSetOperationUtilitiesSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Joel Fischer on 8/27/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLPreloadPresentChoicesOperationUtilities.h"
+
+#import "SDLChoiceCell.h"
+#import "SDLChoiceSet.h"
+#import "SDLGlobals.h"
+#import "SDLImageField+ScreenManagerExtensions.h"
+#import "SDLTextField+ScreenManagerExtensions.h"
+#import "SDLVersion.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+@interface SDLChoiceCell()
+
+@property (assign, nonatomic) UInt16 choiceId;
+
+@end
+
+@interface SDLPreloadPresentChoicesOperationUtilitiesSpec : QuickSpec @end
+@implementation SDLPreloadPresentChoicesOperationUtilitiesSpec
+
+- (NSOrderedSet<SDLChoiceCell *> *)sdl_cellsToLoadWithCount:(UInt16)count {
+ NSMutableOrderedSet<SDLChoiceCell *> *mutableCells = [NSMutableOrderedSet orderedSetWithCapacity:count];
+ for (NSUInteger i = 1; i <= count; i++) {
+ [mutableCells addObject:[[SDLChoiceCell alloc] initWithText:[NSString stringWithFormat:@"Cell %lu", i]]];
+ }
+
+ return mutableCells.copy;
+}
+
+- (NSSet<SDLChoiceCell *> *)sdl_loadedCellsWithStartNum:(UInt16)startNum endNum:(UInt16)endNum {
+ NSMutableSet<SDLChoiceCell *> *mutableCells = [NSMutableSet setWithCapacity:(endNum - startNum)];
+ for (NSUInteger i = startNum; i <= endNum; i++) {
+ SDLChoiceCell *cell = [[SDLChoiceCell alloc] initWithText:[NSString stringWithFormat:@"Loaded Cell %lu", i]];
+ cell.choiceId = (UInt16)i;
+
+ [mutableCells addObject:cell];
+ }
+
+ return mutableCells.copy;
+}
+
+- (void)spec {
+ __block NSOrderedSet<SDLChoiceCell *> *testCellsToLoad = nil;
+ __block NSSet<SDLChoiceCell *> *testLoadedCells = nil;
+
+ describe(@"assigning ids", ^{
+ context(@"when we're on the first loop of assigning ids", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.reachedMaxId = NO;
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 1;
+ });
+
+ context(@"when there's no ids already set", ^{
+ beforeEach(^{
+ testLoadedCells = [NSSet set];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:50];
+ });
+
+ it(@"should set ids starting at 0", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(50));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 1));
+ }
+ });
+ });
+
+ context(@"when ids are already set", ^{
+ context(@"when not reaching the max value", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 100;
+
+ testLoadedCells = [NSSet set];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:50];
+ });
+
+ it(@"should set ids starting at the next id", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(50));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 100));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beFalse());
+ });
+ });
+
+ context(@"when reaching the max value", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 65500;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:0 endNum:65499];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:35];
+ });
+
+ it(@"should set the reachedMaxId BOOL and not loop back over yet", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(35));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 65500));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beFalse());
+ });
+ });
+ });
+ });
+
+ context(@"on subsequent loops of assigning ids", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.reachedMaxId = YES;
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 1;
+ });
+
+ context(@"when loadedCells is not full", ^{
+ context(@"when loaded cells are contiguous at the beginning", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 99;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:0 endNum:99];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:35];
+ });
+
+ it(@"should assign ids after those", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(35));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 100));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beTrue());
+ });
+ });
+
+ context(@"when those items are contiguous in the middle so that assigning cells overlap", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 10;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:3 endNum:10];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:13];
+ });
+
+ it(@"should start assigning from the last used id", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(13));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 11));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beTrue());
+ });
+ });
+
+ context(@"when there are items scattered and overlapping setting cells", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 10;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:3 endNum:10];
+ NSSet<SDLChoiceCell *> *secondLoadedCells = [self sdl_loadedCellsWithStartNum:50 endNum:55];
+ testLoadedCells = [testLoadedCells setByAddingObjectsFromSet:secondLoadedCells];
+
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:10];
+ });
+
+ it(@"start from the last used id", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(10));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(i + 56));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beTrue());
+ });
+ });
+
+ context(@"when not enough open ids are available", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 10;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:3 endNum:10];
+ NSSet<SDLChoiceCell *> *secondLoadedCells = [self sdl_loadedCellsWithStartNum:12 endNum:65533];
+ testLoadedCells = [testLoadedCells setByAddingObjectsFromSet:secondLoadedCells];
+
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:10];
+ });
+ it(@"should assign what it can and the rest should be UINT16_MAX", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(10));
+
+ expect((NSUInteger)testCellsToLoad[0].choiceId).to(equal(65534));
+ expect((NSUInteger)testCellsToLoad[1].choiceId).to(equal(65535));
+ expect((NSUInteger)testCellsToLoad[2].choiceId).to(equal(0));
+ expect((NSUInteger)testCellsToLoad[3].choiceId).to(equal(1));
+ expect((NSUInteger)testCellsToLoad[4].choiceId).to(equal(2));
+ expect((NSUInteger)testCellsToLoad[5].choiceId).to(equal(11));
+ expect((NSUInteger)testCellsToLoad[6].choiceId).to(equal(65535));
+ expect((NSUInteger)testCellsToLoad[7].choiceId).to(equal(65535));
+ expect((NSUInteger)testCellsToLoad[8].choiceId).to(equal(65535));
+ expect((NSUInteger)testCellsToLoad[9].choiceId).to(equal(65535));
+
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beTrue());
+ });
+ });
+ });
+
+ context(@"when loadedCells is full", ^{
+ beforeEach(^{
+ SDLPreloadPresentChoicesOperationUtilities.choiceId = 65535;
+
+ testLoadedCells = [self sdl_loadedCellsWithStartNum:0 endNum:65535];
+ testCellsToLoad = [self sdl_cellsToLoadWithCount:10];
+ });
+
+ it(@"should set all ids to UINT16_MAX", ^{
+ [SDLPreloadPresentChoicesOperationUtilities assignIdsToCells:testCellsToLoad loadedCells:testLoadedCells];
+ expect(testCellsToLoad.count).to(equal(10));
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ expect((NSUInteger)testCellsToLoad[i].choiceId).to(equal(65535));
+ }
+ expect(SDLPreloadPresentChoicesOperationUtilities.reachedMaxId).to(beTrue());
+ });
+ });
+ });
+ });
+
+ describe(@"making cells unique", ^{
+ __block SDLWindowCapability *enabledWindowCapability = nil;
+ __block SDLWindowCapability *primaryTextOnlyCapability = nil;
+
+ beforeEach(^{
+ enabledWindowCapability = [[SDLWindowCapability alloc] init];
+ enabledWindowCapability.textFields = @[
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameSecondaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameTertiaryText characterSet:SDLCharacterSetUtf8 width:500 rows:1]
+ ];
+ enabledWindowCapability.imageFields = @[
+ [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil],
+ [[SDLImageField alloc] initWithName:SDLImageFieldNameChoiceSecondaryImage imageTypeSupported:@[SDLFileTypePNG] imageResolution:nil]
+ ];
+ primaryTextOnlyCapability = [[SDLWindowCapability alloc] init];
+ primaryTextOnlyCapability.textFields = @[
+ [[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1],
+ ];
+
+ testLoadedCells = [NSSet set];
+ });
+
+ context(@"at RPC v7.1", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:7 minor:1 patch:0];
+ });
+
+ context(@"when cells are unique except when stripped", ^{
+ beforeEach(^{
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+ });
+
+ context(@"with full window capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should not set unique titles", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).to(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).to(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"with primary text only capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:primaryTextOnlyCapability];
+ });
+
+ it(@"should set unique titles", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).toNot(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).toNot(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+ });
+
+ context(@"when cells are unique", ^{
+ beforeEach(^{
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 4" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should not set unique titles", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).to(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).to(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"when loaded cells match the cells when stripped", ^{
+ beforeEach(^{
+ testLoadedCells = [NSSet setWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3"],
+ ]];
+
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 4" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+ });
+
+ context(@"with full window capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should not make unique text", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).to(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).to(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"with primary text only capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:primaryTextOnlyCapability];
+ });
+
+ it(@"should not make unique text", ^{
+ expect(testCellsToLoad[0].uniqueText).toNot(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).toNot(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).toNot(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+ });
+ });
+
+ context(@"below RPC v7.1", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:7 minor:0 patch:0];
+ });
+
+ context(@"when cells are unique except when stripped", ^{
+ beforeEach(^{
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 3" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique 1" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should set unique titles except the first and last", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).toNot(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).toNot(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"when cells are unique", ^{
+ beforeEach(^{
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 4" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should not set unique titles", ^{
+ expect(testCellsToLoad[0].uniqueText).to(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).to(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).to(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"when loaded cells match the cells when stripped", ^{
+ beforeEach(^{
+ testLoadedCells = [NSSet setWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3"],
+ ]];
+
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:@[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1" secondaryText:@"Unique 2" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 4" secondaryText:@"Unique" tertiaryText:nil voiceCommands:nil artwork:nil secondaryArtwork:nil]
+ ]];
+ });
+
+ context(@"with full window capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:enabledWindowCapability];
+ });
+
+ it(@"should make unique text", ^{
+ expect(testCellsToLoad[0].uniqueText).toNot(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).toNot(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).toNot(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+
+ context(@"with primary text only capability", ^{
+ beforeEach(^{
+ [SDLPreloadPresentChoicesOperationUtilities makeCellsToUploadUnique:testCellsToLoad.mutableCopy basedOnLoadedCells:testLoadedCells.mutableCopy windowCapability:primaryTextOnlyCapability];
+ });
+
+ it(@"should not make unique text", ^{
+ expect(testCellsToLoad[0].uniqueText).toNot(equal(testCellsToLoad[0].text));
+ expect(testCellsToLoad[1].uniqueText).toNot(equal(testCellsToLoad[1].text));
+ expect(testCellsToLoad[2].uniqueText).toNot(equal(testCellsToLoad[2].text));
+ expect(testCellsToLoad[3].uniqueText).to(equal(testCellsToLoad[3].text));
+ });
+ });
+ });
+ });
+ });
+
+ describe(@"updating a choice set based on loaded cells and cells to upload", ^{
+ __block SDLChoiceSet *testChoiceSet = nil;
+ __block NSArray<SDLChoiceCell *> *basicChoiceCells = nil;
+ __block NSMutableArray<SDLChoiceCell *> *testLoadedCellsArray = nil;
+
+ beforeEach(^{
+ basicChoiceCells = @[
+ [[SDLChoiceCell alloc] initWithText:@"Cell 1"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 2"],
+ [[SDLChoiceCell alloc] initWithText:@"Cell 3"],
+ ];
+
+ // Has all three cells with no ids
+ testChoiceSet = [[SDLChoiceSet alloc] init];
+ testChoiceSet.choices = basicChoiceCells;
+
+ // Has all three cells with different ids
+ testCellsToLoad = [NSOrderedSet orderedSetWithArray:basicChoiceCells range:NSMakeRange(0, 3) copyItems:YES];
+ for (NSUInteger i = 0; i < testCellsToLoad.count; i++) {
+ testCellsToLoad[i].choiceId = i;
+ }
+
+ // Loaded cells has first two items with different ids
+ testLoadedCellsArray = [[NSMutableArray alloc] initWithArray:basicChoiceCells copyItems:YES];
+ [testLoadedCellsArray removeLastObject];
+ for (NSUInteger i = 0; i < testLoadedCellsArray.count; i++) {
+ testLoadedCellsArray[i].choiceId = i + 10;
+ }
+ testLoadedCells = [NSSet setWithArray:testLoadedCellsArray];
+ });
+
+ context(@"when there are no loaded cells", ^{
+ it(@"should have all cells the same as cells to upload", ^{
+ [SDLPreloadPresentChoicesOperationUtilities updateChoiceSet:testChoiceSet withLoadedCells:[NSSet set] cellsToUpload:testCellsToLoad.set];
+
+ for (NSUInteger i = 0; i < testChoiceSet.choices.count; i++) {
+ expect((NSUInteger)testChoiceSet.choices[i].choiceId).to(equal(testCellsToLoad[i].choiceId));
+ }
+ });
+ });
+
+ context(@"when some loaded cells match", ^{
+ it(@"should use the loaded cells when possible", ^{
+ [SDLPreloadPresentChoicesOperationUtilities updateChoiceSet:testChoiceSet withLoadedCells:testLoadedCells cellsToUpload:testCellsToLoad.set];
+
+ expect((NSUInteger)testChoiceSet.choices[0].choiceId).to(equal(testLoadedCellsArray[0].choiceId));
+ expect((NSUInteger)testChoiceSet.choices[1].choiceId).to(equal(testLoadedCellsArray[1].choiceId));
+ expect((NSUInteger)testChoiceSet.choices[2].choiceId).to(equal(testCellsToLoad[2].choiceId));
+ });
+ });
+ });
+}
+
+@end
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPresentChoiceSetOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPresentChoiceSetOperationSpec.m
deleted file mode 100644
index f918c6909..000000000
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLPresentChoiceSetOperationSpec.m
+++ /dev/null
@@ -1,549 +0,0 @@
-#import <Quick/Quick.h>
-#import <Nimble/Nimble.h>
-#import <OCMock/OCMock.h>
-
-#import "SDLPresentChoiceSetOperation.h"
-
-#import "SDLCancelInteraction.h"
-#import "SDLCancelInteractionResponse.h"
-#import "SDLChoiceCell.h"
-#import "SDLChoiceSet.h"
-#import "SDLChoiceSetDelegate.h"
-#import "SDLError.h"
-#import "SDLFunctionID.h"
-#import "SDLKeyboardDelegate.h"
-#import "SDLOnKeyboardInput.h"
-#import "SDLKeyboardProperties.h"
-#import "SDLPerformInteraction.h"
-#import "SDLPerformInteractionResponse.h"
-#import "SDLRPCNotificationNotification.h"
-#import "SDLGlobals.h"
-#import "SDLSetGlobalProperties.h"
-#import "SDLSetGlobalPropertiesResponse.h"
-#import "SDLVersion.h"
-#import "TestConnectionManager.h"
-
-@interface SDLChoiceSet()
-
-@property (nullable, copy, nonatomic) SDLChoiceSetCanceledHandler canceledHandler;
-
-@end
-
-QuickSpecBegin(SDLPresentChoiceSetOperationSpec)
-
-describe(@"present choice operation", ^{
- __block TestConnectionManager *testConnectionManager = nil;
- __block SDLPresentChoiceSetOperation *testOp = nil;
-
- __block SDLInteractionMode testInteractionMode = SDLInteractionModeBoth;
- __block SDLChoiceSet *testChoiceSet = nil;
- __block id<SDLChoiceSetDelegate> testChoiceDelegate = nil;
- __block NSArray<SDLChoiceCell *> *testChoices = nil;
- __block int testCancelID = 98;
-
- __block id<SDLKeyboardDelegate> testKeyboardDelegate = nil;
- __block SDLKeyboardProperties *testKeyboardProperties = nil;
-
- __block BOOL hasCalledOperationCompletionHandler = NO;
- __block NSError *resultError = nil;
- __block SDLWindowCapability *windowCapability = nil;
-
- beforeEach(^{
- resultError = nil;
- hasCalledOperationCompletionHandler = NO;
-
- testConnectionManager = [[TestConnectionManager alloc] init];
-
- testChoiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
- SDLChoiceCell *cell1 = [[SDLChoiceCell alloc] initWithText:@"Cell 1"];
- testChoices = @[cell1];
- testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:@"Test Title" delegate:testChoiceDelegate layout:SDLChoiceSetLayoutTiles timeout:13 initialPromptString:@"Test initial prompt" timeoutPromptString:@"Test timeout prompt" helpPromptString:@"Test help prompt" vrHelpList:nil choices:testChoices];
-
- windowCapability = [[SDLWindowCapability alloc] init];
- testKeyboardDelegate = OCMProtocolMock(@protocol(SDLKeyboardDelegate));
- OCMStub([testKeyboardDelegate customKeyboardConfiguration]).andReturn(nil);
- testKeyboardProperties = [[SDLKeyboardProperties alloc] initWithLanguage:SDLLanguageArSa keyboardLayout:SDLKeyboardLayoutAZERTY keypressMode:SDLKeypressModeResendCurrentEntry limitedCharacterList:nil autoCompleteList:nil maskInputCharacters:nil customKeys:nil];
- });
-
- it(@"should have a priority of 'normal'", ^{
- testOp = [[SDLPresentChoiceSetOperation alloc] init];
-
- expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
- });
-
- describe(@"running a non-searchable choice set operation", ^{
- beforeEach(^{
- testOp = [[SDLPresentChoiceSetOperation alloc] initWithConnectionManager:testConnectionManager choiceSet:testChoiceSet mode:testInteractionMode keyboardProperties:nil keyboardDelegate:nil cancelID:testCancelID windowCapability:windowCapability];
- testOp.completionBlock = ^{
- hasCalledOperationCompletionHandler = YES;
- };
- [testOp start];
- });
-
- it(@"should not update global keyboard properties", ^{
- expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLSetGlobalProperties class]));
- });
-
- describe(@"presenting the choice set", ^{
- it(@"should send the perform interaction", ^{
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLPerformInteraction class]));
- SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject;
- expect(request.initialText).to(equal(testChoiceSet.title));
- expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt));
- expect(request.interactionMode).to(equal(testInteractionMode));
- expect(request.interactionLayout).to(equal(SDLLayoutModeIconOnly));
- expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt));
- expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt));
- expect(request.timeout).to(equal(testChoiceSet.timeout * 1000));
- expect(request.vrHelp).to(beNil());
- expect(request.interactionChoiceSetIDList).to(equal(@[@65535]));
- expect(request.cancelID).to(equal(testCancelID));
- });
-
- describe(@"after a perform interaction response", ^{
- __block UInt16 responseChoiceId = UINT16_MAX;
- __block SDLTriggerSource responseTriggerSource = SDLTriggerSourceMenu;
-
- beforeEach(^{
- SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init];
- response.success = @YES;
- response.choiceID = @(responseChoiceId);
- response.triggerSource = responseTriggerSource;
-
- [testConnectionManager respondToLastRequestWithResponse:response];
- });
-
- it(@"should not reset the keyboard properties and should be finished", ^{
- expect(testConnectionManager.receivedRequests.lastObject).toNot(beAnInstanceOf([SDLSetGlobalProperties class]));
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
- expect(testOp.isFinished).to(beTrue());
- expect(testOp.selectedCell).to(equal(testChoices.firstObject));
- expect(testOp.selectedTriggerSource).to(equal(responseTriggerSource));
- });
- });
- });
-
- describe(@"Canceling the choice set", ^{
- __block SDLPresentChoiceSetOperation *testCancelOp = nil;
-
- beforeEach(^{
- testCancelOp = [[SDLPresentChoiceSetOperation alloc] initWithConnectionManager:testConnectionManager choiceSet:testChoiceSet mode:testInteractionMode keyboardProperties:nil keyboardDelegate:nil cancelID:testCancelID windowCapability:windowCapability];
- testCancelOp.completionBlock = ^{
- hasCalledOperationCompletionHandler = YES;
- };
- });
-
- context(@"Head unit supports the `CancelInteration` RPC", ^{
- beforeEach(^{
- SDLVersion *supportedVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0];
- id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
- OCMStub([globalMock rpcVersion]).andReturn(supportedVersion);
- });
-
- context(@"If the operation is executing", ^{
- beforeEach(^{
- [testCancelOp start];
-
- expect(testCancelOp.isExecuting).to(beTrue());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
-
- [testChoiceSet cancel];
- });
-
- it(@"should attempt to send a cancel interaction", ^{
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).to(beAnInstanceOf([SDLCancelInteraction class]));
- expect(lastRequest.cancelID).to(equal(testCancelID));
- expect(lastRequest.functionID).to(equal([SDLFunctionID.sharedInstance functionIdForName:SDLRPCFunctionNamePerformInteraction]));
- });
-
- context(@"If the cancel interaction was successful", ^{
- beforeEach(^{
- SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init];
- testCancelInteractionResponse.success = @YES;
- [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse];
- });
-
- it(@"should not error", ^{
- expect(testCancelOp.error).to(beNil());
- });
-
- it(@"should not finish", ^{
- expect(hasCalledOperationCompletionHandler).to(beFalse());
- expect(testCancelOp.isExecuting).to(beTrue());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
- });
- });
-
- context(@"If the cancel interaction was not successful", ^{
- __block NSError *testError = [NSError sdl_lifecycle_notConnectedError];
-
- beforeEach(^{
- SDLCancelInteractionResponse *testCancelInteractionResponse = [[SDLCancelInteractionResponse alloc] init];
- testCancelInteractionResponse.success = @NO;
- [testConnectionManager respondToLastRequestWithResponse:testCancelInteractionResponse error:testError];
- });
-
- it(@"should error", ^{
- expect(testCancelOp.error).to(equal(testError));
- });
-
- it(@"should not finish", ^{
- expect(hasCalledOperationCompletionHandler).to(beFalse());
- expect(testCancelOp.isExecuting).to(beTrue());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
- });
- });
- });
-
- context(@"If the operation has already finished", ^{
- beforeEach(^{
- [testCancelOp finishOperation];
-
- expect(testCancelOp.isExecuting).to(beFalse());
- expect(testCancelOp.isFinished).to(beTrue());
- expect(testCancelOp.isCancelled).to(beFalse());
-
- [testChoiceSet cancel];
- });
-
- it(@"should not attempt to send a cancel interaction", ^{
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
- });
- });
-
- context(@"If the started operation has been canceled", ^{
- beforeEach(^{
- [testCancelOp start];
- [testCancelOp cancel];
-
- expect(testCancelOp.isExecuting).to(beTrue());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beTrue());
-
- [testChoiceSet cancel];
- });
-
- it(@"should not attempt to send a cancel interaction", ^{
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
- });
-
- it(@"should not finish", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beFalse());
- expect(testCancelOp.isExecuting).toEventually(beTrue());
- expect(testCancelOp.isFinished).toEventually(beFalse());
- expect(testCancelOp.isCancelled).toEventually(beTrue());
- });
- });
-
- context(@"If the operation has not started", ^{
- beforeEach(^{
- expect(testCancelOp.isExecuting).to(beFalse());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
-
- [testChoiceSet cancel];
- });
-
- it(@"should not attempt to send a cancel interaction", ^{
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
- });
-
- context(@"Once the operation has started", ^{
- beforeEach(^{
- [testCancelOp start];
- });
-
- it(@"should not attempt to send a cancel interaction", ^{
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
- });
-
- it(@"should finish", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
- expect(testCancelOp.isExecuting).toEventually(beFalse());
- expect(testCancelOp.isFinished).toEventually(beTrue());
- expect(testCancelOp.isCancelled).toEventually(beTrue());
- });
- });
- });
- });
-
- context(@"Head unit does not support the `CancelInteration` RPC", ^{
- beforeEach(^{
- SDLVersion *unsupportedVersion = [SDLVersion versionWithMajor:5 minor:1 patch:0];
- id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
- OCMStub([globalMock rpcVersion]).andReturn(unsupportedVersion);
- });
-
- it(@"should not attempt to send a cancel interaction if the operation is executing", ^{
- [testCancelOp start];
-
- expect(testCancelOp.isExecuting).to(beTrue());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
-
- [testChoiceSet cancel];
-
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
- });
-
- it(@"should cancel the operation if it has not yet been run", ^{
- expect(testCancelOp.isExecuting).to(beFalse());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beFalse());
-
- [testChoiceSet cancel];
-
- SDLCancelInteraction *lastRequest = testConnectionManager.receivedRequests.lastObject;
- expect(lastRequest).toNot(beAnInstanceOf([SDLCancelInteraction class]));
-
- expect(testCancelOp.isExecuting).to(beFalse());
- expect(testCancelOp.isFinished).to(beFalse());
- expect(testCancelOp.isCancelled).to(beTrue());
- });
- });
- });
- });
-
- describe(@"running a searchable choice set operation", ^{
- beforeEach(^{
- testOp = [[SDLPresentChoiceSetOperation alloc] initWithConnectionManager:testConnectionManager choiceSet:testChoiceSet mode:testInteractionMode keyboardProperties:testKeyboardProperties keyboardDelegate:testKeyboardDelegate cancelID:testCancelID windowCapability:windowCapability];
-
- testOp.completionBlock = ^{
- hasCalledOperationCompletionHandler = YES;
- };
- [testOp start];
- });
-
- it(@"should ask for custom properties", ^{
- OCMVerify([testKeyboardDelegate customKeyboardConfiguration]);
- });
-
- it(@"should update global keyboard properties", ^{
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
- });
-
- describe(@"presenting the keyboard", ^{
- beforeEach(^{
- SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init];
- response.success = @YES;
- [testConnectionManager respondToLastRequestWithResponse:response];
- });
-
- it(@"should send the perform interaction", ^{
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLPerformInteraction class]));
- SDLPerformInteraction *request = testConnectionManager.receivedRequests.lastObject;
- expect(request.initialText).to(equal(testChoiceSet.title));
- expect(request.initialPrompt).to(equal(testChoiceSet.initialPrompt));
- expect(request.interactionMode).to(equal(testInteractionMode));
- expect(request.interactionLayout).to(equal(SDLLayoutModeIconWithSearch));
- expect(request.timeoutPrompt).to(equal(testChoiceSet.timeoutPrompt));
- expect(request.helpPrompt).to(equal(testChoiceSet.helpPrompt));
- expect(request.timeout).to(equal(testChoiceSet.timeout * 1000));
- expect(request.vrHelp).to(beNil());
- expect(request.interactionChoiceSetIDList).to(equal(@[@65535]));
- expect(request.cancelID).to(equal(testCancelID));
- });
-
- it(@"should respond to submitted notifications", ^{
- NSString *inputData = @"Test";
- SDLRPCNotificationNotification *notification = nil;
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventSubmitted;
- input.data = inputData;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted];
- }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }]]);
-
- OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventSubmitted];
- }]]);
- });
-
- it(@"should respond to voice request notifications", ^{
- SDLRPCNotificationNotification *notification = nil;
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventVoice;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice];
- }] text:[OCMArg isNil]]);
-
- OCMVerify([testKeyboardDelegate userDidSubmitInput:[OCMArg isNil] withEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventVoice];
- }]]);
- });
-
- it(@"should respond to abort notifications", ^{
- SDLRPCNotificationNotification *notification = nil;
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventAborted;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted];
- }] text:[OCMArg isNil]]);
-
- OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventAborted];
- }]]);
- });
-
- it(@"should respond to enabled keyboard event", ^{
- SDLRPCNotificationNotification *notification = nil;
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventInputKeyMaskEnabled;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
- }] text:[OCMArg isNil]]);
-
- OCMVerify([testKeyboardDelegate keyboardDidUpdateInputMask:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventInputKeyMaskEnabled];
- }]]);
- });
-
- it(@"should respond to cancellation notifications", ^{
- SDLRPCNotificationNotification *notification = nil;
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventCancelled;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled];
- }] text:[OCMArg isNil]]);
-
- OCMVerify([testKeyboardDelegate keyboardDidAbortWithReason:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventCancelled];
- }]]);
- });
-
- it(@"should respond to text input notification with autocomplete", ^{
- NSString *inputData = @"Test";
- SDLRPCNotificationNotification *notification = nil;
-
- OCMStub([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg any] autoCompleteResultsHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]);
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventKeypress;
- input.data = inputData;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress];
- }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }]]);
-
- OCMVerify([testKeyboardDelegate updateAutocompleteWithInput:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }] autoCompleteResultsHandler:[OCMArg any]]);
-
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
-
- SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- expect(setProperties.keyboardProperties.autoCompleteText).to(equal(inputData));
-#pragma clang diagnostic pop
- });
-
- it(@"should respond to text input notification with character set", ^{
- NSString *inputData = @"Test";
- SDLRPCNotificationNotification *notification = nil;
-
- OCMStub([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg any] completionHandler:([OCMArg invokeBlockWithArgs:@[inputData], nil])]);
-
- // Submit notification
- SDLOnKeyboardInput *input = [[SDLOnKeyboardInput alloc] init];
- input.event = SDLKeyboardEventKeypress;
- input.data = inputData;
- notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveKeyboardInputNotification object:nil rpcNotification:input];
-
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- OCMVerify([testKeyboardDelegate keyboardDidSendEvent:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(SDLKeyboardEvent)obj isEqualToEnum:SDLKeyboardEventKeypress];
- }] text:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }]]);
-
- OCMVerify([testKeyboardDelegate updateCharacterSetWithInput:[OCMArg checkWithBlock:^BOOL(id obj) {
- return [(NSString *)obj isEqualToString:inputData];
- }] completionHandler:[OCMArg any]]);
-
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
-
- SDLSetGlobalProperties *setProperties = testConnectionManager.receivedRequests.lastObject;
- expect(setProperties.keyboardProperties.limitedCharacterList).to(equal(@[inputData]));
- });
-
- describe(@"after a perform interaction response", ^{
- beforeEach(^{
- SDLPerformInteractionResponse *response = [[SDLPerformInteractionResponse alloc] init];
- response.success = @YES;
-
- [testConnectionManager respondToLastRequestWithResponse:response];
- });
-
- it(@"should reset the keyboard properties", ^{
- expect(testConnectionManager.receivedRequests.lastObject).to(beAnInstanceOf([SDLSetGlobalProperties class]));
- });
-
- describe(@"after the reset response", ^{
- beforeEach(^{
- SDLSetGlobalPropertiesResponse *response = [[SDLSetGlobalPropertiesResponse alloc] init];
- response.success = @YES;
- [testConnectionManager respondToLastRequestWithResponse:response];
- });
-
- it(@"should be finished", ^{
- expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
- expect(testOp.isFinished).toEventually(beTrue());
- });
- });
- });
- });
- });
-});
-
-QuickSpecEnd
diff --git a/SmartDeviceLinkTests/RPCSpecs/PayloadSpecs/SDLSecurityQueryPayloadSpec.m b/SmartDeviceLinkTests/RPCSpecs/PayloadSpecs/SDLSecurityQueryPayloadSpec.m
new file mode 100644
index 000000000..5ebe9ce28
--- /dev/null
+++ b/SmartDeviceLinkTests/RPCSpecs/PayloadSpecs/SDLSecurityQueryPayloadSpec.m
@@ -0,0 +1,80 @@
+//
+// SDLSecurityQueryPayloadSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Frank Elias on 8/12/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLRPCParameterNames.h"
+#import "SDLRPCFunctionNames.h"
+#import "SDLSecurityQueryPayload.h"
+#import "SDLSecurityQueryErrorCode.h"
+
+QuickSpecBegin(SDLSecurityQueryPayloadSpec)
+
+__block SDLSecurityQueryPayload* testPayload;
+__block NSDictionary* dict = @{@"id": @"3", @"text": @"SDL does not support encryption"};
+
+NSData* (^testData)(void) = ^NSData* {
+ NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:0];
+ NSData* binaryData = [NSData dataWithBytes:"PrimitiveString" length:strlen("PrimitiveString")];
+
+ Byte header[12] = {0x20, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0x43, 0x00, 0x00, 0x00, 0x00};
+ *(UInt32 *)&header[8] = CFSwapInt32HostToBig((unsigned int)jsonData.length);
+
+ NSMutableData *data = [NSMutableData dataWithCapacity:12 + jsonData.length];
+ [data appendBytes:&header length:12];
+ [data appendData:jsonData];
+ [data appendData:binaryData];
+
+ return data;
+};
+
+beforeSuite(^{
+ testPayload = [[SDLSecurityQueryPayload alloc] init];
+
+ testPayload.queryType = 0x20;
+ testPayload.queryID = 0x02;
+ testPayload.sequenceNumber = 0x1443;
+ testPayload.jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:0];
+ testPayload.binaryData = [NSData dataWithBytes:"PrimitiveString" length:strlen("PrimitiveString")];
+});
+
+describe(@"Getter/Setter Tests", ^ {
+ it(@"should set and get correctly", ^ {
+ expect(@(testPayload.queryType)).to(equal(SDLSecurityQueryTypeNotification));
+ expect(@(testPayload.queryID)).to(equal(SDLSecurityQueryIdSendInternalError));
+ expect(@(testPayload.sequenceNumber)).to(equal(@0x1443));
+ expect([NSJSONSerialization JSONObjectWithData:testPayload.jsonData options:0 error:0]).to(equal(dict));
+ expect([NSString stringWithUTF8String:[testPayload binaryData].bytes]).to(equal(@"PrimitiveString"));
+ });
+});
+
+describe(@"Data Tests", ^ {
+ it(@"should convert to byte data correctly", ^ {
+ expect(testPayload.convertToData).to(equal(testData()));
+ });
+});
+
+describe(@"RPCPayloadWithData Test", ^ {
+ it(@"should convert from byte data correctly", ^ {
+ SDLSecurityQueryPayload* constructedPayload = [SDLSecurityQueryPayload securityPayloadWithData:testData()];
+
+ expect(@(constructedPayload.queryType)).to(equal(SDLSecurityQueryTypeNotification));
+ expect(@(constructedPayload.queryID)).to(equal(SDLSecurityQueryIdSendInternalError));
+ expect(@(constructedPayload.sequenceNumber)).to(equal(@0x1443));
+ NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:constructedPayload.jsonData options:0 error:0];
+ expect(jsonDict).to(equal(dict));
+ expect(jsonDict[@"text"]).to(equal(SDLSecurityQueryErrorCodeNotSupported));
+ expect([NSString stringWithUTF8String:[constructedPayload binaryData].bytes]).to(equal(@"PrimitiveString"));
+ });
+});
+
+QuickSpecEnd