// 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/model/push_notification_account_context_manager.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/values.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/scoped_user_pref_update.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_manager.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
namespace {
// Contains the pref service and key to access the permissions dict, and the
// client key to access the bool field within the dict that holds the
// permission state for a client.
struct PermissionsPref {
raw_ptr<PrefService> service;
const std::string key;
const std::string client_key;
};
} // namespace
@implementation PushNotificationAccountContextManager {
// Used to retrieve Profiles located at a given path.
raw_ptr<ProfileManagerIOS> _profileManager;
// A dictionary that maps a user's GAIA ID to an unsigned integer representing
// the number of times the account is signed in across BrowserStates.
std::map<std::string, size_t> _contextMap;
}
- (instancetype)initWithProfileManager:(ProfileManagerIOS*)manager {
self = [super init];
if (self) {
_profileManager = manager;
ProfileAttributesStorageIOS* storage =
manager->GetProfileAttributesStorage();
const size_t numberOfProfiles = storage->GetNumberOfProfiles();
for (size_t i = 0; i < numberOfProfiles; i++) {
ProfileAttributesIOS attr = storage->GetAttributesForProfileAtIndex(i);
[self addAccount:attr.GetGaiaId()];
}
}
return self;
}
- (BOOL)addAccount:(const std::string&)gaiaID {
if (gaiaID.empty()) {
return NO;
}
_contextMap[gaiaID] += 1;
return YES;
}
- (BOOL)removeAccount:(const std::string&)gaiaID {
auto iterator = _contextMap.find(gaiaID);
DCHECK(iterator != _contextMap.end());
if (iterator == _contextMap.end()) {
// The account was unexpectedly not found, so return NO to indicate that
// it was not removed.
return NO;
}
size_t& occurrencesAcrossBrowserStates = iterator->second;
occurrencesAcrossBrowserStates--;
if (occurrencesAcrossBrowserStates > 0) {
return NO;
}
_contextMap.erase(iterator);
return YES;
}
- (void)enablePushNotification:(PushNotificationClientId)clientID
forAccount:(const std::string&)gaiaID {
PermissionsPref pref = [self prefsForClient:clientID account:gaiaID];
// TODO:(crbug.com/1445551) Restore to DCHECK when signing into Chrome via
// ConsistencySigninPromo UI updates the ProfileAttributesStorageIOS.
if (!pref.service) {
return;
}
ScopedDictPrefUpdate update(pref.service, pref.key);
update->Set(pref.client_key, true);
base::UmaHistogramEnumeration("IOS.PushNotification.Client.Enabled",
clientID);
}
- (void)disablePushNotification:(PushNotificationClientId)clientID
forAccount:(const std::string&)gaiaID {
PermissionsPref pref = [self prefsForClient:clientID account:gaiaID];
// TODO:(crbug.com/1445551) Restore to DCHECK when signing into Chrome via
// ConsistencySigninPromo UI updates the ProfileAttributesStorageIOS.
if (!pref.service) {
return;
}
ScopedDictPrefUpdate update(pref.service, pref.key);
update->Set(pref.client_key, false);
base::UmaHistogramEnumeration("IOS.PushNotification.Client.Disabled",
clientID);
}
- (BOOL)isPushNotificationEnabledForClient:(PushNotificationClientId)clientID
forAccount:(const std::string&)gaiaID {
PermissionsPref pref = [self prefsForClient:clientID account:gaiaID];
// TODO:(crbug.com/1445551) Restore to DCHECK when signing into Chrome via
// ConsistencySigninPromo UI updates the ProfileAttributesStorageIOS.
if (!pref.service) {
return NO;
}
return pref.service->GetDict(pref.key)
.FindBool(pref.client_key)
.value_or(false);
}
- (NSDictionary<NSString*, NSNumber*>*)preferenceMapForAccount:
(const std::string&)gaiaID {
ChromeBrowserState* browserState = [self chromeBrowserStateFrom:gaiaID];
NSMutableDictionary<NSString*, NSNumber*>* result =
[[NSMutableDictionary alloc] init];
if (!browserState) {
return result;
}
const base::Value::Dict& pref = browserState->GetPrefs()->GetDict(
prefs::kFeaturePushNotificationPermissions);
for (const auto&& [key, value] : pref) {
[result setObject:@(value.GetBool()) forKey:base::SysUTF8ToNSString(key)];
}
return result;
}
- (NSArray<NSString*>*)accountIDs {
NSMutableArray<NSString*>* keys =
[[NSMutableArray alloc] initWithCapacity:_contextMap.size()];
for (auto const& context : _contextMap) {
[keys addObject:base::SysUTF8ToNSString(context.first)];
}
return keys;
}
- (NSUInteger)registrationCountForAccount:(const std::string&)gaiaID {
DCHECK(base::Contains(_contextMap, gaiaID));
return _contextMap[gaiaID];
}
#pragma mark - Private
// Returns the ChromeBrowserState that has the given gaiaID set as the primary
// account. TODO(crbug.com/40250402) 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:(const std::string&)gaiaID {
ProfileAttributesStorageIOS* storage =
_profileManager->GetProfileAttributesStorage();
const size_t numberOfProfiles = storage->GetNumberOfProfiles();
for (size_t i = 0; i < numberOfProfiles; i++) {
ProfileAttributesIOS attr = storage->GetAttributesForProfileAtIndex(i);
if (gaiaID == attr.GetGaiaId()) {
return _profileManager->GetProfileWithName(attr.GetProfileName());
}
}
return nullptr;
}
// Returns the appropriate `PermissionsPref` for the given `clientID` and
// `gaiaID`. This can be either BrowserState prefs or LocalState prefs.
- (PermissionsPref)prefsForClient:(PushNotificationClientId)clientID
account:(const std::string&)gaiaID {
std::string clientKey =
PushNotificationClientManager::PushNotificationClientIdToString(clientID);
switch (clientID) {
case PushNotificationClientId::kCommerce:
case PushNotificationClientId::kContent:
case PushNotificationClientId::kSports: {
ChromeBrowserState* browserState = [self chromeBrowserStateFrom:gaiaID];
if (!browserState) {
// TODO:(crbug.com/1445551) Restore to DCHECK when signing into Chrome
// via ConsistencySigninPromo UI updates the
// ProfileAttributesStorageIOS.
return {nullptr, prefs::kFeaturePushNotificationPermissions, clientKey};
}
return {browserState->GetPrefs(),
prefs::kFeaturePushNotificationPermissions, clientKey};
}
case PushNotificationClientId::kTips:
case PushNotificationClientId::kSafetyCheck:
return {GetApplicationContext()->GetLocalState(),
prefs::kAppLevelPushNotificationPermissions, clientKey};
}
}
@end