Skip to content

Commit

Permalink
Merge pull request #623 from wix/UserActivity
Browse files Browse the repository at this point in the history
iOS: Add support for userActivity API
  • Loading branch information
rotemmiz authored Mar 23, 2018
2 parents 698acd4 + 2714db7 commit 792a782
Show file tree
Hide file tree
Showing 28 changed files with 618 additions and 275 deletions.
7 changes: 5 additions & 2 deletions detox/ios/Detox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
397EC9B61E7EDE0B00D5F2BB /* EarlGreyStatistics.m in Sources */ = {isa = PBXBuildFile; fileRef = 397EC9B41E7EDE0B00D5F2BB /* EarlGreyStatistics.m */; };
39A34C711E30F10D00BEBB59 /* DetoxAppDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
39A34C721E30F10D00BEBB59 /* DetoxAppDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 39A34C701E30F10D00BEBB59 /* DetoxAppDelegateProxy.m */; };
39AB2D31205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39AB2D30205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift */; };
39C3C3511DBF9A13008177E1 /* EarlGrey.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 394767DC1DBF991E00D72256 /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
39C3C3531DBF9A19008177E1 /* SocketRocket.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 394767E91DBF992400D72256 /* SocketRocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
39CEFCDB1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CEFCDA1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift */; };
Expand Down Expand Up @@ -239,9 +240,9 @@
397EC9B41E7EDE0B00D5F2BB /* EarlGreyStatistics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EarlGreyStatistics.m; sourceTree = "<group>"; };
39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetoxAppDelegateProxy.h; sourceTree = "<group>"; };
39A34C701E30F10D00BEBB59 /* DetoxAppDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetoxAppDelegateProxy.m; sourceTree = "<group>"; };
39AB2D30205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetoxUserActivityDispatcher.swift; sourceTree = "<group>"; };
39CEFCDA1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetoxUserNotificationDispatcher.swift; sourceTree = "<group>"; };
39D91D2E202B4128008DD636 /* test */ = {isa = PBXFileReference; lastKnownFileType = folder; name = test; path = ../test; sourceTree = "<group>"; };
39FFD9451FD730A600C97030 /* DetoxCrashHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DetoxCrashHandler.mm; sourceTree = "<group>"; };
39D91D2E202B4128008DD636 /* test */ = {isa = PBXFileReference; lastKnownFileType = folder; name = test; path = ../test; sourceTree = "<group>"; };
39F642201FDD5EB000468FED /* DTXLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DTXLogging.h; path = DTXLoggingInfra/DTXLogging.h; sourceTree = SOURCE_ROOT; };
39F642271FDD5EB000468FED /* DTXLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DTXLogging.m; path = DTXLoggingInfra/DTXLogging.m; sourceTree = SOURCE_ROOT; };
39F6422A1FDD5EEC00468FED /* Detox.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Detox.pch; sourceTree = "<group>"; };
Expand Down Expand Up @@ -367,6 +368,7 @@
39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */,
39A34C701E30F10D00BEBB59 /* DetoxAppDelegateProxy.m */,
39CEFCDA1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift */,
39AB2D30205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift */,
394767A61DBF987E00D72256 /* DTXMethodInvocation.h */,
394767A71DBF987E00D72256 /* DTXMethodInvocation.m */,
394767A81DBF987E00D72256 /* TestFailureHandler.h */,
Expand Down Expand Up @@ -709,6 +711,7 @@
391FA5EA1E7FD96D0056F82F /* GREYIdlingResourcePrettyPrint.m in Sources */,
394767CF1DBF98D900D72256 /* ReactNativeSupport.m in Sources */,
394767B51DBF987E00D72256 /* TestRunner.m in Sources */,
39AB2D31205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift in Sources */,
394767D31DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m in Sources */,
394767C01DBF98A700D72256 /* GREYCondition+Detox.m in Sources */,
A7F76A161ED33DE500FFE77E /* WXAnimatedDisplayLinkIdlingResource.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down Expand Up @@ -57,7 +56,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -37,7 +36,6 @@
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
5 changes: 3 additions & 2 deletions detox/ios/Detox/DetoxAppDelegateProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

@property (class, nonatomic, strong, readonly) DetoxAppDelegateProxy* currentAppDelegateProxy;

- (void)dispatchUserNotificationFromDataURL:(NSURL*)userNotificationDataURL delayUntilActive:(BOOL)delay;
- (void)dispatchOpenURL:(NSURL*)URL options:(NSDictionary*)options delayUntilActive:(BOOL)delay;
- (void)__dtx_dispatchUserActivityFromDataURL:(NSURL*)userActivityDataURL delayUntilActive:(BOOL)delay;
- (void)__dtx_dispatchUserNotificationFromDataURL:(NSURL*)userNotificationDataURL delayUntilActive:(BOOL)delay;
- (void)__dtx_dispatchOpenURL:(NSURL*)URL options:(NSDictionary*)options delayUntilActive:(BOOL)delay;

@end
169 changes: 112 additions & 57 deletions detox/ios/Detox/DetoxAppDelegateProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,37 @@

static DetoxAppDelegateProxy* _currentAppDelegateProxy;
static NSMutableArray<NSDictionary*>* _pendingOpenURLs;
static NSMutableArray<NSURL*>* _pendingUserNotifications;
static NSMutableArray<DetoxUserNotificationDispatcher*>* _pendingUserNotificationDispatchers;
static NSMutableArray<DetoxUserActivityDispatcher*>* _pendingUserActivityDispatchers;
static DetoxUserActivityDispatcher* _pendingLaunchUserActivityDispatcher;
static DetoxUserNotificationDispatcher* _pendingLaunchUserNotificationDispatcher;

static COSTouchVisualizerWindow* _touchVisualizerWindow;

static NSURL* _launchUserNotificationDataURL()
{
NSString* userNotificationDataPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"detoxUserNotificationDataURL"];

if(userNotificationDataPath == nil)
{
return nil;
}

return [NSURL fileURLWithPath:userNotificationDataPath];
}

static NSURL* _launchUserActivityDataURL()
{
NSString* userActivityDataPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"detoxUserActivityDataURL"];

if(userActivityDataPath == nil)
{
return nil;
}

return [NSURL fileURLWithPath:userActivityDataPath];
}

@interface UIWindow (DTXEventProxy) @end

@implementation UIWindow (DTXEventProxy)
Expand Down Expand Up @@ -95,7 +122,20 @@ + (void)load
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_pendingOpenURLs = [NSMutableArray new];
_pendingUserNotifications = [NSMutableArray new];
_pendingUserNotificationDispatchers = [NSMutableArray new];
_pendingUserActivityDispatchers = [NSMutableArray new];

NSURL* url = _launchUserActivityDataURL();
if(url)
{
_pendingLaunchUserActivityDispatcher = [[DetoxUserActivityDispatcher alloc] initWithUserActivityDataURL:url];
}

url = _launchUserNotificationDataURL();
if(url)
{
_pendingLaunchUserNotificationDispatcher = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:url];
}

Method m = class_getInstanceMethod([UIApplication class], @selector(setDelegate:));
void (*orig)(id, SEL, id<UIApplicationDelegate>) = (void*)method_getImplementation(m);
Expand Down Expand Up @@ -128,8 +168,6 @@ - (void)__dtx_canaryInTheCoalMine {}

- (void)__dtx_applicationDidLaunchNotification:(NSNotification*)notification
{
[self.__dtx_userNotificationDispatcher dispatchOnAppDelegate:self simulateDuringLaunch:YES];

dispatch_async(dispatch_get_main_queue(), ^{
_touchVisualizerWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
_touchVisualizerWindow.windowLevel = 100000000000;
Expand All @@ -140,69 +178,55 @@ - (void)__dtx_applicationDidLaunchNotification:(NSNotification*)notification
});
}

- (NSURL*)_userNotificationDataURL
{
NSString* userNotificationDataPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"detoxUserNotificationDataURL"];

if(userNotificationDataPath == nil)
{
return nil;
}

return [NSURL fileURLWithPath:userNotificationDataPath];
}

- (NSURL*)_URLOverride
- (NSURL*)__dtx_URLOverride
{
return [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:@"detoxURLOverride"]];
}

- (NSString*)_sourceAppOverride
- (NSString*)__dtx_sourceAppOverride
{
return [[NSUserDefaults standardUserDefaults] objectForKey:@"detoxSourceAppOverride"];
}

- (NSDictionary*)_prepareLaunchOptions:(NSDictionary*)launchOptions userNotificationDispatcher:(DetoxUserNotificationDispatcher*)dispatcher
- (NSDictionary*)__dtx_prepareLaunchOptions:(NSDictionary*)launchOptions userNotificationDispatcher:(DetoxUserNotificationDispatcher*)notificationDispatcher userActivityDispatcher:(DetoxUserActivityDispatcher*)activityDispatcher
{
NSMutableDictionary* rv = [launchOptions mutableCopy] ?: [NSMutableDictionary new];

if(dispatcher)
if(notificationDispatcher)
{
rv[UIApplicationLaunchOptionsRemoteNotificationKey] = [dispatcher remoteNotification];
rv[UIApplicationLaunchOptionsRemoteNotificationKey] = [notificationDispatcher remoteNotification];
}
else

if(activityDispatcher)
{
NSURL* openURLOverride = [self _URLOverride];
if(openURLOverride)
{
rv[UIApplicationLaunchOptionsURLKey] = openURLOverride;
}
NSString* originalAppOverride = [self _sourceAppOverride];
if(originalAppOverride)
{
rv[UIApplicationLaunchOptionsSourceApplicationKey] = originalAppOverride;
}
NSUserActivity* userActivity = [activityDispatcher userActivity];

NSDictionary* userActivityDictionary = @{
@"UIApplicationLaunchOptionsUserActivityKey": userActivity,
UIApplicationLaunchOptionsUserActivityTypeKey: userActivity.activityType,
};

rv[UIApplicationLaunchOptionsUserActivityDictionaryKey] = userActivityDictionary;
}

return rv;
}

- (DetoxUserNotificationDispatcher*)__dtx_userNotificationDispatcher
{
DetoxUserNotificationDispatcher* rv = objc_getAssociatedObject(self, _cmd);

if([self _userNotificationDataURL])
NSURL* openURLOverride = [self __dtx_URLOverride];
if(openURLOverride)
{
rv = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:[self _userNotificationDataURL]];
objc_setAssociatedObject(self, _cmd, rv, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
rv[UIApplicationLaunchOptionsURLKey] = openURLOverride;
}
NSString* originalAppOverride = [self __dtx_sourceAppOverride];
if(originalAppOverride)
{
rv[UIApplicationLaunchOptionsSourceApplicationKey] = originalAppOverride;
}

return rv;
}

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id>*)launchOptions
{
launchOptions = [self _prepareLaunchOptions:launchOptions userNotificationDispatcher:self.__dtx_userNotificationDispatcher];
launchOptions = [self __dtx_prepareLaunchOptions:launchOptions userNotificationDispatcher:_pendingLaunchUserNotificationDispatcher userActivityDispatcher:_pendingLaunchUserActivityDispatcher];

BOOL rv = YES;
if([class_getSuperclass(object_getClass(self)) instancesRespondToSelector:_cmd])
Expand All @@ -217,7 +241,7 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions
{
launchOptions = [self _prepareLaunchOptions:launchOptions userNotificationDispatcher:self.__dtx_userNotificationDispatcher];
launchOptions = [self __dtx_prepareLaunchOptions:launchOptions userNotificationDispatcher:_pendingLaunchUserNotificationDispatcher userActivityDispatcher:_pendingLaunchUserActivityDispatcher];

BOOL rv = YES;
if([class_getSuperclass(object_getClass(self)) instancesRespondToSelector:_cmd])
Expand All @@ -227,9 +251,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
rv = super_class(&super, _cmd, application, launchOptions);
}

if(self.__dtx_userNotificationDispatcher == nil && [self _URLOverride] && [class_getSuperclass(object_getClass(self)) instancesRespondToSelector:@selector(application:openURL:options:)])
[_pendingLaunchUserNotificationDispatcher dispatchOnAppDelegate:self simulateDuringLaunch:YES];
[_pendingLaunchUserActivityDispatcher dispatchOnAppDelegate:self];

_pendingLaunchUserNotificationDispatcher = nil;
_pendingLaunchUserActivityDispatcher = nil;

if([self __dtx_URLOverride] && [class_getSuperclass(object_getClass(self)) instancesRespondToSelector:@selector(application:openURL:options:)])
{
[self application:application openURL:[self _URLOverride] options:launchOptions];
[self application:application openURL:[self __dtx_URLOverride] options:launchOptions];
}

return rv;
Expand All @@ -246,14 +276,19 @@ - (void)applicationWillEnterForeground:(UIApplication *)application

dispatch_async(dispatch_get_main_queue(), ^{
[_pendingOpenURLs enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self _actualDispatchOpenURL:obj];
[self __dtx_actualDispatchOpenURL:obj];
}];
[_pendingOpenURLs removeAllObjects];

[_pendingUserNotifications enumerateObjectsUsingBlock:^(NSURL * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self _actualDispatchUserNotificationFromDataURL:obj];
[_pendingUserNotificationDispatchers enumerateObjectsUsingBlock:^(DetoxUserNotificationDispatcher * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self __dtx_actualDispatchUserNotificationWithDispatcher:obj];
}];
[_pendingUserNotifications removeAllObjects];
[_pendingUserNotificationDispatchers removeAllObjects];

[_pendingUserActivityDispatchers enumerateObjectsUsingBlock:^(DetoxUserActivityDispatcher * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self __dtx_actualDispatchUserActivityWithDispatcher:obj];
}];
[_pendingUserActivityDispatchers removeAllObjects];
});
}

Expand All @@ -262,33 +297,53 @@ - (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow
return YES;
}

- (void)_actualDispatchUserNotificationFromDataURL:(NSURL*)userNotificationDataURL
- (void)__dtx_actualDispatchUserActivityWithDispatcher:(DetoxUserActivityDispatcher*)dispatcher
{
[dispatcher dispatchOnAppDelegate:self];
}

- (void)__dtx_dispatchUserActivityFromDataURL:(NSURL*)userActivityDataURL delayUntilActive:(BOOL)delay
{
DetoxUserActivityDispatcher* dispatcher = [[DetoxUserActivityDispatcher alloc] initWithUserActivityDataURL:userActivityDataURL];

if(delay)
{
[_pendingUserActivityDispatchers addObject:dispatcher];
}
else
{
[self __dtx_actualDispatchUserActivityWithDispatcher:dispatcher];
}
}

- (void)__dtx_actualDispatchUserNotificationWithDispatcher:(DetoxUserNotificationDispatcher*)dispatcher
{
DetoxUserNotificationDispatcher* dispatcher = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:userNotificationDataURL];
[dispatcher dispatchOnAppDelegate:self simulateDuringLaunch:NO];
}

- (void)dispatchUserNotificationFromDataURL:(NSURL*)userNotificationDataURL delayUntilActive:(BOOL)delay
- (void)__dtx_dispatchUserNotificationFromDataURL:(NSURL*)userNotificationDataURL delayUntilActive:(BOOL)delay
{
DetoxUserNotificationDispatcher* dispatcher = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:userNotificationDataURL];

if(delay)
{
[_pendingUserNotifications addObject:userNotificationDataURL];
[_pendingUserNotificationDispatchers addObject:dispatcher];
}
else
{
[self _actualDispatchUserNotificationFromDataURL:userNotificationDataURL];
[self __dtx_actualDispatchUserNotificationWithDispatcher:dispatcher];
}
}

- (void)_actualDispatchOpenURL:(NSDictionary*)URLAndOptions
- (void)__dtx_actualDispatchOpenURL:(NSDictionary*)URLAndOptions
{
if([self respondsToSelector:@selector(application:openURL:options:)])
{
[self application:[UIApplication sharedApplication] openURL:URLAndOptions[@"URL"] options:URLAndOptions[@"options"]];
}
}

- (void)dispatchOpenURL:(NSURL*)URL options:(NSDictionary*)options delayUntilActive:(BOOL)delay
- (void)__dtx_dispatchOpenURL:(NSURL*)URL options:(NSDictionary*)options delayUntilActive:(BOOL)delay
{
NSDictionary* payload = NSDictionaryOfVariableBindings(URL, options);

Expand All @@ -298,7 +353,7 @@ - (void)dispatchOpenURL:(NSURL*)URL options:(NSDictionary*)options delayUntilAct
}
else
{
[self _actualDispatchOpenURL:payload];
[self __dtx_actualDispatchOpenURL:payload];
}
}

Expand Down
Loading

0 comments on commit 792a782

Please sign in to comment.