aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Example/Core/Tests/FIRAppTest.m130
-rw-r--r--Example/Messaging/Tests/FIRMessagingTest.m40
-rw-r--r--Firebase/Core/FIRApp.m85
-rw-r--r--Firebase/Core/Private/FIRAppInternal.h28
-rw-r--r--Firebase/Messaging/FIRMessaging+FIRApp.m1
-rw-r--r--Firebase/Messaging/FIRMessaging.m7
-rw-r--r--Firebase/Messaging/FIRMessaging_Private.h4
7 files changed, 291 insertions, 4 deletions
diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m
index 6825e6a..abf1d38 100644
--- a/Example/Core/Tests/FIRAppTest.m
+++ b/Example/Core/Tests/FIRAppTest.m
@@ -42,6 +42,9 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
+ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version;
+ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version;
++ (nullable NSNumber *)readDataCollectionSwitchFromPlist;
++ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app;
+
@end
@interface FIRAppTest : FIRTestCase
@@ -552,6 +555,133 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
[FIRApp validateAppIDFingerprint:@"1:1337:ios:deadbeef:ab" withVersion:kGoodVersionV1]);
}
+#pragma mark - Automatic Data Collection Tests
+
+- (void)testGlobalDataCollectionNoFlags {
+ // Test: No flags set.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionPlistSetEnabled {
+ // Test: Plist set to enabled, no override.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@YES);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionPlistSetDisabled {
+ // Test: Plist set to disabled, no override.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@NO);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserSpecifiedEnabled {
+ // Test: User specified as enabled, no plist value.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@YES);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserSpecifiedDisabled {
+ // Test: User specified as disabled, no plist value.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserOverriddenEnabled {
+ // Test: User specified as enabled, with plist set as disabled.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@NO);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@YES);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserOverriddenDisabled {
+ // Test: User specified as disabled, with plist set as enabled.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@YES);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionWriteToDefaults {
+ id defaultsMock = OCMPartialMock([NSUserDefaults standardUserDefaults]);
+ [FIRApp configure];
+
+ FIRApp *app = [FIRApp defaultApp];
+ app.automaticDataCollectionEnabled = YES;
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
+ OCMVerify([defaultsMock setObject:@YES forKey:key]);
+
+ [FIRApp defaultApp].automaticDataCollectionEnabled = NO;
+ OCMVerify([defaultsMock setObject:@NO forKey:key]);
+
+ [defaultsMock stopMocking];
+}
+
+- (void)testGlobalDataCollectionClearedAfterDelete {
+ // Configure and disable data collection for the default FIRApp.
+ [FIRApp configure];
+ FIRApp *app = [FIRApp defaultApp];
+ app.automaticDataCollectionEnabled = NO;
+ XCTAssertFalse(app.isAutomaticDataCollectionEnabled);
+
+ // Delete the app, and verify that the switch was reset.
+ XCTestExpectation *deleteFinished =
+ [self expectationWithDescription:@"The app should successfully delete."];
+ [app deleteApp:^(BOOL success) {
+ if (success) {
+ [deleteFinished fulfill];
+ }
+ }];
+
+ // Wait for the delete to complete.
+ [self waitForExpectations:@[ deleteFinished ] timeout:1];
+
+ // Set up the default app again, and check the data collection flag.
+ [FIRApp configure];
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionNoDiagnosticsSent {
+ [FIRApp configure];
+
+ // Stub out reading from user defaults since stubbing out the BOOL has issues. If the data
+ // collection switch is disabled, the `sendLogs` call should return immediately and not fire a
+ // notification.
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+ OCMReject([self.notificationCenterMock postNotificationName:kFIRAppDiagnosticsNotification
+ object:OCMOCK_ANY
+ userInfo:OCMOCK_ANY]);
+ NSError *error = [NSError errorWithDomain:@"com.firebase" code:42 userInfo:nil];
+ [[FIRApp defaultApp] sendLogsWithServiceName:@"Service" version:@"Version" error:error];
+}
+
#pragma mark - Internal Methods
- (void)testAuthGetUID {
diff --git a/Example/Messaging/Tests/FIRMessagingTest.m b/Example/Messaging/Tests/FIRMessagingTest.m
index adc830d..61ff136 100644
--- a/Example/Messaging/Tests/FIRMessagingTest.m
+++ b/Example/Messaging/Tests/FIRMessagingTest.m
@@ -75,6 +75,46 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
XCTAssertTrue(_messaging.isAutoInitEnabled);
}
+- (void)testAutoInitEnableFlagOverrideGlobalTrue {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+
+ self.messaging.autoInitEnabled = NO;
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableFlagOverrideGlobalFalse {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+
+ self.messaging.autoInitEnabled = NO;
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableGlobalDefaultTrue {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableGlobalDefaultFalse {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(NO);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
#pragma mark - Direct Channel Establishment Testing
// Should connect with valid token and application in foreground
diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m
index 3d05f12..717da4e 100644
--- a/Firebase/Core/FIRApp.m
+++ b/Firebase/Core/FIRApp.m
@@ -46,6 +46,11 @@ NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
NSString *const kFIRAppNameKey = @"FIRAppNameKey";
NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
+NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
+ @"/google/firebase/global_data_collection_enabled:%@";
+NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
+ @"FirebaseAutomaticDataCollectionEnabled";
+
NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
@@ -227,6 +232,7 @@ static NSMutableDictionary *sLibraryVersions;
if (sAllApps && sAllApps[self.name]) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
[sAllApps removeObjectForKey:self.name];
+ [self clearDataCollectionSwitchFromUserDefaults];
if ([self.name isEqualToString:kFIRDefaultAppName]) {
sDefaultApp = nil;
}
@@ -332,6 +338,30 @@ static NSMutableDictionary *sLibraryVersions;
return [_options copy];
}
+- (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
+ [[NSUserDefaults standardUserDefaults] setBool:automaticDataCollectionEnabled forKey:key];
+}
+
+- (BOOL)isAutomaticDataCollectionEnabled {
+ // Check if it's been manually set before in code, and use that as the higher priority value.
+ NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
+ if (defaultsObject) {
+ return [defaultsObject boolValue];
+ }
+
+ // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
+ // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
+ // no performance impact calling multiple times.
+ NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
+ if (collectionEnabledPlistValue) {
+ return [collectionEnabledPlistValue boolValue];
+ }
+
+ return YES;
+}
+
#pragma mark - private
+ (void)sendNotificationsToSDKs:(FIRApp *)app {
@@ -613,11 +643,64 @@ static NSMutableDictionary *sLibraryVersions;
}
// end App ID validation
-#pragma mark
+
+#pragma mark - Reading From Plist & User Defaults
+
+/**
+ * Clears the data collection switch from the standard NSUserDefaults for easier testing and
+ * readability.
+ */
+- (void)clearDataCollectionSwitchFromUserDefaults {
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
+}
+
+/**
+ * Reads the data collection switch from the standard NSUserDefaults for easier testing and
+ * readability.
+ */
++ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
+ // Read the object in user defaults, and only return if it's an NSNumber.
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
+ id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
+ if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
+ return collectionEnabledDefaultsObject;
+ }
+
+ return nil;
+}
+
+/**
+ * Reads the data collection switch from the Info.plist for easier testing and readability. Will
+ * only read once from the plist and return the cached value.
+ */
++ (nullable NSNumber *)readDataCollectionSwitchFromPlist {
+ static NSNumber *collectionEnabledPlistObject;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
+ id plistValue = [[NSBundle mainBundle]
+ objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
+ if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
+ collectionEnabledPlistObject = (NSNumber *)plistValue;
+ }
+ });
+
+ return collectionEnabledPlistObject;
+}
+
+#pragma mark - Sending Logs
- (void)sendLogsWithServiceName:(NSString *)serviceName
version:(NSString *)version
error:(NSError *)error {
+ // If the user has manually turned off data collection, return and don't send logs.
+ if (![self isAutomaticDataCollectionEnabled]) {
+ return;
+ }
+
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeSDK),
kFIRAppDiagnosticsSDKNameKey : serviceName,
diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h
index b7cf5e8..66979eb 100644
--- a/Firebase/Core/Private/FIRAppInternal.h
+++ b/Firebase/Core/Private/FIRAppInternal.h
@@ -60,6 +60,22 @@ extern NSString *const kFIRAppIsDefaultAppKey;
extern NSString *const kFIRAppNameKey;
extern NSString *const kFIRGoogleAppIDKey;
+/**
+ * The format string for the User Defaults key used for storing the data collection enabled flag.
+ * This includes formatting to append the Firebase App's name.
+ */
+extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
+
+/**
+ * The plist key used for storing the data collection enabled flag.
+ */
+extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
+
+/**
+ * A notification fired containing diagnostic information when SDK errors occur.
+ */
+extern NSString *const kFIRAppDiagnosticsNotification;
+
/** @var FIRAuthStateDidChangeInternalNotification
@brief The name of the @c NSNotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
@@ -181,6 +197,18 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
*/
- (nullable NSString *)getUID;
+/**
+ * WARNING: THIS SETTING DOES NOT WORK YET. IT WILL BE MOVED TO THE PUBLIC HEADER ONCE ALL SDKS
+ * CONFORM TO THIS PREFERENCE. DO NOT RELY ON IT.
+ *
+ * Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES`
+ * unless `FirebaseAutomaticDataCollectionEnabled` is set to `NO` in your app's Info.plist. This
+ * value is persisted across runs of the app so that it can be set once when users have consented to
+ * collection.
+ */
+@property(nonatomic, readwrite, getter=isAutomaticDataCollectionEnabled)
+ BOOL automaticDataCollectionEnabled;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Messaging/FIRMessaging+FIRApp.m b/Firebase/Messaging/FIRMessaging+FIRApp.m
index 58ae3af..d48a3b4 100644
--- a/Firebase/Messaging/FIRMessaging+FIRApp.m
+++ b/Firebase/Messaging/FIRMessaging+FIRApp.m
@@ -72,6 +72,7 @@
}
self.fcmSenderID = [options.GCMSenderID copy];
+ self.globalAutomaticDataCollectionEnabled = [app isAutomaticDataCollectionEnabled];
// Swizzle remote-notification-related methods (app delegate and UNUserNotificationCenter)
if ([FIRMessagingRemoteNotificationsProxy canSwizzleMethods]) {
diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m
index 339bd7d..6222420 100644
--- a/Firebase/Messaging/FIRMessaging.m
+++ b/Firebase/Messaging/FIRMessaging.m
@@ -75,7 +75,7 @@ NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled =
NSString *const kFIRMessagingAPNSTokenType = @"APNSTokenType"; // APNS Token type key stored in user info.
-static NSString *const kFIRMessagingPlistAutoInitEnabled =
+NSString *const kFIRMessagingPlistAutoInitEnabled =
@"FirebaseMessagingAutoInitEnabled"; // Auto Init Enabled key stored in Info.plist
@interface FIRMessagingMessageInfo ()
@@ -471,8 +471,9 @@ static NSString *const kFIRMessagingPlistAutoInitEnabled =
if (isAutoInitEnabledObject) {
return [isAutoInitEnabledObject boolValue];
}
- // If none of above exists, we default assume FCM auto init is enabled.
- return YES;
+
+ // If none of above exists, we default to the global switch that comes from FIRApp.
+ return self.isGlobalAutomaticDataCollectionEnabled;
}
- (void)setAutoInitEnabled:(BOOL)autoInitEnabled {
diff --git a/Firebase/Messaging/FIRMessaging_Private.h b/Firebase/Messaging/FIRMessaging_Private.h
index 46daee0..6bac99d 100644
--- a/Firebase/Messaging/FIRMessaging_Private.h
+++ b/Firebase/Messaging/FIRMessaging_Private.h
@@ -25,6 +25,7 @@ typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) {
kFIRMessagingReachabilityReachableViaWWAN,
};
+FOUNDATION_EXPORT NSString *const kFIRMessagingPlistAutoInitEnabled;
FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
@interface FIRMessagingRemoteMessage ()
@@ -37,6 +38,9 @@ FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
#pragma mark - Private API
+// The data collection flag from Core.
+@property(nonatomic, readwrite, getter=isGlobalAutomaticDataCollectionEnabled) BOOL globalAutomaticDataCollectionEnabled;
+
- (NSString *)defaultFcmToken;
- (FIRMessagingClient *)client;
- (FIRMessagingPubSub *)pubsub;