diff --git a/Makefile b/Makefile index 080ff8c8..625dc6a3 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ iphone: lib/iphone/phonegap-min.js lib/iphone/phonegap-min.js: lib/iphone/phonegap.js $(JAVA) -jar util/yuicompressor-2.4.2.jar --charset UTF-8 -o $@ lib/iphone/phonegap.js -lib/iphone/phonegap.js: javascripts/phonegap.js.base javascripts/acceleration.js javascripts/accelerometer.js javascripts/camera.js javascripts/contact.js javascripts/debugconsole.js javascripts/device.js javascripts/file.js javascripts/geolocation.js javascripts/compass.js javascripts/map.js javascripts/media.js javascripts/notification.js javascripts/orientation.js javascripts/position.js javascripts/sms.js javascripts/telephony.js javascripts/uicontrols.js javascripts/iphone/accelerometer.js javascripts/iphone/bonjour.js javascripts/iphone/contact.js javascripts/iphone/camera.js javascripts/iphone/debugconsole.js javascripts/iphone/device.js javascripts/iphone/geolocation.js javascripts/iphone/compass.js javascripts/iphone/media.js javascripts/iphone/notification.js javascripts/iphone/phonegap.js javascripts/iphone/uicontrols.js +lib/iphone/phonegap.js: javascripts/phonegap.js.base javascripts/acceleration.js javascripts/accelerometer.js javascripts/camera.js javascripts/contact.js javascripts/debugconsole.js javascripts/device.js javascripts/file.js javascripts/geolocation.js javascripts/compass.js javascripts/map.js javascripts/media.js javascripts/notification.js javascripts/orientation.js javascripts/position.js javascripts/sms.js javascripts/telephony.js javascripts/uicontrols.js javascripts/network.js javascripts/iphone/accelerometer.js javascripts/iphone/bonjour.js javascripts/iphone/contact.js javascripts/iphone/camera.js javascripts/iphone/debugconsole.js javascripts/iphone/device.js javascripts/iphone/geolocation.js javascripts/iphone/compass.js javascripts/iphone/media.js javascripts/iphone/notification.js javascripts/iphone/phonegap.js javascripts/iphone/uicontrols.js javascripts/iphone/network.js $(RM_RF) lib/iphone $(MKPATH) lib/iphone $(RM_F) $@ @@ -65,6 +65,7 @@ lib/iphone/phonegap.js: javascripts/phonegap.js.base javascripts/acceleration.js $(CAT) javascripts/sms.js >> $@ $(CAT) javascripts/telephony.js >> $@ $(CAT) javascripts/uicontrols.js >> $@ + $(CAT) javascripts/network.js >> $@ $(CAT) javascripts/iphone/accelerometer.js >> $@ $(CAT) javascripts/iphone/bonjour.js >> $@ $(CAT) javascripts/iphone/contact.js >> $@ @@ -77,6 +78,8 @@ lib/iphone/phonegap.js: javascripts/phonegap.js.base javascripts/acceleration.js $(CAT) javascripts/iphone/notification.js >> $@ $(CAT) javascripts/iphone/phonegap.js >> $@ $(CAT) javascripts/iphone/uicontrols.js >> $@ + $(CAT) javascripts/iphone/network.js >> $@ + blackberry: lib/blackberry/phonegap-min.js lib/blackberry/phonegap-min.js: lib/blackberry/phonegap.js diff --git a/iphone/Classes/Network.h b/iphone/Classes/Network.h new file mode 100644 index 00000000..0983f80e --- /dev/null +++ b/iphone/Classes/Network.h @@ -0,0 +1,23 @@ +// +// Network.h +// PhoneGap +// +// Created by Shazron Abdullah on 29/07/09. +// Copyright 2009 Nitobi Inc. All rights reserved. +// + +#import +#import "PhoneGapCommand.h" + +@class Reachability; + +@interface Network : PhoneGapCommand { + +} + +- (void) isReachable:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +- (void) reachabilityChanged:(NSNotification *)note; +- (void) updateReachability:(NSString*)callback; + +@end diff --git a/iphone/Classes/Network.m b/iphone/Classes/Network.m new file mode 100644 index 00000000..3dea5c7e --- /dev/null +++ b/iphone/Classes/Network.m @@ -0,0 +1,64 @@ +// +// Network.m +// PhoneGap +// +// Created by Shazron Abdullah on 29/07/09. +// Copyright 2009 Nitobi Inc. All rights reserved. +// + +#import "Network.h" +#import "Reachability.h" +#import "Categories.h" + +@implementation Network + +- (void) isReachable:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSUInteger argc = [arguments count]; + NSString* hostName = nil, *callback = nil; + + if (argc > 0) hostName = [arguments objectAtIndex:0]; + if (argc > 1) callback = [arguments objectAtIndex:1]; + + if (argc < 1) { + NSLog(@"Network.startReachability: Missing 1st argument (hostName)."); + return; + } + + if ([options existsValue:@"true" forKey:@"isIpAddress"]) { + [[Reachability sharedReachability] setAddress:hostName]; + } else { + [[Reachability sharedReachability] setHostName:hostName]; + } + + //[[Reachability sharedReachability] setNetworkStatusNotificationsEnabled:YES]; + [self updateReachability:callback]; + + //[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:@"kNetworkReachabilityChangedNotification" object:nil]; +} + +- (void)reachabilityChanged:(NSNotification *)note +{ + [self updateReachability:nil]; +} + +- (void)updateReachability:(NSString*)callback +{ + NSString* jsCallback = @"navigator.network.updateReachability"; + if (callback) + jsCallback = callback; + + NSString* status = [[NSString alloc] initWithFormat:@"%@({ hostName: '%@', ipAddress: '%@', remoteHostStatus: %d, internetConnectionStatus: %d, localWiFiConnectionStatus: %d });", + jsCallback, + [[Reachability sharedReachability] hostName], + [[Reachability sharedReachability] address], + [[Reachability sharedReachability] remoteHostStatus], + [[Reachability sharedReachability] internetConnectionStatus], + [[Reachability sharedReachability] localWiFiConnectionStatus]]; + + + [webView stringByEvaluatingJavaScriptFromString:status]; + [status release]; +} + +@end diff --git a/iphone/Classes/Reachability.h b/iphone/Classes/Reachability.h new file mode 100644 index 00000000..18beaea3 --- /dev/null +++ b/iphone/Classes/Reachability.h @@ -0,0 +1,122 @@ +/* + +File: Reachability.h +Abstract: SystemConfiguration framework wrapper. + +Version: 1.5 + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. +("Apple") in consideration of your agreement to the following terms, and your +use, installation, modification or redistribution of this Apple software +constitutes acceptance of these terms. If you do not agree with these terms, +please do not use, install, modify or redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and subject +to these terms, Apple grants you a personal, non-exclusive license, under +Apple's copyrights in this original Apple software (the "Apple Software"), to +use, reproduce, modify and redistribute the Apple Software, with or without +modifications, in source and/or binary forms; provided that if you redistribute +the Apple Software in its entirety and without modifications, you must retain +this notice and the following text and disclaimers in all such redistributions +of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Inc. may be used +to endorse or promote products derived from the Apple Software without specific +prior written permission from Apple. Except as expressly stated in this notice, +no other rights or licenses, express or implied, are granted by Apple herein, +including but not limited to any patent rights that may be infringed by your +derivative works or by other works in which the Apple Software may be +incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN +COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2008 Apple Inc. All Rights Reserved. + +*/ + +#import +#import + +@class Reachability; + +@interface Reachability : NSObject { + +@private + BOOL _networkStatusNotificationsEnabled; + + NSString *_hostName; + NSString *_address; + + NSMutableDictionary *_reachabilityQueries; +} + +/* + An enumeration that defines the return values of the network state + of the device. + */ +typedef enum { + NotReachable = 0, + ReachableViaCarrierDataNetwork, + ReachableViaWiFiNetwork +} NetworkStatus; + + +// Set to YES to register for changes in network status. Otherwise reachability queries +// will be handled synchronously. +@property BOOL networkStatusNotificationsEnabled; +// The remote host whose reachability will be queried. +// Either this or 'addressName' must be set. +@property (nonatomic, retain) NSString *hostName; +// The IP address of the remote host whose reachability will be queried. +// Either this or 'hostName' must be set. +@property (nonatomic, retain) NSString *address; +// A cache of ReachabilityQuery objects, which encapsulate a SCNetworkReachabilityRef, a host or address, and a run loop. The keys are host names or addresses. +@property (nonatomic, assign) NSMutableDictionary *reachabilityQueries; + +// This class is intended to be used as a singleton. ++ (Reachability *)sharedReachability; + +// Is self.hostName is not nil, determines its reachability. +// If self.hostName is nil and self.address is not nil, determines the reachability of self.address. +- (NetworkStatus)remoteHostStatus; +// Is the device able to communicate with Internet hosts? If so, through which network interface? +- (NetworkStatus)internetConnectionStatus; +// Is the device able to communicate with hosts on the local WiFi network? (Typically these are Bonjour hosts). +- (NetworkStatus)localWiFiConnectionStatus; + +/* + When reachability change notifications are posted, the callback method 'ReachabilityCallback' is called + and posts a notification that the client application can observe to learn about changes. + */ +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info); + +@end + +@interface ReachabilityQuery : NSObject +{ +@private + SCNetworkReachabilityRef _reachabilityRef; + CFMutableArrayRef _runLoops; + NSString *_hostNameOrAddress; +} +// Keep around each network reachability query object so that we can +// register for updates from those objects. +@property (nonatomic) SCNetworkReachabilityRef reachabilityRef; +@property (nonatomic, retain) NSString *hostNameOrAddress; +@property (nonatomic) CFMutableArrayRef runLoops; + +- (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop; + +@end + diff --git a/iphone/Classes/Reachability.m b/iphone/Classes/Reachability.m new file mode 100644 index 00000000..c1f159c5 --- /dev/null +++ b/iphone/Classes/Reachability.m @@ -0,0 +1,585 @@ +/* + +File: Reachability.m +Abstract: SystemConfiguration framework wrapper. + +Version: 1.5 + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. +("Apple") in consideration of your agreement to the following terms, and your +use, installation, modification or redistribution of this Apple software +constitutes acceptance of these terms. If you do not agree with these terms, +please do not use, install, modify or redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and subject +to these terms, Apple grants you a personal, non-exclusive license, under +Apple's copyrights in this original Apple software (the "Apple Software"), to +use, reproduce, modify and redistribute the Apple Software, with or without +modifications, in source and/or binary forms; provided that if you redistribute +the Apple Software in its entirety and without modifications, you must retain +this notice and the following text and disclaimers in all such redistributions +of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Inc. may be used +to endorse or promote products derived from the Apple Software without specific +prior written permission from Apple. Except as expressly stated in this notice, +no other rights or licenses, express or implied, are granted by Apple herein, +including but not limited to any patent rights that may be infringed by your +derivative works or by other works in which the Apple Software may be +incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN +COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2008 Apple Inc. All Rights Reserved. + +*/ + +#import +#import +#import +#import +#import +#include + +#import "Reachability.h" +#import + +static NSString *kLinkLocalAddressKey = @"169.254.0.0"; +static NSString *kDefaultRouteKey = @"0.0.0.0"; + +static Reachability *_sharedReachability; + +// A class extension that declares internal methods for this class. +@interface Reachability() +- (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; +- (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags; +- (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName; +- (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)address; +- (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)outAddress; +- (void)stopListeningForReachabilityChanges; +@end + +@implementation Reachability + +@synthesize networkStatusNotificationsEnabled = _networkStatusNotificationsEnabled; +@synthesize hostName = _hostName; +@synthesize address = _address; +@synthesize reachabilityQueries = _reachabilityQueries; + ++ (Reachability *)sharedReachability +{ + if (!_sharedReachability) { + _sharedReachability = [[Reachability alloc] init]; + // Clients of Reachability will typically call [[Reachability sharedReachability] setHostName:] + // before calling one of the status methods. + _sharedReachability.hostName = nil; + _sharedReachability.address = nil; + _sharedReachability.networkStatusNotificationsEnabled = NO; + _sharedReachability.reachabilityQueries = [[NSMutableDictionary alloc] init]; + } + return _sharedReachability; +} + +- (void) dealloc +{ + [self stopListeningForReachabilityChanges]; + + [_sharedReachability.reachabilityQueries release]; + [_sharedReachability release]; + [super dealloc]; +} + +- (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags +{ + // kSCNetworkReachabilityFlagsReachable indicates that the specified nodename or address can + // be reached using the current network configuration. + BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable; + + // This flag indicates that the specified nodename or address can + // be reached using the current network configuration, but a + // connection must first be established. + // + // If the flag is false, we don't have a connection. But because CFNetwork + // automatically attempts to bring up a WWAN connection, if the WWAN reachability + // flag is present, a connection is not required. + BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired); + if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) { + noConnectionRequired = YES; + } + + return (isReachable && noConnectionRequired) ? YES : NO; +} + +// Returns whether or not the current host name is reachable with the current network configuration. +- (BOOL)isHostReachable:(NSString *)host +{ + if (!host || ![host length]) { + return NO; + } + + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [host UTF8String]); + BOOL gotFlags = SCNetworkReachabilityGetFlags(reachability, &flags); + + CFRelease(reachability); + + if (!gotFlags) { + return NO; + } + + return [self isReachableWithoutRequiringConnection:flags]; +} + +// This returns YES if the address 169.254.0.0 is reachable without requiring a connection. +- (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags +{ + // Look in the cache of reachability queries for one that matches this query. + ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kLinkLocalAddressKey]; + SCNetworkReachabilityRef adHocWiFiNetworkReachability = query.reachabilityRef; + + // If a cached reachability query was not found, create one. + if (!adHocWiFiNetworkReachability) { + + // Build a sockaddr_in that we can pass to the address reachability query. + struct sockaddr_in sin; + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + sin.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + adHocWiFiNetworkReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&sin); + + query = [[[ReachabilityQuery alloc] init] autorelease]; + query.hostNameOrAddress = kLinkLocalAddressKey; + query.reachabilityRef = adHocWiFiNetworkReachability; + + // Add the reachability query to the cache. + [self.reachabilityQueries setObject:query forKey:kLinkLocalAddressKey]; + } + + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register + // to receive notifications from it in the current run loop, which may be different than the run loop + // that was previously used when registering the SCNetworkReachabilityRef for notifications. + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. + // By default, they are not enabled. + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; + + SCNetworkReachabilityFlags addressReachabilityFlags; + BOOL gotFlags = SCNetworkReachabilityGetFlags(adHocWiFiNetworkReachability, &addressReachabilityFlags); + if (!gotFlags) { + // There was an error getting the reachability flags. + return NO; + } + + // Callers of this method might want to use the reachability flags, so if an 'out' parameter + // was passed in, assign the reachability flags to it. + if (outFlags) { + *outFlags = addressReachabilityFlags; + } + + return [self isReachableWithoutRequiringConnection:addressReachabilityFlags]; +} + +// ReachabilityCallback is registered as the callback for network state changes in startListeningForReachabilityChanges. +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName:@"kNetworkReachabilityChangedNotification" object:nil]; + + [pool release]; +} + +// Perform a reachability query for the address 0.0.0.0. If that address is reachable without +// requiring a connection, a network interface is available. We'll have to do more work to +// determine which network interface is available. +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags +{ + ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kDefaultRouteKey]; + SCNetworkReachabilityRef defaultRouteReachability = query.reachabilityRef; + + // If a cached reachability query was not found, create one. + if (!defaultRouteReachability) { + + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); + + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; + query.hostNameOrAddress = kDefaultRouteKey; + query.reachabilityRef = defaultRouteReachability; + + [self.reachabilityQueries setObject:query forKey:kDefaultRouteKey]; + } + + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register + // to receive notifications from it in the current run loop, which may be different than the run loop + // that was previously used when registering the SCNetworkReachabilityRef for notifications. + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. + // By default, they are not enabled. + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; + + SCNetworkReachabilityFlags flags; + BOOL gotFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); + if (!gotFlags) { + return NO; + } + + BOOL isReachable = [self isReachableWithoutRequiringConnection:flags]; + + // Callers of this method might want to use the reachability flags, so if an 'out' parameter + // was passed in, assign the reachability flags to it. + if (outFlags) { + *outFlags = flags; + } + + return isReachable; +} + +// Be a good citizen and unregister for network state changes when the application terminates. +- (void)stopListeningForReachabilityChanges +{ + // Walk through the cache that holds SCNetworkReachabilityRefs for reachability + // queries to particular hosts or addresses. + NSEnumerator *enumerator = [self.reachabilityQueries objectEnumerator]; + ReachabilityQuery *reachabilityQuery; + + while (reachabilityQuery = [enumerator nextObject]) { + + CFArrayRef runLoops = reachabilityQuery.runLoops; + NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(runLoops); + + for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) { + CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(runLoops, runLoopCounter); + + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityQuery.reachabilityRef, nextRunLoop, kCFRunLoopDefaultMode); + } + + CFArrayRemoveAllValues(reachabilityQuery.runLoops); + } +} + +/* + Create a SCNetworkReachabilityRef for hostName, which lets us determine if hostName + is currently reachable, and lets us register to receive notifications when the + reachability of hostName changes. + */ +- (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName +{ + if (!hostName || ![hostName length]) { + return NULL; + } + + // Look in the cache for an existing SCNetworkReachabilityRef for hostName. + ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:hostName]; + SCNetworkReachabilityRef reachabilityRefForHostName = cachedQuery.reachabilityRef; + + if (reachabilityRefForHostName) { + return reachabilityRefForHostName; + } + + // Didn't find an existing SCNetworkReachabilityRef for hostName, so create one ... + reachabilityRefForHostName = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [hostName UTF8String]); + + NSAssert1(reachabilityRefForHostName != NULL, @"Failed to create SCNetworkReachabilityRef for host: %@", hostName); + + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; + query.hostNameOrAddress = hostName; + query.reachabilityRef = reachabilityRefForHostName; + + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register + // to receive notifications from it in the current run loop, which may be different than the run loop + // that was previously used when registering the SCNetworkReachabilityRef for notifications. + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. + // By default, they are not enabled. + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; + + // ... and add it to the cache. + [self.reachabilityQueries setObject:query forKey:hostName]; + return reachabilityRefForHostName; +} + +/* + Create a SCNetworkReachabilityRef for the IP address in addressString, which lets us determine if + the address is currently reachable, and lets us register to receive notifications when the + reachability of the address changes. + */ +- (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)addressString +{ + if (!addressString || ![addressString length]) { + return NULL; + } + + struct sockaddr_in address; + + BOOL gotAddress = [self addressFromString:addressString address:&address]; + if (!gotAddress) { + // The attempt to convert addressString to a sockaddr_in failed. + NSAssert1(gotAddress != NO, @"Failed to convert an IP address string to a sockaddr_in: %@", addressString); + return NULL; + } + + // Look in the cache for an existing SCNetworkReachabilityRef for addressString. + ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:addressString]; + SCNetworkReachabilityRef reachabilityRefForAddress = cachedQuery.reachabilityRef; + + if (reachabilityRefForAddress) { + return reachabilityRefForAddress; + } + + // Didn't find an existing SCNetworkReachabilityRef for addressString, so create one. + reachabilityRefForAddress = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&address); + + NSAssert1(reachabilityRefForAddress != NULL, @"Failed to create SCNetworkReachabilityRef for address: %@", addressString); + + ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; + query.hostNameOrAddress = addressString; + query.reachabilityRef = reachabilityRefForAddress; + + // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. + // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register + // to receive notifications from it in the current run loop, which may be different than the run loop + // that was previously used when registering the SCNetworkReachabilityRef for notifications. + // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. + // By default, they are not enabled. + [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; + + // ... and add it to the cache. + [self.reachabilityQueries setObject:query forKey:addressString]; + return reachabilityRefForAddress; +} + +- (NetworkStatus)remoteHostStatus +{ + /* + If the current host name or address is reachable, determine which network interface it is reachable through. + If the host is reachable and the reachability flags include kSCNetworkReachabilityFlagsIsWWAN, it + is reachable through the carrier data network. If the host is reachable and the reachability + flags do not include kSCNetworkReachabilityFlagsIsWWAN, it is reachable through the WiFi network. + */ + + SCNetworkReachabilityRef reachabilityRef = nil; + if (self.hostName) { + reachabilityRef = [self reachabilityRefForHostName:self.hostName]; + + } else if (self.address) { + reachabilityRef = [self reachabilityRefForAddress:self.address]; + + } else { + NSAssert(self.hostName != nil && self.address != nil, @"No hostName or address specified. Cannot determine reachability."); + return NotReachable; + } + + if (!reachabilityRef) { + return NotReachable; + } + + SCNetworkReachabilityFlags reachabilityFlags; + BOOL gotFlags = SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags); + if (!gotFlags) { + return NotReachable; + } + + BOOL reachable = [self isReachableWithoutRequiringConnection:reachabilityFlags]; + + if (!reachable) { + return NotReachable; + } + if (reachabilityFlags & ReachableViaCarrierDataNetwork) { + return ReachableViaCarrierDataNetwork; + } + + return ReachableViaWiFiNetwork; +} + +- (NetworkStatus)internetConnectionStatus +{ + /* + To determine if the device has an Internet connection, query the address + 0.0.0.0. If it's reachable without requiring a connection, first check + for the kSCNetworkReachabilityFlagsIsDirect flag, which tell us if the connection + is to an ad-hoc WiFi network. If it is not, the device can access the Internet. + The next thing to determine is how the device can access the Internet, which + can either be through the carrier data network (EDGE or other service) or through + a WiFi connection. + + Note: Knowing that the device has an Internet connection is not the same as + knowing if the device can reach a particular host. To know that, use + -[Reachability remoteHostStatus]. + */ + + SCNetworkReachabilityFlags defaultRouteFlags; + BOOL defaultRouteIsAvailable = [self isNetworkAvailableFlags:&defaultRouteFlags]; + if (defaultRouteIsAvailable) { + + if (defaultRouteFlags & kSCNetworkReachabilityFlagsIsDirect) { + + // The connection is to an ad-hoc WiFi network, so Internet access is not available. + return NotReachable; + } + else if (defaultRouteFlags & ReachableViaCarrierDataNetwork) { + return ReachableViaCarrierDataNetwork; + } + + return ReachableViaWiFiNetwork; + } + + return NotReachable; +} + +- (NetworkStatus)localWiFiConnectionStatus +{ + SCNetworkReachabilityFlags selfAssignedAddressFlags; + + /* + To determine if the WiFi connection is to a local ad-hoc network, + check the availability of the address 169.254.x.x. That's an address + in the self-assigned range, and the device will have a self-assigned IP + when it's connected to a ad-hoc WiFi network. So to test if the device + has a self-assigned IP, look for the kSCNetworkReachabilityFlagsIsDirect flag + in the address query. If it's present, we know that the WiFi connection + is to an ad-hoc network. + */ + // This returns YES if the address 169.254.0.0 is reachable without requiring a connection. + BOOL hasLinkLocalNetworkAccess = [self isAdHocWiFiNetworkAvailableFlags:&selfAssignedAddressFlags]; + + if (hasLinkLocalNetworkAccess && (selfAssignedAddressFlags & kSCNetworkReachabilityFlagsIsDirect)) { + return ReachableViaWiFiNetwork; + } + + return NotReachable; +} + +// Convert an IP address from an NSString to a sockaddr_in * that can be used to create +// the reachability request. +- (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address +{ + if (!IPAddress || ![IPAddress length]) { + return NO; + } + + memset((char *) address, sizeof(struct sockaddr_in), 0); + address->sin_family = AF_INET; + address->sin_len = sizeof(struct sockaddr_in); + + int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr); + if (conversionResult == 0) { + NSAssert1(conversionResult != 1, @"Failed to convert the IP address string into a sockaddr_in: %@", IPAddress); + return NO; + } + + return YES; +} + +@end + +@interface ReachabilityQuery () +- (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop; +@end + +@implementation ReachabilityQuery + +@synthesize reachabilityRef = _reachabilityRef; +@synthesize runLoops = _runLoops; +@synthesize hostNameOrAddress = _hostNameOrAddress; + +- (id)init +{ + self = [super init]; + if (self != nil) { + self.runLoops = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + } + return self; +} + +- (void)dealloc +{ + CFRelease(self.runLoops); + [super dealloc]; +} + +- (BOOL)isScheduledOnRunLoop:(CFRunLoopRef)runLoop +{ + NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(self.runLoops); + + for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) { + CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(self.runLoops, runLoopCounter); + + if (nextRunLoop == runLoop) { + return YES; + } + } + + return NO; +} + +- (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop +{ + // Only register for network state changes if the client has specifically enabled them. + if ([[Reachability sharedReachability] networkStatusNotificationsEnabled] == NO) { + return; + } + + if (!inRunLoop) { + return; + } + + CFRunLoopRef runLoop = [inRunLoop getCFRunLoop]; + + // Notifications of status changes for each reachability query can be scheduled on multiple run loops. + // To support that, register for notifications for each runLoop. + // -isScheduledOnRunLoop: iterates over all of the run loops that have previously been used + // to register for notifications. If one is found that matches the passed in runLoop argument, there's + // no need to register for notifications again. If one is not found, register for notifications + // using the current runLoop. + if (![self isScheduledOnRunLoop:runLoop]) { + + CFRunLoopRef notificationRunLoop = [self startListeningForReachabilityChanges:self.reachabilityRef onRunLoop:runLoop]; + if (notificationRunLoop) { + CFArrayAppendValue(self.runLoops, notificationRunLoop); + } + } +} + +// Register to receive changes to the 'reachability' query so that we can update the +// user interface when the network state changes. +- (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop +{ + if (!reachability) { + return NULL; + } + + if (!runLoop) { + return NULL; + } + + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + SCNetworkReachabilitySetCallback(reachability, ReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(reachability, runLoop, kCFRunLoopDefaultMode); + + return runLoop; +} + + +@end diff --git a/iphone/PhoneGap.xcodeproj/project.pbxproj b/iphone/PhoneGap.xcodeproj/project.pbxproj index 432323a7..b87bf9b8 100755 --- a/iphone/PhoneGap.xcodeproj/project.pbxproj +++ b/iphone/PhoneGap.xcodeproj/project.pbxproj @@ -20,6 +20,9 @@ 28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD733E0D9D9553002E5188 /* MainWindow.xib */; }; 303554C1100E56B7002C5EC0 /* Camera.m in Sources */ = {isa = PBXBuildFile; fileRef = 303554C0100E56B7002C5EC0 /* Camera.m */; }; 30355582100E7477002C5EC0 /* NSData+Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = 30355581100E7477002C5EC0 /* NSData+Base64.m */; }; + 30C531981020E3B20017260B /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C531971020E3B20017260B /* Reachability.m */; }; + 30C5319B1020E3CF0017260B /* Network.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C5319A1020E3CF0017260B /* Network.m */; }; + 30C531A01020E4310017260B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30C5319F1020E4310017260B /* SystemConfiguration.framework */; }; 30CD0F0F0FCBED3F00AA6748 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3023AB3D0FCB6FC40012AC33 /* AddressBookUI.framework */; }; 30EC9AEC0FCDF4DD00A52C71 /* LoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 30EC9AEB0FCDF4DD00A52C71 /* LoadingView.m */; }; 30EC9B110FCDFC9900A52C71 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EC9B100FCDFC9900A52C71 /* CoreGraphics.framework */; }; @@ -70,6 +73,11 @@ 303554C0100E56B7002C5EC0 /* Camera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Camera.m; sourceTree = ""; }; 30355580100E7477002C5EC0 /* NSData+Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Base64.h"; sourceTree = ""; }; 30355581100E7477002C5EC0 /* NSData+Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Base64.m"; sourceTree = ""; }; + 30C531961020E3B20017260B /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; + 30C531971020E3B20017260B /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + 30C531991020E3CF0017260B /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = ""; }; + 30C5319A1020E3CF0017260B /* Network.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Network.m; sourceTree = ""; }; + 30C5319F1020E4310017260B /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 30EC9AEA0FCDF4DD00A52C71 /* LoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingView.h; sourceTree = ""; }; 30EC9AEB0FCDF4DD00A52C71 /* LoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingView.m; sourceTree = ""; }; 30EC9B100FCDFC9900A52C71 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -125,6 +133,7 @@ BB8FC69F0F5BB58D007C7DCE /* CFNetwork.framework in Frameworks */, 30EC9B110FCDFC9900A52C71 /* CoreGraphics.framework in Frameworks */, 30EC9B220FCDFCC500A52C71 /* QuartzCore.framework in Frameworks */, + 30C531A01020E4310017260B /* SystemConfiguration.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -162,6 +171,8 @@ 1B1B50FB0F95000A00ECD4E6 /* Commands */ = { isa = PBXGroup; children = ( + 30C531961020E3B20017260B /* Reachability.h */, + 30C531971020E3B20017260B /* Reachability.m */, 30355580100E7477002C5EC0 /* NSData+Base64.h */, 30355581100E7477002C5EC0 /* NSData+Base64.m */, BB8FC5C10F5B3EC4007C7DCE /* Bonjour.h */, @@ -186,6 +197,8 @@ 1B1B4E6A0F938A0500ECD4E6 /* UIControls.m */, 303554BF100E56B7002C5EC0 /* Camera.h */, 303554C0100E56B7002C5EC0 /* Camera.m */, + 30C531991020E3CF0017260B /* Network.h */, + 30C5319A1020E3CF0017260B /* Network.m */, ); name = Commands; sourceTree = ""; @@ -227,6 +240,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 30C5319F1020E4310017260B /* SystemConfiguration.framework */, 30EC9B210FCDFCC500A52C71 /* QuartzCore.framework */, 30EC9B100FCDFC9900A52C71 /* CoreGraphics.framework */, 079B0A090E45734600755F37 /* CoreLocation.framework */, @@ -361,6 +375,8 @@ 30EF08140FCFC8E0009B3930 /* OCABMutableMultiValue.m in Sources */, 303554C1100E56B7002C5EC0 /* Camera.m in Sources */, 30355582100E7477002C5EC0 /* NSData+Base64.m in Sources */, + 30C531981020E3B20017260B /* Reachability.m in Sources */, + 30C5319B1020E3CF0017260B /* Network.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/javascripts/iphone/network.js b/javascripts/iphone/network.js new file mode 100644 index 00000000..893bd70a --- /dev/null +++ b/javascripts/iphone/network.js @@ -0,0 +1,20 @@ +// Gets the function name of a Function object, else uses "alert" if anonymous +function GetFunctionName(fn) +{ + if (fn) { + var m = fn.toString().match(/^\s*function\s+([^\s\(]+)/); + return m ? m[1] : "alert"; + } else { + return null; + } +} + +/** + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options (isIpAddress:boolean) + */ +Network.prototype.isReachable = function(hostName, successCallback, options) { + PhoneGap.exec("Network.isReachable", hostName, GetFunctionName(successCallback), options); +} \ No newline at end of file diff --git a/javascripts/network.js b/javascripts/network.js new file mode 100644 index 00000000..00803619 --- /dev/null +++ b/javascripts/network.js @@ -0,0 +1,46 @@ +/** + * This class contains information about any NetworkStatus. + * @constructor + */ +function NetworkStatus() { + this.code = null; + this.message = ""; +} + +NetworkStatus.NOT_REACHABLE = 0; +NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; +NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2; + +/** + * This class provides access to device Network data (reachability). + * @constructor + */ +function Network() { + /** + * The last known Network status. + * { hostName: string, ipAddress: string, + remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) } + */ + this.lastReachability = null; +}; + +/** + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options (isIpAddress:boolean) + */ +Network.prototype.isReachable = function(hostName, successCallback, options) { +} + +/** + * Called by the geolocation framework when the reachability status has changed. + * @param {Reachibility} reachability The current reachability status. + */ +Network.prototype.updateReachability = function(reachability) { + this.lastReachability = reachability; +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.network == "undefined") navigator.network = new Network(); +});