[go: nahoru, domu]

blob: ebdf12f75f201b60a9b580bd29f8588a3e0aa4ae [file] [log] [blame]
// Copyright 2020 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/ui/settings/safety_check/safety_check_mediator.h"
#import "base/apple/foundation_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/numerics/safe_conversions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/time/time.h"
#import "base/version.h"
#import "components/password_manager/core/browser/leak_detection_dialog_utils.h"
#import "components/password_manager/core/browser/password_sync_util.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/prefs/pref_service.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/safety_check/safety_check.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "components/version_info/version_info.h"
#import "ios/chrome/browser/omaha/omaha_service.h"
#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager_factory.h"
#import "ios/chrome/browser/passwords/password_check_observer_bridge.h"
#import "ios/chrome/browser/passwords/password_checkup_utils.h"
#import "ios/chrome/browser/passwords/password_store_observer_bridge.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/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/ui/settings/cells/settings_check_item.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_constants.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_mediator+private.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_navigation_commands.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/safety_check/safety_check_utils.h"
#import "ios/chrome/browser/ui/settings/utils/observable_boolean.h"
#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
#import "ios/chrome/browser/upgrade/upgrade_constants.h"
#import "ios/chrome/browser/upgrade/upgrade_recommended_details.h"
#import "ios/chrome/browser/upgrade/upgrade_utils.h"
#import "ios/chrome/common/channel_info.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
#import "ios/chrome/grit/ios_chromium_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/common/url_scheme_util.h"
#import "net/base/mac/url_conversions.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/time_format.h"
#import "url/gurl.h"
using l10n_util::GetNSString;
using password_manager::WarningType;
using password_manager::features::IsPasswordCheckupEnabled;
namespace {
// The size of leading symbol icons.
constexpr NSInteger kLeadingSymbolImagePointSize = 22;
typedef NSArray<TableViewItem*>* ItemArray;
typedef NS_ENUM(NSInteger, SafteyCheckItemType) {
// CheckTypes section.
UpdateItemType = kItemTypeEnumZero,
PasswordItemType,
SafeBrowsingItemType,
HeaderItem,
// CheckStart section.
CheckStartItemType,
TimestampFooterItem,
};
// The minimum time each of the three checks should show a running state. This
// is to prevent any check that finshes quicky from causing the UI to appear
// jittery. The numbers are all different so that no 2 tests finish at the same
// time if they all end up using their min delays.
constexpr double kUpdateRowMinDelay = 2.0;
constexpr double kPasswordRowMinDelay = 1.5;
constexpr double kSafeBrowsingRowMinDelay = 3.0;
// Returns true if any of the save passwords are insecure.
bool FoundInsecurePasswords(PasswordCheckRowStates password_check_row_state) {
switch (password_check_row_state) {
case PasswordCheckRowStateSafe:
case PasswordCheckRowStateDefault:
case PasswordCheckRowStateRunning:
case PasswordCheckRowStateDisabled:
case PasswordCheckRowStateError:
return false;
case PasswordCheckRowStateUnmutedCompromisedPasswords:
case PasswordCheckRowStateReusedPasswords:
case PasswordCheckRowStateWeakPasswords:
case PasswordCheckRowStateDismissedWarnings:
return true;
}
}
// Helper method to determine whether the password check item is tappable or
// not.
bool IsPasswordCheckItemTappable(
PasswordCheckRowStates password_check_row_state) {
switch (password_check_row_state) {
case PasswordCheckRowStateUnmutedCompromisedPasswords:
return true;
case PasswordCheckRowStateReusedPasswords:
case PasswordCheckRowStateWeakPasswords:
case PasswordCheckRowStateDismissedWarnings:
case PasswordCheckRowStateSafe:
return IsPasswordCheckupEnabled();
case PasswordCheckRowStateDefault:
case PasswordCheckRowStateRunning:
case PasswordCheckRowStateDisabled:
case PasswordCheckRowStateError:
return false;
}
}
// Resets the state of the given SettingsCheckItem.
void ResetSettingsCheckItem(SettingsCheckItem* item) {
item.enabled = YES;
item.indicatorHidden = YES;
item.infoButtonHidden = YES;
item.trailingImage = nil;
item.trailingImageTintColor = nil;
item.accessoryType = UITableViewCellAccessoryNone;
}
} // namespace
@interface SafetyCheckMediator () <BooleanObserver, PasswordCheckObserver> {
scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;
// A helper object for observing changes in the password check status
// and changes to the compromised credentials list. It needs to be destroyed
// before `_passwordCheckManager`, so it needs to be declared afterwards.
std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
}
// Header for the Safety Check page.
@property(nonatomic, strong) TableViewLinkHeaderFooterItem* headerItem;
// If the Safe Browsing preference is managed.
@property(nonatomic, assign) BOOL safeBrowsingPreferenceManaged;
// The service responsible for password check feature.
@property(nonatomic, assign) scoped_refptr<IOSChromePasswordCheckManager>
passwordCheckManager;
// If any checks in safety check are still running.
@property(nonatomic, assign, readonly) BOOL checksRemaining;
// Service used to check if user is signed in.
@property(nonatomic, assign) AuthenticationService* authService;
// Service to check if passwords are synced.
@property(nonatomic, assign) syncer::SyncService* syncService;
// Service used to check user preference values.
@property(nonatomic, assign, readonly) PrefService* userPrefService;
// When the check was started.
@property(nonatomic, assign) base::Time checkStartTime;
@end
@implementation SafetyCheckMediator
@synthesize passwordCheckManager = _passwordCheckManager;
- (instancetype)initWithUserPrefService:(PrefService*)userPrefService
passwordCheckManager:
(scoped_refptr<IOSChromePasswordCheckManager>)
passwordCheckManager
authService:(AuthenticationService*)authService
syncService:(syncer::SyncService*)syncService {
self = [super init];
if (self) {
DCHECK(userPrefService);
DCHECK(passwordCheckManager);
DCHECK(authService);
DCHECK(syncService);
_userPrefService = userPrefService;
_authService = authService;
_syncService = syncService;
_passwordCheckManager = passwordCheckManager;
_currentPasswordCheckState = _passwordCheckManager->GetPasswordCheckState();
_passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
self, _passwordCheckManager.get());
_safeBrowsingPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kSafeBrowsingEnabled];
_safeBrowsingPreference.observer = self;
_safeBrowsingPreferenceManaged =
userPrefService->IsManagedPreference(prefs::kSafeBrowsingEnabled);
_enhancedSafeBrowsingPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kSafeBrowsingEnhanced];
_enhancedSafeBrowsingPreference.observer = self;
_headerItem =
[[TableViewLinkHeaderFooterItem alloc] initWithType:HeaderItem];
_headerItem.text =
l10n_util::GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_PAGE_HEADER);
_updateCheckRowState = UpdateCheckRowStateDefault;
_updateCheckItem = [[SettingsCheckItem alloc] initWithType:UpdateItemType];
_updateCheckItem.text =
l10n_util::GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_TITLE);
UIImage* updateCheckIcon = DefaultSymbolTemplateWithPointSize(
kInfoCircleSymbol, kLeadingSymbolImagePointSize);
_updateCheckItem.leadingIcon = updateCheckIcon;
_updateCheckItem.leadingIconTintColor = [UIColor colorNamed:kGrey400Color];
ResetSettingsCheckItem(_updateCheckItem);
// Show unsafe state if the app is out of date and safety check already
// found an issue.
if (!IsAppUpToDate() && PreviousSafetyCheckIssueFound()) {
_updateCheckRowState = UpdateCheckRowStateOutOfDate;
}
_previousUpdateCheckRowState = _updateCheckRowState;
_passwordCheckRowState = PasswordCheckRowStateDefault;
_passwordCheckItem =
[[SettingsCheckItem alloc] initWithType:PasswordItemType];
_passwordCheckItem.text =
l10n_util::GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_PASSWORDS_TITLE);
UIImage* passwordCheckIcon = CustomSymbolTemplateWithPointSize(
kPasswordSymbol, kLeadingSymbolImagePointSize);
_passwordCheckItem.leadingIcon = passwordCheckIcon;
_passwordCheckItem.leadingIconTintColor =
[UIColor colorNamed:kGrey400Color];
ResetSettingsCheckItem(_passwordCheckItem);
// Show unsafe state if user already ran safety check and there are insecure
// credentials.
std::vector<password_manager::CredentialUIEntry> insecureCredentials =
_passwordCheckManager->GetInsecureCredentials();
if (!insecureCredentials.empty() && PreviousSafetyCheckIssueFound()) {
_passwordCheckRowState =
[self passwordCheckRowStateFromHighestPriorityWarningType:
insecureCredentials];
}
_previousPasswordCheckRowState = _passwordCheckRowState;
_safeBrowsingCheckRowState = SafeBrowsingCheckRowStateDefault;
_previousSafeBrowsingCheckRowState = _safeBrowsingCheckRowState;
_safeBrowsingCheckItem =
[[SettingsCheckItem alloc] initWithType:SafeBrowsingItemType];
_safeBrowsingCheckItem.text = l10n_util::GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_TITLE);
UIImage* safeBrowsingCheckIcon =
CustomSymbolWithPointSize(kPrivacySymbol, kLeadingSymbolImagePointSize);
_safeBrowsingCheckItem.leadingIcon = safeBrowsingCheckIcon;
_safeBrowsingCheckItem.leadingIconTintColor =
[UIColor colorNamed:kGrey400Color];
ResetSettingsCheckItem(_safeBrowsingCheckItem);
_checkStartState = CheckStartStateDefault;
_checkStartItem =
[[TableViewTextItem alloc] initWithType:CheckStartItemType];
_checkStartItem.text = GetNSString(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON);
_checkStartItem.textColor = [UIColor colorNamed:kBlueColor];
_checkStartItem.accessibilityTraits |= UIAccessibilityTraitButton;
}
return self;
}
- (void)setConsumer:(id<SafetyCheckConsumer>)consumer {
if (_consumer == consumer)
return;
_consumer = consumer;
NSArray* checkItems = @[
self.updateCheckItem, self.passwordCheckItem, self.safeBrowsingCheckItem
];
[_consumer setCheckItems:checkItems];
[_consumer setSafetyCheckHeaderItem:self.headerItem];
[_consumer setCheckStartItem:self.checkStartItem];
// Need to reconfigure the safety check items if there are remaining issues
// from the last check ran.
[self reconfigurePasswordCheckItem];
[self reconfigureUpdateCheckItem];
[self reconfigureSafeBrowsingCheckItem];
}
#pragma mark - Public Methods
- (void)startCheckIfNotRunning {
if (self.checksRemaining) {
return;
}
[self startCheck];
}
#pragma mark - PasswordCheckObserver
- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
if (state == self.currentPasswordCheckState) {
return;
}
// If password check reports the device is offline, propogate this information
// to the update check.
if (state == PasswordCheckState::kOffline) {
[self handleUpdateCheckOffline];
}
self.passwordCheckRowState = [self computePasswordCheckRowState:state];
// Push update to the display.
[self reconfigurePasswordCheckItem];
}
- (void)insecureCredentialsDidChange {
self.passwordCheckRowState =
[self computePasswordCheckRowState:self.currentPasswordCheckState];
// Push update to the display.
[self reconfigurePasswordCheckItem];
}
#pragma mark - SafetyCheckServiceDelegate
- (void)didSelectItem:(TableViewItem*)item {
SafteyCheckItemType type = static_cast<SafteyCheckItemType>(item.type);
switch (type) {
// Few selections are handled here explicitly, but all states are laid out
// to have one location that shows all actions that are taken from the
// safety check page.
// i tap: tap on the info i handled by infoButtonWasTapped.
case UpdateItemType: {
switch (self.updateCheckRowState) {
case UpdateCheckRowStateDefault: // No tap action.
case UpdateCheckRowStateRunning: // No tap action.
case UpdateCheckRowStateUpToDate: // No tap action.
case UpdateCheckRowStateChannel: // No tap action.
case UpdateCheckRowStateManaged: // i tap: Managed state popover.
case UpdateCheckRowStateOmahaError: // i tap: Show error popover.
case UpdateCheckRowStateNetError: // i tap: Show error popover.
break;
case UpdateCheckRowStateOutOfDate: { // i tap: Go to app store.
NSString* updateLocation = [[NSUserDefaults standardUserDefaults]
stringForKey:kIOSChromeUpgradeURLKey];
base::RecordAction(base::UserMetricsAction(
"Settings.SafetyCheck.RelaunchAfterUpdates"));
base::UmaHistogramEnumeration(
kSafetyCheckInteractions,
SafetyCheckInteractions::kUpdatesRelaunch);
[self.handler showUpdateAtLocation:updateLocation];
break;
}
}
break;
}
case PasswordItemType: {
switch (self.passwordCheckRowState) {
case PasswordCheckRowStateDefault: // No tap action.
case PasswordCheckRowStateRunning: // No tap action.
case PasswordCheckRowStateDisabled: // i tap: Show error popover.
case PasswordCheckRowStateError: // i tap: Show error popover.
break;
case PasswordCheckRowStateSafe:
case PasswordCheckRowStateReusedPasswords:
case PasswordCheckRowStateWeakPasswords:
case PasswordCheckRowStateDismissedWarnings:
case PasswordCheckRowStateUnmutedCompromisedPasswords: // Go to
// password
// issues or
// password
// checkup page.
base::RecordAction(
base::UserMetricsAction("Settings.SafetyCheck.ManagePasswords"));
base::UmaHistogramEnumeration(
kSafetyCheckInteractions,
SafetyCheckInteractions::kPasswordsManage);
if (IsPasswordCheckupEnabled()) {
[self.handler showPasswordCheckupPage];
} else {
password_manager::LogPasswordCheckReferrer(
password_manager::PasswordCheckReferrer::kSafetyCheck);
[self.handler showPasswordIssuesPage];
}
break;
}
break;
}
case SafeBrowsingItemType: {
switch (self.safeBrowsingCheckRowState) {
case SafeBrowsingCheckRowStateDefault: // No tap action.
case SafeBrowsingCheckRowStateRunning: // No tap action.
case SafeBrowsingCheckRowStateManaged: // i tap: Managed state popover.
break;
case SafeBrowsingCheckRowStateSafe:
case SafeBrowsingCheckRowStateUnsafe: // Show Safe Browsing settings.
[self.handler showSafeBrowsingPreferencePage];
break;
}
break;
}
case CheckStartItemType: { // Start or stop a safety check.
[self checkStartOrCancel];
break;
}
case HeaderItem:
case TimestampFooterItem:
break;
}
}
- (BOOL)isItemClickable:(TableViewItem*)item {
SafteyCheckItemType type = static_cast<SafteyCheckItemType>(item.type);
switch (type) {
case UpdateItemType:
return self.updateCheckRowState == UpdateCheckRowStateOutOfDate;
case PasswordItemType:
return IsPasswordCheckItemTappable(self.passwordCheckRowState);
case CheckStartItemType:
return YES;
case SafeBrowsingItemType:
return safe_browsing::GetSafeBrowsingState(*self.userPrefService) ==
safe_browsing::SafeBrowsingState::STANDARD_PROTECTION ||
self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateUnsafe;
case HeaderItem:
case TimestampFooterItem:
return NO;
}
}
- (BOOL)isItemWithErrorInfo:(TableViewItem*)item {
SafteyCheckItemType type = static_cast<SafteyCheckItemType>(item.type);
return (type != CheckStartItemType);
}
- (void)infoButtonWasTapped:(UIButton*)buttonView
usingItemType:(NSInteger)itemType {
// Show the managed popover if needed.
if (itemType == SafeBrowsingItemType &&
self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateManaged) {
[self.handler showManagedInfoFrom:buttonView];
return;
}
if (itemType == SafeBrowsingItemType) {
// Directly open Safe Browsing settings instead of showing a popover.
[self.handler showSafeBrowsingPreferencePage];
return;
}
if (itemType == UpdateItemType &&
self.updateCheckRowState == UpdateCheckRowStateManaged) {
[self.handler showManagedInfoFrom:buttonView];
return;
}
// If not managed compute error info to show in popover, if available.
NSAttributedString* info = [self popoverInfoForType:itemType];
// If `info` is empty there is no popover to display.
if (!info)
return;
// Push popover to coordinator.
[self.handler showErrorInfoFrom:buttonView withText:info];
}
#pragma mark - BooleanObserver
- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
[self checkAndReconfigureSafeBrowsingState];
}
#pragma mark - Private methods
// Computes the text needed for a popover on `itemType` if available.
- (NSAttributedString*)popoverInfoForType:(NSInteger)itemType {
SafteyCheckItemType type = static_cast<SafteyCheckItemType>(itemType);
switch (type) {
case PasswordItemType:
return [self passwordCheckErrorInfo];
case UpdateItemType:
return [self updateCheckErrorInfoString];
case CheckStartItemType:
case HeaderItem:
case SafeBrowsingItemType:
case TimestampFooterItem:
return nil;
}
}
// Computes the appropriate display state of the password check row based on
// `currentPasswordCheckState`.
- (PasswordCheckRowStates)computePasswordCheckRowState:
(PasswordCheckState)newState {
BOOL wasRunning =
self.currentPasswordCheckState == PasswordCheckState::kRunning;
self.currentPasswordCheckState = newState;
std::vector<password_manager::CredentialUIEntry> insecureCredentials =
_passwordCheckManager->GetInsecureCredentials();
BOOL noInsecurePasswords = insecureCredentials.empty();
switch (self.currentPasswordCheckState) {
case PasswordCheckState::kRunning:
return PasswordCheckRowStateRunning;
case PasswordCheckState::kNoPasswords:
return PasswordCheckRowStateDisabled;
case PasswordCheckState::kSignedOut:
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kSignedOut);
if (!IsPasswordCheckupEnabled() && !noInsecurePasswords) {
return PasswordCheckRowStateUnmutedCompromisedPasswords;
}
return PasswordCheckRowStateError;
case PasswordCheckState::kOffline:
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kOffline);
if (!IsPasswordCheckupEnabled() && !noInsecurePasswords) {
return PasswordCheckRowStateUnmutedCompromisedPasswords;
}
return PasswordCheckRowStateError;
case PasswordCheckState::kQuotaLimit:
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kQuotaLimit);
if (!IsPasswordCheckupEnabled() && !noInsecurePasswords) {
return PasswordCheckRowStateUnmutedCompromisedPasswords;
}
return PasswordCheckRowStateError;
case PasswordCheckState::kOther:
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kError);
if (!IsPasswordCheckupEnabled() && !noInsecurePasswords) {
return PasswordCheckRowStateUnmutedCompromisedPasswords;
}
return PasswordCheckRowStateError;
case PasswordCheckState::kCanceled:
case PasswordCheckState::kIdle: {
if (!IsPasswordCheckupEnabled() && !noInsecurePasswords) {
if (wasRunning) {
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kCompromisedExist);
}
return PasswordCheckRowStateUnmutedCompromisedPasswords;
} else if (self.currentPasswordCheckState == PasswordCheckState::kIdle) {
// Safe state is only possible after the state transitioned from
// kRunning to kIdle.
if (wasRunning) {
if (noInsecurePasswords) {
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kSafe);
return PasswordCheckRowStateSafe;
}
// Reaching this point means that the kIOSPasswordCheckup feature is
// enabled and that there are insecure passwords.
return [self passwordCheckRowStateFromHighestPriorityWarningType:
insecureCredentials];
}
}
return PasswordCheckRowStateDefault;
}
}
}
// Returns the right PasswordCheckRowState depending on the highest priority
// warning type.
- (PasswordCheckRowStates)passwordCheckRowStateFromHighestPriorityWarningType:
(const std::vector<password_manager::CredentialUIEntry>&)
insecureCredentials {
switch (GetWarningOfHighestPriority(insecureCredentials)) {
case WarningType::kCompromisedPasswordsWarning:
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kCompromisedExist);
return PasswordCheckRowStateUnmutedCompromisedPasswords;
case WarningType::kReusedPasswordsWarning:
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kReusedPasswordsExist);
return PasswordCheckRowStateReusedPasswords;
case WarningType::kWeakPasswordsWarning:
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kWeakPasswordsExist);
return PasswordCheckRowStateWeakPasswords;
case WarningType::kDismissedWarningsWarning:
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kMutedCompromisedExist);
return PasswordCheckRowStateDismissedWarnings;
case WarningType::kNoInsecurePasswordsWarning:
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kSafe);
return PasswordCheckRowStateSafe;
}
}
// Computes the appropriate error info to be displayed in the updates popover.
- (NSAttributedString*)updateCheckErrorInfoString {
NSString* message;
switch (self.updateCheckRowState) {
case UpdateCheckRowStateDefault:
case UpdateCheckRowStateRunning:
case UpdateCheckRowStateUpToDate:
case UpdateCheckRowStateOutOfDate:
case UpdateCheckRowStateManaged:
return nil;
case UpdateCheckRowStateOmahaError:
message = l10n_util::GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_ERROR_INFO);
break;
case UpdateCheckRowStateNetError:
message = l10n_util::GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OFFLINE_INFO);
break;
case UpdateCheckRowStateChannel:
break;
}
return [self attributedStringWithText:message link:GURL()];
}
// Computes the appropriate error info to be displayed in the passwords popover.
- (NSAttributedString*)passwordCheckErrorInfo {
if (!self.passwordCheckManager->GetInsecureCredentials().empty()) {
return nil;
}
NSString* message;
GURL linkURL;
switch (self.currentPasswordCheckState) {
case PasswordCheckState::kRunning:
case PasswordCheckState::kNoPasswords:
message = IsPasswordCheckupEnabled()
? l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECKUP_ERROR_NO_PASSWORDS)
: l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECK_ERROR_NO_PASSWORDS);
break;
case PasswordCheckState::kCanceled:
case PasswordCheckState::kIdle:
return nil;
case PasswordCheckState::kSignedOut:
message =
IsPasswordCheckupEnabled()
? l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECKUP_ERROR_SIGNED_OUT)
: l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_SIGNED_OUT);
break;
case PasswordCheckState::kOffline:
message =
IsPasswordCheckupEnabled()
? l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OFFLINE)
: l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_OFFLINE);
break;
case PasswordCheckState::kQuotaLimit:
if ([self canUseAccountPasswordCheckup]) {
message =
IsPasswordCheckupEnabled()
? l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECKUP_ERROR_QUOTA_LIMIT_VISIT_GOOGLE)
: l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECK_ERROR_QUOTA_LIMIT_VISIT_GOOGLE);
linkURL = password_manager::GetPasswordCheckupURL(
password_manager::PasswordCheckupReferrer::kPasswordCheck);
} else {
message = IsPasswordCheckupEnabled()
? l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECKUP_ERROR_QUOTA_LIMIT)
: l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECK_ERROR_QUOTA_LIMIT);
}
break;
case PasswordCheckState::kOther:
message =
IsPasswordCheckupEnabled()
? l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OTHER)
: l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_OTHER);
break;
}
return [self attributedStringWithText:message link:linkURL];
}
// Computes whether user is capable to run password check in Google Account.
- (BOOL)canUseAccountPasswordCheckup {
return password_manager::sync_util::GetAccountForSaving(self.userPrefService,
self.syncService) &&
!self.syncService->GetUserSettings()->IsEncryptEverythingEnabled();
}
// Configures check error info with a link for popovers.
- (NSAttributedString*)attributedStringWithText:(NSString*)text
link:(GURL)link {
NSDictionary* textAttributes = @{
NSFontAttributeName :
[UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline],
NSForegroundColorAttributeName : [UIColor colorNamed:kTextSecondaryColor]
};
if (link.is_empty()) {
return [[NSMutableAttributedString alloc] initWithString:text
attributes:textAttributes];
}
NSDictionary* linkAttributes =
@{NSLinkAttributeName : net::NSURLWithGURL(link)};
return AttributedStringFromStringWithLink(text, textAttributes,
linkAttributes);
}
// Upon a tap of checkStartItem either starts or cancels a safety check.
- (void)checkStartOrCancel {
// If a check is already running cancel it.
if (self.checksRemaining) {
self.checkDidRun = NO;
// Revert checks that are still running to their previous state.
if (self.updateCheckRowState == UpdateCheckRowStateRunning) {
self.updateCheckRowState = self.previousUpdateCheckRowState;
[self reconfigureUpdateCheckItem];
}
if (self.passwordCheckRowState == PasswordCheckRowStateRunning) {
self.passwordCheckManager->StopPasswordCheck();
self.passwordCheckRowState = self.previousPasswordCheckRowState;
[self reconfigurePasswordCheckItem];
}
if (self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateRunning) {
self.safeBrowsingCheckRowState = self.previousSafeBrowsingCheckRowState;
[self reconfigureSafeBrowsingCheckItem];
}
// Change checkStartItem to default state.
self.checkStartState = CheckStartStateDefault;
[self reconfigureCheckStartSection];
return;
}
[self startCheck];
}
// Starts a safety check
- (void)startCheck {
// Otherwise start a check.
self.checkStartTime = base::Time::Now();
// Record the current state of the checks.
self.previousUpdateCheckRowState = self.updateCheckRowState;
self.previousPasswordCheckRowState = self.passwordCheckRowState;
self.previousSafeBrowsingCheckRowState = self.safeBrowsingCheckRowState;
// Set check items to spinning wheel.
self.updateCheckRowState = UpdateCheckRowStateRunning;
self.passwordCheckRowState = PasswordCheckRowStateRunning;
self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateRunning;
// Record all running.
base::RecordAction(base::UserMetricsAction("Settings.SafetyCheck.Start"));
base::UmaHistogramEnumeration(kSafetyCheckInteractions,
SafetyCheckInteractions::kStarted);
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kChecking);
base::UmaHistogramEnumeration(kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kChecking);
base::UmaHistogramEnumeration(kSafetyCheckMetricsSafeBrowsing,
safety_check::SafeBrowsingStatus::kChecking);
// Change checkStartItem to cancel state.
self.checkStartState = CheckStartStateCancel;
// Hide the timestamp while running.
[self.consumer setTimestampFooterItem:nil];
self.checkDidRun = YES;
// Update the display.
[self reconfigureUpdateCheckItem];
[self reconfigurePasswordCheckItem];
[self reconfigureSafeBrowsingCheckItem];
[self reconfigureCheckStartSection];
// The display should be changed to loading icons before any checks are
// started.
if (self.checksRemaining) {
// Only perfom update check on supported channels.
switch (::GetChannel()) {
case version_info::Channel::STABLE:
case version_info::Channel::BETA:
case version_info::Channel::DEV: {
[self performUpdateCheck];
break;
}
case version_info::Channel::CANARY:
case version_info::Channel::UNKNOWN: {
[self possiblyDelayReconfigureUpdateCheckItemWithState:
UpdateCheckRowStateChannel];
break;
}
}
__weak __typeof__(self) weakSelf = self;
// This handles a discrepancy between password check and safety check. In
// password check a user cannot start a check if they have no passwords, but
// in safety check they can, but the `passwordCheckManager` won't even start
// a check. This if block below allows safety check to push the disabled
// state after check now is pressed.
if (self.currentPasswordCheckState == PasswordCheckState::kNoPasswords) {
// Want to show the loading wheel momentarily.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kPasswordRowMinDelay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// Check if the check was cancelled while waiting, we do not want to
// push a completed state to the UI if the check was cancelled.
if (weakSelf.checksRemaining) {
weakSelf.passwordCheckRowState = PasswordCheckRowStateDisabled;
[weakSelf reconfigurePasswordCheckItem];
base::UmaHistogramEnumeration(
kSafetyCheckMetricsPasswords,
safety_check::PasswordsStatus::kNoPasswords);
}
});
} else {
self.passwordCheckManager->StartPasswordCheck();
}
// Want to show the loading wheel momentarily.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kSafeBrowsingRowMinDelay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// Check if the check was cancelled while waiting, we do not want to
// push a completed state to the UI if the check was cancelled.
if (weakSelf.checksRemaining)
[weakSelf checkAndReconfigureSafeBrowsingState];
});
}
}
// Checks if any of the safety checks are still running, resets `checkStartItem`
// if all checks have finished.
- (void)resetsCheckStartItemIfNeeded {
if (self.checksRemaining) {
return;
}
// If a check has finished and issues were found, update the timestamp.
BOOL issuesFound =
(self.updateCheckRowState == UpdateCheckRowStateOutOfDate) ||
(FoundInsecurePasswords(self.passwordCheckRowState));
if (self.checkDidRun && issuesFound) {
[self updateTimestampOfLastCheck];
self.checkDidRun = NO;
} else if (self.checkDidRun && !issuesFound) {
// Clear the timestamp if the last check found no issues.
[[NSUserDefaults standardUserDefaults]
setDouble:base::Time().ToDoubleT()
forKey:kTimestampOfLastIssueFoundKey];
self.checkDidRun = NO;
}
// If no checks are still running, reset `checkStartItem`.
self.checkStartState = CheckStartStateDefault;
[self reconfigureCheckStartSection];
// Since no checks are running, attempt to show the timestamp.
[self showTimestampIfNeeded];
}
// Computes if any of the safety checks are still running.
- (BOOL)checksRemaining {
BOOL passwordCheckRunning =
self.passwordCheckRowState == PasswordCheckRowStateRunning;
BOOL safeBrowsingCheckRunning =
self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateRunning;
BOOL updateCheckRunning =
self.updateCheckRowState == UpdateCheckRowStateRunning;
return updateCheckRunning || passwordCheckRunning || safeBrowsingCheckRunning;
}
// Updates `updateCheckItem` to reflect the device being offline if the check
// was running.
- (void)handleUpdateCheckOffline {
if (self.updateCheckRowState == UpdateCheckRowStateRunning) {
self.updateCheckRowState = UpdateCheckRowStateNetError;
[self reconfigureUpdateCheckItem];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kFailedOffline);
}
}
// Verifies if the Omaha service returned an answer, if not sets
// `updateCheckItem` to an Omaha error state.
- (void)verifyUpdateCheckComplete {
// If still in running state assume Omaha error.
if (self.updateCheckRowState == UpdateCheckRowStateRunning) {
self.updateCheckRowState = UpdateCheckRowStateOmahaError;
[self reconfigureUpdateCheckItem];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kFailed);
}
}
// If the update check would have completed too quickly, making the UI appear
// jittery, delay the reconfigure call, using `newRowState`.
- (void)possiblyDelayReconfigureUpdateCheckItemWithState:
(UpdateCheckRowStates)newRowState {
double secondsSinceStart =
base::Time::Now().ToDoubleT() - self.checkStartTime.ToDoubleT();
double minDelay = kUpdateRowMinDelay;
if (secondsSinceStart < minDelay) {
// Want to show the loading wheel for minimum time.
__weak __typeof__(self) weakSelf = self;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)((minDelay - secondsSinceStart) * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// Check if the check was cancelled while waiting, we do not want to
// push a completed state to the UI if the check was cancelled.
if (weakSelf.checksRemaining) {
weakSelf.updateCheckRowState = newRowState;
[weakSelf reconfigureUpdateCheckItem];
}
});
} else {
self.updateCheckRowState = newRowState;
[self reconfigureUpdateCheckItem];
}
}
// Processes the response from the Omaha service.
- (void)handleOmahaResponse:(const UpgradeRecommendedDetails&)details {
// If before the response the check was canceled, or Omaha assumed faulty,
// do nothing.
if (self.updateCheckRowState != UpdateCheckRowStateRunning) {
return;
}
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if (details.is_up_to_date) {
[self possiblyDelayReconfigureUpdateCheckItemWithState:
UpdateCheckRowStateUpToDate];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kUpdated);
} else {
// upgradeURL and next_version are only set if not up to date.
const GURL& upgradeUrl = details.upgrade_url;
if (!upgradeUrl.is_valid()) {
[self possiblyDelayReconfigureUpdateCheckItemWithState:
UpdateCheckRowStateOmahaError];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kFailed);
return;
}
if (!details.next_version.size() ||
!base::Version(details.next_version).IsValid()) {
[self possiblyDelayReconfigureUpdateCheckItemWithState:
UpdateCheckRowStateOmahaError];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kFailed);
return;
}
[self possiblyDelayReconfigureUpdateCheckItemWithState:
UpdateCheckRowStateOutOfDate];
base::UmaHistogramEnumeration(kSafetyCheckMetricsUpdates,
safety_check::UpdateStatus::kOutdated);
// Valid results, update all NSUserDefaults.
[defaults setValue:base::SysUTF8ToNSString(upgradeUrl.spec())
forKey:kIOSChromeUpgradeURLKey];
[defaults setValue:base::SysUTF8ToNSString(details.next_version)
forKey:kIOSChromeNextVersionKey];
// Treat the safety check finding the device out of date as if the update
// infobar was just shown to not overshow the infobar to the user.
[defaults setObject:[NSDate date] forKey:kLastInfobarDisplayTimeKey];
}
}
// Performs the update check and triggers the display update to
// `updateCheckItem`.
- (void)performUpdateCheck {
__weak __typeof__(self) weakSelf = self;
OmahaService::CheckNow(base::BindOnce(^(UpgradeRecommendedDetails details) {
[weakSelf handleOmahaResponse:details];
}));
// If after 30 seconds the Omaha server has not responded, assume Omaha error.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[weakSelf verifyUpdateCheckComplete];
});
}
// Performs the Safe Browsing check and triggers the display update/
- (void)checkAndReconfigureSafeBrowsingState {
if (!self.safeBrowsingPreferenceManaged) {
if (self.safeBrowsingPreference.value) {
self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateSafe;
base::UmaHistogramEnumeration(kSafetyCheckMetricsSafeBrowsing,
safety_check::SafeBrowsingStatus::kEnabled);
} else {
self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateUnsafe;
base::UmaHistogramEnumeration(
kSafetyCheckMetricsSafeBrowsing,
safety_check::SafeBrowsingStatus::kDisabled);
}
}
if (self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateUnsafe &&
self.safeBrowsingPreferenceManaged) {
self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateManaged;
base::UmaHistogramEnumeration(
kSafetyCheckMetricsSafeBrowsing,
safety_check::SafeBrowsingStatus::kDisabledByAdmin);
}
[self reconfigureSafeBrowsingCheckItem];
}
// Reconfigures the display of the `updateCheckItem` based on current state of
// `updateCheckRowState`.
- (void)reconfigureUpdateCheckItem {
// Reset state to prevent conflicts.
ResetSettingsCheckItem(self.updateCheckItem);
// On any item update, see if `checkStartItem` should be updated.
[self resetsCheckStartItemIfNeeded];
switch (self.updateCheckRowState) {
case UpdateCheckRowStateDefault: {
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_DESCRIPTION);
break;
}
case UpdateCheckRowStateRunning: {
self.updateCheckItem.indicatorHidden = NO;
break;
}
case UpdateCheckRowStateUpToDate: {
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_UP_TO_DATE_DESC);
self.updateCheckItem.warningState = WarningState::kSafe;
break;
}
case UpdateCheckRowStateOutOfDate: {
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OUT_OF_DATE_DESC);
self.updateCheckItem.warningState = WarningState::kSevereWarning;
self.updateCheckItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
break;
}
case UpdateCheckRowStateManaged: {
self.updateCheckItem.infoButtonHidden = NO;
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_MANAGED_DESC);
break;
}
case UpdateCheckRowStateOmahaError: {
self.updateCheckItem.infoButtonHidden = NO;
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_ERROR_DESC);
break;
}
case UpdateCheckRowStateNetError: {
self.updateCheckItem.infoButtonHidden = NO;
self.updateCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OFFLINE_DESC);
break;
}
case UpdateCheckRowStateChannel: {
switch (::GetChannel()) {
case version_info::Channel::STABLE:
case version_info::Channel::DEV:
break;
case version_info::Channel::BETA: {
self.updateCheckItem.detailText = GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_CHANNEL_BETA_DESC);
break;
}
case version_info::Channel::CANARY:
case version_info::Channel::UNKNOWN: {
self.updateCheckItem.detailText = GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_CHANNEL_CANARY_DESC);
break;
}
}
}
}
[self.consumer reconfigureCellsForItems:@[ self.updateCheckItem ]];
}
// Reconfigures the display of the `passwordCheckItem` based on current state of
// `passwordCheckRowState`.
- (void)reconfigurePasswordCheckItem {
// Reset state to prevent conflicts.
ResetSettingsCheckItem(self.passwordCheckItem);
// Set the accessory type.
if (IsPasswordCheckItemTappable(self.passwordCheckRowState)) {
self.passwordCheckItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
}
// On any item update, see if `checkStartItem` should be updated.
[self resetsCheckStartItemIfNeeded];
switch (self.passwordCheckRowState) {
case PasswordCheckRowStateDefault: {
self.passwordCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_PASSWORDS_DESCRIPTION);
break;
}
case PasswordCheckRowStateRunning: {
if (IsPasswordCheckupEnabled()) {
self.passwordCheckItem.detailText =
GetNSString(IDS_IOS_SAFETY_CHECK_PASSWORD_CHECKUP_ONGOING);
}
self.passwordCheckItem.indicatorHidden = NO;
break;
}
case PasswordCheckRowStateSafe: {
DCHECK(self.passwordCheckManager->GetInsecureCredentials().empty());
self.passwordCheckItem.detailText =
base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
IDS_IOS_PASSWORD_CHECKUP_COMPROMISED_COUNT, 0));
self.passwordCheckItem.warningState = WarningState::kSafe;
break;
}
case PasswordCheckRowStateUnmutedCompromisedPasswords: {
NSInteger compromisedPasswordCount =
IsPasswordCheckupEnabled()
? GetPasswordCountForWarningType(
WarningType::kCompromisedPasswordsWarning,
self.passwordCheckManager->GetInsecureCredentials())
: self.passwordCheckManager->GetInsecureCredentials().size();
self.passwordCheckItem.detailText =
base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
IsPasswordCheckupEnabled()
? IDS_IOS_PASSWORD_CHECKUP_COMPROMISED_COUNT
: IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT,
compromisedPasswordCount));
self.passwordCheckItem.warningState = WarningState::kSevereWarning;
break;
}
case PasswordCheckRowStateReusedPasswords: {
NSInteger reusedPasswordCount = GetPasswordCountForWarningType(
WarningType::kReusedPasswordsWarning,
self.passwordCheckManager->GetInsecureCredentials());
self.passwordCheckItem.detailText =
l10n_util::GetNSStringF(IDS_IOS_PASSWORD_CHECKUP_REUSED_COUNT,
base::NumberToString16(reusedPasswordCount));
self.passwordCheckItem.trailingImage = nil;
break;
}
case PasswordCheckRowStateWeakPasswords: {
NSInteger weakPasswordCount = GetPasswordCountForWarningType(
WarningType::kWeakPasswordsWarning,
self.passwordCheckManager->GetInsecureCredentials());
self.passwordCheckItem.detailText =
base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
IDS_IOS_PASSWORD_CHECKUP_WEAK_COUNT, weakPasswordCount));
self.passwordCheckItem.trailingImage = nil;
break;
}
case PasswordCheckRowStateDismissedWarnings: {
NSInteger dismissedWarningCount = GetPasswordCountForWarningType(
WarningType::kDismissedWarningsWarning,
self.passwordCheckManager->GetInsecureCredentials());
self.passwordCheckItem.detailText =
base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
IDS_IOS_PASSWORD_CHECKUP_DISMISSED_COUNT, dismissedWarningCount));
self.passwordCheckItem.trailingImage = nil;
break;
}
case PasswordCheckRowStateDisabled:
case PasswordCheckRowStateError: {
self.passwordCheckItem.detailText =
l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR);
self.passwordCheckItem.infoButtonHidden = NO;
break;
}
}
[self.consumer reconfigureCellsForItems:@[ self.passwordCheckItem ]];
}
// Reconfigures the display of the `safeBrowsingCheckItem` based on current
// state of `safeBrowsingCheckRowState`.
- (void)reconfigureSafeBrowsingCheckItem {
// Reset state to prevent conflicts.
ResetSettingsCheckItem(self.safeBrowsingCheckItem);
// On any item update, see if `checkStartItem` should be updated.
[self resetsCheckStartItemIfNeeded];
switch (self.safeBrowsingCheckRowState) {
case SafeBrowsingCheckRowStateDefault: {
self.safeBrowsingCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DESCRIPTION);
break;
}
case SafeBrowsingCheckRowStateRunning: {
self.safeBrowsingCheckItem.indicatorHidden = NO;
break;
}
case SafeBrowsingCheckRowStateManaged: {
self.safeBrowsingCheckItem.infoButtonHidden = NO;
self.safeBrowsingCheckItem.detailText =
GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_MANAGED_DESC);
break;
}
case SafeBrowsingCheckRowStateSafe: {
self.safeBrowsingCheckItem.detailText =
[self safeBrowsingCheckItemDetailText];
self.safeBrowsingCheckItem.warningState = WarningState::kSafe;
if (safe_browsing::GetSafeBrowsingState(*self.userPrefService) ==
safe_browsing::SafeBrowsingState::STANDARD_PROTECTION) {
self.safeBrowsingCheckItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
}
break;
}
case SafeBrowsingCheckRowStateUnsafe: {
self.safeBrowsingCheckItem.detailText = GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_DESC);
self.safeBrowsingCheckItem.warningState = WarningState::kSevereWarning;
self.safeBrowsingCheckItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
break;
}
}
[self.consumer reconfigureCellsForItems:@[ self.safeBrowsingCheckItem ]];
}
// Chooses the Safe Browsing detail text string that should be used based on the
// Safe Browsing preference chosen.
- (NSString*)safeBrowsingCheckItemDetailText {
safe_browsing::SafeBrowsingState safeBrowsingState =
safe_browsing::GetSafeBrowsingState(*self.userPrefService);
switch (safeBrowsingState) {
case safe_browsing::SafeBrowsingState::STANDARD_PROTECTION:
return GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_STANDARD_PROTECTION_ENABLED_DESC_WITH_ENHANCED_PROTECTION);
case safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION:
return GetNSString(
IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENHANCED_PROTECTION_ENABLED_DESC);
default:
NOTREACHED();
return nil;
}
}
// Updates the display of checkStartItem based on its current state.
- (void)reconfigureCheckStartSection {
if (self.checkStartState == CheckStartStateDefault) {
self.checkStartItem.text = GetNSString(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON);
} else {
self.checkStartItem.text =
GetNSString(IDS_IOS_CANCEL_PASSWORD_CHECK_BUTTON);
}
[self.consumer reconfigureCellsForItems:@[ self.checkStartItem ]];
}
// Updates the timestamp of when safety check last found an issue.
- (void)updateTimestampOfLastCheck {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setDouble:base::Time::Now().ToDoubleT()
forKey:kTimestampOfLastIssueFoundKey];
}
// Shows the timestamp if the last safety check found issues.
- (void)showTimestampIfNeeded {
if (PreviousSafetyCheckIssueFound()) {
TableViewLinkHeaderFooterItem* footerItem =
[[TableViewLinkHeaderFooterItem alloc]
initWithType:TimestampFooterItem];
footerItem.text = [self formatElapsedTimeSinceLastCheck];
[self.consumer setTimestampFooterItem:footerItem];
} else {
// Hide the timestamp if the last safety check didn't find issues.
[self.consumer setTimestampFooterItem:nil];
}
}
// Formats the last safety check issues found timestamp for display.
- (NSString*)formatElapsedTimeSinceLastCheck {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
base::Time lastCompletedCheck = base::Time::FromDoubleT(
[defaults doubleForKey:kTimestampOfLastIssueFoundKey]);
base::TimeDelta elapsedTime = base::Time::Now() - lastCompletedCheck;
std::u16string timestamp;
// If check found issues less than 1 minuete ago.
if (elapsedTime < base::Minutes(1)) {
timestamp = l10n_util::GetStringUTF16(IDS_IOS_CHECK_FINISHED_JUST_NOW);
} else {
timestamp = ui::TimeFormat::SimpleWithMonthAndYear(
ui::TimeFormat::FORMAT_ELAPSED, ui::TimeFormat::LENGTH_LONG,
elapsedTime, true);
}
return l10n_util::GetNSStringF(
IDS_IOS_SETTINGS_SAFETY_CHECK_ISSUES_FOUND_TIME, timestamp);
}
@end