// Copyright 2023 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/ntp/model/set_up_list.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "components/password_manager/core/browser/password_manager_util.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_service.h"
#import "components/sync/base/user_selectable_type.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/ntp/model/set_up_list_delegate.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item_type.h"
#import "ios/chrome/browser/ntp/model/set_up_list_metrics.h"
#import "ios/chrome/browser/ntp/model/set_up_list_prefs.h"
#import "ios/chrome/browser/push_notification/model/constants.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/sync/model/enterprise_utils.h"
using set_up_list_prefs::SetUpListItemState;
namespace {
bool GetIsItemComplete(SetUpListItemType type,
PrefService* prefs,
PrefService* local_state,
AuthenticationService* auth_service) {
switch (type) {
case SetUpListItemType::kSignInSync:
return auth_service->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
case SetUpListItemType::kDefaultBrowser:
return IsChromeLikelyDefaultBrowser();
case SetUpListItemType::kAutofill:
return password_manager_util::IsCredentialProviderEnabledOnStartup(prefs);
case SetUpListItemType::kNotifications: {
id<SystemIdentity> identity =
auth_service->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
if (IsIOSTipsNotificationsEnabled()) {
return push_notification_settings::
IsMobileNotificationsEnabledForAnyClient(
base::SysNSStringToUTF8(identity.gaiaID), prefs);
} else {
return push_notification_settings::
GetMobileNotificationPermissionStatusForClient(
PushNotificationClientId::kContent,
base::SysNSStringToUTF8(identity.gaiaID)) ||
push_notification_settings::
GetMobileNotificationPermissionStatusForClient(
PushNotificationClientId::kSports,
base::SysNSStringToUTF8(identity.gaiaID));
}
}
case SetUpListItemType::kFollow:
case SetUpListItemType::kAllSet:
NOTREACHED();
}
}
SetUpListItem* BuildItem(SetUpListItemType type,
PrefService* prefs,
PrefService* local_state,
AuthenticationService* auth_service) {
SetUpListItemState state = set_up_list_prefs::GetItemState(local_state, type);
SetUpListItemState new_state = state;
bool complete = false;
switch (state) {
case SetUpListItemState::kUnknown:
case SetUpListItemState::kNotComplete:
complete = GetIsItemComplete(type, prefs, local_state, auth_service);
// If complete, mark it as "not in list" for next time, but add to list
// this time.
new_state = complete ? SetUpListItemState::kCompleteInList
: SetUpListItemState::kNotComplete;
set_up_list_prefs::SetItemState(local_state, type, new_state);
if (complete) {
set_up_list_metrics::RecordItemCompleted(type);
}
return [[SetUpListItem alloc] initWithType:type complete:complete];
case SetUpListItemState::kCompleteInList:
return [[SetUpListItem alloc] initWithType:type complete:YES];
case SetUpListItemState::kCompleteNotInList:
return nil;
}
}
// Adds the item to the array if it is not `nil`.
void AddItemIfNotNil(NSMutableArray* array, id item) {
if (item) {
[array addObject:item];
}
}
// Returns true if signin is allowed / enabled.
bool IsSigninEnabled(AuthenticationService* auth_service) {
switch (auth_service->GetServiceStatus()) {
case AuthenticationService::ServiceStatus::SigninForcedByPolicy:
case AuthenticationService::ServiceStatus::SigninAllowed:
return true;
case AuthenticationService::ServiceStatus::SigninDisabledByUser:
case AuthenticationService::ServiceStatus::SigninDisabledByPolicy:
case AuthenticationService::ServiceStatus::SigninDisabledByInternal:
return false;
}
}
// Returns `YES` if all items are complete.
BOOL AllItemsComplete(NSArray<SetUpListItem*>* items) {
for (SetUpListItem* item in items) {
if (!item.complete) {
return NO;
}
}
return YES;
}
} // namespace
@interface SetUpList () <PrefObserverDelegate>
@end
@implementation SetUpList {
// Local state prefs that store item state.
raw_ptr<PrefService> _localState;
// Bridge to listen to pref changes.
std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
// Registrar for pref changes notifications.
PrefChangeRegistrar _prefChangeRegistrar;
// YES if the Notification item should be included in `allItems`.
BOOL _shouldIncludeNotificationItem;
}
+ (instancetype)buildFromPrefs:(PrefService*)prefs
localState:(PrefService*)localState
syncService:(syncer::SyncService*)syncService
authenticationService:(AuthenticationService*)authService
contentNotificationEnabled:(BOOL)isContentNotificationEnabled {
if (IsHomeCustomizationEnabled() &&
!prefs->GetBoolean(prefs::kHomeCustomizationMagicStackSetUpListEnabled)) {
return nil;
}
if (!IsHomeCustomizationEnabled() &&
set_up_list_prefs::IsSetUpListDisabled(localState)) {
return nil;
}
NSMutableArray<SetUpListItem*>* items =
[[NSMutableArray<SetUpListItem*> alloc] init];
auto AddSignInItem = ^{
if (IsSigninEnabled(authService) &&
!syncService->HasDisableReason(
syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY) &&
!HasManagedSyncDataType(syncService)) {
AddItemIfNotNil(items, BuildItem(SetUpListItemType::kSignInSync, prefs,
localState, authService));
}
};
AddItemIfNotNil(items, BuildItem(SetUpListItemType::kDefaultBrowser, prefs,
localState, authService));
AddItemIfNotNil(items, BuildItem(SetUpListItemType::kAutofill, prefs,
localState, authService));
// Add notification item if any of the feature is enabled.
if (IsIOSTipsNotificationsEnabled() || isContentNotificationEnabled) {
AddItemIfNotNil(items, BuildItem(SetUpListItemType::kNotifications, prefs,
localState, authService));
}
AddSignInItem();
// Once all items are complete, set them to disappear from the list the next
// time so that the list will be empty and the "All Set" item will not show.
if (AllItemsComplete(items)) {
for (SetUpListItem* item in items) {
set_up_list_prefs::SetItemState(localState, item.type,
SetUpListItemState::kCompleteNotInList);
}
set_up_list_prefs::MarkAllItemsComplete(localState);
}
// TODO(crbug.com/40262090): Add a Follow item to the Set Up List.
return [[self alloc] initWithItems:items
localState:localState
authenticationService:authService
contentNotificationEnabled:isContentNotificationEnabled];
}
- (instancetype)initWithItems:(NSArray<SetUpListItem*>*)items
localState:(PrefService*)localState
authenticationService:(AuthenticationService*)authService
contentNotificationEnabled:(BOOL)isContentNotificationEnabled {
self = [super init];
if (self) {
_items = items;
_localState = localState;
_prefObserverBridge = std::make_unique<PrefObserverBridge>(self);
_prefChangeRegistrar.Init(localState);
_prefObserverBridge->ObserveChangesForPreference(
set_up_list_prefs::kSigninSyncItemState, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
set_up_list_prefs::kDefaultBrowserItemState, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
set_up_list_prefs::kAutofillItemState, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
set_up_list_prefs::kFollowItemState, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
set_up_list_prefs::kNotificationsItemState, &_prefChangeRegistrar);
_shouldIncludeNotificationItem =
IsIOSTipsNotificationsEnabled() || isContentNotificationEnabled;
}
return self;
}
- (void)disconnect {
_prefChangeRegistrar.RemoveAll();
_prefObserverBridge.reset();
_localState = nullptr;
}
- (BOOL)allItemsComplete {
return AllItemsComplete(self.items);
}
- (NSArray<SetUpListItem*>*)allItems {
NSMutableArray* itemTypes = [[NSMutableArray alloc]
initWithObjects:@(int(SetUpListItemType::kSignInSync)),
@(int(SetUpListItemType::kDefaultBrowser)),
@(int(SetUpListItemType::kAutofill)), nil];
if (_shouldIncludeNotificationItem) {
[itemTypes addObject:@(int(SetUpListItemType::kNotifications))];
}
for (SetUpListItem* item in _items) {
[itemTypes removeObject:@(int(item.type))];
}
NSMutableArray* completeItems = [_items mutableCopy];
for (NSNumber* typeNum in itemTypes) {
[completeItems
addObject:[[SetUpListItem alloc]
initWithType:(SetUpListItemType)[typeNum intValue]
complete:YES]];
}
return completeItems;
}
#pragma mark - PrefObserverDelegate
- (void)onPreferenceChanged:(const std::string&)preferenceName {
SetUpListItem* item = [self itemForPrefName:preferenceName];
if (!item) {
return;
}
SetUpListItemState state =
set_up_list_prefs::GetItemState(_localState, item.type);
if (state == SetUpListItemState::kCompleteInList) {
[item markComplete];
BOOL allItemsComplete = [self allItemsComplete];
[self.delegate setUpListItemDidComplete:item
allItemsCompleted:allItemsComplete];
if (allItemsComplete) {
set_up_list_prefs::MarkAllItemsComplete(_localState);
}
}
}
#pragma mark - Private
// Returns the item with a type that matches the given `prefName`.
- (SetUpListItem*)itemForPrefName:(const std::string&)preferenceName {
for (SetUpListItem* item in self.items) {
if (preferenceName == set_up_list_prefs::PrefNameForItem(item.type)) {
return item;
}
}
return nil;
}
@end