chromium/ios/chrome/browser/safety_check_notifications/utils/utils.mm

// 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/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "ios/chrome/browser/safety_check_notifications/utils/constants.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;
}

// 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 =
      [NSMutableDictionary dictionaryWithDictionary:@{
        kSafetyCheckPasswordNotificationID : @YES,
        kSafetyCheckNotificationCheckResultKey :
            base::SysUTF8ToNSString(NameForSafetyCheckState(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 @{
    kSafetyCheckUpdateChromeNotificationID : @YES,
    kSafetyCheckNotificationCheckResultKey :
        base::SysUTF8ToNSString(NameForSafetyCheckState(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 @{
    kSafetyCheckSafeBrowsingNotificationID : @YES,
    kSafetyCheckNotificationCheckResultKey :
        base::SysUTF8ToNSString(NameForSafetyCheckState(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;
}

}  // namespace

UNNotificationRequest* PasswordNotificationRequest(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  UNNotificationContent* content =
      NotificationForPasswordCheckState(state, insecure_password_counts);

  if (!content) {
    return nil;
  }

  // TODO(crbug.com/362475364): Enable Password notification trigger
  // to be configurable via Finch to allow for better testing and
  // experimentation.
  return [UNNotificationRequest
      requestWithIdentifier:kSafetyCheckPasswordNotificationID
                    content:content
                    trigger:[UNTimeIntervalNotificationTrigger
                                triggerWithTimeInterval:
                                    kSafetyCheckNotificationDefaultDelay
                                        .InSecondsF()
                                                repeats:NO]];
}

UNNotificationContent* NotificationForPasswordCheckState(
    PasswordSafetyCheckState state,
    password_manager::InsecurePasswordCounts insecure_password_counts) {
  if (state == PasswordSafetyCheckState::kUnmutedCompromisedPasswords) {
    CHECK_GT(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) {
    CHECK_GT(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) {
    CHECK_GT(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;
}

UNNotificationRequest* UpdateChromeNotificationRequest(
    UpdateChromeSafetyCheckState state) {
  UNNotificationContent* content = NotificationForUpdateChromeCheckState(state);

  if (!content) {
    return nil;
  }

  // TODO(crbug.com/362475364): Enable Update Chrome notification trigger
  // to be configurable via Finch to allow for better testing and
  // experimentation.
  return [UNNotificationRequest
      requestWithIdentifier:kSafetyCheckUpdateChromeNotificationID
                    content:content
                    trigger:[UNTimeIntervalNotificationTrigger
                                triggerWithTimeInterval:
                                    kSafetyCheckNotificationDefaultDelay
                                        .InSecondsF()
                                                repeats:NO]];
}

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;
}

UNNotificationRequest* SafeBrowsingNotificationRequest(
    SafeBrowsingSafetyCheckState state) {
  UNNotificationContent* content = NotificationForSafeBrowsingCheckState(state);

  if (!content) {
    return nil;
  }

  // TODO(crbug.com/362475364): Enable Safe Browsing notification trigger
  // to be configurable via Finch to allow for better testing and
  // experimentation.
  return [UNNotificationRequest
      requestWithIdentifier:kSafetyCheckSafeBrowsingNotificationID
                    content:content
                    trigger:[UNTimeIntervalNotificationTrigger
                                triggerWithTimeInterval:
                                    kSafetyCheckNotificationDefaultDelay
                                        .InSecondsF()
                                                repeats:NO]];
}

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;
}