Skip to content

Commit

Permalink
Merge pull request #87 from calabash/feature/more-simulator-preferenc…
Browse files Browse the repository at this point in the history
…es-fixes

Fix UIA :preferences strategy for Xcode 6.1 and iOS < 8.1 simulators
  • Loading branch information
jmoody committed Oct 24, 2014
2 parents 78cbe0f + 44b7413 commit 848eb47
Showing 1 changed file with 33 additions and 71 deletions.
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

0 comments on commit 848eb47

Please sign in to comment.