Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/*
2
 * Copyright 2018 Google
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
#import <TargetConditionals.h>
18
#if TARGET_OS_IOS || TARGET_OS_TV
19
 
20
#import <Foundation/Foundation.h>
21
#import <UIKit/UIKit.h>
22
 
23
#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
24
#import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMActionURLFollower.h"
25
 
26
NS_EXTENSION_UNAVAILABLE("Firebase In App Messaging is not supported for iOS extensions.")
27
@interface FIRIAMActionURLFollower ()
28
@property(nonatomic, readonly, nonnull, copy) NSSet<NSString *> *appCustomURLSchemesSet;
29
@property(nonatomic, readonly) BOOL isOldAppDelegateOpenURLDefined;
30
@property(nonatomic, readonly) BOOL isNewAppDelegateOpenURLDefined;
31
@property(nonatomic, readonly) BOOL isContinueUserActivityMethodDefined;
32
 
33
@property(nonatomic, readonly, nullable) id<UIApplicationDelegate> appDelegate;
34
@property(nonatomic, readonly, nonnull) UIApplication *mainApplication;
35
@end
36
 
37
NS_EXTENSION_UNAVAILABLE("Firebase In App Messaging is not supported for iOS extensions.")
38
@implementation FIRIAMActionURLFollower
39
 
40
+ (FIRIAMActionURLFollower *)actionURLFollower {
41
  static FIRIAMActionURLFollower *URLFollower;
42
  static dispatch_once_t onceToken;
43
 
44
  dispatch_once(&onceToken, ^{
45
    NSMutableArray<NSString *> *customSchemeURLs = [[NSMutableArray alloc] init];
46
 
47
    // Reading the custom url list from the environment.
48
    NSBundle *appBundle = [NSBundle mainBundle];
49
    if (appBundle) {
50
      id URLTypesID = [appBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"];
51
      if ([URLTypesID isKindOfClass:[NSArray class]]) {
52
        NSArray *urlTypesArray = (NSArray *)URLTypesID;
53
 
54
        for (id nextURLType in urlTypesArray) {
55
          if ([nextURLType isKindOfClass:[NSDictionary class]]) {
56
            NSDictionary *nextURLTypeDict = (NSDictionary *)nextURLType;
57
            id nextSchemeArray = nextURLTypeDict[@"CFBundleURLSchemes"];
58
            if (nextSchemeArray && [nextSchemeArray isKindOfClass:[NSArray class]]) {
59
              [customSchemeURLs addObjectsFromArray:nextSchemeArray];
60
            }
61
          }
62
        }
63
      }
64
    }
65
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM300010",
66
                @"Detected %d custom URL schemes from environment", (int)customSchemeURLs.count);
67
 
68
    if ([NSThread isMainThread]) {
69
      // We can not dispatch sychronously to main queue if we are already in main queue. That
70
      // can cause deadlock.
71
      URLFollower = [[FIRIAMActionURLFollower alloc]
72
          initWithCustomURLSchemeArray:customSchemeURLs
73
                       withApplication:UIApplication.sharedApplication];
74
    } else {
75
      // If we are not on main thread, dispatch it to main queue since it invovles calling UIKit
76
      // methods, which are required to be carried out on main queue.
77
      dispatch_sync(dispatch_get_main_queue(), ^{
78
        URLFollower = [[FIRIAMActionURLFollower alloc]
79
            initWithCustomURLSchemeArray:customSchemeURLs
80
                         withApplication:UIApplication.sharedApplication];
81
      });
82
    }
83
  });
84
  return URLFollower;
85
}
86
 
87
- (instancetype)initWithCustomURLSchemeArray:(NSArray<NSString *> *)customURLScheme
88
                             withApplication:(UIApplication *)application {
89
  if (self = [super init]) {
90
    _appCustomURLSchemesSet = [NSSet setWithArray:customURLScheme];
91
    _mainApplication = application;
92
    _appDelegate = [application delegate];
93
 
94
    if (_appDelegate) {
95
      _isOldAppDelegateOpenURLDefined = [_appDelegate
96
          respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)];
97
 
98
      _isNewAppDelegateOpenURLDefined =
99
          [_appDelegate respondsToSelector:@selector(application:openURL:options:)];
100
 
101
      _isContinueUserActivityMethodDefined = [_appDelegate
102
          respondsToSelector:@selector(application:continueUserActivity:restorationHandler:)];
103
    }
104
  }
105
  return self;
106
}
107
 
108
- (void)followActionURL:(NSURL *)actionURL withCompletionBlock:(void (^)(BOOL success))completion {
109
  // So this is the logic of the url following flow
110
  //  1 If it's a http or https link
111
  //     1.1 If delegate implements application:continueUserActivity:restorationHandler: and calling
112
  //       it returns YES: the flow stops here: we have finished the url-following action
113
  //     1.2 In other cases: fall through to step 3
114
  //  2 If the URL scheme matches any element in appCustomURLSchemes
115
  //     2.1 Triggers application:openURL:options: or
116
  //     application:openURL:sourceApplication:annotation:
117
  //          depending on their availability.
118
  //  3 Use UIApplication openURL: or openURL:options:completionHandler: to have iOS system to deal
119
  //     with the url following.
120
  //
121
  //  The rationale for doing step 1 and 2 instead of simply doing step 3 for all cases are:
122
  //     I)  calling UIApplication openURL with the universal link targeted for current app would
123
  //         not cause the link being treated as a universal link. See apple doc at
124
  // https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html
125
  //         So step 1 is trying to handle this gracefully
126
  //     II) If there are other apps on the same device declaring the same custom url scheme as for
127
  //         the current app, doing step 3 directly have the risk of triggering another app for
128
  //         handling the custom scheme url: See the note about "If more than one third-party" from
129
  // https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html
130
  //         So step 2 is to optimize user experience by short-circuiting the engagement with iOS
131
  //         system
132
 
133
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240007", @"Following action url %@", actionURL);
134
 
135
  if ([self.class isHttpOrHttpsScheme:actionURL]) {
136
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240001", @"Try to treat it as a universal link.");
137
    if ([self followURLWithContinueUserActivity:actionURL]) {
138
      completion(YES);
139
      return;  // following the url has been fully handled by App Delegate's
140
               // continueUserActivity method
141
    }
142
  } else if ([self isCustomSchemeForCurrentApp:actionURL]) {
143
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240002", @"Custom URL scheme matches.");
144
    if ([self followURLWithAppDelegateOpenURLActivity:actionURL]) {
145
      completion(YES);
146
      return;  // following the url has been fully handled by App Delegate's openURL method
147
    }
148
  }
149
 
150
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240003", @"Open the url via iOS.");
151
  [self followURLViaIOS:actionURL withCompletionBlock:completion];
152
}
153
 
154
// Try to handle the url as a custom scheme url link by triggering
155
// application:openURL:options: on App's delegate object directly.
156
// @return YES if that delegate method is defined and returns YES.
157
- (BOOL)followURLWithAppDelegateOpenURLActivity:(NSURL *)url {
158
  if (self.isNewAppDelegateOpenURLDefined) {
159
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM210008",
160
                @"iOS 9+ version of App Delegate's application:openURL:options: method detected");
161
    return [self.appDelegate application:self.mainApplication openURL:url options:@{}];
162
  }
163
 
164
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240010",
165
              @"No approriate openURL method defined for App Delegate");
166
  return NO;
167
}
168
 
169
// Try to handle the url as a universal link by triggering
170
// application:continueUserActivity:restorationHandler: on App's delegate object directly.
171
// @return YES if that delegate method is defined and seeing a YES being returned from
172
// trigging it
173
- (BOOL)followURLWithContinueUserActivity:(NSURL *)url {
174
  if (self.isContinueUserActivityMethodDefined) {
175
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240004",
176
                @"App delegate responds to application:continueUserActivity:restorationHandler:."
177
                 "Simulating action url opening from a web browser.");
178
    NSUserActivity *userActivity =
179
        [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];
180
    userActivity.webpageURL = url;
181
    BOOL handled = [self.appDelegate application:self.mainApplication
182
                            continueUserActivity:userActivity
183
                              restorationHandler:^(NSArray *restorableObjects) {
184
                                // mimic system behavior of triggering restoreUserActivityState:
185
                                // method on each element of restorableObjects
186
                                for (id nextRestoreObject in restorableObjects) {
187
                                  if ([nextRestoreObject isKindOfClass:[UIResponder class]]) {
188
                                    UIResponder *responder = (UIResponder *)nextRestoreObject;
189
                                    [responder restoreUserActivityState:userActivity];
190
                                  }
191
                                }
192
                              }];
193
    if (handled) {
194
      FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240005",
195
                  @"App handling acton URL returns YES, no more further action taken");
196
    } else {
197
      FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240004", @"App handling acton URL returns NO.");
198
    }
199
    return handled;
200
  } else {
201
    return NO;
202
  }
203
}
204
 
205
- (void)followURLViaIOS:(NSURL *)url withCompletionBlock:(void (^)(BOOL success))completion {
206
  if ([self.mainApplication respondsToSelector:@selector(openURL:options:completionHandler:)]) {
207
    NSDictionary *options = @{};
208
    [self.mainApplication
209
                  openURL:url
210
                  options:options
211
        completionHandler:^(BOOL success) {
212
          FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM240006", @"openURL result is %d", success);
213
          completion(success);
214
        }];
215
  }
216
}
217
 
218
- (BOOL)isCustomSchemeForCurrentApp:(NSURL *)url {
219
  NSString *schemeInLowerCase = [url.scheme lowercaseString];
220
  return [self.appCustomURLSchemesSet containsObject:schemeInLowerCase];
221
}
222
 
223
+ (BOOL)isHttpOrHttpsScheme:(NSURL *)url {
224
  NSString *schemeInLowerCase = [url.scheme lowercaseString];
225
  return
226
      [schemeInLowerCase isEqualToString:@"https"] || [schemeInLowerCase isEqualToString:@"http"];
227
}
228
@end
229
 
230
#endif  // TARGET_OS_IOS || TARGET_OS_TV