summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2017-09-27 16:33:41 -0400
committerGitHub <noreply@github.com>2017-09-27 16:33:41 -0400
commiteca3589a66ef012919871865c1c9139e25454891 (patch)
treec4385df78d4a0e5fa33f1e06155a77c06129d8d7
parent100ccd00695fe05a20fe052503736cbc57021bcd (diff)
parent3c211b33f6b4c8e31b601006bed92acedf3bf9c3 (diff)
downloadsdl_ios-eca3589a66ef012919871865c1c9139e25454891.tar.gz
Merge pull request #751 from ChrisB-Elektrobit/feature/haptic_manager_rebase
Implement SDL 0090 SDLHapticHitTester Protocol and Implement SDL 0081 Automatic Haptic Interface Detection
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj20
-rw-r--r--SmartDeviceLink/SDLHapticHitTester.h26
-rw-r--r--SmartDeviceLink/SDLHapticInterface.h32
-rw-r--r--SmartDeviceLink/SDLHapticManager.h19
-rw-r--r--SmartDeviceLink/SDLHapticManager.m154
-rw-r--r--SmartDeviceLink/SDLNotificationConstants.h1
-rw-r--r--SmartDeviceLink/SDLNotificationConstants.m1
-rw-r--r--SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m383
8 files changed, 636 insertions, 0 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 3e1818046..4e56793ab 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -231,6 +231,11 @@
1680B11C1A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */; };
1680B11D1A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */; };
1680B11E1A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */; };
+ 1FF7DAB61F75B27300B46C30 /* SDLHapticInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FF7DAB51F75B27300B46C30 /* SDLHapticInterface.h */; };
+ 1FF7DAB81F75B28E00B46C30 /* SDLHapticHitTester.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FF7DAB71F75B28E00B46C30 /* SDLHapticHitTester.h */; };
+ 1FF7DABA1F75B2A800B46C30 /* SDLHapticManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FF7DAB91F75B2A800B46C30 /* SDLHapticManager.h */; };
+ 1FF7DABC1F75B2BF00B46C30 /* SDLHapticManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FF7DABB1F75B2BF00B46C30 /* SDLHapticManager.m */; };
+ 1FF7DAC01F75CF6C00B46C30 /* SDLHapticManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */; };
1E4920B11F6A6443008F2CC3 /* SDLRadioState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E5AD05F1F207AB10029B8AF /* SDLRadioState.m */; };
1E4920B21F6A6455008F2CC3 /* SDLGetInteriorVehicleDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E5AD08B1F20BC320029B8AF /* SDLGetInteriorVehicleDataResponse.m */; };
1E4920B31F6A6463008F2CC3 /* SDLOnInteriorVehicleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E5AD0971F20C0FB0029B8AF /* SDLOnInteriorVehicleData.m */; };
@@ -1413,6 +1418,11 @@
1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolMessageAssemblerSpec.m; sourceTree = "<group>"; };
1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolMessageDisassemblerSpec.m; sourceTree = "<group>"; };
1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolReceivedMessageRouterSpec.m; sourceTree = "<group>"; };
+ 1FF7DAB51F75B27300B46C30 /* SDLHapticInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLHapticInterface.h; sourceTree = "<group>"; };
+ 1FF7DAB71F75B28E00B46C30 /* SDLHapticHitTester.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLHapticHitTester.h; sourceTree = "<group>"; };
+ 1FF7DAB91F75B2A800B46C30 /* SDLHapticManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLHapticManager.h; sourceTree = "<group>"; };
+ 1FF7DABB1F75B2BF00B46C30 /* SDLHapticManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLHapticManager.m; sourceTree = "<group>"; };
+ 1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLHapticManagerSpec.m; path = ProxySpecs/SDLHapticManagerSpec.m; sourceTree = "<group>"; };
1E5AD0321F1F3AA30029B8AF /* SDLRemoteControlCapabilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLRemoteControlCapabilities.h; sourceTree = "<group>"; };
1E5AD0331F1F3AA30029B8AF /* SDLRemoteControlCapabilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLRemoteControlCapabilities.m; sourceTree = "<group>"; };
1E5AD0361F1F4E390029B8AF /* SDLClimateControlCapabilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLClimateControlCapabilities.h; sourceTree = "<group>"; };
@@ -3001,6 +3011,8 @@
5D61FB641A84238B00846EE7 /* SDLProxy.m */,
5D61FB651A84238B00846EE7 /* SDLProxyFactory.h */,
5D61FB661A84238B00846EE7 /* SDLProxyFactory.m */,
+ 1FF7DAB91F75B2A800B46C30 /* SDLHapticManager.h */,
+ 1FF7DABB1F75B2BF00B46C30 /* SDLHapticManager.m */,
);
name = Proxy;
sourceTree = "<group>";
@@ -3682,6 +3694,8 @@
isa = PBXGroup;
children = (
5D61FB671A84238B00846EE7 /* SDLProxyListener.h */,
+ 1FF7DAB71F75B28E00B46C30 /* SDLHapticHitTester.h */,
+ 1FF7DAB51F75B27300B46C30 /* SDLHapticInterface.h */,
);
name = "@protocols";
sourceTree = "<group>";
@@ -3752,6 +3766,7 @@
5D59DD451B14FDD000BE744D /* ProxySpecs */ = {
isa = PBXGroup;
children = (
+ 1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */,
5D59DD461B14FDEE00BE744D /* SDLLockScreenStatusManagerSpec.m */,
DA661E2B1E553E7E001C1345 /* SDLStreamingMediaManagerSpec.m */,
);
@@ -4519,6 +4534,7 @@
5D61FE0D1A84238C00846EE7 /* SDLVrCapabilities.h in Headers */,
5DBF06271E64A91D00A5CF03 /* SDLLogFileModule.h in Headers */,
5D61FC531A84238C00846EE7 /* SDLButtonEventMode.h in Headers */,
+ 1FF7DAB61F75B27300B46C30 /* SDLHapticInterface.h in Headers */,
5D61FC781A84238C00846EE7 /* SDLDeleteFileResponse.h in Headers */,
5DA240001F325621009C0313 /* SDLStreamingMediaConfiguration.h in Headers */,
5D61FC5F1A84238C00846EE7 /* SDLCharacterSet.h in Headers */,
@@ -4657,6 +4673,7 @@
5DB996601F28C6ED002D8795 /* SDLControlFramePayloadVideoStartServiceAck.h in Headers */,
5DCF76F51ACDBAD300BB647B /* SDLSendLocation.h in Headers */,
5D61FC9E1A84238C00846EE7 /* SDLEncodedSyncPData.h in Headers */,
+ 1FF7DABA1F75B2A800B46C30 /* SDLHapticManager.h in Headers */,
5D61FC291A84238C00846EE7 /* SDLAbstractProtocol.h in Headers */,
5D61FDE11A84238C00846EE7 /* SDLTurn.h in Headers */,
5D61FC801A84238C00846EE7 /* SDLDeleteSubMenuResponse.h in Headers */,
@@ -4720,6 +4737,7 @@
5D61FC5D1A84238C00846EE7 /* SDLChangeRegistrationResponse.h in Headers */,
5D61FDCD1A84238C00846EE7 /* SDLTimerMode.h in Headers */,
E9C32B961AB20BA200F283AF /* SDLTimer.h in Headers */,
+ 1FF7DAB81F75B28E00B46C30 /* SDLHapticHitTester.h in Headers */,
5D61FC3D1A84238C00846EE7 /* SDLAlertResponse.h in Headers */,
5D61FDAD1A84238C00846EE7 /* SDLSubscribeButton.h in Headers */,
5D8204261BCEA8A600D0A41B /* SDLPermissionManager.h in Headers */,
@@ -5351,6 +5369,7 @@
5D61FCA41A84238C00846EE7 /* SDLEndAudioPassThru.m in Sources */,
5D8B17541AC9E11B006A6E1C /* SDLDialNumberResponse.m in Sources */,
DA6223BE1E7B088200878689 /* CVPixelBufferRef+SDLUtil.m in Sources */,
+ 1FF7DABC1F75B2BF00B46C30 /* SDLHapticManager.m in Sources */,
EED5CA021F4D18EC00F04000 /* SDLRAWH264Packetizer.m in Sources */,
5D61FC851A84238C00846EE7 /* SDLDeviceLevelStatus.m in Sources */,
5D9FDA981F2A7D3F00A495C8 /* emhashmap.c in Sources */,
@@ -5609,6 +5628,7 @@
162E82DE1A9BDE8B00906325 /* SDLFuelCutoffStatusSpec.m in Sources */,
162E83271A9BDE8B00906325 /* SDLCreateInteractionChoiceSetSpec.m in Sources */,
162E83111A9BDE8B00906325 /* SDLOnButtonEventSpec.m in Sources */,
+ 1FF7DAC01F75CF6C00B46C30 /* SDLHapticManagerSpec.m in Sources */,
162E82FA1A9BDE8B00906325 /* SDLSoftButtonTypeSpec.m in Sources */,
162E83751A9BDE8B00906325 /* SDLButtonCapabilitiesSpec.m in Sources */,
DA9F7EAF1DCC063400ACAE48 /* SDLLocationCoordinateSpec.m in Sources */,
diff --git a/SmartDeviceLink/SDLHapticHitTester.h b/SmartDeviceLink/SDLHapticHitTester.h
new file mode 100644
index 000000000..7678ccf28
--- /dev/null
+++ b/SmartDeviceLink/SDLHapticHitTester.h
@@ -0,0 +1,26 @@
+//
+// SDLHapticHitTester.h
+// SmartDeviceLink-iOS
+//
+// Copyright © 2017 smartdevicelink. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class SDLTouch;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol SDLHapticHitTester <NSObject>
+
+/**
+ Determines which view was selected based on SDLTouch object. If no view gets matched null value will be returned.
+
+ @param touch SDLTouch which has touch coordinates
+ @return matched UIView object or null
+ */
+- (nullable UIView *)viewForSDLTouch:(SDLTouch *)touch;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLHapticInterface.h b/SmartDeviceLink/SDLHapticInterface.h
new file mode 100644
index 000000000..987f10e7c
--- /dev/null
+++ b/SmartDeviceLink/SDLHapticInterface.h
@@ -0,0 +1,32 @@
+//
+// SDLHapticInterface.h
+// SmartDeviceLink-iOS
+//
+// Copyright © 2017 smartdevicelink. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "SDLConnectionManagerType.h"
+
+@class SDLManager;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol SDLHapticInterface <NSObject>
+
+/**
+ Initializes haptic interface. After initializing the application must call updateInterfaceLayout to process the UIWindow. Application must update later view changes in the window by sending SDLDidUpdateProjectionView notification.
+
+ @param window UIWindow to be stored in haptic interface
+ @param connectionManager Object of a class that implements ConnectionManagerType. This is used for RPC communication.
+ */
+- (instancetype)initWithWindow:(UIWindow *)window connectionManager:(id<SDLConnectionManagerType>)connectionManager;
+
+/**
+ updateInterfaceLayout crawls through the view hierarchy, updates and keep tracks of views to be reported through Haptic RPC. This function is automatically called when SDLDidUpdateProjectionView notification is sent by the application.
+ */
+- (void)updateInterfaceLayout;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLHapticManager.h b/SmartDeviceLink/SDLHapticManager.h
new file mode 100644
index 000000000..96ab95a1a
--- /dev/null
+++ b/SmartDeviceLink/SDLHapticManager.h
@@ -0,0 +1,19 @@
+//
+// SDLHapticManager.h
+// SmartDeviceLink-iOS
+//
+// Copyright © 2017 smartdevicelink. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "SDLHapticInterface.h"
+#import "SDLHapticHitTester.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLHapticManager : NSObject <SDLHapticInterface, SDLHapticHitTester>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLHapticManager.m b/SmartDeviceLink/SDLHapticManager.m
new file mode 100644
index 000000000..95c69454e
--- /dev/null
+++ b/SmartDeviceLink/SDLHapticManager.m
@@ -0,0 +1,154 @@
+//
+// SDLHapticManager.m
+// SmartDeviceLink-iOS
+//
+// Copyright © 2017 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLHapticManager.h"
+#import "SDLNotificationConstants.h"
+#import "SDLRectangle.h"
+#import "SDLHapticRect.h"
+#import "SDLSendHapticData.h"
+#import "SDLTouch.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLHapticManager()
+
+/**
+ The projection window associated with the Haptic Manager
+ */
+@property (nonatomic, weak) UIWindow *projectionWindow;
+
+/**
+ Array of focusable view objects extracted from the projection window
+ */
+@property (nonatomic, strong) NSMutableArray<UIView *> *focusableViews;
+
+/**
+ reference to SDLConnectionManager
+ */
+@property (nonatomic, weak) id<SDLConnectionManagerType> connectionManager;
+@end
+
+
+@implementation SDLHapticManager
+
+- (instancetype)initWithWindow:(UIWindow *)window connectionManager:(id<SDLConnectionManagerType>)connectionManager{
+ self = [super init];
+ if(!self) {
+ return nil;
+ }
+
+ _projectionWindow = window;
+ _connectionManager = connectionManager;
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_projectionViewUpdated:) name:SDLDidUpdateProjectionView object:nil];
+
+ return self;
+}
+
+- (void)updateInterfaceLayout {
+ self.focusableViews = [[NSMutableArray alloc] init];
+ [self sdl_parseViewHierarchy:self.projectionWindow.subviews.lastObject];
+
+ // If there is a preferred view bring that into top of the array
+ NSUInteger preferredViewIndex = [self.focusableViews indexOfObject:self.projectionWindow.subviews.lastObject.preferredFocusedView];
+ if (preferredViewIndex != NSNotFound && self.focusableViews.count > 1) {
+ [self.focusableViews exchangeObjectAtIndex:preferredViewIndex withObjectAtIndex:0];
+ }
+
+ [self sdl_sendHapticRPC];
+}
+
+/**
+ Crawls through the views recursively and adds focusable view into the member array
+
+ @param currentView is the view hierarchy to be processed
+ */
+- (void)sdl_parseViewHierarchy:(UIView *)currentView {
+ if (currentView == nil) {
+ SDLLogW(@"Error: Cannot parse nil view");
+ return;
+ }
+
+ NSArray *focusableSubviews = [currentView.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView * _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
+ return (evaluatedObject.canBecomeFocused || [evaluatedObject isKindOfClass:[UIButton class]]);
+ }]];
+
+ BOOL isButton = [currentView isKindOfClass:[UIButton class]];
+ //if current view is focusable and it doesn't have any focusable sub views then add the cuurent view and return
+ if ((currentView.canBecomeFocused || isButton) && focusableSubviews.count == 0) {
+ [self.focusableViews addObject:currentView];
+ return;
+ }
+ // if current view has focusable sub views parse them recursively
+ else if (currentView.subviews.count > 0) {
+ NSArray<UIView *> *subviews = currentView.subviews;
+
+ for (UIView *childView in subviews) {
+ [self sdl_parseViewHierarchy:childView];
+ }
+ }
+ //else just return
+ else {
+ return;
+ }
+}
+
+/**
+ Iterates through the focusable views, extracts rectangular parameters, creates Haptic RPC request and sends it
+ */
+- (void)sdl_sendHapticRPC {
+ NSMutableArray<SDLHapticRect *> *hapticRects = [[NSMutableArray alloc] init];
+
+ for (UIView *view in self.focusableViews) {
+ CGPoint originOnScreen = [self.projectionWindow convertPoint:view.frame.origin toView:nil];
+ CGRect convertedRect = {originOnScreen, view.bounds.size};
+ SDLRectangle* rect = [[SDLRectangle alloc] initWithCGRect:(convertedRect)];
+ // using the view index as the id field in SendHapticData request (should be guaranteed unique)
+ NSUInteger rectId = [self.focusableViews indexOfObject:view];
+ SDLHapticRect *hapticRect = [[SDLHapticRect alloc] initWithId:(UInt32)rectId rect:rect];
+ [hapticRects addObject:hapticRect];
+ }
+
+ SDLSendHapticData* hapticRPC = [[SDLSendHapticData alloc] initWithHapticRectData:hapticRects];
+ [self.connectionManager sendManagerRequest:hapticRPC withResponseHandler:nil];
+}
+
+#pragma mark SDLHapticHitTester functions
+- (nullable UIView *)viewForSDLTouch:(SDLTouch *)touch {
+ UIView *selectedView = nil;
+
+ for (UIView *view in self.focusableViews) {
+ //Convert the absolute location to local location and check if that falls within view boundary
+ CGPoint localPoint = [view convertPoint:touch.location fromView:self.projectionWindow];
+ if ([view pointInside:localPoint withEvent:nil]) {
+ if (selectedView != nil) {
+ selectedView = nil;
+ break;
+ //the point has been indentified in two views. We cannot identify which with confidence.
+ } else {
+ selectedView = view;
+ }
+ }
+ }
+
+ return selectedView;
+}
+
+#pragma mark notifications
+/**
+ Function that gets called when projection view updated notification occurs.
+
+ @param notification object with notification data
+ */
+- (void)sdl_projectionViewUpdated:(NSNotification *)notification {
+ [self updateInterfaceLayout];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLNotificationConstants.h b/SmartDeviceLink/SDLNotificationConstants.h
index 63c381d9d..be7c0b2aa 100644
--- a/SmartDeviceLink/SDLNotificationConstants.h
+++ b/SmartDeviceLink/SDLNotificationConstants.h
@@ -82,6 +82,7 @@ extern SDLNotificationName const SDLTransportDidConnect;
extern SDLNotificationName const SDLDidReceiveError;
extern SDLNotificationName const SDLDidReceiveLockScreenIcon;
extern SDLNotificationName const SDLDidBecomeReady;
+extern SDLNotificationName const SDLDidUpdateProjectionView;
/**
* NSNotification names associated with specific RPC responses.
diff --git a/SmartDeviceLink/SDLNotificationConstants.m b/SmartDeviceLink/SDLNotificationConstants.m
index 1605596c2..813a07ca6 100644
--- a/SmartDeviceLink/SDLNotificationConstants.m
+++ b/SmartDeviceLink/SDLNotificationConstants.m
@@ -18,6 +18,7 @@ SDLNotificationName const SDLDidReceiveError = @"com.sdl.general.error";
SDLNotificationName const SDLDidReceiveLockScreenIcon = @"com.sdl.general.lockscreenIconReceived";
SDLNotificationName const SDLDidBecomeReady = @"com.sdl.notification.managerReady";
SDLNotificationName const SDLDidReceiveVehicleIconNotification = @"com.sdl.notification.vehicleIcon";
+SDLNotificationName const SDLDidUpdateProjectionView = @"com.sdl.notification.projectionViewUpdate";
#pragma mark - RPC Responses
SDLNotificationName const SDLDidReceiveAddCommandResponse = @"com.sdl.response.addCommand";
diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m
new file mode 100644
index 000000000..527fe8e44
--- /dev/null
+++ b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m
@@ -0,0 +1,383 @@
+//
+// SDLHapticManagerSpec.m
+// SmartDeviceLink-iOS
+//
+// Copyright © 2017 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLHapticManager.h"
+#import "SDLSendHapticData.h"
+#import "SDLManager.h"
+#import "SDLTouchCoord.h"
+#import "SDLTouchEvent.h"
+#import "SDLTouch.h"
+#import "SDLRectangle.h"
+#import "SDLLifecycleManager.h"
+
+SDLTouch* generateTouchEvent(int xCoord, int yCoord)
+{
+ unsigned long timeStamp = 10;
+ SDLTouchCoord* firstCoord = [[SDLTouchCoord alloc] init];
+ firstCoord.x = @(xCoord);
+ firstCoord.y = @(yCoord);
+
+ SDLTouchEvent* firstTouchEvent = [[SDLTouchEvent alloc] init];
+ firstTouchEvent.touchEventId = @0;
+ firstTouchEvent.coord = [NSArray arrayWithObject:firstCoord];
+ firstTouchEvent.timeStamp = [NSMutableArray arrayWithObject:@(timeStamp)];
+ SDLTouch* firstTouch = [[SDLTouch alloc] initWithTouchEvent:firstTouchEvent];
+ return firstTouch;
+}
+
+BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect)
+{
+ expect(sdlRectangle.x).to(equal(cgRect.origin.x));
+ expect(sdlRectangle.y).to(equal(cgRect.origin.y));
+ expect(sdlRectangle.width).to(equal(cgRect.size.width));
+ expect(sdlRectangle.height).to(equal(cgRect.size.height));
+ return YES;
+}
+
+QuickSpecBegin(SDLHapticManagerSpec)
+
+describe(@"the haptic manager", ^{
+ __block UIWindow *uiWindow;
+ __block UIViewController *uiViewController;
+
+ __block SDLHapticManager *hapticManager;
+ __block SDLSendHapticData* sentHapticRequest;
+
+ __block id sdlLifecycleManager = OCMClassMock([SDLLifecycleManager class]);
+ __block CGRect viewRect1;
+ __block CGRect viewRect2;
+
+ beforeEach(^{
+ hapticManager = nil;
+ sentHapticRequest = nil;
+
+ uiWindow = [[UIWindow alloc] init];
+ uiViewController = [[UIViewController alloc] init];
+
+ [uiWindow addSubview:uiViewController.view];
+
+ OCMExpect( [[sdlLifecycleManager stub] sendManagerRequest:[OCMArg checkWithBlock:^BOOL(id value){
+ BOOL isFirstArg = [value isKindOfClass:[SDLSendHapticData class]];
+ if(isFirstArg) {
+ sentHapticRequest = value;
+ }
+ return YES;
+ }] withResponseHandler:[OCMArg any]]);
+
+ });
+
+ context(@"when initialized with no focusable view", ^{
+ beforeEach(^{
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have no focusable view", ^{
+ OCMVerify(sdlLifecycleManager);
+ expect(sentHapticRequest.hapticRectData.count).to(equal(0));
+ });
+ });
+
+ context(@"when initialized with single view", ^{
+ beforeEach(^{
+ viewRect1 = CGRectMake(101, 101, 50, 50);
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1];
+ [uiWindow insertSubview:textField1 aboveSubview:uiWindow];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have one view", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 1;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect = hapticRectData[0];
+ SDLRectangle *sdlRect = sdlhapticRect.rect;
+
+ compareRectangle(sdlRect, viewRect1);
+ }
+ });
+ });
+
+ context(@"when initialized with single button view", ^{
+ beforeEach(^{
+ viewRect1 = CGRectMake(101, 101, 50, 50);
+ UIButton *button = [[UIButton alloc] initWithFrame:viewRect1];
+ [uiWindow addSubview:button];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have one view", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 1;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect = hapticRectData[0];
+ SDLRectangle *sdlRect = sdlhapticRect.rect;
+
+ compareRectangle(sdlRect, viewRect1);
+ }
+ });
+ });
+
+ context(@"when initialized with no views and then updated with two additional views", ^{
+ beforeEach(^{
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+
+ viewRect1 = CGRectMake(101, 101, 50, 50);
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1];
+ [uiViewController.view addSubview:textField1];
+
+ viewRect2 = CGRectMake(201, 201, 50, 50);
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2];
+ [uiViewController.view addSubview:textField2];
+
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have two views", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 2;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect1 = hapticRectData[0];
+ SDLRectangle *sdlRect1 = sdlhapticRect1.rect;
+
+ SDLHapticRect *sdlhapticRect2 = hapticRectData[1];
+ SDLRectangle *sdlRect2 = sdlhapticRect2.rect;
+
+ compareRectangle(sdlRect1, viewRect1);
+ compareRectangle(sdlRect2, viewRect2);
+ }
+ });
+ });
+
+ context(@"when initialized with nested views", ^{
+ beforeEach(^{
+ UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)];
+ [uiViewController.view addSubview:textField];
+
+ viewRect1 = CGRectMake(110, 110, 10, 10);
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1];
+ [textField addSubview:textField1];
+
+ viewRect2 = CGRectMake(130, 130, 10, 10);
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2];
+ [textField addSubview:textField2];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have only leaf views added", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 2;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect1 = hapticRectData[0];
+ SDLRectangle *sdlRect1 = sdlhapticRect1.rect;
+
+ SDLHapticRect *sdlhapticRect2 = hapticRectData[1];
+ SDLRectangle *sdlRect2 = sdlhapticRect2.rect;
+
+ compareRectangle(sdlRect1, viewRect1);
+ compareRectangle(sdlRect2, viewRect2);
+ }
+ });
+ });
+
+ context(@"when initialized with nested button views", ^{
+ beforeEach(^{
+ UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(101, 101, 50, 50)];
+ [uiViewController.view addSubview:button];
+
+ viewRect1 = CGRectMake(110, 110, 10, 10);
+ UIButton *button1 = [[UIButton alloc] initWithFrame:viewRect1];
+ [button addSubview:button1];
+
+ viewRect2 = CGRectMake(130, 130, 10, 10);
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2];
+ [button addSubview:textField2];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have only leaf views added", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 2;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect1 = hapticRectData[0];
+ SDLRectangle *sdlRect1 = sdlhapticRect1.rect;
+
+ SDLHapticRect *sdlhapticRect2 = hapticRectData[1];
+ SDLRectangle *sdlRect2 = sdlhapticRect2.rect;
+
+ compareRectangle(sdlRect1, viewRect1);
+ compareRectangle(sdlRect2, viewRect2);
+ }
+ });
+ });
+
+ context(@"when initialized with two views and then updated with one view removed", ^{
+ beforeEach(^{
+ viewRect1 = CGRectMake(101, 101, 50, 50);
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1];
+ [uiViewController.view addSubview:textField1];
+
+ viewRect2 = CGRectMake(201, 201, 50, 50);
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2];
+ [uiViewController.view addSubview:textField2];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+
+ [textField2 removeFromSuperview];
+
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should have one view", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 1;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect = hapticRectData[0];
+ SDLRectangle *sdlRect = sdlhapticRect.rect;
+
+ compareRectangle(sdlRect, viewRect1);
+ }
+ });
+ });
+
+ context(@"when initialized with one view and notified after adding one more view", ^{
+ beforeEach(^{
+ viewRect1 = CGRectMake(101, 101, 50, 50);
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1];
+ [uiViewController.view addSubview:textField1];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+
+ viewRect2 = CGRectMake(201, 201, 50, 50);
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2];
+ [uiViewController.view addSubview:textField2];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:SDLDidUpdateProjectionView object:nil];
+ });
+
+ it(@"should have two views", ^{
+ OCMVerify(sdlLifecycleManager);
+
+ int expectedCount = 2;
+ expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount));
+
+ if(sentHapticRequest.hapticRectData.count == expectedCount) {
+ NSArray<SDLHapticRect *> *hapticRectData = sentHapticRequest.hapticRectData;
+ SDLHapticRect *sdlhapticRect1 = hapticRectData[0];
+ SDLRectangle *sdlRect1 = sdlhapticRect1.rect;
+
+ SDLHapticRect *sdlhapticRect2 = hapticRectData[1];
+ SDLRectangle *sdlRect2 = sdlhapticRect2.rect;
+
+ compareRectangle(sdlRect1, viewRect1);
+ compareRectangle(sdlRect2, viewRect2);
+ }
+ });
+ });
+
+ context(@"when touched inside a view", ^{
+ beforeEach(^{
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)];
+ [uiViewController.view addSubview:textField1];
+
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:CGRectMake(201, 201, 50, 50)];
+ [uiViewController.view addSubview:textField2];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should return a view object", ^{
+ SDLTouch* collisionTouch1 = generateTouchEvent(125, 120);
+ UIView* view1 = [hapticManager viewForSDLTouch:collisionTouch1];
+
+ expect(view1).toNot(beNil());
+
+ SDLTouch* collisionTouch2 = generateTouchEvent(202, 249);
+ UIView* view2 = [hapticManager viewForSDLTouch:collisionTouch2];
+
+ expect(view2).toNot(beNil());
+ });
+ });
+
+ context(@"when touched in overlapping views' area", ^{
+ beforeEach(^{
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)];
+ [uiViewController.view addSubview:textField1];
+
+ UITextField *textField2 = [[UITextField alloc] initWithFrame:CGRectMake(126, 126, 50, 50)];
+ [uiViewController.view addSubview:textField2];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+
+ it(@"should return no view object", ^{
+ SDLTouch* collisionTouch = generateTouchEvent(130, 130);
+ UIView* view = [hapticManager viewForSDLTouch:collisionTouch];
+
+ expect(view).to(beNil());
+ });
+ });
+
+ context(@"when touched outside view boundary", ^{
+ beforeEach(^{
+ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)];
+ [uiWindow insertSubview:textField1 aboveSubview:uiWindow];
+
+ hapticManager = [[SDLHapticManager alloc] initWithWindow:uiWindow connectionManager:sdlLifecycleManager];
+ [hapticManager updateInterfaceLayout];
+ });
+ it(@"should return nil", ^{
+ SDLTouch* collisionTouch = generateTouchEvent(0, 228);
+ UIView* view = [hapticManager viewForSDLTouch:collisionTouch];
+ expect(view).to(beNil());
+ });
+
+ });
+});
+
+QuickSpecEnd