diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index d13a74534..fc67b26e8 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -787,6 +787,26 @@ 5DEE55C51B864AB3004F0D0F /* OHHTTPStubs.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5DEE55C41B864AB3004F0D0F /* OHHTTPStubs.framework.dSYM */; }; 5DEE55C61B8666A8004F0D0F /* OHHTTPStubs.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5DEE55C11B864A7D004F0D0F /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5DF2BB9D1B94E38A00CE5994 /* SDLURLSessionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF2BB9C1B94E38A00CE5994 /* SDLURLSessionSpec.m */; }; + DA4353DF1D271FD10099B8C4 /* CGPointUtilSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4353DE1D271FD10099B8C4 /* CGPointUtilSpec.m */; }; + DA4353E31D2720A30099B8C4 /* SDLPinchGestureSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4353E21D2720A30099B8C4 /* SDLPinchGestureSpec.m */; }; + DA4353E91D2721680099B8C4 /* DispatchTimerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4353E61D2721680099B8C4 /* DispatchTimerSpec.m */; }; + DA4353EA1D2721680099B8C4 /* SDLTouchManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4353E71D2721680099B8C4 /* SDLTouchManagerSpec.m */; }; + DA4353EB1D2721680099B8C4 /* SDLTouchSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4353E81D2721680099B8C4 /* SDLTouchSpec.m */; }; + DAC572571D1067270004288B /* SDLTouchManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC572551D1067270004288B /* SDLTouchManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DAC572581D1067270004288B /* SDLTouchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC572561D1067270004288B /* SDLTouchManager.m */; }; + DAC5725B1D10B81E0004288B /* SDLTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC572591D10B81E0004288B /* SDLTouch.m */; }; + DAC5725C1D10B81E0004288B /* SDLTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC5725A1D10B81E0004288B /* SDLTouch.h */; }; + DAC572621D10C5020004288B /* SDLPinchGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC572601D10C5020004288B /* SDLPinchGesture.m */; }; + DAC572631D10C5020004288B /* SDLPinchGesture.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC572611D10C5020004288B /* SDLPinchGesture.h */; }; + DAC572661D10C5640004288B /* CGPoint_Util.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC572641D10C5640004288B /* CGPoint_Util.m */; }; + DAC572671D10C5640004288B /* CGPoint_Util.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC572651D10C5640004288B /* CGPoint_Util.h */; }; + DAC5726A1D10D5FC0004288B /* dispatch_timer.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC572681D10D5FC0004288B /* dispatch_timer.m */; }; + DAC5726B1D10D5FC0004288B /* dispatch_timer.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC572691D10D5FC0004288B /* dispatch_timer.h */; }; + DAC5726C1D11B4840004288B /* SDLTouchManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC5725F1D10BD690004288B /* SDLTouchManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DACBA1DC1D22D46D002356F8 /* SDLOnTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D61FB331A84238B00846EE7 /* SDLOnTouchEvent.m */; }; + DACBA1DE1D22D642002356F8 /* SDLTouchType.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D61FBEB1A84238C00846EE7 /* SDLTouchType.m */; }; + DACBA1DF1D22D6CE002356F8 /* SDLTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D61FBE71A84238C00846EE7 /* SDLTouchEvent.m */; }; + DACBA1E01D22E856002356F8 /* SDLTouchCoord.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D61FBE51A84238C00846EE7 /* SDLTouchCoord.m */; }; E9C32B861AB20B4300F283AF /* NSThread+ThreadIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = E9C32B841AB20B4300F283AF /* NSThread+ThreadIndex.h */; }; E9C32B871AB20B4300F283AF /* NSThread+ThreadIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C32B851AB20B4300F283AF /* NSThread+ThreadIndex.m */; }; E9C32B911AB20BA200F283AF /* SDLIAPSession.h in Headers */ = {isa = PBXBuildFile; fileRef = E9C32B891AB20BA200F283AF /* SDLIAPSession.h */; }; @@ -1662,6 +1682,22 @@ 5DEE55C11B864A7D004F0D0F /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; 5DEE55C41B864AB3004F0D0F /* OHHTTPStubs.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = OHHTTPStubs.framework.dSYM; path = Carthage/Build/iOS/OHHTTPStubs.framework.dSYM; sourceTree = ""; }; 5DF2BB9C1B94E38A00CE5994 /* SDLURLSessionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLURLSessionSpec.m; path = "UtilitiesSpecs/HTTP Connection/SDLURLSessionSpec.m"; sourceTree = ""; }; + DA4353DE1D271FD10099B8C4 /* CGPointUtilSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CGPointUtilSpec.m; path = UtilitiesSpecs/Touches/CGPointUtilSpec.m; sourceTree = ""; }; + DA4353E21D2720A30099B8C4 /* SDLPinchGestureSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLPinchGestureSpec.m; path = UtilitiesSpecs/Touches/SDLPinchGestureSpec.m; sourceTree = ""; }; + DA4353E61D2721680099B8C4 /* DispatchTimerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DispatchTimerSpec.m; path = UtilitiesSpecs/Touches/DispatchTimerSpec.m; sourceTree = ""; }; + DA4353E71D2721680099B8C4 /* SDLTouchManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLTouchManagerSpec.m; path = UtilitiesSpecs/Touches/SDLTouchManagerSpec.m; sourceTree = ""; }; + DA4353E81D2721680099B8C4 /* SDLTouchSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLTouchSpec.m; path = UtilitiesSpecs/Touches/SDLTouchSpec.m; sourceTree = ""; }; + DAC572551D1067270004288B /* SDLTouchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLTouchManager.h; sourceTree = ""; }; + DAC572561D1067270004288B /* SDLTouchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLTouchManager.m; sourceTree = ""; }; + DAC572591D10B81E0004288B /* SDLTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLTouch.m; sourceTree = ""; }; + DAC5725A1D10B81E0004288B /* SDLTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLTouch.h; sourceTree = ""; }; + DAC5725F1D10BD690004288B /* SDLTouchManagerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLTouchManagerDelegate.h; sourceTree = ""; }; + DAC572601D10C5020004288B /* SDLPinchGesture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLPinchGesture.m; sourceTree = ""; }; + DAC572611D10C5020004288B /* SDLPinchGesture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLPinchGesture.h; sourceTree = ""; }; + DAC572641D10C5640004288B /* CGPoint_Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGPoint_Util.m; sourceTree = ""; }; + DAC572651D10C5640004288B /* CGPoint_Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGPoint_Util.h; sourceTree = ""; }; + DAC572681D10D5FC0004288B /* dispatch_timer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dispatch_timer.m; sourceTree = ""; }; + DAC572691D10D5FC0004288B /* dispatch_timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dispatch_timer.h; sourceTree = ""; }; E9C32B841AB20B4300F283AF /* NSThread+ThreadIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSThread+ThreadIndex.h"; sourceTree = ""; }; E9C32B851AB20B4300F283AF /* NSThread+ThreadIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSThread+ThreadIndex.m"; sourceTree = ""; }; E9C32B891AB20BA200F283AF /* SDLIAPSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLIAPSession.h; sourceTree = ""; }; @@ -2669,6 +2705,7 @@ 5D5934F61A85189500687FB9 /* Utilities */ = { isa = PBXGroup; children = ( + DAC5724C1D0FE3B60004288B /* Touches */, 5DCC199D1B8221D2004FFAD9 /* HTTP Connection */, E9C32B831AB20B2900F283AF /* @categories */, 5D5934F71A8519A700687FB9 /* JSON */, @@ -2919,6 +2956,7 @@ 5DB92D201AC47AC400C15BB0 /* UtilitiesSpecs */ = { isa = PBXGroup; children = ( + DA1166D71D14601C00438CEA /* Touches */, 5DEE55BE1B8509A5004F0D0F /* HTTP Connection */, 5DB92D2B1AC4A32A00C15BB0 /* Prioritized Objects */, 5DB92D231AC47B2C00C15BB0 /* SDLHexUtilitySpec.m */, @@ -2965,6 +3003,36 @@ name = "HTTP Connection"; sourceTree = ""; }; + DA1166D71D14601C00438CEA /* Touches */ = { + isa = PBXGroup; + children = ( + DA4353E61D2721680099B8C4 /* DispatchTimerSpec.m */, + DA4353E71D2721680099B8C4 /* SDLTouchManagerSpec.m */, + DA4353E81D2721680099B8C4 /* SDLTouchSpec.m */, + DA4353DE1D271FD10099B8C4 /* CGPointUtilSpec.m */, + DA4353E21D2720A30099B8C4 /* SDLPinchGestureSpec.m */, + ); + name = Touches; + sourceTree = ""; + }; + DAC5724C1D0FE3B60004288B /* Touches */ = { + isa = PBXGroup; + children = ( + DAC572551D1067270004288B /* SDLTouchManager.h */, + DAC572561D1067270004288B /* SDLTouchManager.m */, + DAC5725F1D10BD690004288B /* SDLTouchManagerDelegate.h */, + DAC5725A1D10B81E0004288B /* SDLTouch.h */, + DAC572591D10B81E0004288B /* SDLTouch.m */, + DAC572611D10C5020004288B /* SDLPinchGesture.h */, + DAC572601D10C5020004288B /* SDLPinchGesture.m */, + DAC572651D10C5640004288B /* CGPoint_Util.h */, + DAC572641D10C5640004288B /* CGPoint_Util.m */, + DAC572691D10D5FC0004288B /* dispatch_timer.h */, + DAC572681D10D5FC0004288B /* dispatch_timer.m */, + ); + name = Touches; + sourceTree = ""; + }; E9C32B831AB20B2900F283AF /* @categories */ = { isa = PBXGroup; children = ( @@ -2997,6 +3065,7 @@ 5DB92D321AC9C8BA00C15BB0 /* SDLRPCStruct.h in Headers */, 5D61FD151A84238C00846EE7 /* SDLOnLockScreenStatus.h in Headers */, 5D61FD291A84238C00846EE7 /* SDLPerformInteraction.h in Headers */, + DAC572571D1067270004288B /* SDLTouchManager.h in Headers */, 5D61FD521A84238C00846EE7 /* SDLProxyFactory.h in Headers */, 5D61FCDA1A84238C00846EE7 /* SDLJingle.h in Headers */, 5D61FE0D1A84238C00846EE7 /* SDLVRCapabilities.h in Headers */, @@ -3047,6 +3116,7 @@ 5D61FD091A84238C00846EE7 /* SDLOnDriverDistraction.h in Headers */, E9C32B9E1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h in Headers */, 5D61FC4B1A84238C00846EE7 /* SDLBeltStatus.h in Headers */, + DAC5726B1D10D5FC0004288B /* dispatch_timer.h in Headers */, 5D61FC351A84238C00846EE7 /* SDLAirbagStatus.h in Headers */, 5D61FC8A1A84238C00846EE7 /* SDLDiagnosticMessageResponse.h in Headers */, 5D61FC2D1A84238C00846EE7 /* SDLAddCommand.h in Headers */, @@ -3120,6 +3190,7 @@ 5D61FC981A84238C00846EE7 /* SDLECallInfo.h in Headers */, 5D61FD7B1A84238C00846EE7 /* SDLScrollableMessage.h in Headers */, 5D61FD3D1A84238C00846EE7 /* SDLPrimaryAudioSource.h in Headers */, + DAC5725C1D10B81E0004288B /* SDLTouch.h in Headers */, 5D61FCCD1A84238C00846EE7 /* SDLImage.h in Headers */, 5D61FD481A84238C00846EE7 /* SDLProtocolMessage.h in Headers */, 5D61FC8C1A84238C00846EE7 /* SDLDIDResult.h in Headers */, @@ -3169,6 +3240,7 @@ 5D61FC611A84238C00846EE7 /* SDLChoice.h in Headers */, 5D61FC7C1A84238C00846EE7 /* SDLDeleteInteractionChoiceSetResponse.h in Headers */, 5D61FDB91A84238C00846EE7 /* SDLSyncPDataResponse.h in Headers */, + DAC572631D10C5020004288B /* SDLPinchGesture.h in Headers */, 5D61FC311A84238C00846EE7 /* SDLAddSubMenu.h in Headers */, 5D61FD171A84238C00846EE7 /* SDLOnPermissionsChange.h in Headers */, 5D61FDD51A84238C00846EE7 /* SDLTouchEventCapabilities.h in Headers */, @@ -3236,6 +3308,7 @@ 5D8B174F1AC9D266006A6E1C /* SDLDialNumber.h in Headers */, 5D61FCAD1A84238C00846EE7 /* SDLFunctionID.h in Headers */, 5D61FDF51A84238C00846EE7 /* SDLV1ProtocolHeader.h in Headers */, + DAC572671D10C5640004288B /* CGPoint_Util.h in Headers */, 5D61FDF91A84238C00846EE7 /* SDLV2ProtocolHeader.h in Headers */, 5D61FD4A1A84238C00846EE7 /* SDLProtocolMessageAssembler.h in Headers */, 5D61FD4C1A84238C00846EE7 /* SDLProtocolMessageDisassembler.h in Headers */, @@ -3244,6 +3317,7 @@ 5D61FDFB1A84238C00846EE7 /* SDLV2ProtocolMessage.h in Headers */, 5D61FCFC1A84238C00846EE7 /* SDLNames.h in Headers */, 5D61FCFD1A84238C00846EE7 /* SDLObjectWithPriority.h in Headers */, + DAC5726C1D11B4840004288B /* SDLTouchManagerDelegate.h in Headers */, 5D61FD3F1A84238C00846EE7 /* SDLPrioritizedObjectCollection.h in Headers */, 5D61FCBF1A84238C00846EE7 /* SDLHexUtility.h in Headers */, 5D61FCA21A84238C00846EE7 /* SDLEncoder.h in Headers */, @@ -3493,6 +3567,7 @@ 5D61FDB21A84238C00846EE7 /* SDLSubscribeVehicleData.m in Sources */, 5D61FC991A84238C00846EE7 /* SDLECallInfo.m in Sources */, 5D61FD601A84238C00846EE7 /* SDLRegisterAppInterfaceResponse.m in Sources */, + DAC572621D10C5020004288B /* SDLPinchGesture.m in Sources */, 5D61FCF51A84238C00846EE7 /* SDLMaintenanceModeStatus.m in Sources */, 5D61FCD81A84238C00846EE7 /* SDLInteractionMode.m in Sources */, 5D61FCB01A84238C00846EE7 /* SDLGenericResponse.m in Sources */, @@ -3512,6 +3587,7 @@ 5D61FDA41A84238C00846EE7 /* SDLSoftButtonType.m in Sources */, 5D61FC521A84238C00846EE7 /* SDLButtonCapabilities.m in Sources */, 5D61FC791A84238C00846EE7 /* SDLDeleteFileResponse.m in Sources */, + DAC572581D1067270004288B /* SDLTouchManager.m in Sources */, 5D61FC641A84238C00846EE7 /* SDLClusterModeStatus.m in Sources */, 5D61FCF91A84238C00846EE7 /* SDLMenuParams.m in Sources */, 5D61FD7C1A84238C00846EE7 /* SDLScrollableMessage.m in Sources */, @@ -3556,6 +3632,7 @@ 5D61FC3A1A84238C00846EE7 /* SDLAlertManeuver.m in Sources */, 5D61FC5E1A84238C00846EE7 /* SDLChangeRegistrationResponse.m in Sources */, 5D61FC7D1A84238C00846EE7 /* SDLDeleteInteractionChoiceSetResponse.m in Sources */, + DAC572661D10C5640004288B /* CGPoint_Util.m in Sources */, E9C32B9D1AB20C5900F283AF /* EAAccessory+SDLProtocols.m in Sources */, 5D61FCC61A84238C00846EE7 /* SDLHMIZoneCapabilities.m in Sources */, 5D61FD161A84238C00846EE7 /* SDLOnLockScreenStatus.m in Sources */, @@ -3616,6 +3693,7 @@ 5D61FD5C1A84238C00846EE7 /* SDLReadDIDResponse.m in Sources */, 5D61FD321A84238C00846EE7 /* SDLPolicyDataParser.m in Sources */, 5D61FC621A84238C00846EE7 /* SDLChoice.m in Sources */, + DAC5725B1D10B81E0004288B /* SDLTouch.m in Sources */, 5D61FCEB1A84238C00846EE7 /* SDLLayoutMode.m in Sources */, 5D61FC2E1A84238C00846EE7 /* SDLAddCommand.m in Sources */, 5D61FE021A84238C00846EE7 /* SDLVehicleDataNotificationStatus.m in Sources */, @@ -3651,6 +3729,7 @@ 5D61FE0E1A84238C00846EE7 /* SDLVRCapabilities.m in Sources */, 5D61FDC21A84238C00846EE7 /* SDLSystemRequestResponse.m in Sources */, 5D61FD001A84238C00846EE7 /* SDLOnAppInterfaceUnregistered.m in Sources */, + DAC5726A1D10D5FC0004288B /* dispatch_timer.m in Sources */, 5D61FC6C1A84238C00846EE7 /* SDLCreateInteractionChoiceSet.m in Sources */, 5D61FD081A84238C00846EE7 /* SDLOnCommand.m in Sources */, 5D53C46E1B7A99B9003526EA /* SDLStreamingMediaManager.m in Sources */, @@ -3729,6 +3808,7 @@ 162E83121A9BDE8B00906325 /* SDLOnButtonPressSpec.m in Sources */, 162E838D1A9BDE8B00906325 /* SDLStartTimeSpec.m in Sources */, 162E836E1A9BDE8B00906325 /* SDLUnsubscribeButtonResponseSpec.m in Sources */, + DACBA1DC1D22D46D002356F8 /* SDLOnTouchEvent.m in Sources */, 162E835B1A9BDE8B00906325 /* SDLPerformInteractionResponseSpec.m in Sources */, 162E832D1A9BDE8B00906325 /* SDLEncodedSyncPDataSpec.m in Sources */, 5DB92D241AC47B2C00C15BB0 /* SDLHexUtilitySpec.m in Sources */, @@ -3754,6 +3834,7 @@ 162E83811A9BDE8B00906325 /* SDLImageFieldSpec.m in Sources */, 162E834F1A9BDE8B00906325 /* SDLDeleteCommandResponseSpec.m in Sources */, 162E83231A9BDE8B00906325 /* SDLAddSubMenuSpec.m in Sources */, + DA4353E91D2721680099B8C4 /* DispatchTimerSpec.m in Sources */, 162E82F21A9BDE8B00906325 /* SDLPredefinedLayoutSpec.m in Sources */, 162E83521A9BDE8B00906325 /* SDLDeleteSubMenuResponseSpec.m in Sources */, 162E82E91A9BDE8B00906325 /* SDLKeypressModeSpec.m in Sources */, @@ -3775,6 +3856,7 @@ 162E83831A9BDE8B00906325 /* SDLKeyboardPropertiesSpec.m in Sources */, 162E82D11A9BDE8A00906325 /* SDLButtonNameSpec.m in Sources */, 162E839E1A9BDE8B00906325 /* SDLRPCStructSpec.m in Sources */, + DA4353DF1D271FD10099B8C4 /* CGPointUtilSpec.m in Sources */, 162E83291A9BDE8B00906325 /* SDLDeleteFileSpec.m in Sources */, 1680B11D1A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m in Sources */, 162E838E1A9BDE8B00906325 /* SDLSyncMsgVersionSpec.m in Sources */, @@ -3808,6 +3890,7 @@ 5DF2BB9D1B94E38A00CE5994 /* SDLURLSessionSpec.m in Sources */, 162E83381A9BDE8B00906325 /* SDLScrollableMessageSpec.m in Sources */, 162E82E81A9BDE8B00906325 /* SDLKeyboardLayoutSpec.m in Sources */, + DA4353EB1D2721680099B8C4 /* SDLTouchSpec.m in Sources */, 162E83541A9BDE8B00906325 /* SDLEncodedSyncPDataResponseSpec.m in Sources */, 162E83161A9BDE8B00906325 /* SDLOnHashChangeSpec.m in Sources */, 162E82FE1A9BDE8B00906325 /* SDLTBTStateSpec.m in Sources */, @@ -3820,6 +3903,7 @@ 162E82CD1A9BDE8A00906325 /* SDLAudioStreamingStateSpec.m in Sources */, 162E831A1A9BDE8B00906325 /* SDLOnLockScreenStatusSpec.m in Sources */, 162E83431A9BDE8B00906325 /* SDLSyncPDataSpec.m in Sources */, + DACBA1E01D22E856002356F8 /* SDLTouchCoord.m in Sources */, 167ED9461A9BCE5D00797BE5 /* SwiftSpec.swift in Sources */, 162E838B1A9BDE8B00906325 /* SDLSoftButtonCapabilitiesSpec.m in Sources */, 162E834C1A9BDE8B00906325 /* SDLAlertResponseSpec.m in Sources */, @@ -3832,6 +3916,7 @@ 162E83741A9BDE8B00906325 /* SDLBodyInformationSpec.m in Sources */, 162E83641A9BDE8B00906325 /* SDLSetMediaClockTimerResponseSpec.m in Sources */, 162E839C1A9BDE8B00906325 /* SDLRPCRequestSpec.m in Sources */, + DA4353E31D2720A30099B8C4 /* SDLPinchGestureSpec.m in Sources */, 5D8B17561AC9E399006A6E1C /* SDLDialNumberSpec.m in Sources */, 162E833D1A9BDE8B00906325 /* SDLShowConstantTBTSpec.m in Sources */, 162E83651A9BDE8B00906325 /* SDLShowConstantTBTResponseSpec.m in Sources */, @@ -3881,6 +3966,7 @@ 162E83891A9BDE8B00906325 /* SDLScreenParamsSpec.m in Sources */, 162E83441A9BDE8B00906325 /* SDLSystemRequestSpec.m in Sources */, 162E83001A9BDE8B00906325 /* SDLTextFieldNameSpec.m in Sources */, + DA4353EA1D2721680099B8C4 /* SDLTouchManagerSpec.m in Sources */, 162E82FC1A9BDE8B00906325 /* SDLSystemAction.m in Sources */, 162E82CC1A9BDE8A00906325 /* SDLAppInterfaceUnregisteredReasonSpec.m in Sources */, 162E83321A9BDE8B00906325 /* SDLPerformAudioPassThruSpec.m in Sources */, @@ -3900,6 +3986,7 @@ 162E83561A9BDE8B00906325 /* SDLGenericResponseSpec.m in Sources */, 162E82D51A9BDE8A00906325 /* SDLCompassDirectionSpec.m in Sources */, 162E83861A9BDE8B00906325 /* SDLParameterPermissionsSpec.m in Sources */, + DACBA1DF1D22D6CE002356F8 /* SDLTouchEvent.m in Sources */, 162E831B1A9BDE8B00906325 /* SDLOnPermissionsChangeSpec.m in Sources */, 162E83711A9BDE8B00906325 /* SDLAirbagStatusSpec.m in Sources */, 162E82CF1A9BDE8A00906325 /* SDLBitsPerSampleSpec.m in Sources */, @@ -3915,6 +4002,7 @@ 162E82F41A9BDE8B00906325 /* SDLPrimaryAudioSource.m in Sources */, 162E83461A9BDE8B00906325 /* SDLUnsubscribeButtonSpec.m in Sources */, 162E82EB1A9BDE8B00906325 /* SDLLayoutModeSpec.m in Sources */, + DACBA1DE1D22D642002356F8 /* SDLTouchType.m in Sources */, 1680B1181A9CD7AD00DBD79E /* SDLV1ProtocolMessageSpec.m in Sources */, 162E82EC1A9BDE8B00906325 /* SDLLockScreenStatusSpec.m in Sources */, 162E832F1A9BDE8B00906325 /* SDLGetDTCsSpec.m in Sources */, diff --git a/SmartDeviceLink/CGPoint_Util.h b/SmartDeviceLink/CGPoint_Util.h new file mode 100644 index 000000000..9ea06e1b6 --- /dev/null +++ b/SmartDeviceLink/CGPoint_Util.h @@ -0,0 +1,39 @@ +// +// CGPoint_Util.h +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#ifndef CGPoint_Util_h +#define CGPoint_Util_h + +#include +#include + +/** + * @abstract + * Calculate the center of two points. + * @param point1 + * First point. + * @param point2 + * Second point. + * @return CGPoint + * Center of the points. + */ +CGPoint CGPointCenterOfPoints(CGPoint point1, CGPoint point2); + +/** + * @abstract + * Calculate the distance between two points. + * @param point1 + * First point. + * @param point2 + * Second point. + * @return CGFloat + * Distance between the points. + */ +CGFloat CGPointDistanceBetweenPoints(CGPoint point1, CGPoint point2); + +#endif /* CGPoint_Util_h */ diff --git a/SmartDeviceLink/CGPoint_Util.m b/SmartDeviceLink/CGPoint_Util.m new file mode 100644 index 000000000..67be5c9fc --- /dev/null +++ b/SmartDeviceLink/CGPoint_Util.m @@ -0,0 +1,20 @@ +// +// CGPoint_Util.c +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#include "CGPoint_Util.h" +#include "math.h" + +CGPoint CGPointCenterOfPoints(CGPoint point1, CGPoint point2) { + CGFloat xCenter = (point1.x + point2.x) / 2.0f; + CGFloat yCenter = (point1.y + point2.y) / 2.0f; + return CGPointMake(xCenter, yCenter); +} + +CGFloat CGPointDistanceBetweenPoints(CGPoint point1, CGPoint point2) { + return hypotf(point1.x - point2.x, point1.y - point2.y); +} \ No newline at end of file diff --git a/SmartDeviceLink/SDLPinchGesture.h b/SmartDeviceLink/SDLPinchGesture.h new file mode 100644 index 000000000..33617594e --- /dev/null +++ b/SmartDeviceLink/SDLPinchGesture.h @@ -0,0 +1,62 @@ +// +// SDLPinchGesture.h +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import "SDLTouch.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLPinchGesture : NSObject + +/** + * @abstract + * Initializes a pinch gesture. + * @param firstTouch + * First touch of the gesture + * @param secondTouch + * Second touch of the gesture + * @return SDLPinchGesture + * Instance of SDLPinchGesture. + */ +- (instancetype)initWithFirstTouch:(SDLTouch*)firstTouch secondTouch:(SDLTouch*)secondTouch; + +/** + * @abstract + * First touch of a pinch gesture. + */ +@property (nonatomic, strong) SDLTouch* firstTouch; + +/** + * @abstract + * Second touch of a pinch gesture. + */ +@property (nonatomic, strong) SDLTouch* secondTouch; + +/** + * @abstract + * Distance between first and second touches. + */ +@property (nonatomic, assign, readonly) CGFloat distance; + +/** + * @abstract + * Center point between first and second touches. + */ +@property (nonatomic, assign, readonly) CGPoint center; + +/** + * @abstract + * Returns whether or not the pinch gesture is valid. This is true if both touches + * are non null. + */ +@property (nonatomic, assign, readonly) BOOL isValid; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLPinchGesture.m b/SmartDeviceLink/SDLPinchGesture.m new file mode 100644 index 000000000..3e522f8dd --- /dev/null +++ b/SmartDeviceLink/SDLPinchGesture.m @@ -0,0 +1,79 @@ +// +// SDLPinchGesture.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import "SDLPinchGesture.h" + +#import "SDLTouch.h" +#import "CGPoint_Util.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLPinchGesture + +@synthesize distance = _distance; +@synthesize center = _center; + +- (instancetype)initWithFirstTouch:(SDLTouch*)firstTouch secondTouch:(SDLTouch*)secondTouch { + self = [super init]; + if (!self) { + return nil; + } + + _firstTouch = firstTouch; + _secondTouch = secondTouch; + _distance = -1; + _center = CGPointZero; + + return self; +} + +#pragma mark - Setters +- (void)setFirstTouch:(SDLTouch *)firstTouch { + if (firstTouch.identifier == SDLTouchIdentifierFirstFinger) { + _firstTouch = firstTouch; + [self sdl_invalidate]; + } +} + +- (void)setSecondTouch:(SDLTouch *)secondTouch { + if (secondTouch.identifier == SDLTouchIdentifierSecondFinger) { + _secondTouch = secondTouch; + [self sdl_invalidate]; + } +} + +#pragma mark - Getters +- (CGFloat)distance { + if (_distance == -1) { + _distance = CGPointDistanceBetweenPoints(self.firstTouch.location, + self.secondTouch.location); + } + return _distance; +} + +- (CGPoint)center { + if (CGPointEqualToPoint(_center, CGPointZero)) { + _center = CGPointCenterOfPoints(self.firstTouch.location, + self.secondTouch.location); + } + return _center; +} + +- (BOOL)isValid { + return (self.firstTouch != nil) && (self.secondTouch != nil); +} + +#pragma mark - Private +- (void)sdl_invalidate { + _distance = -1; + _center = CGPointZero; +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLProxy.m b/SmartDeviceLink/SDLProxy.m index 1f98bec6a..c4189452d 100644 --- a/SmartDeviceLink/SDLProxy.m +++ b/SmartDeviceLink/SDLProxy.m @@ -29,7 +29,7 @@ #import "SDLProtocolMessage.h" #import "SDLProtocolMessage.h" #import "SDLPutFile.h" -#import "SDLRequestType.h" +#import "SDLRPCPayload.h" #import "SDLRPCPayload.h" #import "SDLRPCRequestFactory.h" #import "SDLRPCResponse.h" @@ -64,7 +64,8 @@ @interface SDLProxy () { @property (copy, nonatomic) NSString *appId; @property (strong, nonatomic) NSMutableSet *mutableProxyListeners; -@property (nonatomic, strong, readwrite) SDLStreamingMediaManager *streamingMediaManager; +@property (nonatomic, strong, readwrite, nullable) SDLStreamingMediaManager *streamingMediaManager; +@property (nonatomic, strong, nullable) SDLDisplayCapabilities* displayCapabilities; @property (nonatomic, strong) NSMutableDictionary *securityManagers; @end @@ -111,6 +112,8 @@ - (void)destructObjects { _transport = nil; _protocol = nil; _mutableProxyListeners = nil; + _streamingMediaManager = nil; + _displayCapabilities = nil; } } @@ -177,8 +180,12 @@ - (NSString *)proxyVersion { - (SDLStreamingMediaManager *)streamingMediaManager { if (_streamingMediaManager == nil) { - _streamingMediaManager = [[SDLStreamingMediaManager alloc] initWithProtocol:self.protocol]; + if (self.displayCapabilities == nil) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"SDLStreamingMediaManager must be accessed only after a successful RegisterAppInterfaceResponse" userInfo:nil]; + } + _streamingMediaManager = [[SDLStreamingMediaManager alloc] initWithProtocol:self.protocol displayCapabilities:self.displayCapabilities]; [self.protocol.protocolDelegateTable addObject:_streamingMediaManager]; + [self.mutableProxyListeners addObject:_streamingMediaManager.touchManager]; } return _streamingMediaManager; @@ -373,6 +380,7 @@ - (void)handleRegisterAppInterfaceResponse:(SDLRPCResponse *)response { NSString *logMessage = [NSString stringWithFormat:@"Framework Version: %@", self.proxyVersion]; [SDLDebugTool logInfo:logMessage withType:SDLDebugType_RPC toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName]; SDLRegisterAppInterfaceResponse *registerResponse = (SDLRegisterAppInterfaceResponse *)response; + self.displayCapabilities = registerResponse.displayCapabilities; self.protocol.securityManager = [self securityManagerForMake:registerResponse.vehicleType.make]; if ([SDLGlobals globals].protocolVersion >= 4) { diff --git a/SmartDeviceLink/SDLStreamingMediaManager.h b/SmartDeviceLink/SDLStreamingMediaManager.h index 9978913e9..59e1917db 100644 --- a/SmartDeviceLink/SDLStreamingMediaManager.h +++ b/SmartDeviceLink/SDLStreamingMediaManager.h @@ -12,6 +12,8 @@ #import "SDLProtocolListener.h" @class SDLAbstractProtocol; +@class SDLDisplayCapabilities; +@class SDLTouchManager; NS_ASSUME_NONNULL_BEGIN @@ -52,8 +54,32 @@ typedef void (^SDLStreamingEncryptionStartBlock)(BOOL success, BOOL encryption, @property (assign, nonatomic, readonly) BOOL videoSessionAuthenticated; @property (assign, nonatomic, readonly) BOOL audioSessionAuthenticated; +/** + * Touch Manager responsible for providing touch event notifications. + */ +@property (nonatomic, strong, readonly) SDLTouchManager* touchManager; + +/** + * The settings used in a VTCompressionSessionRef encoder. These will be verified when the video stream is started. Acceptable properties for this are located in VTCompressionProperties. If set to nil, the defaultVideoEncoderSettings will be used. + * + * @warning Video streaming must not be connected to update the encoder properties. If it is running, issue a stopVideoSession before updating. + */ +@property (strong, nonatomic, null_resettable) NSDictionary* videoEncoderSettings; + +/** + * Provides default video encoder settings used. + */ +@property (strong, nonatomic, readonly) NSDictionary* defaultVideoEncoderSettings; + +/** + * This is the current screen size of a connected display. This will be the size the video encoder uses to encode the raw image data. + */ +@property (assign, nonatomic, readonly) CGSize screenSize; + + +- (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol __deprecated_msg(("Please use initWithProtocol:displayCapabilities: instead")); -- (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol; +- (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol displayCapabilities:(SDLDisplayCapabilities*)displayCapabilities; /** * This method will attempt to start a streaming video session. It will set up iOS's video encoder, and call out to the head unit asking if it will start a video session. diff --git a/SmartDeviceLink/SDLStreamingMediaManager.m b/SmartDeviceLink/SDLStreamingMediaManager.m index 99de10e07..ac5197246 100644 --- a/SmartDeviceLink/SDLStreamingMediaManager.m +++ b/SmartDeviceLink/SDLStreamingMediaManager.m @@ -11,7 +11,11 @@ @import UIKit; #import "SDLAbstractProtocol.h" +#import "SDLDisplayCapabilities.h" #import "SDLGlobals.h" +#import "SDLImageResolution.h" +#import "SDLScreenParams.h" +#import "SDLTouchManager.h" NSString *const SDLErrorDomainStreamingMediaVideo = @"com.sdl.streamingmediamanager.video"; @@ -39,6 +43,8 @@ @interface SDLStreamingMediaManager () @property (copy, nonatomic, nullable) SDLStreamingEncryptionStartBlock videoStartBlock; @property (copy, nonatomic, nullable) SDLStreamingEncryptionStartBlock audioStartBlock; +@property (nonatomic, strong, readwrite) SDLTouchManager *touchManager; + @end @@ -46,14 +52,47 @@ @implementation SDLStreamingMediaManager #pragma mark - Class Lifecycle +- (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol displayCapabilities:(SDLDisplayCapabilities*)displayCapabilities { + self = [self init]; + if (!self) { + return nil; + } + + _protocol = protocol; + + SDLImageResolution* resolution = displayCapabilities.screenParams.resolution; + if (resolution != nil) { + _screenSize = CGSizeMake(resolution.resolutionWidth.floatValue, + resolution.resolutionHeight.floatValue); + } else { + NSLog(@"Could not retrieve screen size. Defaulting to 640 x 480."); + _screenSize = CGSizeMake(640, + 480); + } + + return self; + +} + - (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol { - self = [super init]; + self = [self init]; if (!self) { return nil; } - _compressionSession = NULL; + _protocol = protocol; + return self; +} + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _compressionSession = NULL; + _currentFrameNumber = 0; _videoSessionConnected = NO; _audioSessionConnected = NO; @@ -61,10 +100,24 @@ - (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol { _audioSessionAuthenticated = NO; _encryptVideoSession = NO; _encryptAudioSession = NO; - _protocol = protocol; + _protocol = nil; _videoStartBlock = nil; _audioStartBlock = nil; + + _screenSize = CGSizeMake(640, 480); + _videoEncoderSettings = self.defaultVideoEncoderSettings; + _touchManager = [[SDLTouchManager alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sdl_applicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sdl_applicationDidResignActive:) + name:UIApplicationWillResignActiveNotification + object:nil]; return self; } @@ -182,6 +235,31 @@ - (BOOL)sendAudioData:(NSData *)pcmAudioData { return YES; } +#pragma mark - Update video encoder + +- (void)setVideoEncoderSettings:(NSDictionary * _Nullable)videoEncoderSettings { + if (self.videoSessionConnected) { + @throw [NSException exceptionWithName:SDLErrorDomainStreamingMediaVideo reason:@"Cannot update video encoder settings while video session is connected." userInfo:nil]; + return; + } + + if (videoEncoderSettings) { + _videoEncoderSettings = videoEncoderSettings; + } else { + _videoEncoderSettings = self.defaultVideoEncoderSettings; + } +} + +- (NSDictionary*)defaultVideoEncoderSettings { + static NSDictionary* defaultVideoEncoderSettings = nil; + if (defaultVideoEncoderSettings == nil) { + defaultVideoEncoderSettings = @{ + (__bridge NSString*)kVTCompressionPropertyKey_ProfileLevel : (__bridge NSString*)kVTProfileLevel_H264_Baseline_AutoLevel, + (__bridge NSString*)kVTCompressionPropertyKey_RealTime : @YES + }; + } + return defaultVideoEncoderSettings; +} #pragma mark - SDLProtocolListener Methods @@ -287,8 +365,7 @@ - (BOOL)sdl_configureVideoEncoderWithError:(NSError *__autoreleasing *)error { OSStatus status; // Create a compression session - // TODO (Joel F.)[2015-08-18]: Dimensions should be from the Head Unit - status = VTCompressionSessionCreate(NULL, 640, 480, kCMVideoCodecType_H264, NULL, NULL, NULL, &sdl_videoEncoderOutputCallback, (__bridge void *)self, &_compressionSession); + status = VTCompressionSessionCreate(NULL, self.screenSize.width, self.screenSize.height, kCMVideoCodecType_H264, NULL, NULL, NULL, &sdl_videoEncoderOutputCallback, (__bridge void *)self, &_compressionSession); if (status != noErr) { // TODO: Log the error @@ -299,24 +376,41 @@ - (BOOL)sdl_configureVideoEncoderWithError:(NSError *__autoreleasing *)error { return NO; } - // Set the profile level of the video stream - status = VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); + // Validate that the video encoder properties are valid. + CFDictionaryRef supportedProperties; + status = VTSessionCopySupportedPropertyDictionary(self.compressionSession, &supportedProperties); if (status != noErr) { if (*error != nil) { *error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }]; } - + return NO; } - - // Set the session to compress in real time - status = VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); - if (status != noErr) { - if (*error != nil) { - *error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }]; + + for (NSString* key in self.videoEncoderSettings.allKeys) { + if (CFDictionaryContainsKey(supportedProperties, (__bridge CFStringRef)key) == false) { + if (*error != nil) { + NSString* description = [NSString stringWithFormat:@"\"%@\" is not a supported key.", key]; + *error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{NSLocalizedDescriptionKey : description}]; + } + CFRelease(supportedProperties); + return NO; + } + } + CFRelease(supportedProperties); + + // Populate the video encoder settings from provided dictionary. + for (NSString* key in self.videoEncoderSettings.allKeys) { + id value = self.videoEncoderSettings[key]; + + status = VTSessionSetProperty(self.compressionSession, (__bridge CFStringRef)key, (__bridge CFTypeRef)value); + if (status != noErr) { + if (*error != nil) { + *error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }]; + } + + return NO; } - - return NO; } return YES; @@ -417,6 +511,15 @@ + (dispatch_queue_t)sdl_streamingDataSerialQueue { return streamingDataQueue; } +#pragma mark - Private Functions +- (void)sdl_applicationDidEnterBackground:(NSNotification*)notification { + [self.touchManager cancelPendingTouches]; +} + +- (void)sdl_applicationDidResignActive:(NSNotification*)notification { + [self.touchManager cancelPendingTouches]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLTouch.h b/SmartDeviceLink/SDLTouch.h new file mode 100644 index 000000000..6cfb1f2db --- /dev/null +++ b/SmartDeviceLink/SDLTouch.h @@ -0,0 +1,65 @@ +// +// SDLTouch.h +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +@class SDLTouchEvent; + +typedef enum { + SDLTouchIdentifierFirstFinger = 0, + SDLTouchIdentifierSecondFinger = 1 +} SDLTouchIdentifier; + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLTouch : NSObject + +/** + * @abstract + * Initializes a touch. + * @param touchEvent + * Incoming touch event from onOnTouchEvent notification. + * @return SDLTouch + * Instance of SDLTouch. + */ +- (instancetype)initWithTouchEvent:(SDLTouchEvent*)touchEvent; + +/** + * @abstract + * Identifier of the touch's finger. Refer to SDLTouchIdentifier for valid + * identifiers. + */ +@property (nonatomic, assign, readonly) NSInteger identifier; + +/** + * @abstract + * Location of touch point, in the head unit's coordinate system. + */ +@property (nonatomic, assign, readonly) CGPoint location; + +/** + * @abstract + * Timestamp in which the touch occured. + */ +@property (nonatomic, assign, readonly) NSUInteger timeStamp; + +/** + * @abstract + * Returns whether or not this touch is a first finger. + */ +@property (nonatomic, assign, readonly) BOOL isFirstFinger; + +/** + * @abstract + * Returns whether or not this touch is a second finger. + */ +@property (nonatomic, assign, readonly) BOOL isSecondFinger; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLTouch.m b/SmartDeviceLink/SDLTouch.m new file mode 100644 index 000000000..2481ee69a --- /dev/null +++ b/SmartDeviceLink/SDLTouch.m @@ -0,0 +1,64 @@ +// +// SDLTouch.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import "SDLTouch.h" + +#import "SDLTouchEvent.h" +#import "SDLTouchCoord.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLTouch + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _identifier = -1; + _location = CGPointZero; + _timeStamp = 0; + + return self; +} + +- (instancetype)initWithTouchEvent:(SDLTouchEvent*)touchEvent { + self = [self init]; + if (self) { + _identifier = touchEvent.touchEventId.unsignedIntegerValue; + + id firstTimeStamp = touchEvent.timeStamp.firstObject; + // In the event we receive a null timestamp, we will supply a device timestamp. + if ([firstTimeStamp isKindOfClass:[NSNull class]] + && [firstTimeStamp isEqual:[NSNull null]]) { + _timeStamp = [[NSDate date] timeIntervalSince1970] * 1000.0; + } else { + NSNumber* timeStampNumber = (NSNumber*)firstTimeStamp; + _timeStamp = timeStampNumber.unsignedIntegerValue; + } + + SDLTouchCoord* coord = touchEvent.coord.firstObject; + _location = CGPointMake(coord.x.floatValue, + coord.y.floatValue); + } + return self; +} + +#pragma mark - Getters +- (BOOL)isFirstFinger { + return self.identifier == SDLTouchIdentifierFirstFinger; +} + +- (BOOL)isSecondFinger { + return self.identifier == SDLTouchIdentifierSecondFinger; +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLTouchManager.h b/SmartDeviceLink/SDLTouchManager.h new file mode 100644 index 000000000..b89917218 --- /dev/null +++ b/SmartDeviceLink/SDLTouchManager.h @@ -0,0 +1,63 @@ +// +// SDLTouchManager.h +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import +#import "SDLTouchManagerDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLTouchManager : NSObject + +@property (nonatomic, weak, nullable) id touchEventDelegate; + +/** + * @abstract + * Distance between two taps on the screen, in the head unit's coordinate system, used + * for registering double-tap callbacks. + * @remark + * Default is 50 pixels. + */ +@property (nonatomic, assign) CGFloat tapDistanceThreshold; + +/** + * @abstract + * Time (in seconds) between tap events to register a double-tap callback. + * @remark + * Default is 0.4 seconds. + */ +@property (nonatomic, assign) CGFloat tapTimeThreshold; + +/** + * @abstract + * Time (in seconds) between movement events to register panning or pinching + * callbacks. + * @remark + * Default is 0.05 seconds. + */ +@property (nonatomic, assign) CGFloat movementTimeThreshold; + +/** + * @abstract + * Boolean denoting whether or not the touch manager should deliver touch event + * callbacks. + * @remark + * Default is true. + */ +@property (nonatomic, assign, getter=isTouchEnabled) BOOL touchEnabled; + +/** + * @abstract + * Cancels pending touch event timers that may be in progress. + * @remark + * Currently only impacts the timer used to register single taps. + */ +- (void)cancelPendingTouches; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m new file mode 100644 index 000000000..fc023026f --- /dev/null +++ b/SmartDeviceLink/SDLTouchManager.m @@ -0,0 +1,284 @@ +// +// SDLTouchManager.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import "SDLTouchManager.h" + +#import "dispatch_timer.h" +#import "CGPoint_Util.h" + +#import "SDLDebugTool.h" +#import "SDLOnTouchEvent.h" +#import "SDLPinchGesture.h" +#import "SDLProxyListener.h" +#import "SDLTouch.h" +#import "SDLTouchCoord.h" +#import "SDLTouchEvent.h" +#import "SDLTouchType.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, SDLPerformingTouchType) { + SDLPerformingTouchTypeNone, + SDLPerformingTouchTypeSingleTouch, + SDLPerformingTouchTypeMultiTouch, + SDLPerformingTouchTypePanningTouch +}; + +/*! + * @abstract + * Touch Manager will ignore touches that represent more than 2 fingers on the screen. + */ +static NSUInteger const MaximumNumberOfTouches = 2; + +@interface SDLTouchManager () + +/*! + * @abstract + * First Touch received from onOnTouchEvent. + */ +@property (nonatomic, strong, nullable) SDLTouch* previousTouch; + +/*! + * @abstract + * Cached previous single tap used for double tap detection. + */ +@property (nonatomic, strong, nullable) SDLTouch* singleTapTouch; + +/*! + * @abstract + * Distance of a previously generated pinch event. Used to calculate the scale of zoom motion. + */ +@property (nonatomic, assign) CGFloat previousPinchDistance; + +/*! + * @abstract + * Current in-progress pinch gesture. + */ +@property (nonatomic, strong, nullable) SDLPinchGesture* currentPinchGesture; + +/*! + * @abstract + * Timer used for distinguishing between single & double taps. + */ +@property (nonatomic, strong, nullable) dispatch_source_t singleTapTimer; + +/*! + * @abstract + * Current touch type being performed. + */ +@property (nonatomic, assign) SDLPerformingTouchType performingTouchType; + +@end + +@implementation SDLTouchManager + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + _movementTimeThreshold = 0.05f; + _tapTimeThreshold = 0.4f; + _tapDistanceThreshold = 50.0f; + _touchEnabled = YES; + + return self; +} + +#pragma mark - Public +- (void)cancelPendingTouches { + [self sdl_cancelSingleTapTimer]; +} + +#pragma mark - SDLProxyListener Delegate +- (void)onProxyOpened { } +- (void)onProxyClosed { } +- (void)onOnHMIStatus:(SDLOnHMIStatus *)notification { } +- (void)onOnDriverDistraction:(SDLOnDriverDistraction *)notification { } + +- (void)onOnTouchEvent:(SDLOnTouchEvent *)notification { + if (!self.isTouchEnabled) { + return; + } + + SDLTouchEvent* touchEvent = notification.event.firstObject; + + SDLTouch* touch = [[SDLTouch alloc] initWithTouchEvent:touchEvent]; + + if (touch.identifier > MaximumNumberOfTouches) { + return; + } + + if ([notification.type isEqualToEnum:SDLTouchType.BEGIN]) { + [self sdl_handleTouchBegan:touch]; + } else if ([notification.type isEqualToEnum:SDLTouchType.MOVE]) { + [self sdl_handleTouchMoved:touch]; + } else if ([notification.type isEqualToEnum:SDLTouchType.END]) { + [self sdl_handleTouchEnded:touch]; + } +} + +#pragma mark - Private +- (void)sdl_handleTouchBegan:(SDLTouch*)touch { + if (!touch.isFirstFinger + && !self.isTouchEnabled) { + return; // no-op + } + + _performingTouchType = SDLPerformingTouchTypeSingleTouch; + + switch (touch.identifier) { + case SDLTouchIdentifierFirstFinger: + self.previousTouch = touch; + break; + case SDLTouchIdentifierSecondFinger: + _performingTouchType = SDLPerformingTouchTypeMultiTouch; + self.currentPinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:self.previousTouch + secondTouch:touch]; + self.previousPinchDistance = self.currentPinchGesture.distance; + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidStartAtCenterPoint:)]) { + [self.touchEventDelegate touchManager:self + pinchDidStartAtCenterPoint:self.currentPinchGesture.center]; + } + break; + } +} + +- (void)sdl_handleTouchMoved:(SDLTouch*)touch { + if ((touch.timeStamp - self.previousTouch.timeStamp) <= (self.movementTimeThreshold * NSEC_PER_USEC) + || !self.isTouchEnabled) { + return; // no-op + } + + switch (self.performingTouchType) { + case SDLPerformingTouchTypeMultiTouch: + switch (touch.identifier) { + case SDLTouchIdentifierFirstFinger: + self.currentPinchGesture.firstTouch = touch; + break; + case SDLTouchIdentifierSecondFinger: + self.currentPinchGesture.secondTouch = touch; + break; + } + + + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePinchAtCenterPoint:withScale:)]) { + CGFloat scale = self.currentPinchGesture.distance / self.previousPinchDistance; + [self.touchEventDelegate touchManager:self + didReceivePinchAtCenterPoint:self.currentPinchGesture.center + withScale:scale]; + } + + self.previousPinchDistance = self.currentPinchGesture.distance; + break; + case SDLPerformingTouchTypeSingleTouch: + _performingTouchType = SDLPerformingTouchTypePanningTouch; + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartAtPoint:)]) { + [self.touchEventDelegate touchManager:self + panningDidStartAtPoint:touch.location]; + } + break; + case SDLPerformingTouchTypePanningTouch: + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePanningFromPoint:toPoint:)]) { + [self.touchEventDelegate touchManager:self + didReceivePanningFromPoint:self.previousTouch.location toPoint:touch.location]; + } + break; + case SDLPerformingTouchTypeNone: + break; + } + + self.previousTouch = touch; +} + +- (void)sdl_handleTouchEnded:(SDLTouch*)touch { + if (!self.isTouchEnabled) { + return; // no-op + } + + switch (self.performingTouchType) { + case SDLPerformingTouchTypeMultiTouch: + switch (touch.identifier) { + case SDLTouchIdentifierFirstFinger: + self.currentPinchGesture.firstTouch = touch; + break; + case SDLTouchIdentifierSecondFinger: + self.currentPinchGesture.secondTouch = touch; + break; + } + + if (self.currentPinchGesture.isValid) { + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndAtCenterPoint:)]) { + [self.touchEventDelegate touchManager:self + pinchDidEndAtCenterPoint:self.currentPinchGesture.center]; + } + self.currentPinchGesture = nil; + } + break; + case SDLPerformingTouchTypePanningTouch: + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndAtPoint:)]) { + [self.touchEventDelegate touchManager:self + panningDidEndAtPoint:touch.location]; + } + break; + case SDLPerformingTouchTypeSingleTouch: + if (self.singleTapTimer == nil) { // Initial Tap + self.singleTapTouch = touch; + [self sdl_initializeSingleTapTimerAtPoint:self.singleTapTouch.location]; + } else { // Double Tap + [self sdl_cancelSingleTapTimer]; + + NSUInteger timeStampDelta = touch.timeStamp - self.singleTapTouch.timeStamp; + CGFloat xDelta = fabs(touch.location.x - self.singleTapTouch.location.x); + CGFloat yDelta = fabs(touch.location.y - self.singleTapTouch.location.y); + + if (timeStampDelta <= self.tapTimeThreshold * NSEC_PER_USEC + && xDelta <= self.tapDistanceThreshold + && yDelta <= self.tapDistanceThreshold) { + CGPoint centerPoint = CGPointCenterOfPoints(touch.location, + self.singleTapTouch.location); + if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapAtPoint:)]) { + [self.touchEventDelegate touchManager:self + didReceiveDoubleTapAtPoint:centerPoint]; + } + } + + self.singleTapTouch = nil; + } + break; + case SDLPerformingTouchTypeNone: + break; + } + self.previousTouch = nil; + _performingTouchType = SDLPerformingTouchTypeNone; +} + +- (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { + __weak typeof(self) weakSelf = self; + self.singleTapTimer = dispatch_create_timer(self.tapTimeThreshold, NO, ^{ + typeof(weakSelf) strongSelf = weakSelf; + strongSelf.singleTapTouch = nil; + if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapAtPoint:)]) { + [strongSelf.touchEventDelegate touchManager:strongSelf + didReceiveSingleTapAtPoint:point]; + } + }); +} + +- (void)sdl_cancelSingleTapTimer { + if (self.singleTapTimer == NULL) { + return; + } + dispatch_stop_timer(self.singleTapTimer); + self.singleTapTimer = NULL; +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SDLTouchManagerDelegate.h b/SmartDeviceLink/SDLTouchManagerDelegate.h new file mode 100644 index 000000000..f23a4ec6d --- /dev/null +++ b/SmartDeviceLink/SDLTouchManagerDelegate.h @@ -0,0 +1,104 @@ +// +// SDLTouchManagerDelegate.h +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/14/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +@class SDLTouchManager; + +NS_ASSUME_NONNULL_BEGIN + +@protocol SDLTouchManagerDelegate + +@optional + +/** + * @abstract + * Single tap was received. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Location of the single tap in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager didReceiveSingleTapAtPoint:(CGPoint)point; + +/** + * @abstract + * Double tap was received. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Location of the double tap in the head unit's coordinate system. This is the + * average of the first and second tap. + */ +- (void)touchManager:(SDLTouchManager*)manager didReceiveDoubleTapAtPoint:(CGPoint)point; + +/** + * @abstract + * Panning did start. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Location of the panning start point in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager panningDidStartAtPoint:(CGPoint)point; + +/** + * @abstract + * Panning did move. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param fromPoint + * Location of the panning's previous point in the head unit's coordinate system. + * @param toPoint + * Location of the panning's new point in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager didReceivePanningFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint; + +/** + * @abstract + * Panning did end. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Location of the panning's end point in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager panningDidEndAtPoint:(CGPoint)point; + +/** + * @abstract + * Pinch did start. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Center point of the pinch in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager pinchDidStartAtCenterPoint:(CGPoint)point; + +/** + * @abstract + * Pinch did move. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Center point of the pinch in the head unit's coordinate system. + * @param scale + * Scale relative to the distance between touch points. + */ +- (void)touchManager:(SDLTouchManager*)manager didReceivePinchAtCenterPoint:(CGPoint)point withScale:(CGFloat)scale; + +/** + * @abstract + * Pinch did end. + * @param manager + * Current initalized SDLTouchManager issuing the callback. + * @param point + * Center point of the pinch in the head unit's coordinate system. + */ +- (void)touchManager:(SDLTouchManager*)manager pinchDidEndAtCenterPoint:(CGPoint)point; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/SmartDeviceLink/SmartDeviceLink.h b/SmartDeviceLink/SmartDeviceLink.h index 6fdff29d6..294ac5fa4 100644 --- a/SmartDeviceLink/SmartDeviceLink.h +++ b/SmartDeviceLink/SmartDeviceLink.h @@ -19,6 +19,7 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[]; #import "SDLProxyFactory.h" #import "SDLSecurityType.h" #import "SDLStreamingMediaManager.h" +#import "SDLTouchManager.h" #import "SDLTTSChunkFactory.h" /***** Debug *****/ @@ -38,6 +39,7 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[]; #import "SDLAbstractProtocol.h" #import "SDLProtocol.h" #import "SDLProtocolListener.h" +#import "SDLTouchManagerDelegate.h" // Header #import "SDLProtocolHeader.h" diff --git a/SmartDeviceLink/dispatch_timer.h b/SmartDeviceLink/dispatch_timer.h new file mode 100644 index 000000000..a35068390 --- /dev/null +++ b/SmartDeviceLink/dispatch_timer.h @@ -0,0 +1,18 @@ +// +// dispatch_timer.h +// MobileNav +// +// Created by Muller, Alexander (A.) on 5/12/16. +// Copyright © 2016 Alex Muller. All rights reserved. +// + +#ifndef dispatch_timer_h +#define dispatch_timer_h + +#include +#include + +dispatch_source_t dispatch_create_timer(double afterInterval, bool repeating, dispatch_block_t block); +void dispatch_stop_timer(dispatch_source_t timer); + +#endif /* dispatch_timer_h */ diff --git a/SmartDeviceLink/dispatch_timer.m b/SmartDeviceLink/dispatch_timer.m new file mode 100644 index 000000000..7e2a5bec2 --- /dev/null +++ b/SmartDeviceLink/dispatch_timer.m @@ -0,0 +1,38 @@ +// +// dispatch_timer.c +// MobileNav +// +// Created by Muller, Alexander (A.) on 5/12/16. +// Copyright © 2016 Alex Muller. All rights reserved. +// + +#include "dispatch_timer.h" + +dispatch_source_t dispatch_create_timer(double afterInterval, bool repeating, dispatch_block_t block) { + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + queue); + dispatch_source_set_timer(timer, + dispatch_time(DISPATCH_TIME_NOW, afterInterval * NSEC_PER_SEC), + afterInterval * NSEC_PER_SEC, + (1ull * NSEC_PER_SEC) / 10); + dispatch_source_set_event_handler(timer, ^{ + if (!repeating) { + dispatch_stop_timer(timer); + } + if (block) { + block(); + } + }); + dispatch_resume(timer); + + return timer; +} + +void dispatch_stop_timer(dispatch_source_t timer) { + dispatch_source_set_event_handler(timer, NULL); + dispatch_source_cancel(timer); +} diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/CGPointUtilSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/CGPointUtilSpec.m new file mode 100644 index 000000000..f4c227291 --- /dev/null +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/CGPointUtilSpec.m @@ -0,0 +1,72 @@ +// +// CGPointUtilSpec.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 7/1/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import +#import +#import + +#import "CGPoint_Util.h" + +QuickSpecBegin(CGPointUtilSpec) + +describe(@"CGPoint_Util Tests", ^{ + __block CGPoint first; + __block CGPoint second; + context(@"For two positive points", ^{ + beforeEach(^{ + first = CGPointMake(100, 200); + second = CGPointMake(300, 400); + }); + + it(@"should properly calculate the center between points", ^{ + CGPoint center = CGPointCenterOfPoints(first, second); + expect(@(center.x)).to(equal(@200)); + expect(@(center.y)).to(equal(@300)); + }); + it(@"should properly calculate the distance between points", ^{ + CGFloat distance = CGPointDistanceBetweenPoints(first, second); + expect(@(distance)).to(beCloseTo(@282.8427).within(0.0001)); + }); + }); + context(@"For two negative points", ^{ + beforeEach(^{ + first = CGPointMake(-100, -200); + second = CGPointMake(-300, -400); + }); + + it(@"should properly calculate the center between points", ^{ + CGPoint center = CGPointCenterOfPoints(first, second); + expect(@(center.x)).to(equal(@(-200))); + expect(@(center.y)).to(equal(@(-300))); + }); + it(@"should properly calculate the distance between points", ^{ + CGFloat distance = CGPointDistanceBetweenPoints(first, second); + expect(@(distance)).to(beCloseTo(@282.8427).within(0.0001)); + }); + }); + context(@"For one positive and one negative point", ^{ + beforeEach(^{ + first = CGPointMake(100, 200); + second = CGPointMake(-300, -400); + }); + + it(@"should properly calculate the center between points", ^{ + CGPoint center = CGPointCenterOfPoints(first, second); + expect(@(center.x)).to(equal(@(-100))); + expect(@(center.y)).to(equal(@(-100))); + }); + it(@"should properly calculate the distance between points", ^{ + CGFloat distance = CGPointDistanceBetweenPoints(first, second); + expect(@(distance)).to(beCloseTo(@721.1103).within(0.0001)); + }); + }); +}); + +QuickSpecEnd \ No newline at end of file diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/DispatchTimerSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/DispatchTimerSpec.m new file mode 100644 index 000000000..771d2c4bb --- /dev/null +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/DispatchTimerSpec.m @@ -0,0 +1,47 @@ +// +// DispatchTimerSpec.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 7/1/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import +#import +#import + +#import "dispatch_timer.h" + +QuickSpecBegin(DispatchTimerSpec) + +describe(@"dispatch_timer Tests", ^{ + context(@"Creating", ^{ + it(@"should be successful within specified time", ^{ + waitUntilTimeout(4, ^(void (^done)(void)) { + __block double currentTime = [[NSDate date] timeIntervalSince1970]; + dispatch_create_timer(2.5, false, ^{ + double difference = [[NSDate date] timeIntervalSince1970] - currentTime; + expect(@(difference)).to(beCloseTo(@2.5).within(0.1)); + done(); + }); + }); + }); + + it(@"should be cancellable and not fire", ^{ + __block dispatch_source_t timer; + waitUntilTimeout(2, ^(void (^done)(void)) { + timer = dispatch_create_timer(2.5, false, ^{ + fail(); + }); + [NSThread sleepForTimeInterval:0.5]; + dispatch_stop_timer(timer); + done(); + }); + expect(@(dispatch_source_testcancel(timer))).to(beGreaterThan(@0)); + }); + }); +}); + +QuickSpecEnd \ No newline at end of file diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLPinchGestureSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLPinchGestureSpec.m new file mode 100644 index 000000000..99293ac8c --- /dev/null +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLPinchGestureSpec.m @@ -0,0 +1,182 @@ +// +// SDLPinchGestureSpec.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 7/1/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import +#import +#import + +#import "SDLPinchGesture.h" +#import "SDLTouchCoord.h" +#import "SDLTouchEvent.h" + +QuickSpecBegin(SDLPinchGestureSpec) + +describe(@"SDLPinchGesture Tests", ^{ + context(@"SDLPinchGestureZero", ^{ + __block SDLPinchGesture* pinchGesture = [[SDLPinchGesture alloc] init];; + + it(@"should correctly initialize", ^{ + expect(pinchGesture.firstTouch).to(beNil()); + expect(pinchGesture.secondTouch).to(beNil()); + expect(@(pinchGesture.distance)).to(equal(@0)); + expect(@(CGPointEqualToPoint(pinchGesture.center, CGPointZero))).to(beTruthy()); + }); + + it(@"should not be a valid SDLPinchGesture", ^{ + expect(@(pinchGesture.isValid)).to(beFalsy()); + }); + }); + + context(@"SDLPinchGestureMake", ^{ + __block SDLPinchGesture* pinchGesture; + __block unsigned long timeStamp = [[NSDate date] timeIntervalSince1970] * 1000; + __block unsigned long secondTimeStamp = timeStamp + 1000; + + beforeEach(^{ + SDLTouchCoord* firstCoord = [[SDLTouchCoord alloc] init]; + firstCoord.x = @100; + firstCoord.y = @200; + + SDLTouchEvent* firstTouchEvent = [[SDLTouchEvent alloc] init]; + firstTouchEvent.touchEventId = @0; + firstTouchEvent.coord = [NSMutableArray arrayWithObject:firstCoord]; + firstTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(timeStamp)]; + + SDLTouch* firstTouch = [[SDLTouch alloc] initWithTouchEvent:firstTouchEvent]; + + SDLTouchCoord* secondCoord = [[SDLTouchCoord alloc] init]; + secondCoord.x = @200; + secondCoord.y = @300; + + SDLTouchEvent* secondTouchEvent = [[SDLTouchEvent alloc] init]; + secondTouchEvent.touchEventId = @1; + secondTouchEvent.coord = [NSMutableArray arrayWithObject:secondCoord]; + secondTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondTimeStamp)]; + + SDLTouch* secondTouch = [[SDLTouch alloc] initWithTouchEvent:secondTouchEvent]; + + pinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:firstTouch + secondTouch:secondTouch]; + }); + + it(@"should correctly initialize", ^{ + expect(@(pinchGesture.firstTouch.identifier)).to(equal(@(SDLTouchIdentifierFirstFinger))); + expect(@(pinchGesture.firstTouch.location.x)).to(equal(@100)); + expect(@(pinchGesture.firstTouch.location.y)).to(equal(@200)); + expect(@(pinchGesture.firstTouch.timeStamp)).to(equal(@(timeStamp))); + + expect(@(pinchGesture.secondTouch.identifier)).to(equal(@(SDLTouchIdentifierSecondFinger))); + expect(@(pinchGesture.secondTouch.location.x)).to(equal(@200)); + expect(@(pinchGesture.secondTouch.location.y)).to(equal(@300)); + expect(@(pinchGesture.secondTouch.timeStamp)).to(equal(@(secondTimeStamp))); + + expect(@(pinchGesture.distance)).to(beCloseTo(@141.4213).within(0.0001)); + expect(@(pinchGesture.center.x)).to(equal(@150)); + expect(@(pinchGesture.center.y)).to(equal(@250)); + }); + + it(@"should be a valid SDLPinchGesture", ^{ + expect(@(pinchGesture.isValid)).to(beTruthy()); + }); + }); + + context(@"updating SDLPinchGesture", ^{ + __block SDLPinchGesture* pinchGesture; + __block unsigned long timeStamp = [[NSDate date] timeIntervalSince1970] * 1000; + __block unsigned long secondTimeStamp = timeStamp + 1000; + __block unsigned long newTimeStamp = timeStamp + 1000; + + __block SDLTouch* newFirstTouch; + __block SDLTouch* newSecondTouch; + + beforeEach(^{ + SDLTouchCoord* firstCoord = [[SDLTouchCoord alloc] init]; + firstCoord.x = @100; + firstCoord.y = @200; + + SDLTouchEvent* firstTouchEvent = [[SDLTouchEvent alloc] init]; + firstTouchEvent.touchEventId = @0; + firstTouchEvent.coord = [NSMutableArray arrayWithObject:firstCoord]; + firstTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(timeStamp)]; + + SDLTouch* firstTouch = [[SDLTouch alloc] initWithTouchEvent:firstTouchEvent]; + + SDLTouchCoord* secondCoord = [[SDLTouchCoord alloc] init]; + secondCoord.x = @200; + secondCoord.y = @300; + + SDLTouchEvent* secondTouchEvent = [[SDLTouchEvent alloc] init]; + secondTouchEvent.touchEventId = @1; + secondTouchEvent.coord = [NSMutableArray arrayWithObject:secondCoord]; + secondTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondTimeStamp)]; + + SDLTouch* secondTouch = [[SDLTouch alloc] initWithTouchEvent:secondTouchEvent]; + + pinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:firstTouch + secondTouch:secondTouch]; + + SDLTouchCoord* newCoord = [[SDLTouchCoord alloc] init]; + newCoord.x = @150; + newCoord.y = @250; + + SDLTouchEvent* newFirstTouchEvent = [[SDLTouchEvent alloc] init]; + newFirstTouchEvent.touchEventId = @0; + newFirstTouchEvent.coord = [NSMutableArray arrayWithObject:newCoord]; + newFirstTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(newTimeStamp)]; + + SDLTouchEvent* newSecondTouchEvent = [[SDLTouchEvent alloc] init]; + newSecondTouchEvent.touchEventId = @1; + newSecondTouchEvent.coord = [NSMutableArray arrayWithObject:newCoord]; + newSecondTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(newTimeStamp)]; + + newFirstTouch = [[SDLTouch alloc] initWithTouchEvent:newFirstTouchEvent]; + newSecondTouch = [[SDLTouch alloc] initWithTouchEvent:newSecondTouchEvent]; + }); + + it(@"should update first point correctly", ^{ + pinchGesture.firstTouch = newFirstTouch; + + expect(@(pinchGesture.firstTouch.identifier)).to(equal(@(SDLTouchIdentifierFirstFinger))); + expect(@(pinchGesture.firstTouch.location.x)).to(equal(@150)); + expect(@(pinchGesture.firstTouch.location.y)).to(equal(@250)); + expect(@(pinchGesture.firstTouch.timeStamp)).to(equal(@(newTimeStamp))); + + expect(@(pinchGesture.secondTouch.identifier)).to(equal(@(SDLTouchIdentifierSecondFinger))); + expect(@(pinchGesture.secondTouch.location.x)).to(equal(@200)); + expect(@(pinchGesture.secondTouch.location.y)).to(equal(@300)); + expect(@(pinchGesture.secondTouch.timeStamp)).to(equal(@(secondTimeStamp))); + + expect(@(pinchGesture.distance)).to(beCloseTo(@(70.7107)).within(0.0001)); + expect(@(pinchGesture.center.x)).to(equal(@175)); + expect(@(pinchGesture.center.y)).to(equal(@275)); + + }); + + it(@"should update second point correctly", ^{ + pinchGesture.secondTouch = newSecondTouch; + + expect(@(pinchGesture.firstTouch.identifier)).to(equal(@(SDLTouchIdentifierFirstFinger))); + expect(@(pinchGesture.firstTouch.location.x)).to(equal(@100)); + expect(@(pinchGesture.firstTouch.location.y)).to(equal(@200)); + expect(@(pinchGesture.firstTouch.timeStamp)).to(equal(@(timeStamp))); + + expect(@(pinchGesture.secondTouch.identifier)).to(equal(@(SDLTouchIdentifierSecondFinger))); + expect(@(pinchGesture.secondTouch.location.x)).to(equal(@150)); + expect(@(pinchGesture.secondTouch.location.y)).to(equal(@250)); + expect(@(pinchGesture.secondTouch.timeStamp)).to(equal(@(newTimeStamp))); + + expect(@(pinchGesture.distance)).to(beCloseTo(@70.7107).within(0.0001)); + expect(@(pinchGesture.center.x)).to(equal(@125)); + expect(@(pinchGesture.center.y)).to(equal(@225)); + }); + }); +}); + +QuickSpecEnd \ No newline at end of file diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m new file mode 100644 index 000000000..21639b6b7 --- /dev/null +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m @@ -0,0 +1,609 @@ +// +// SDLTouchManagerSpec.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 6/17/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import +#import +#import + +#import "SDLOnTouchEvent.h" +#import "SDLTouchCoord.h" +#import "SDLTouchEvent.h" +#import "SDLTouchManager.h" +#import "SDLTouchType.h" + +QuickSpecBegin(SDLTouchManagerSpec) + +describe(@"SDLTouchManager Tests", ^{ + + __block SDLTouchManager* touchManager; + + context(@"initializing", ^{ + it(@"should correctly have default properties", ^{ + SDLTouchManager* touchManager = [[SDLTouchManager alloc] init]; + expect(touchManager.touchEventDelegate).to(beNil()); + expect(@(touchManager.tapDistanceThreshold)).to(equal(@50)); + expect(@(touchManager.tapTimeThreshold)).to(beCloseTo(@0.4).within(0.0001)); + expect(@(touchManager.movementTimeThreshold)).to(beCloseTo(@0.05).within(0.0001)); + expect(@(touchManager.isTouchEnabled)).to(beTruthy()); + }); + + }); + + describe(@"touch events", ^{ + typedef void (^DelegateCallbackBlock)(NSInvocation* invocation); + + __block id delegateMock; + __block CGPoint controlPoint; + __block BOOL didCallSingleTap; + __block BOOL didCallDoubleTap; + __block BOOL didCallBeginPan; + __block BOOL didCallMovePan; + __block BOOL didCallEndPan; + __block BOOL didCallBeginPinch; + __block BOOL didCallMovePinch; + __block BOOL didCallEndPinch; + + __block DelegateCallbackBlock singleTapTests; + __block DelegateCallbackBlock doubleTapTests; + __block DelegateCallbackBlock panStartTests; + __block DelegateCallbackBlock panMoveTests; + __block DelegateCallbackBlock panEndTests; + __block DelegateCallbackBlock pinchStartTests; + __block DelegateCallbackBlock pinchMoveTests; + __block DelegateCallbackBlock pinchEndTests; + + __block void (^performTouchEvent)(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) = ^(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) { + SEL onOnTouchEvent = NSSelectorFromString(@"onOnTouchEvent:"); + ((void (*)(id, SEL, id))[touchManager methodForSelector:onOnTouchEvent])(touchManager, onOnTouchEvent, onTouchEvent); + }; + + beforeEach(^{ + touchManager = [[SDLTouchManager alloc] init]; + delegateMock = OCMProtocolMock(@protocol(SDLTouchManagerDelegate)); + touchManager.touchEventDelegate = delegateMock; + controlPoint = CGPointMake(100, 200); + + didCallSingleTap = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallSingleTap = YES; + + singleTapTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] didReceiveSingleTapAtPoint:CGPointZero]; + + singleTapTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallDoubleTap = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallDoubleTap = YES; + + doubleTapTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] didReceiveDoubleTapAtPoint:CGPointZero]; + + doubleTapTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallBeginPan = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallBeginPan = YES; + + panStartTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] panningDidStartAtPoint:CGPointZero]; + + panStartTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallMovePan = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallMovePan = YES; + + panMoveTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] didReceivePanningFromPoint:CGPointZero toPoint:CGPointZero]; + + panMoveTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallEndPan = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallEndPan = YES; + + panEndTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] panningDidEndAtPoint:CGPointZero]; + + panEndTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallBeginPinch = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallBeginPinch = YES; + + pinchStartTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] pinchDidStartAtCenterPoint:CGPointZero]; + + pinchStartTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallMovePinch = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallMovePinch = YES; + + pinchMoveTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] didReceivePinchAtCenterPoint:CGPointZero withScale:0]; + + pinchMoveTests = ^(NSInvocation* invocation) { + fail(); + }; + + didCallEndPinch = NO; + [[[[delegateMock stub] andDo:^(NSInvocation* invocation) { + didCallEndPinch = YES; + + pinchEndTests(invocation); + }] ignoringNonObjectArgs] touchManager:[OCMArg any] pinchDidEndAtCenterPoint:CGPointZero]; + + pinchEndTests = ^(NSInvocation* invocation) { + fail(); + }; + }); + + describe(@"single finger", ^{ + __block SDLTouchCoord* firstTouchCoord; + __block NSUInteger firstTouchTimeStamp; + + __block SDLOnTouchEvent* firstOnTouchEventStart; + __block SDLOnTouchEvent* firstOnTouchEventEnd; + + beforeEach(^{ + firstTouchCoord = [[SDLTouchCoord alloc] init]; + firstTouchCoord.x = @(controlPoint.x); + firstTouchCoord.y = @(controlPoint.y); + + firstTouchTimeStamp = [[NSDate date] timeIntervalSince1970] * 1000.0; + + SDLTouchEvent* touchEvent = [[SDLTouchEvent alloc] init]; + touchEvent.touchEventId = @0; + touchEvent.coord = [NSMutableArray arrayWithObject:firstTouchCoord]; + touchEvent.timeStamp = [NSMutableArray arrayWithObject:@(firstTouchTimeStamp)]; + + firstOnTouchEventStart = [[SDLOnTouchEvent alloc] init]; + firstOnTouchEventStart.type = SDLTouchType.BEGIN; + firstOnTouchEventStart.event = [NSMutableArray arrayWithObject:touchEvent]; + + firstOnTouchEventEnd = [[SDLOnTouchEvent alloc] init]; + firstOnTouchEventEnd.type = SDLTouchType.END; + firstOnTouchEventEnd.event = [NSMutableArray arrayWithObject:touchEvent]; + }); + + describe(@"when receiving a single tap", ^{ + it(@"should correctly handle a single tap", ^{ + + singleTapTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, controlPoint))).to(beTruthy()); + }; + + performTouchEvent(touchManager, firstOnTouchEventStart); + + performTouchEvent(touchManager, firstOnTouchEventEnd); + + expect(@(didCallSingleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallDoubleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + }); + }); + + describe(@"when receiving a double tap", ^{ + __block CGPoint averagePoint; + + __block SDLTouchEvent* secondTouchEvent; + + __block SDLOnTouchEvent* secondOnTouchEventStart; + __block SDLOnTouchEvent* secondOnTouchEventEnd; + + beforeEach(^{ + secondOnTouchEventStart = [[SDLOnTouchEvent alloc] init]; + secondOnTouchEventStart.type = SDLTouchType.BEGIN; + + secondOnTouchEventEnd = [[SDLOnTouchEvent alloc] init]; + secondOnTouchEventEnd.type = SDLTouchType.END; + + secondTouchEvent = [[SDLTouchEvent alloc] init]; + secondTouchEvent.touchEventId = @0; + NSUInteger secondTouchTimeStamp = firstTouchTimeStamp + (touchManager.tapTimeThreshold - 0.1) * 1000; + secondTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondTouchTimeStamp)]; + }); + + context(@"near the same point", ^{ + beforeEach(^{ + SDLTouchCoord* touchCoord = [[SDLTouchCoord alloc] init]; + touchCoord.x = @(firstTouchCoord.x.floatValue + touchManager.tapDistanceThreshold); + touchCoord.y = @(firstTouchCoord.y.floatValue + touchManager.tapDistanceThreshold); + + secondTouchEvent.coord = [NSMutableArray arrayWithObject:touchCoord]; + + secondOnTouchEventStart.event = [NSMutableArray arrayWithObject:secondTouchEvent]; + + secondOnTouchEventEnd.event = [NSMutableArray arrayWithObject:secondTouchEvent]; + + averagePoint = CGPointMake((firstTouchCoord.x.floatValue + touchCoord.x.floatValue) / 2.0f, + (firstTouchCoord.y.floatValue + touchCoord.y.floatValue) / 2.0f); + }); + + it(@"should issue delegate callbacks", ^{ + doubleTapTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, averagePoint))).to(beTruthy()); + }; + + performTouchEvent(touchManager, firstOnTouchEventStart); + performTouchEvent(touchManager, firstOnTouchEventEnd); + performTouchEvent(touchManager, secondOnTouchEventStart); + performTouchEvent(touchManager, secondOnTouchEventEnd); + + expect(@(didCallSingleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallDoubleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallBeginPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + }); + }); + + context(@"not near the same point", ^{ + beforeEach(^{ + SDLTouchCoord* touchCoord = [[SDLTouchCoord alloc] init]; + touchCoord.x = @(firstTouchCoord.x.floatValue + touchManager.tapDistanceThreshold + 1); + touchCoord.y = @(firstTouchCoord.y.floatValue + touchManager.tapDistanceThreshold + 1); + + secondTouchEvent.coord = [NSMutableArray arrayWithObject:touchCoord]; + + secondOnTouchEventStart.event = [NSMutableArray arrayWithObject:secondTouchEvent]; + + secondOnTouchEventEnd.event = [NSMutableArray arrayWithObject:secondTouchEvent]; + }); + + it(@"should should not issue delegate callbacks", ^{ + performTouchEvent(touchManager, firstOnTouchEventStart); + performTouchEvent(touchManager, firstOnTouchEventEnd); + performTouchEvent(touchManager, secondOnTouchEventStart); + performTouchEvent(touchManager, secondOnTouchEventEnd); + + expect(@(didCallSingleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallDoubleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + }); + }); + }); + }); + context(@"when receiving a pan", ^{ + __block CGPoint panStartPoint; + __block CGPoint panMovePoint; + __block CGPoint panSecondMovePoint; + __block CGPoint panEndPoint; + + __block CGFloat distanceMoveX = 10; + __block CGFloat distanceMoveY = 20; + + __block SDLOnTouchEvent* panStartOnTouchEvent; + __block SDLOnTouchEvent* panMoveOnTouchEvent; + __block SDLOnTouchEvent* panSecondMoveOnTouchEvent; + __block SDLOnTouchEvent* panEndOnTouchEvent; + + beforeEach(^{ + // Finger touch down + panStartPoint = controlPoint; + + SDLTouchCoord* panStartTouchCoord = [[SDLTouchCoord alloc] init]; + panStartTouchCoord.x = @(panStartPoint.x); + panStartTouchCoord.y = @(panStartPoint.y); + + CGFloat movementTimeThresholdOffset = (touchManager.movementTimeThreshold + .01) * 1000; + + NSUInteger panStartTimeStamp = ([[NSDate date] timeIntervalSince1970] * 1000) + movementTimeThresholdOffset; + + SDLTouchEvent* panStartTouchEvent = [[SDLTouchEvent alloc] init]; + panStartTouchEvent.coord = [NSMutableArray arrayWithObject:panStartTouchCoord]; + panStartTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(panStartTimeStamp)]; + + panStartOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + panStartOnTouchEvent.event = [NSMutableArray arrayWithObject:panStartTouchEvent]; + panStartOnTouchEvent.type = SDLTouchType.BEGIN; + + // Finger Move + panMovePoint = CGPointMake(panStartPoint.x + distanceMoveX, panStartPoint.y + distanceMoveY); + + SDLTouchCoord* panMoveTouchCoord = [[SDLTouchCoord alloc] init]; + panMoveTouchCoord.x = @(panMovePoint.x); + panMoveTouchCoord.y = @(panMovePoint.y); + + NSUInteger panMoveTimeStamp = panStartTimeStamp + movementTimeThresholdOffset; + + SDLTouchEvent* panMoveTouchEvent = [[SDLTouchEvent alloc] init]; + panMoveTouchEvent.coord = [NSMutableArray arrayWithObject:panMoveTouchCoord]; + panMoveTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(panMoveTimeStamp)]; + + panMoveOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + panMoveOnTouchEvent.event = [NSMutableArray arrayWithObject:panMoveTouchEvent]; + panMoveOnTouchEvent.type = SDLTouchType.MOVE; + + // Finger Move + panSecondMovePoint = CGPointMake(panMovePoint.x + distanceMoveX, panMovePoint.y + distanceMoveY); + + SDLTouchCoord* panSecondMoveTouchCoord = [[SDLTouchCoord alloc] init]; + panSecondMoveTouchCoord.x = @(panSecondMovePoint.x); + panSecondMoveTouchCoord.y = @(panSecondMovePoint.y); + + NSUInteger panSecondMoveTimeStamp = panMoveTimeStamp + movementTimeThresholdOffset; + + SDLTouchEvent* panSecondMoveTouchEvent = [[SDLTouchEvent alloc] init]; + panSecondMoveTouchEvent.coord = [NSMutableArray arrayWithObject:panSecondMoveTouchCoord]; + panSecondMoveTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(panSecondMoveTimeStamp)]; + + panSecondMoveOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + panSecondMoveOnTouchEvent.event = [NSMutableArray arrayWithObject:panSecondMoveTouchEvent]; + panSecondMoveOnTouchEvent.type = SDLTouchType.MOVE; + + // Finger End + panEndPoint = CGPointMake(panSecondMovePoint.x, panSecondMovePoint.y); + + SDLTouchCoord* panEndTouchCoord = [[SDLTouchCoord alloc] init]; + panEndTouchCoord.x = @(panEndPoint.x); + panEndTouchCoord.y = @(panEndPoint.y); + + NSUInteger panEndTimeStamp = panSecondMoveTimeStamp + movementTimeThresholdOffset; + + SDLTouchEvent* panEndTouchEvent = [[SDLTouchEvent alloc] init]; + panEndTouchEvent.coord = [NSMutableArray arrayWithObject:panEndTouchCoord]; + panEndTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(panEndTimeStamp)]; + + panEndOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + panEndOnTouchEvent.event = [NSMutableArray arrayWithObject:panEndTouchEvent]; + panEndOnTouchEvent.type = SDLTouchType.END; + }); + + it(@"should correctly give all pan callbacks", ^{ + panStartTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, panMovePoint))).to(beTruthy()); + }; + + panMoveTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint startPoint; + CGPoint endPoint; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&startPoint atIndex:3]; + [invocation getArgument:&endPoint atIndex:4]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(startPoint, panMovePoint))).to(beTruthy()); + expect(@(CGPointEqualToPoint(endPoint, panSecondMovePoint))).to(beTruthy()); + }; + + panEndTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, panEndPoint))).to(beTruthy()); + }; + + performTouchEvent(touchManager, panStartOnTouchEvent); + performTouchEvent(touchManager, panMoveOnTouchEvent); + performTouchEvent(touchManager, panSecondMoveOnTouchEvent); + performTouchEvent(touchManager, panEndOnTouchEvent); + + expect(@(didCallSingleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallDoubleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallMovePan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallEndPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallBeginPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + + }); + }); + context(@"when receiving a pinch", ^{ + + __block CGPoint pinchStartCenter; + __block CGPoint pinchMoveCenter; + __block CGFloat pinchMoveScale; + __block CGPoint pinchEndCenter; + + __block SDLOnTouchEvent* pinchStartFirstFingerOnTouchEvent; + __block SDLOnTouchEvent* pinchStartSecondFingerOnTouchEvent; + __block SDLOnTouchEvent* pinchMoveSecondFingerOnTouchEvent; + __block SDLOnTouchEvent* pinchEndSecondFingerOnTouchEvent; + + beforeEach(^{ + // First finger touch down + SDLTouchCoord* firstFingerTouchCoord = [[SDLTouchCoord alloc] init]; + firstFingerTouchCoord.x = @(controlPoint.x); + firstFingerTouchCoord.y = @(controlPoint.y); + + NSUInteger firstFingerTimeStamp = [[NSDate date] timeIntervalSince1970] * 1000; + + SDLTouchEvent* firstFingerTouchEvent = [[SDLTouchEvent alloc] init]; + firstFingerTouchEvent.coord = [NSMutableArray arrayWithObject:firstFingerTouchCoord]; + firstFingerTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(firstFingerTimeStamp)]; + + pinchStartFirstFingerOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + pinchStartFirstFingerOnTouchEvent.event = [NSMutableArray arrayWithObject:firstFingerTouchEvent]; + pinchStartFirstFingerOnTouchEvent.type = SDLTouchType.BEGIN; + + // Second finger touch down + SDLTouchCoord* secondFingerTouchCoord = [[SDLTouchCoord alloc] init]; + secondFingerTouchCoord.x = @(firstFingerTouchCoord.x.floatValue + 100); + secondFingerTouchCoord.y = @(firstFingerTouchCoord.y.floatValue + 100); + + NSUInteger secondFingerTimeStamp = firstFingerTimeStamp; + + SDLTouchEvent* secondFingerTouchEvent = [[SDLTouchEvent alloc] init]; + secondFingerTouchEvent.touchEventId = @1; + secondFingerTouchEvent.coord = [NSMutableArray arrayWithObject:secondFingerTouchCoord]; + secondFingerTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondFingerTimeStamp)]; + + pinchStartSecondFingerOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + pinchStartSecondFingerOnTouchEvent.event = [NSMutableArray arrayWithObject:secondFingerTouchEvent]; + pinchStartSecondFingerOnTouchEvent.type = SDLTouchType.BEGIN; + + pinchStartCenter = CGPointMake((firstFingerTouchCoord.x.floatValue + secondFingerTouchCoord.x.floatValue) / 2.0f, + (firstFingerTouchCoord.y.floatValue + secondFingerTouchCoord.y.floatValue) / 2.0f); + + CGFloat pinchStartDistance = hypotf(firstFingerTouchCoord.x.floatValue - secondFingerTouchCoord.x.floatValue, + firstFingerTouchCoord.y.floatValue - secondFingerTouchCoord.y.floatValue); + + // Second finger move + SDLTouchCoord* secondFingerMoveTouchCoord = [[SDLTouchCoord alloc] init]; + secondFingerMoveTouchCoord.x = @(secondFingerTouchCoord.x.floatValue - 50); + secondFingerMoveTouchCoord.y = @(secondFingerTouchCoord.y.floatValue - 40); + + NSUInteger secondFingerMoveTimeStamp = secondFingerTimeStamp + ((touchManager.movementTimeThreshold + 0.1) * 1000); + + SDLTouchEvent* secondFingerMoveTouchEvent = [[SDLTouchEvent alloc] init]; + secondFingerMoveTouchEvent.touchEventId = @1; + secondFingerMoveTouchEvent.coord = [NSMutableArray arrayWithObject:secondFingerMoveTouchCoord]; + secondFingerMoveTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondFingerMoveTimeStamp)]; + + pinchMoveSecondFingerOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + pinchMoveSecondFingerOnTouchEvent.event = [NSMutableArray arrayWithObject:secondFingerMoveTouchEvent]; + pinchMoveSecondFingerOnTouchEvent.type = SDLTouchType.MOVE; + + pinchMoveCenter = CGPointMake((firstFingerTouchCoord.x.floatValue + secondFingerMoveTouchCoord.x.floatValue) / 2.0f, + (firstFingerTouchCoord.y.floatValue + secondFingerMoveTouchCoord.y.floatValue) / 2.0f); + + CGFloat pinchMoveDistance = hypotf(firstFingerTouchCoord.x.floatValue - secondFingerMoveTouchCoord.x.floatValue, + firstFingerTouchCoord.y.floatValue - secondFingerMoveTouchCoord.y.floatValue); + + pinchMoveScale = pinchMoveDistance / pinchStartDistance; + + // Second finger end + SDLTouchCoord* secondFingerEndTouchCoord = secondFingerMoveTouchCoord; + + NSUInteger secondFingerEndTimeStamp = secondFingerMoveTimeStamp + ((touchManager.movementTimeThreshold + 0.1) * 1000); + + SDLTouchEvent* secondFingerEndTouchEvent = [[SDLTouchEvent alloc] init]; + secondFingerEndTouchEvent.touchEventId = @1; + secondFingerEndTouchEvent.coord = [NSMutableArray arrayWithObject:secondFingerEndTouchCoord]; + secondFingerEndTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(secondFingerEndTimeStamp)]; + + pinchEndSecondFingerOnTouchEvent = [[SDLOnTouchEvent alloc] init]; + pinchEndSecondFingerOnTouchEvent.event = [NSMutableArray arrayWithObject:secondFingerEndTouchEvent]; + pinchEndSecondFingerOnTouchEvent.type = SDLTouchType.END; + + pinchEndCenter = CGPointMake((firstFingerTouchCoord.x.floatValue + secondFingerEndTouchCoord.x.floatValue) / 2.0f, + (firstFingerTouchCoord.y.floatValue + secondFingerEndTouchCoord.y.floatValue) / 2.0f); + }); + + it(@"should correctly give all pinch callback", ^{ + pinchStartTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, pinchStartCenter))).to(beTruthy()); + }; + + pinchMoveTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + CGFloat scale; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + [invocation getArgument:&scale atIndex:4]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, pinchMoveCenter))).to(beTruthy()); + expect(@(scale)).to(beCloseTo(@(pinchMoveScale)).within(0.0001)); + }; + + pinchEndTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + + CGPoint point; + + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:3]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, pinchEndCenter))).to(beTruthy()); + }; + + performTouchEvent(touchManager, pinchStartFirstFingerOnTouchEvent); + performTouchEvent(touchManager, pinchStartSecondFingerOnTouchEvent); + performTouchEvent(touchManager, pinchMoveSecondFingerOnTouchEvent); + performTouchEvent(touchManager, pinchEndSecondFingerOnTouchEvent); + + expect(@(didCallSingleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallDoubleTap)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallMovePan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallEndPan)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beFalsy()); + expect(@(didCallBeginPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallMovePinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + expect(@(didCallEndPinch)).withTimeout(touchManager.tapTimeThreshold + 0.1).toEventually(beTruthy()); + }); + }); + }); +}); + +QuickSpecEnd diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchSpec.m new file mode 100644 index 000000000..c629cd72e --- /dev/null +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchSpec.m @@ -0,0 +1,107 @@ +// +// SDLTouchSpecs.m +// SmartDeviceLink-iOS +// +// Created by Muller, Alexander (A.) on 7/1/16. +// Copyright © 2016 smartdevicelink. All rights reserved. +// + +#import + +#import +#import +#import + +#import "SDLTouchEvent.h" +#import "SDLTouchCoord.h" +#import "SDLTouch.h" + +QuickSpecBegin(SDLTouchSpec) + +describe(@"SDLTouch Tests", ^{ + context(@"SDLTouchZero", ^{ + __block SDLTouch* touch = [[SDLTouch alloc] init]; + + it(@"should correctly initialize", ^{ + expect(@(touch.identifier)).to(equal(@(-1))); + expect(@(CGPointEqualToPoint(touch.location, CGPointZero))).to(beTruthy()); + expect(@(touch.timeStamp)).to(equal(@0)); + }); + + it(@"should not equal First Finger Identifier", ^{ + expect(@(touch.isFirstFinger)).to(beFalsy()); + }); + + it(@"should not equal Second Finger Identifier", ^{ + expect(@(touch.isSecondFinger)).to(beFalsy()); + }); + }); + + context(@"For First Finger Identifiers", ^{ + __block unsigned long timeStamp = [[NSDate date] timeIntervalSince1970] * 1000; + __block SDLTouch* touch; + + beforeSuite(^{ + SDLTouchCoord* coord = [[SDLTouchCoord alloc] init]; + coord.x = @100; + coord.y = @200; + + SDLTouchEvent* touchEvent = [[SDLTouchEvent alloc] init]; + touchEvent.touchEventId = @0; + touchEvent.coord = [NSMutableArray arrayWithObject:coord]; + touchEvent.timeStamp = [NSMutableArray arrayWithObject:@(timeStamp)]; + + touch = [[SDLTouch alloc] initWithTouchEvent:touchEvent]; + }); + + it(@"should correctly make a SDLTouch struct", ^{ + expect(@(touch.identifier)).to(equal(@(SDLTouchIdentifierFirstFinger))); + expect(@(touch.location.x)).to(equal(@100)); + expect(@(touch.location.y)).to(equal(@200)); + expect(@(touch.timeStamp)).to(equal(@(timeStamp))); + }); + + it(@"should equal First Finger Identifier", ^{ + expect(@(touch.isFirstFinger)).to(beTruthy()); + }); + + it(@"should not equal Second Finger Identifier", ^{ + expect(@(touch.isSecondFinger)).to(beFalsy()); + }); + }); + + context(@"For Second Finger Identifiers", ^{ + __block unsigned long timeStamp = [[NSDate date] timeIntervalSince1970] * 1000; + __block SDLTouch* touch; + + beforeSuite(^{ + SDLTouchCoord* coord = [[SDLTouchCoord alloc] init]; + coord.x = @100; + coord.y = @200; + + SDLTouchEvent* touchEvent = [[SDLTouchEvent alloc] init]; + touchEvent.touchEventId = @1; + touchEvent.coord = [NSMutableArray arrayWithObject:coord]; + touchEvent.timeStamp = [NSMutableArray arrayWithObject:@(timeStamp)]; + + touch = [[SDLTouch alloc] initWithTouchEvent:touchEvent]; + }); + + it(@"should correctly make a SDLTouch struct", ^{ + expect(@(touch.identifier)).to(equal(@(SDLTouchIdentifierSecondFinger))); + expect(@(touch.location.x)).to(equal(@100)); + expect(@(touch.location.y)).to(equal(@200)); + expect(@(touch.timeStamp)).to(equal(@(timeStamp))); + }); + + it(@"should equal First Finger Identifier", ^{ + expect(@(touch.isFirstFinger)).to(beFalsy()); + }); + + it(@"should not equal Second Finger Identifier", ^{ + expect(@(touch.isSecondFinger)).to(beTruthy()); + }); + }); +}); + +QuickSpecEnd \ No newline at end of file