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

Fix UIA :preferences strategy for Xcode 6.1 and iOS < 8.1 simulators #87

Merged
merged 5 commits into from
Oct 24, 2014
Merged
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
104 changes: 33 additions & 71 deletions calabash/Classes/Utils/LPUIAUserPrefsChannel.m
Original file line number Diff line number Diff line change
Expand Up @@ -200,105 +200,67 @@ - (NSDictionary *) requestForCommand:(NSString *) command {
// Xcode 5
// ~/Library/Application Support/iPhone Simulator/[system version]/Library/Preferences/[bundle ID].plist
//
// Xcode 6
// Xcode 6.0*
// ~/Library/Developer/CoreSimulator/Devices/[UDID]/data/Library/Preferences/[bundle ID].plist
// see http://stackoverflow.com/questions/4977673/reading-preferences-set-by-uiautomations-uiaapplication-setpreferencesvaluefork
//
// Also on Xcode 6 - When the app is first installed and launched, this plist
// file _does not exist_. However, if you ask the NSUserDefaults for its
// contents you will get back a non-empty string. It looks like NSUserDefaults
// is a concatenation of system level preferences (language, locale, newstand,
// etc.) and the application's own preferences.
//
// It is not clear whether or not the fact that file does not exist until
// the app tries to store something will influence UIA interactions.
// // Xcode 6.1
// Xcode 6.1 + iOS 8.1 - all other simulator SDKs use Xcode 6.0* rules.
// ~/Library/Developer/CoreSimulator/Devices/[Sim UDID]/data/Containers/Data/Application/[App UDID]/Library/Preferences/[bundle id].plist
- (NSString *)simulatorPreferencesPath {
static NSString *path = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

// 1. Add a token to NSUserDefaults
// Xcode 6.0 and Xcode 6.1 have different locations for the preferences
// plist. It is impossible to know at runtime which to use a priori, so
// we set a token using NSUserDefaults and look for the plist that contains
// the token.
static NSString *const tokenKey = @"sh.calabash.preferences-token";
CFUUIDRef udid = CFUUIDCreate(NULL);
NSString *tokenValue = (NSString *) CFUUIDCreateString(NULL, udid);
[[NSUserDefaults standardUserDefaults] setObject:tokenValue forKey:tokenKey];

if (udid != NULL) { CFRelease(udid); }
if (tokenValue) { [tokenValue release]; }

[[NSUserDefaults standardUserDefaults] synchronize];

NSString *plistName = [NSString stringWithFormat:@"%@.plist", [[NSBundle mainBundle] bundleIdentifier]];

// 2. Find the app's Library directory so we can deduce the plist path.
// 1. Find the app's Library directory so we can deduce the plist path.
NSArray *userLibDirURLs = [[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask];
NSURL *userLibraryURL = [userLibDirURLs lastObject];
NSString *userLibraryPath = [userLibraryURL path];

// 3. Use the the library path to deduce the simulator environment.
// 2. Use the the library path to deduce the simulator environment.

if ([userLibraryPath rangeOfString:@"CoreSimulator"].location == NSNotFound) {
// 3. Xcode < 6 environment.
NSString *sandboxPath = [userLibraryPath substringToIndex:([userLibraryPath rangeOfString:@"Applications"].location)];
NSString *relativePlistPath = [NSString stringWithFormat:@"Library/Preferences/%@", plistName];
NSString *unsanitizedPlistPath = [sandboxPath stringByAppendingPathComponent:relativePlistPath];
path = [[unsanitizedPlistPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] copy];
} else {
// First candidate. Xcode >= 6.1 CoreSimulator enviroments; preferences
// plist is in the application sandbox.
path = [[self stringForXcode61PreferencesPlistWithUserLibraryPath:userLibraryPath
plistName:plistName
tokenKey:tokenKey
tokenValue:tokenValue] copy];

// Second candidate. Xcode < 6.1 CoreSimulator environments; preferences
// plist is in the Simulator Library/Preferences.
if (!path) {
path = [[self stringForXcode60PreferencesPlistWithUserLibraryPath:userLibraryPath
plistName:plistName] copy];

/*
3. CoreSimulator environments

* In Xcode 6.1 + iOS >= 8.1, UIAutomation and NSUserDefaults do IO on
the same plist in the app's sandbox.
* In Xcode 6.1 and iOS < 8.1, UIAutomation does IO on the a file in
< SIM DIRECTORY >/data/Library/Preferences/ and NSUserDefaults does
IO on a plist in the app's sandbox.
* In Xcode 6.0*, NSUserDefaults and UIAutomation do IO on the same plist
in < SIM DIRECTORY >/data/Library/Preferences.

Since iOS 8.1 only ships with Xcode 6.1, we can check the system version
at runtime and choose the correct plist.
*/

NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
if ([systemVersion compare:@"8.1" options:NSNumericSearch] != NSOrderedAscending) {
NSString *relativePlistPath = [NSString stringWithFormat:@"Preferences/%@", plistName];
NSString *unsanitizedPlistPath = [userLibraryPath stringByAppendingPathComponent:relativePlistPath];
path = [[unsanitizedPlistPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] copy];
} else {
NSRange range = [userLibraryPath rangeOfString:@"data"];
NSString *simulatorDataPath = [userLibraryPath substringToIndex:range.location + range.length];
NSString *relativePlistPath = [NSString stringWithFormat:@"Library/Preferences/%@", plistName];
NSString *unsanitizedPlistPath = [simulatorDataPath stringByAppendingPathComponent:relativePlistPath];
path = [[unsanitizedPlistPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] copy];
}
}
});
NSLog(@"NSUserDefaults path = %@", path);
return path;
}

- (NSString *) stringForXcode61PreferencesPlistWithUserLibraryPath:(NSString *) aUserLibraryPath
plistName:(NSString *) aPlistName
tokenKey:(NSString *) aTokenKey
tokenValue:(NSString *) aTokenValue {
NSString *relativePlistPath = [NSString stringWithFormat:@"Preferences/%@", aPlistName];
NSString *unsanitizedPlistPath = [aUserLibraryPath stringByAppendingPathComponent:relativePlistPath];
NSString *path = [unsanitizedPlistPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
return nil;
}

NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
if ([[dictionary objectForKey:aTokenKey] isEqualToString:aTokenValue]) {
return path;
} else {
return nil;
}
}

- (NSString *) stringForXcode60PreferencesPlistWithUserLibraryPath:(NSString *) aUserLibraryPath
plistName:(NSString *) aPlistName {
// Xcode 6.0 and 6.0.1 have a buggy NSUserDefaults implementation. Values
// may or may not be written when [NSUserDefaults synchronize] is called, so
// it is not worth checking for the key/token pair.
NSRange range = [aUserLibraryPath rangeOfString:@"data"];
NSString *simulatorDataPath = [aUserLibraryPath substringToIndex:range.location + range.length];
NSString *relativePlistPath = [NSString stringWithFormat:@"Library/Preferences/%@", aPlistName];
NSString *unsanitizedPlistPath = [simulatorDataPath stringByAppendingPathComponent:relativePlistPath];
return [unsanitizedPlistPath stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}

#endif // TARGET_IPHONE_SIMULATOR

@end