Added Three New Functions to the PushNotificationService Interface
In this CL, PushNotificationService interface defined three new
functions. In a future CL, these functions will be implemented
downstream. These functions are necessary for integrating the push
notification infrastructure into the sign-in pipeline while supporting a
multi-profile environment.
This is a multi-part CL:
=> https://crrev.com/c/4013727
- https://crrev.com/i/5080653
Bug: 1376956
Change-Id: I8017369dd7df850700c524e9364f4b1de583fd44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4013727
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Daniel White <danieltwhite@google.com>
Cr-Commit-Position: refs/heads/main@{#1083308}
diff --git a/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm b/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
index 6428d39..86299fa3 100644
--- a/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
+++ b/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
@@ -24,6 +24,12 @@
void RegisterDevice(PushNotificationConfiguration* config,
void (^completion_handler)(NSError* error)) final;
void UnregisterDevice(void (^completion_handler)(NSError* error)) final;
+ bool DeviceTokenIsSet() const final;
+
+ protected:
+ // PushNotificationService implementation.
+ void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+ CompletionHandler completion_handler) final;
};
void ChromiumPushNotificationService::RegisterDevice(
@@ -58,6 +64,27 @@
}));
}
+bool ChromiumPushNotificationService::DeviceTokenIsSet() const {
+ return false;
+}
+
+void ChromiumPushNotificationService::SetAccountsToDevice(
+ NSArray<NSString*>* account_ids,
+ void (^completion_handler)(NSError* error)) {
+ // Chromium does not initialize the device's connection to the push
+ // notification server. As a result, the `completion_handler` is called with
+ // a NSFeatureUnsupportedError.
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(^() {
+ NSError* error =
+ [NSError errorWithDomain:kChromiumPushNotificationErrorDomain
+ code:NSFeatureUnsupportedError
+ userInfo:nil];
+ completion_handler(error);
+ }));
+}
+
} // namespace
std::unique_ptr<PushNotificationService> CreatePushNotificationService() {
diff --git a/ios/chrome/browser/push_notification/BUILD.gn b/ios/chrome/browser/push_notification/BUILD.gn
index 533e8f2..20637ac 100644
--- a/ios/chrome/browser/push_notification/BUILD.gn
+++ b/ios/chrome/browser/push_notification/BUILD.gn
@@ -5,6 +5,8 @@
source_set("push_notification_service") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
+ "push_notification_account_context_manager.h",
+ "push_notification_account_context_manager.mm",
"push_notification_client_manager.h",
"push_notification_client_manager.mm",
"push_notification_configuration.h",
diff --git a/ios/chrome/browser/push_notification/push_notification_account_context_manager.h b/ios/chrome/browser/push_notification/push_notification_account_context_manager.h
new file mode 100644
index 0000000..397386e
--- /dev/null
+++ b/ios/chrome/browser/push_notification/push_notification_account_context_manager.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
+#define IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+
+class BrowserStateInfoCache;
+
+namespace ios {
+class ChromeBrowserStateManager;
+
+}
+
+// This class is intended to store the permissions for each push notification
+// enabled feature for a given account and the number of times the account is
+// signed in across BrowserStates.
+@interface PushNotificationAccountContext : NSObject
+// A dictionary that maps the string value of a push notification client id to
+// the perf service value for that push notification enable feature.
+@property(nonatomic, copy) NSDictionary<NSString*, NSNumber*>* preferenceMap;
+// A counter that stores the number of times a given account is used across
+// BrowserStates.
+@property(nonatomic, readonly) NSUInteger occurrencesAcrossBrowserStates;
+@end
+
+// The purpose of this class is to manage the mapping between GaiaIDs and its
+// context data related to push notifications.
+@interface PushNotificationAccountContextManager : NSObject
+
+// The designated initializer. `BrowserStateInfoCache` must not be nil.
+- (instancetype)initWithChromeBrowserStateManager:
+ (ios::ChromeBrowserStateManager*)manager NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Adds the account to the manager if the account is not signed into the device
+// in any BrowserState. This function returns a BOOL value indicating whether
+// the account was added to the manager.
+- (BOOL)addAccount:(NSString*)gaiaID;
+
+// Removes the account from the manager if the account is not signed into the
+// device in any BrowserState. This function returns a BOOL value indicating
+// whether the account was removed from the maanger.
+- (BOOL)removeAccount:(NSString*)gaiaID;
+
+// A dictionary that maps a user's GAIA ID to an object containing the account's
+// preferences for all push notification enabled features and an number
+// representing the number of times the account is signed in across
+// BrowserStates.
+@property(nonatomic, readonly)
+ NSDictionary<NSString*, PushNotificationAccountContext*>* contextMap;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm b/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm
new file mode 100644
index 0000000..4c28430d
--- /dev/null
+++ b/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm
@@ -0,0 +1,145 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/push_notification/push_notification_account_context_manager.h"
+
+#import "base/strings/sys_string_conversions.h"
+#import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/browser_state/browser_state_info_cache.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#import "ios/chrome/browser/prefs/pref_names.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface PushNotificationAccountContext ()
+
+@property(nonatomic, assign) NSUInteger occurrencesAcrossBrowserStates;
+
+@end
+
+@implementation PushNotificationAccountContext
+@end
+
+@implementation PushNotificationAccountContextManager {
+ // Used to retrieve BrowserStates located at a given path.
+ ios::ChromeBrowserStateManager* _chromeBrowserStateManager;
+ // Maps a GaiaID to the given account's AccountContext object.
+ NSMutableDictionary<NSString*, PushNotificationAccountContext*>* _contextMap;
+}
+
+- (instancetype)initWithChromeBrowserStateManager:
+ (ios::ChromeBrowserStateManager*)manager {
+ self = [super init];
+
+ if (self) {
+ _contextMap = [[NSMutableDictionary alloc] init];
+ _chromeBrowserStateManager = manager;
+ BrowserStateInfoCache* infoCache = manager->GetBrowserStateInfoCache();
+
+ const size_t numberOfBrowserStates = infoCache->GetNumberOfBrowserStates();
+ for (size_t i = 0; i < numberOfBrowserStates; i++) {
+ NSString* gaiaID =
+ base::SysUTF8ToNSString(infoCache->GetGAIAIdOfBrowserStateAtIndex(i));
+ base::FilePath path = infoCache->GetPathOfBrowserStateAtIndex(i);
+ ChromeBrowserState* chromeBrowserState =
+ _chromeBrowserStateManager->GetBrowserState(path);
+ [self addAccount:gaiaID withBrowserState:chromeBrowserState];
+ }
+ }
+
+ return self;
+}
+
+- (BOOL)addAccount:(NSString*)gaiaID {
+ ChromeBrowserState* chromeBrowserState = [self chromeBrowserStateFrom:gaiaID];
+ if (!chromeBrowserState)
+ return NO;
+
+ return [self addAccount:gaiaID
+ withBrowserState:[self chromeBrowserStateFrom:gaiaID]];
+}
+
+- (BOOL)removeAccount:(NSString*)gaiaID {
+ PushNotificationAccountContext* context = _contextMap[gaiaID];
+ DCHECK(context);
+
+ context.occurrencesAcrossBrowserStates--;
+ if (context.occurrencesAcrossBrowserStates > 0)
+ return NO;
+
+ [_contextMap removeObjectForKey:gaiaID];
+ return YES;
+}
+
+#pragma mark - Properties
+
+- (NSDictionary<NSString*, PushNotificationAccountContext*>*)contextMap {
+ return _contextMap;
+}
+
+#pragma mark - Private
+
+// Create a new AccountContext object for the given gaiaID, if the gaia id does
+// not already exist in the dictionary, and maps the gaiaID to the new context
+// object.
+- (BOOL)addAccount:(NSString*)gaiaID
+ withBrowserState:(ChromeBrowserState*)browserState {
+ DCHECK(browserState);
+
+ if (!gaiaID.length)
+ return NO;
+
+ PushNotificationAccountContext* context = _contextMap[gaiaID];
+ if (context) {
+ context.occurrencesAcrossBrowserStates++;
+ return NO;
+ }
+
+ PrefService* prefService = browserState->GetPrefs();
+ NSMutableDictionary<NSString*, NSNumber*>* preferenceMap =
+ [[NSMutableDictionary alloc] init];
+ const base::Value::Dict& permissions =
+ prefService->GetDict(prefs::kFeaturePushNotificationPermissions);
+
+ for (const auto pair : permissions) {
+ preferenceMap[base::SysUTF8ToNSString(pair.first)] =
+ [NSNumber numberWithBool:pair.second.GetBool()];
+ }
+
+ context = [[PushNotificationAccountContext alloc] init];
+ context.preferenceMap = preferenceMap;
+ context.occurrencesAcrossBrowserStates = 1;
+ _contextMap[gaiaID] = context;
+
+ return YES;
+}
+
+// Returns the ChromeBrowserState that has the given gaiaID set as the primary
+// account. TODO(crbug.com/1400732) Implement policy that computes correct
+// permission set. This function naively chooses the first ChromeBrowserState
+// that is associated with the given gaiaID. In a multi-profile environment
+// where the given gaiaID is signed into multiple profiles, it is possible that
+// the push notification enabled features' permissions may be incorrectly
+// applied.
+- (ChromeBrowserState*)chromeBrowserStateFrom:(NSString*)gaiaID {
+ BrowserStateInfoCache* infoCache =
+ _chromeBrowserStateManager->GetBrowserStateInfoCache();
+ const size_t numberOfBrowserStates = infoCache->GetNumberOfBrowserStates();
+ for (size_t i = 0; i < numberOfBrowserStates; i++) {
+ NSString* browserStateGaiaID =
+ base::SysUTF8ToNSString(infoCache->GetGAIAIdOfBrowserStateAtIndex(i));
+
+ if (gaiaID == browserStateGaiaID) {
+ base::FilePath path = infoCache->GetPathOfBrowserStateAtIndex(i);
+ return _chromeBrowserStateManager->GetBrowserState(path);
+ }
+ }
+
+ return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/push_notification/push_notification_configuration.h b/ios/chrome/browser/push_notification/push_notification_configuration.h
index aa38d51..ed3f350 100644
--- a/ios/chrome/browser/push_notification/push_notification_configuration.h
+++ b/ios/chrome/browser/push_notification/push_notification_configuration.h
@@ -7,6 +7,7 @@
#import <Foundation/Foundation.h>
+@class PushNotificationAccountContextManager;
@protocol SingleSignOnService;
using GaiaIdToPushNotificationPreferenceMap =
@@ -28,10 +29,18 @@
// SingleSignOnService used by PushNotificationService.
@property(nonatomic, strong) id<SingleSignOnService> ssoService;
-// A dictionary that maps a user's GAIA ID to its preferences for all push
-// notification enabled features.
+// DEPRECATED. Please use the `contextManager.contextMap` instead. A dictionary
+// that maps a user's GAIA ID to its preferences for all push notification
+// enabled features.
@property(nonatomic, copy) GaiaIdToPushNotificationPreferenceMap* preferenceMap;
+// A dictionary that maps a user's GAIA ID to an object containing the account's
+// preferences for all push notification enabled features and an number
+// representing the number of times the account is signed in across
+// BrowserStates.
+@property(nonatomic, copy)
+ PushNotificationAccountContextManager* contextManager;
+
@end
-#endif // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CONFIGURATION_H_
\ No newline at end of file
+#endif // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CONFIGURATION_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_service.h b/ios/chrome/browser/push_notification/push_notification_service.h
index c5aaa0e8..09e64dc 100644
--- a/ios/chrome/browser/push_notification/push_notification_service.h
+++ b/ios/chrome/browser/push_notification/push_notification_service.h
@@ -12,6 +12,10 @@
namespace user_prefs {
class PrefRegistrySyncable;
} // namespace user_prefs
+namespace ios {
+class ChromeBrowserStateManager;
+}
+@class PushNotificationAccountContextManager;
class PushNotificationClientManager;
// Service responsible for establishing connection and interacting
@@ -35,6 +39,21 @@
// the operation successfully or unsuccessfully completes.
virtual void UnregisterDevice(CompletionHandler completion_handler) = 0;
+ // Returns whether the device has retrieved and stored its APNS device token.
+ virtual bool DeviceTokenIsSet() const;
+
+ // Registers the new account to the push notification server. In a multi
+ // BrowserState environment, the PushNotificationService tracks the signed in
+ // account across BrowserStates.
+ void RegisterAccount(NSString* account_id,
+ CompletionHandler completion_handler);
+
+ // Unregisters the account from the push notification server. In a multi
+ // BrowserState environment, the account will not be signed out until it's
+ // signed out across BrowserStates.
+ void UnregisterAccount(NSString* account_id,
+ CompletionHandler completion_handler);
+
// Updates the current user's push notification preferences with the push
// notification server.
void UpdateFeaturePushNotificationPreferences(
@@ -51,10 +70,30 @@
// Returns PushNotificationService's PushNotificationClientManager.
PushNotificationClientManager* GetPushNotificationClientManager();
+ protected:
+ PushNotificationService(ios::ChromeBrowserStateManager* manager);
+ // Registers the device with the push notification server. By supplying a list
+ // of the GAIA IDs currently logged into Chrome on the device and the device's
+ // APNS token, the server associates the GAIA IDs to the device, which allows
+ // the server to begin sending push notifications to that device. When this
+ // method is called, the server overwrites the accountIDs that are associated
+ // with the device token with the given array of accountIDs. Thus, to
+ // unregister an account from receiving push notifications on the device, this
+ // method should be called with an array of accountIDs that omits the account
+ // that is intended to be unregistered.
+ virtual void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+ CompletionHandler completion_handler) {}
+
private:
// The PushNotificationClientManager manages all interactions between the
// system and push notification enabled features.
std::unique_ptr<PushNotificationClientManager> client_manager_;
+
+ // Stores a mapping of each account's GAIA ID signed into the device to its
+ // context object. This object contains the account's pref service values
+ // pertaining to push notification supported features and the number of times
+ // the given account is signed in across multiple browser states.
+ __strong PushNotificationAccountContextManager* context_manager_;
};
#endif // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_SERVICE_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_service.mm b/ios/chrome/browser/push_notification/push_notification_service.mm
index 272baf8..1cc868d 100644
--- a/ios/chrome/browser/push_notification/push_notification_service.mm
+++ b/ios/chrome/browser/push_notification/push_notification_service.mm
@@ -7,7 +7,9 @@
#import "base/strings/string_number_conversions.h"
#import "base/values.h"
#import "components/pref_registry/pref_registry_syncable.h"
+#import "ios/chrome/browser/application_context/application_context.h"
#import "ios/chrome/browser/prefs/pref_names.h"
+#import "ios/chrome/browser/push_notification/push_notification_account_context_manager.h"
#import "ios/chrome/browser/push_notification/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/push_notification_client_manager.h"
@@ -16,7 +18,15 @@
#endif
PushNotificationService::PushNotificationService()
- : client_manager_(std::make_unique<PushNotificationClientManager>()) {}
+ : PushNotificationService(
+ GetApplicationContext()->GetChromeBrowserStateManager()) {}
+
+PushNotificationService::PushNotificationService(
+ ios::ChromeBrowserStateManager* manager)
+ : client_manager_(std::make_unique<PushNotificationClientManager>()) {
+ context_manager_ = [[PushNotificationAccountContextManager alloc]
+ initWithChromeBrowserStateManager:manager];
+}
PushNotificationService::~PushNotificationService() = default;
@@ -25,6 +35,28 @@
return client_manager_.get();
}
+bool PushNotificationService::DeviceTokenIsSet() const {
+ return false;
+}
+
+void PushNotificationService::RegisterAccount(
+ NSString* account_id,
+ CompletionHandler completion_handler) {
+ if ([context_manager_ addAccount:account_id]) {
+ SetAccountsToDevice(context_manager_.contextMap.allKeys,
+ completion_handler);
+ }
+}
+
+void PushNotificationService::UnregisterAccount(
+ NSString* account_id,
+ CompletionHandler completion_handler) {
+ if ([context_manager_ removeAccount:account_id]) {
+ SetAccountsToDevice(context_manager_.contextMap.allKeys,
+ completion_handler);
+ }
+}
+
void PushNotificationService::RegisterBrowserStatePrefs(
user_prefs::PrefRegistrySyncable* registry) {
base::Value::Dict feature_push_notification_permission = base::Value::Dict();
diff --git a/ios/chrome/test/providers/push_notification/test_push_notification.mm b/ios/chrome/test/providers/push_notification/test_push_notification.mm
index 628ccc7..41661499 100644
--- a/ios/chrome/test/providers/push_notification/test_push_notification.mm
+++ b/ios/chrome/test/providers/push_notification/test_push_notification.mm
@@ -27,6 +27,12 @@
void RegisterDevice(PushNotificationConfiguration* config,
void (^completion_handler)(NSError* error)) final;
void UnregisterDevice(void (^completion_handler)(NSError* error)) final;
+ bool DeviceTokenIsSet() const final;
+
+ protected:
+ // PushNotificationService implementation.
+ void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+ CompletionHandler completion_handler) final;
};
void TestPushNotificationService::RegisterDevice(
@@ -59,8 +65,28 @@
}));
}
+bool TestPushNotificationService::DeviceTokenIsSet() const {
+ return false;
+}
+
+void TestPushNotificationService::SetAccountsToDevice(
+ NSArray<NSString*>* account_ids,
+ void (^completion_handler)(NSError* error)) {
+ // Test implementation does nothing. As a result, the `completion_handler` is
+ // called with a NSFeatureUnsupportedError.
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(^() {
+ NSError* error =
+ [NSError errorWithDomain:kTestPushNotificationErrorDomain
+ code:NSFeatureUnsupportedError
+ userInfo:nil];
+ completion_handler(error);
+ }));
+}
+
std::unique_ptr<PushNotificationService> CreatePushNotificationService() {
return std::make_unique<TestPushNotificationService>();
}
} // namespace provider
-} // namespace ios
\ No newline at end of file
+} // namespace ios