added iOS source code
[wl-app.git] / iOS / Pods / GoogleUtilities / GoogleUtilities / Reachability / GULReachabilityChecker.m
1 // Copyright 2017 Google
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #import <Foundation/Foundation.h>
16
17 #import "GULReachabilityChecker+Internal.h"
18 #import "Private/GULReachabilityChecker.h"
19 #import "Private/GULReachabilityMessageCode.h"
20
21 #import <GoogleUtilities/GULLogger.h>
22 #import <GoogleUtilities/GULReachabilityChecker.h>
23
24 static GULLoggerService kGULLoggerReachability = @"[GULReachability]";
25
26 static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
27                                  SCNetworkReachabilityFlags flags,
28                                  void *info);
29
30 static const struct GULReachabilityApi kGULDefaultReachabilityApi = {
31     SCNetworkReachabilityCreateWithName,
32     SCNetworkReachabilitySetCallback,
33     SCNetworkReachabilityScheduleWithRunLoop,
34     SCNetworkReachabilityUnscheduleFromRunLoop,
35     CFRelease,
36 };
37
38 static NSString *const kGULReachabilityUnknownStatus = @"Unknown";
39 static NSString *const kGULReachabilityConnectedStatus = @"Connected";
40 static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
41
42 @interface GULReachabilityChecker ()
43
44 @property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi;
45 @property(nonatomic, assign) GULReachabilityStatus reachabilityStatus;
46 @property(nonatomic, copy) NSString *host;
47 @property(nonatomic, assign) SCNetworkReachabilityRef reachability;
48
49 @end
50
51 @implementation GULReachabilityChecker
52
53 @synthesize reachabilityApi = reachabilityApi_;
54 @synthesize reachability = reachability_;
55
56 - (const struct GULReachabilityApi *)reachabilityApi {
57   return reachabilityApi_;
58 }
59
60 - (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi {
61   if (reachability_) {
62     GULLogError(kGULLoggerReachability, NO,
63                 [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
64                 @"Cannot change reachability API while reachability is running. "
65                 @"Call stop first.");
66     return;
67   }
68   reachabilityApi_ = reachabilityApi;
69 }
70
71 @synthesize reachabilityStatus = reachabilityStatus_;
72 @synthesize host = host_;
73 @synthesize reachabilityDelegate = reachabilityDelegate_;
74
75 - (BOOL)isActive {
76   return reachability_ != nil;
77 }
78
79 - (void)setReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate {
80   if (reachabilityDelegate &&
81       (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) {
82     GULLogError(kGULLoggerReachability, NO,
83                 [NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005],
84                 @"Reachability delegate doesn't conform to Reachability protocol.");
85     return;
86   }
87   reachabilityDelegate_ = reachabilityDelegate;
88 }
89
90 - (instancetype)initWithReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate
91                                     withHost:(NSString *)host {
92   self = [super init];
93
94   if (!host || !host.length) {
95     GULLogError(kGULLoggerReachability, NO,
96                 [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
97                 @"Invalid host specified");
98     return nil;
99   }
100   if (self) {
101     [self setReachabilityDelegate:reachabilityDelegate];
102     reachabilityApi_ = &kGULDefaultReachabilityApi;
103     reachabilityStatus_ = kGULReachabilityUnknown;
104     host_ = [host copy];
105     reachability_ = nil;
106   }
107   return self;
108 }
109
110 - (void)dealloc {
111   reachabilityDelegate_ = nil;
112   [self stop];
113 }
114
115 - (BOOL)start {
116   if (!reachability_) {
117     reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
118     if (!reachability_) {
119       return NO;
120     }
121     SCNetworkReachabilityContext context = {
122         0,                       /* version */
123         (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
124         NULL,                    /* retain */
125         NULL,                    /* release */
126         NULL                     /* copyDescription */
127     };
128     if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
129         !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
130                                                  kCFRunLoopCommonModes)) {
131       reachabilityApi_->releaseFn(reachability_);
132       reachability_ = nil;
133
134       GULLogError(kGULLoggerReachability, NO,
135                   [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
136                   @"Failed to start reachability handle");
137       return NO;
138     }
139   }
140   GULLogDebug(kGULLoggerReachability, NO,
141               [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
142               @"Monitoring the network status");
143   return YES;
144 }
145
146 - (void)stop {
147   if (reachability_) {
148     reachabilityStatus_ = kGULReachabilityUnknown;
149     reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
150                                               kCFRunLoopCommonModes);
151     reachabilityApi_->releaseFn(reachability_);
152     reachability_ = nil;
153   }
154 }
155
156 - (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
157   GULReachabilityStatus status = kGULReachabilityNotReachable;
158   // If the Reachable flag is not set, we definitely don't have connectivity.
159   if (flags & kSCNetworkReachabilityFlagsReachable) {
160     // Reachable flag is set. Check further flags.
161     if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
162 // Connection required flag is not set, so we have connectivity.
163 #if TARGET_OS_IOS || TARGET_OS_TV
164       status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
165                                                            : kGULReachabilityViaWifi;
166 #elif TARGET_OS_OSX
167       status = kGULReachabilityViaWifi;
168 #endif
169     } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
170                          kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
171                !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
172 // If the connection on demand or connection on traffic flag is set, and user intervention
173 // is not required, we have connectivity.
174 #if TARGET_OS_IOS || TARGET_OS_TV
175       status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
176                                                            : kGULReachabilityViaWifi;
177 #elif TARGET_OS_OSX
178       status = kGULReachabilityViaWifi;
179 #endif
180     }
181   }
182   return status;
183 }
184
185 - (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
186   GULReachabilityStatus status = [self statusForFlags:flags];
187   if (reachabilityStatus_ != status) {
188     NSString *reachabilityStatusString;
189     if (status == kGULReachabilityUnknown) {
190       reachabilityStatusString = kGULReachabilityUnknownStatus;
191     } else {
192       reachabilityStatusString = (status == kGULReachabilityNotReachable)
193                                      ? kGULReachabilityDisconnectedStatus
194                                      : kGULReachabilityConnectedStatus;
195     }
196
197     GULLogDebug(kGULLoggerReachability, NO,
198                 [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
199                 @"Network status has changed. Code:%@, status:%@", @(status),
200                 reachabilityStatusString);
201     reachabilityStatus_ = status;
202     [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
203   }
204 }
205
206 @end
207
208 static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
209                                  SCNetworkReachabilityFlags flags,
210                                  void *info) {
211   GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info;
212   [checker reachabilityFlagsChanged:flags];
213 }
214
215 // This function used to be at the top of the file, but it was moved here
216 // as a workaround for a suspected compiler bug. When compiled in Release mode
217 // and run on an iOS device with WiFi disabled, the reachability code crashed
218 // when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
219 // After unsuccessfully trying to diagnose the cause of the crash, it was
220 // discovered that moving this function to the end of the file magically fixed
221 // the crash. If you are going to edit this file, exercise caution and make sure
222 // to test thoroughly with an iOS device under various network conditions.
223 const NSString *GULReachabilityStatusString(GULReachabilityStatus status) {
224   switch (status) {
225     case kGULReachabilityUnknown:
226       return @"Reachability Unknown";
227
228     case kGULReachabilityNotReachable:
229       return @"Not reachable";
230
231     case kGULReachabilityViaWifi:
232       return @"Reachable via Wifi";
233
234     case kGULReachabilityViaCellular:
235       return @"Reachable via Cellular Data";
236
237     default:
238       return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
239   }
240 }