Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS: Add support for userActivity API #623

Merged
merged 15 commits into from
Mar 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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