blob: d3c931b4d8085a70dbc9b4c8b1a6509b3f9a3317 [file] [log] [blame]
// Copyright 2024 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/safety_check_notifications/utils/utils.h"
#import "base/metrics/histogram_functions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "google_apis/gaia/gaia_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
#import "ios/chrome/browser/safety_check_notifications/utils/constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/public/features/system_flags.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// Returns the corresponding number of insecure passwords for the given `state`
// using `insecure_password_counts`. If no insecure passwords are associated
// with the given `state`, returns `nil`.
NSNumber* InsecurePasswordCount(
PasswordSafetyCheckState state,
password_manager::InsecurePasswordCounts insecure_password_counts) {
if (state == PasswordSafetyCheckState::kUnmutedCompromisedPasswords) {
return [NSNumber numberWithInt:insecure_password_counts.compromised_count];
}
if (state == PasswordSafetyCheckState::kReusedPasswords) {
return [NSNumber numberWithInt:insecure_password_counts.reused_count];
}
if (state == PasswordSafetyCheckState::kWeakPasswords) {
return [NSNumber numberWithInt:insecure_password_counts.weak_count];
}
return nil;
}
// Base helper for creating each Safety Check notification's userInfo
// dictionary.
template <typename SafetyCheckStateType>
NSMutableDictionary* BaseUserInfoForNotification(NSString* type_id_key,
SafetyCheckStateType state) {
return [@{
type_id_key : @YES,
kSafetyCheckNotificationCheckResultKey :
base::SysUTF8ToNSString(NameForSafetyCheckState(state))
} mutableCopy];
}
// Returns a user info dictionary for a Password safety check notification.
// Contains the check result state, insecure password count, and a notification
// ID for logging.
NSDictionary* UserInfoForPasswordNotification(
PasswordSafetyCheckState state,
password_manager::InsecurePasswordCounts insecure_password_counts) {
NSMutableDictionary* user_info =
BaseUserInfoForNotification(kSafetyCheckPasswordNotificationID, state);
NSNumber* insecure_password_count =
InsecurePasswordCount(state, insecure_password_counts);
if (insecure_password_count) {
user_info[kSafetyCheckNotificationInsecurePasswordCountKey] =
insecure_password_count;
}
return user_info;
}
// Returns a user info dictionary for an Update Chrome safety check
// notification. Contains the check result state and a notification ID for
// logging.
NSDictionary* UserInfoForUpdateChromeNotification(
UpdateChromeSafetyCheckState state) {
return BaseUserInfoForNotification(kSafetyCheckUpdateChromeNotificationID,
state);
}
// Returns a user info dictionary for a Safe Browsing safety check notification.
// Contains the check result state and a notification ID for logging.
NSDictionary* UserInfoForSafeBrowsingNotification(
SafeBrowsingSafetyCheckState state) {
return BaseUserInfoForNotification(kSafetyCheckSafeBrowsingNotificationID,
state);
}
// Returns a notification content object with the provided `title`, `body`, and
// `user_info`. Includes a default notification sound.
UNNotificationContent* NotificationContent(NSString* title,
NSString* body,
NSDictionary* user_info) {
UNMutableNotificationContent* content =
[[UNMutableNotificationContent alloc] init];
content.title = title;
content.body = body;
content.userInfo = user_info;
content.sound = UNNotificationSound.defaultSound;
return content;
}
// Returns the time duration of user inactivity required before Safety Check
// notifications are triggered. This duration can be modified through
// Experimental settings or Finch. If no override is set, a default value is
// used.
base::TimeDelta InactiveThresholdForNotifications() {
// Check if an override is set via experimental flags.
std::optional<int> forced_threshold_seconds = experimental_flags::
GetForcedInactivityThresholdForSafetyCheckNotifications();
if (forced_threshold_seconds.has_value()) {
return base::Seconds(forced_threshold_seconds.value());
}
return InactiveThresholdForSafetyCheckNotifications();
}
// Helper to create the final scheduled request if `content` is valid.
std::optional<ScheduledNotificationRequest> CreateScheduledNotificationRequest(
UNNotificationContent* content,
NSString* identifier) {
if (!content) {
return std::nullopt;
}
return ScheduledNotificationRequest{
.identifier = identifier,
.content = content,
.time_interval = InactiveThresholdForNotifications()};
}
} // namespace
void LogSafetyCheckNotificationOptInSource(
SafetyCheckNotificationsOptInSource opt_in_source,
SafetyCheckNotificationsOptInSource opt_out_source) {
bool is_notifications_enabled = push_notification_settings::
GetMobileNotificationPermissionStatusForClient(
PushNotificationClientId::kSafetyCheck, GaiaId());
base::UmaHistogramEnumeration(
"IOS.Notifications.SafetyCheck.NotificationsOptInSource",
is_notifications_enabled ? opt_out_source : opt_in_source);
}
std::optional<ScheduledNotificationRequest> GetPasswordNotificationRequest(
PasswordSafetyCheckState state,
password_manager::InsecurePasswordCounts insecure_password_counts) {
UNNotificationContent* content =
NotificationForPasswordCheckState(state, insecure_password_counts);
return CreateScheduledNotificationRequest(content,
kSafetyCheckPasswordNotificationID);
}
UNNotificationContent* NotificationForPasswordCheckState(
PasswordSafetyCheckState state,
password_manager::InsecurePasswordCounts insecure_password_counts) {
if (state == PasswordSafetyCheckState::kUnmutedCompromisedPasswords &&
insecure_password_counts.compromised_count > 0) {
return NotificationContent(
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
l10n_util::GetPluralNSStringF(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_COMPROMISED_PASSWORD,
insecure_password_counts.compromised_count),
UserInfoForPasswordNotification(state, insecure_password_counts));
}
if (state == PasswordSafetyCheckState::kReusedPasswords &&
insecure_password_counts.reused_count > 0) {
return NotificationContent(
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
l10n_util::GetPluralNSStringF(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_REUSED_PASSWORD,
insecure_password_counts.reused_count),
UserInfoForPasswordNotification(state, insecure_password_counts));
}
if (state == PasswordSafetyCheckState::kWeakPasswords &&
insecure_password_counts.weak_count > 0) {
return NotificationContent(
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_CHANGE_PASSWORDS),
l10n_util::GetPluralNSStringF(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_WEAK_PASSWORD,
insecure_password_counts.weak_count),
UserInfoForPasswordNotification(state, insecure_password_counts));
}
return nil;
}
std::optional<ScheduledNotificationRequest> GetUpdateChromeNotificationRequest(
UpdateChromeSafetyCheckState state) {
UNNotificationContent* content = NotificationForUpdateChromeCheckState(state);
return CreateScheduledNotificationRequest(
content, kSafetyCheckUpdateChromeNotificationID);
}
UNNotificationContent* NotificationForUpdateChromeCheckState(
UpdateChromeSafetyCheckState state) {
if (state == UpdateChromeSafetyCheckState::kOutOfDate) {
return NotificationContent(
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_UPDATE_BROWSER),
l10n_util::GetNSString(IDS_IOS_SAFETY_CHECK_DESCRIPTION_UPDATE_CHROME),
UserInfoForUpdateChromeNotification(state));
}
return nil;
}
std::optional<ScheduledNotificationRequest> GetSafeBrowsingNotificationRequest(
SafeBrowsingSafetyCheckState state) {
UNNotificationContent* content = NotificationForSafeBrowsingCheckState(state);
return CreateScheduledNotificationRequest(
content, kSafetyCheckSafeBrowsingNotificationID);
}
UNNotificationContent* NotificationForSafeBrowsingCheckState(
SafeBrowsingSafetyCheckState state) {
if (state == SafeBrowsingSafetyCheckState::kUnsafe) {
return NotificationContent(
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_ADD_BROWSING_PROTECTION),
l10n_util::GetNSString(
IDS_IOS_SAFETY_CHECK_NOTIFICATIONS_BROWSING_PROTECTION_OFF),
UserInfoForSafeBrowsingNotification(state));
}
return nil;
}
std::optional<SafetyCheckNotificationType> ParseSafetyCheckNotificationType(
UNNotificationRequest* request) {
if ([request.identifier
isEqualToString:kSafetyCheckUpdateChromeNotificationID]) {
return SafetyCheckNotificationType::kUpdateChrome;
}
if ([request.identifier isEqualToString:kSafetyCheckPasswordNotificationID]) {
return SafetyCheckNotificationType::kPasswords;
}
if ([request.identifier
isEqualToString:kSafetyCheckSafeBrowsingNotificationID]) {
return SafetyCheckNotificationType::kSafeBrowsing;
}
return std::nullopt;
}