| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // 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" |
| |
| #include "base/mac/foundation_util.h" |
| #import "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/version.h" |
| #include "components/password_manager/core/browser/leak_detection_dialog_utils.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/safe_browsing/core/common/safe_browsing_prefs.h" |
| #include "components/safe_browsing/core/features.h" |
| #include "components/safety_check/safety_check.h" |
| #include "components/version_info/version_info.h" |
| #include "ios/chrome/browser/application_context.h" |
| #import "ios/chrome/browser/omaha/omaha_service.h" |
| #include "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h" |
| #include "ios/chrome/browser/passwords/ios_chrome_password_check_manager_factory.h" |
| #include "ios/chrome/browser/passwords/password_check_observer_bridge.h" |
| #include "ios/chrome/browser/passwords/password_store_observer_bridge.h" |
| #include "ios/chrome/browser/pref_names.h" |
| #import "ios/chrome/browser/signin/authentication_service.h" |
| #include "ios/chrome/browser/sync/sync_setup_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_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/ui/settings/utils/settings_utils.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h" |
| #import "ios/chrome/browser/ui/ui_feature_flags.h" |
| #import "ios/chrome/browser/ui/util/uikit_ui_util.h" |
| #include "ios/chrome/browser/upgrade/upgrade_constants.h" |
| #include "ios/chrome/browser/upgrade/upgrade_recommended_details.h" |
| #include "ios/chrome/browser/upgrade/upgrade_utils.h" |
| #include "ios/chrome/common/channel_info.h" |
| #import "ios/chrome/common/string_util.h" |
| #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #include "ios/chrome/grit/ios_chromium_strings.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #import "ios/web/common/url_scheme_util.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/time_format.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using l10n_util::GetNSString; |
| using safe_browsing::kSafeBrowsingAvailableOnIOS; |
| |
| namespace { |
| |
| constexpr char kSafetyCheckMetricsUpdates[] = |
| "Settings.SafetyCheck.UpdatesResult"; |
| constexpr char kSafetyCheckMetricsPasswords[] = |
| "Settings.SafetyCheck.PasswordsResult"; |
| constexpr char kSafetyCheckMetricsSafeBrowsing[] = |
| "Settings.SafetyCheck.SafeBrowsingResult"; |
| constexpr char kSafetyCheckInteractions[] = "Settings.SafetyCheck.Interactions"; |
| |
| typedef NSArray<TableViewItem*>* ItemArray; |
| |
| typedef NS_ENUM(NSInteger, SafteyCheckItemType) { |
| // CheckTypes section. |
| UpdateItemType = kItemTypeEnumZero, |
| PasswordItemType, |
| SafeBrowsingItemType, |
| HeaderItem, |
| // CheckStart section. |
| CheckStartItemType, |
| TimestampFooterItem, |
| }; |
| |
| } // namespace |
| |
| @interface SafetyCheckMediator () <BooleanObserver, PasswordCheckObserver> { |
| // A helper object for observing changes in the password check status |
| // and changes to the compromised credentials list. |
| std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver; |
| } |
| |
| // Header for the Safety Check page. |
| @property(nonatomic, strong) TableViewLinkHeaderFooterItem* headerItem; |
| |
| // SettingsCheckItem used to display the state of the Safe Browsing check. |
| @property(nonatomic, strong) SettingsCheckItem* safeBrowsingCheckItem; |
| |
| // Current state of the Safe Browsing check. |
| @property(nonatomic, assign) |
| SafeBrowsingCheckRowStates safeBrowsingCheckRowState; |
| |
| // Previous on load or finished check state of the Safe Browsing check. |
| @property(nonatomic, assign) |
| SafeBrowsingCheckRowStates previousSafeBrowsingCheckRowState; |
| |
| // SettingsCheckItem used to display the state of the update check. |
| @property(nonatomic, strong) SettingsCheckItem* updateCheckItem; |
| |
| // Current state of the update check. |
| @property(nonatomic, assign) UpdateCheckRowStates updateCheckRowState; |
| |
| // Previous on load or finished check state of the update check. |
| @property(nonatomic, assign) UpdateCheckRowStates previousUpdateCheckRowState; |
| |
| // SettingsCheckItem used to display the state of the password check. |
| @property(nonatomic, strong) SettingsCheckItem* passwordCheckItem; |
| |
| // Current state of the password check. |
| @property(nonatomic, assign) PasswordCheckRowStates passwordCheckRowState; |
| |
| // Previous on load or finished check state of the password check. |
| @property(nonatomic, assign) |
| PasswordCheckRowStates previousPasswordCheckRowState; |
| |
| // Row button to start the safety check. |
| @property(nonatomic, strong) TableViewTextItem* checkStartItem; |
| |
| // Current state of the start safety check row button. |
| @property(nonatomic, assign) CheckStartStates checkStartState; |
| |
| // Preference value for Safe Browsing. |
| @property(nonatomic, strong, readonly) |
| PrefBackedBoolean* safeBrowsingPreference; |
| |
| // 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; |
| |
| // Current state of password check. |
| @property(nonatomic, assign) PasswordCheckState currentPasswordCheckState; |
| |
| // 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) SyncSetupService* syncService; |
| |
| // Whether or not a safety check just ran. |
| @property(nonatomic, assign) BOOL checkDidRun; |
| |
| @end |
| |
| @implementation SafetyCheckMediator |
| |
| - (instancetype)initWithUserPrefService:(PrefService*)userPrefService |
| passwordCheckManager: |
| (scoped_refptr<IOSChromePasswordCheckManager>) |
| passwordCheckManager |
| authService:(AuthenticationService*)authService |
| syncService:(SyncSetupService*)syncService { |
| self = [super init]; |
| if (self) { |
| DCHECK(userPrefService); |
| DCHECK(passwordCheckManager); |
| DCHECK(authService); |
| DCHECK(syncService); |
| |
| _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); |
| |
| _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 = [[UIImage imageNamed:@"settings_info"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| _updateCheckItem.leadingImage = updateCheckIcon; |
| _updateCheckItem.leadingImageTintColor = [UIColor colorNamed:kGrey400Color]; |
| _updateCheckItem.enabled = YES; |
| _updateCheckItem.indicatorHidden = YES; |
| _updateCheckItem.infoButtonHidden = YES; |
| _updateCheckItem.trailingImage = nil; |
| |
| // 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 = [[UIImage imageNamed:@"password_key"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| _passwordCheckItem.leadingImage = passwordCheckIcon; |
| _passwordCheckItem.leadingImageTintColor = |
| [UIColor colorNamed:kGrey400Color]; |
| _passwordCheckItem.enabled = YES; |
| _passwordCheckItem.indicatorHidden = YES; |
| _passwordCheckItem.infoButtonHidden = YES; |
| _passwordCheckItem.trailingImage = nil; |
| |
| // Show unsafe state if user already ran safety check and there are |
| // compromised credentials. |
| if (!_passwordCheckManager->GetCompromisedCredentials().empty() && |
| PreviousSafetyCheckIssueFound()) { |
| _passwordCheckRowState = PasswordCheckRowStateUnSafe; |
| } |
| |
| _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 = |
| [[UIImage imageNamed:@"settings_safe_browsing"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| _safeBrowsingCheckItem.leadingImage = safeBrowsingCheckIcon; |
| _safeBrowsingCheckItem.leadingImageTintColor = |
| [UIColor colorNamed:kGrey400Color]; |
| _safeBrowsingCheckItem.enabled = YES; |
| _safeBrowsingCheckItem.indicatorHidden = YES; |
| _safeBrowsingCheckItem.infoButtonHidden = YES; |
| _safeBrowsingCheckItem.trailingImage = nil; |
| |
| _checkStartState = CheckStartStateDefault; |
| _checkStartItem = |
| [[TableViewTextItem alloc] initWithType:CheckStartItemType]; |
| _checkStartItem.text = GetNSString(IDS_IOS_CHECK_PASSWORDS_NOW_BUTTON); |
| _checkStartItem.textColor = [UIColor colorNamed:kBlueColor]; |
| } |
| 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 - 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)compromisedCredentialsDidChange: |
| (password_manager::InsecureCredentialsManager::CredentialsView)credentials { |
| 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 PasswordCheckRowStateSafe: // No tap action. |
| case PasswordCheckRowStateDisabled: // i tap: Show error popover. |
| case PasswordCheckRowStateError: // i tap: Show error popover. |
| break; |
| case PasswordCheckRowStateUnSafe: // Go to password issues page. |
| base::RecordAction( |
| base::UserMetricsAction("Settings.SafetyCheck.ManagePasswords")); |
| base::UmaHistogramEnumeration( |
| kSafetyCheckInteractions, |
| SafetyCheckInteractions::kPasswordsManage); |
| [self.handler showPasswordIssuesPage]; |
| break; |
| } |
| break; |
| } |
| case SafeBrowsingItemType: { |
| switch (self.safeBrowsingCheckRowState) { |
| case SafeBrowsingCheckRowStateDefault: // No tap action. |
| case SafeBrowsingCheckRowStateRunning: // No tap action. |
| case SafeBrowsingCheckRowStateSafe: // No tap action. |
| case SafeBrowsingCheckRowStateManaged: // i tap: Managed state popover. |
| case SafeBrowsingCheckRowStateUnsafe: // i tap: Show error popover with |
| // link to Safe Browsing toggle |
| // page. |
| break; |
| } |
| break; |
| } |
| case CheckStartItemType: { // Start or stop a safety check. |
| [self checkStartOrCancel]; |
| break; |
| } |
| case HeaderItem: |
| case TimestampFooterItem: |
| break; |
| } |
| } |
| |
| - (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 == UpdateItemType && |
| self.updateCheckRowState == UpdateCheckRowStateManaged) { |
| [self.handler showManagedInfoFrom:buttonView]; |
| return; |
| } |
| |
| // If not managed compute error info to show in popover, if available. |
| NSAttributedString* info = [self getPopoverInfoForType: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 { |
| // TODO(crbug.com/1078782): Handle safe browsing state changes to reward user |
| // for fixing state. |
| return; |
| } |
| |
| #pragma mark - Private methods |
| |
| // Computes the text needed for a popover on |itemType| if available. |
| - (NSAttributedString*)getPopoverInfoForType:(NSInteger)itemType { |
| SafteyCheckItemType type = static_cast<SafteyCheckItemType>(itemType); |
| switch (type) { |
| case PasswordItemType: |
| return [self passwordCheckErrorInfo]; |
| case SafeBrowsingItemType: { |
| NSString* message = l10n_util::GetNSString( |
| IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_INFO); |
| GURL safeBrowsingURL( |
| base::SysNSStringToUTF8(kSafeBrowsingSafetyCheckStringURL)); |
| return [self attributedStringWithText:message link:safeBrowsingURL]; |
| } |
| case UpdateItemType: |
| return [self updateCheckErrorInfoString]; |
| case CheckStartItemType: |
| case HeaderItem: |
| 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; |
| |
| BOOL noCompromisedPasswords = |
| self.passwordCheckManager->GetCompromisedCredentials().empty(); |
| |
| switch (self.currentPasswordCheckState) { |
| case PasswordCheckState::kRunning: |
| return PasswordCheckRowStateRunning; |
| case PasswordCheckState::kNoPasswords: |
| return PasswordCheckRowStateDefault; |
| case PasswordCheckState::kSignedOut: |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kSignedOut); |
| return noCompromisedPasswords ? PasswordCheckRowStateError |
| : PasswordCheckRowStateUnSafe; |
| case PasswordCheckState::kOffline: |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kOffline); |
| return noCompromisedPasswords ? PasswordCheckRowStateError |
| : PasswordCheckRowStateUnSafe; |
| case PasswordCheckState::kQuotaLimit: |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kQuotaLimit); |
| return noCompromisedPasswords ? PasswordCheckRowStateError |
| : PasswordCheckRowStateUnSafe; |
| case PasswordCheckState::kOther: |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kError); |
| return noCompromisedPasswords ? PasswordCheckRowStateError |
| : PasswordCheckRowStateUnSafe; |
| case PasswordCheckState::kCanceled: |
| case PasswordCheckState::kIdle: { |
| if (!noCompromisedPasswords) { |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kCompromisedExist); |
| return PasswordCheckRowStateUnSafe; |
| } else if (self.currentPasswordCheckState == PasswordCheckState::kIdle) { |
| // Safe state is only possible after the state transitioned from |
| // kRunning to kIdle. |
| if (wasRunning) { |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kSafe); |
| return PasswordCheckRowStateSafe; |
| } else { |
| return PasswordCheckRowStateDefault; |
| } |
| } |
| return PasswordCheckRowStateDefault; |
| } |
| } |
| } |
| |
| // Computes the appropriate error info to be displayed in the updates popover. |
| - (NSAttributedString*)updateCheckErrorInfoString { |
| NSString* message; |
| GURL linkURL; |
| |
| 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:linkURL]; |
| } |
| |
| // Computes the appropriate error info to be displayed in the passwords popover. |
| - (NSAttributedString*)passwordCheckErrorInfo { |
| if (!self.passwordCheckManager->GetCompromisedCredentials().empty()) |
| return nil; |
| |
| NSString* message; |
| GURL linkURL; |
| |
| switch (self.currentPasswordCheckState) { |
| case PasswordCheckState::kRunning: |
| case PasswordCheckState::kNoPasswords: |
| message = |
| l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_NO_PASSWORDS); |
| break; |
| case PasswordCheckState::kCanceled: |
| case PasswordCheckState::kIdle: |
| return nil; |
| case PasswordCheckState::kSignedOut: |
| message = l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_SIGNED_OUT); |
| break; |
| case PasswordCheckState::kOffline: |
| message = l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_OFFLINE); |
| break; |
| case PasswordCheckState::kQuotaLimit: |
| if ([self canUseAccountPasswordCheckup]) { |
| message = l10n_util::GetNSString( |
| IDS_IOS_PASSWORD_CHECK_ERROR_QUOTA_LIMIT_VISIT_GOOGLE); |
| linkURL = password_manager::GetPasswordCheckupURL( |
| password_manager::PasswordCheckupReferrer::kPasswordCheck); |
| } else { |
| message = |
| l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECK_ERROR_QUOTA_LIMIT); |
| } |
| break; |
| case PasswordCheckState::kOther: |
| message = 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 self.authService->IsAuthenticated() && |
| self.syncService->IsSyncEnabled() && |
| !self.syncService->IsEncryptEverythingEnabled(); |
| } |
| |
| // Configures check error info with a link for popovers. |
| - (NSAttributedString*)attributedStringWithText:(NSString*)text |
| link:(GURL)link { |
| NSRange range; |
| |
| NSString* strippedText = ParseStringWithLink(text, &range); |
| |
| NSRange fullRange = NSMakeRange(0, strippedText.length); |
| NSMutableAttributedString* attributedText = |
| [[NSMutableAttributedString alloc] initWithString:strippedText]; |
| [attributedText addAttribute:NSForegroundColorAttributeName |
| value:[UIColor colorNamed:kTextSecondaryColor] |
| range:fullRange]; |
| |
| [attributedText |
| addAttribute:NSFontAttributeName |
| value:[UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] |
| range:fullRange]; |
| |
| if (range.location != NSNotFound && range.length != 0) { |
| NSURL* URL = net::NSURLWithGURL(link); |
| id linkValue = URL ? URL : @""; |
| [attributedText addAttribute:NSLinkAttributeName |
| value:linkValue |
| range:range]; |
| } |
| return attributedText; |
| } |
| |
| // 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; |
| } |
| |
| // Otherwise start a check. |
| |
| // 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::SafetyCheck::UpdateStatus::kChecking); |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kChecking); |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsSafeBrowsing, |
| safety_check::SafetyCheck::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: { |
| // Want to show the loading wheel momentarily. |
| dispatch_after( |
| dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.75 * NSEC_PER_SEC)), |
| dispatch_get_main_queue(), ^{ |
| // Check if the check was cancelled while waiting. |
| if (self.checksRemaining) { |
| self.updateCheckRowState = UpdateCheckRowStateChannel; |
| [self reconfigureUpdateCheckItem]; |
| } |
| }); |
| break; |
| } |
| } |
| // 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)(1.5 * NSEC_PER_SEC)), |
| dispatch_get_main_queue(), ^{ |
| // Check if the check was cancelled while waiting. |
| if (self.checksRemaining) { |
| self.passwordCheckRowState = PasswordCheckRowStateDisabled; |
| [self reconfigurePasswordCheckItem]; |
| |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsPasswords, |
| safety_check::SafetyCheck::PasswordsStatus::kNoPasswords); |
| } |
| }); |
| } else { |
| self.passwordCheckManager->StartPasswordCheck(); |
| } |
| // Want to show the loading wheel momentarily. |
| dispatch_after( |
| dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.75 * NSEC_PER_SEC)), |
| dispatch_get_main_queue(), ^{ |
| // Check if the check was cancelled while waiting. |
| if (self.checksRemaining) |
| [self checkAndReconfigureSafeBrowsingState]; |
| }); |
| } |
| return; |
| } |
| |
| // 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) || |
| (self.passwordCheckRowState == PasswordCheckRowStateUnSafe); |
| if (self.checkDidRun && issuesFound) { |
| [self updateTimestampOfLastCheck]; |
| 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]; |
| return; |
| } |
| |
| // 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::SafetyCheck::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::SafetyCheck::UpdateStatus::kFailed); |
| } |
| return; |
| } |
| |
| - (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.updateCheckRowState = UpdateCheckRowStateUpToDate; |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsUpdates, |
| safety_check::SafetyCheck::UpdateStatus::kUpdated); |
| [defaults setBool:details.is_up_to_date forKey:kIOSChromeUpToDateKey]; |
| } else { |
| // upgradeURL and next_version are only set if not up to date. |
| const GURL& upgradeUrl = details.upgrade_url; |
| |
| if (!upgradeUrl.is_valid()) { |
| self.updateCheckRowState = UpdateCheckRowStateOmahaError; |
| [self reconfigureUpdateCheckItem]; |
| |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsUpdates, |
| safety_check::SafetyCheck::UpdateStatus::kFailed); |
| return; |
| } |
| |
| if (!details.next_version.size() || |
| !base::Version(details.next_version).IsValid()) { |
| self.updateCheckRowState = UpdateCheckRowStateOmahaError; |
| [self reconfigureUpdateCheckItem]; |
| |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsUpdates, |
| safety_check::SafetyCheck::UpdateStatus::kFailed); |
| return; |
| } |
| |
| self.updateCheckRowState = UpdateCheckRowStateOutOfDate; |
| |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsUpdates, |
| safety_check::SafetyCheck::UpdateStatus::kOutdated); |
| |
| // Valid results, update all NSUserDefaults. |
| [defaults setValue:base::SysUTF8ToNSString(upgradeUrl.spec()) |
| forKey:kIOSChromeUpgradeURLKey]; |
| [defaults setValue:base::SysUTF8ToNSString(details.next_version) |
| forKey:kIOSChromeNextVersionKey]; |
| [defaults setBool:details.is_up_to_date forKey:kIOSChromeUpToDateKey]; |
| |
| // 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]; |
| } |
| [self reconfigureUpdateCheckItem]; |
| return; |
| } |
| |
| // 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::SafetyCheck::SafeBrowsingStatus::kEnabled); |
| } else { |
| self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateUnsafe; |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsSafeBrowsing, |
| safety_check::SafetyCheck::SafeBrowsingStatus::kDisabled); |
| } |
| } |
| if (self.safeBrowsingCheckRowState == SafeBrowsingCheckRowStateUnsafe && |
| self.safeBrowsingPreferenceManaged) { |
| self.safeBrowsingCheckRowState = SafeBrowsingCheckRowStateManaged; |
| base::UmaHistogramEnumeration( |
| kSafetyCheckMetricsSafeBrowsing, |
| safety_check::SafetyCheck::SafeBrowsingStatus::kDisabledByAdmin); |
| } |
| |
| [self reconfigureSafeBrowsingCheckItem]; |
| } |
| |
| // Reconfigures the display of the |updateCheckItem| based on current state of |
| // |updateCheckRowState|. |
| - (void)reconfigureUpdateCheckItem { |
| // Reset state to prevent conflicts. |
| self.updateCheckItem.enabled = YES; |
| self.updateCheckItem.indicatorHidden = YES; |
| self.updateCheckItem.infoButtonHidden = YES; |
| self.updateCheckItem.trailingImage = nil; |
| self.updateCheckItem.trailingImageTintColor = nil; |
| self.updateCheckItem.accessoryType = UITableViewCellAccessoryNone; |
| |
| // 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: { |
| UIImage* safeIconImage = [[UIImage imageNamed:@"settings_safe_state"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| self.updateCheckItem.trailingImage = safeIconImage; |
| self.updateCheckItem.trailingImageTintColor = |
| [UIColor colorNamed:kGreenColor]; |
| self.updateCheckItem.detailText = |
| GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_UP_TO_DATE_DESC); |
| break; |
| } |
| case UpdateCheckRowStateOutOfDate: { |
| UIImage* unSafeIconImage = [[UIImage imageNamed:@"settings_unsafe_state"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| self.updateCheckItem.trailingImage = unSafeIconImage; |
| self.updateCheckItem.trailingImageTintColor = |
| [UIColor colorNamed:kRedColor]; |
| self.updateCheckItem.detailText = |
| GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_UPDATES_OUT_OF_DATE_DESC); |
| 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. |
| self.passwordCheckItem.enabled = YES; |
| self.passwordCheckItem.indicatorHidden = YES; |
| self.passwordCheckItem.infoButtonHidden = YES; |
| self.passwordCheckItem.trailingImage = nil; |
| self.passwordCheckItem.trailingImageTintColor = nil; |
| self.passwordCheckItem.accessoryType = UITableViewCellAccessoryNone; |
| |
| // 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: { |
| self.passwordCheckItem.indicatorHidden = NO; |
| break; |
| } |
| case PasswordCheckRowStateSafe: { |
| DCHECK(self.passwordCheckManager->GetCompromisedCredentials().empty()); |
| UIImage* safeIconImage = [[UIImage imageNamed:@"settings_safe_state"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| self.passwordCheckItem.detailText = |
| base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16( |
| IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT, 0)); |
| self.passwordCheckItem.trailingImage = safeIconImage; |
| self.passwordCheckItem.trailingImageTintColor = |
| [UIColor colorNamed:kGreenColor]; |
| break; |
| } |
| case PasswordCheckRowStateUnSafe: { |
| self.passwordCheckItem.detailText = |
| base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16( |
| IDS_IOS_CHECK_PASSWORDS_COMPROMISED_COUNT, |
| self.passwordCheckManager->GetCompromisedCredentials().size())); |
| UIImage* unSafeIconImage = [[UIImage imageNamed:@"settings_unsafe_state"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| self.passwordCheckItem.trailingImage = unSafeIconImage; |
| self.passwordCheckItem.trailingImageTintColor = |
| [UIColor colorNamed:kRedColor]; |
| self.passwordCheckItem.accessoryType = |
| UITableViewCellAccessoryDisclosureIndicator; |
| 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. |
| self.safeBrowsingCheckItem.enabled = YES; |
| self.safeBrowsingCheckItem.indicatorHidden = YES; |
| self.safeBrowsingCheckItem.infoButtonHidden = YES; |
| self.safeBrowsingCheckItem.trailingImage = nil; |
| self.safeBrowsingCheckItem.trailingImageTintColor = nil; |
| self.safeBrowsingCheckItem.accessoryType = UITableViewCellAccessoryNone; |
| |
| // 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: { |
| UIImage* safeIconImage = [[UIImage imageNamed:@"settings_safe_state"] |
| imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| self.safeBrowsingCheckItem.trailingImage = safeIconImage; |
| self.safeBrowsingCheckItem.trailingImageTintColor = |
| [UIColor colorNamed:kGreenColor]; |
| self.safeBrowsingCheckItem.detailText = |
| GetNSString(IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENABLED_DESC); |
| break; |
| } |
| case SafeBrowsingCheckRowStateUnsafe: { |
| self.safeBrowsingCheckItem.infoButtonHidden = NO; |
| self.safeBrowsingCheckItem.detailText = GetNSString( |
| IDS_IOS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_DESC); |
| break; |
| } |
| } |
| |
| [self.consumer reconfigureCellsForItems:@[ self.safeBrowsingCheckItem ]]; |
| } |
| |
| // 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 a safety check has previously 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 safety check has never found 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; |
| |
| base::string16 timestamp; |
| // If check found issues less than 1 minuete ago. |
| if (elapsedTime < base::TimeDelta::FromMinutes(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 |