// 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/tips_notifications/model/utils.h"
#import "base/time/time.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// Holds the l10n string ids for tips notification content.
struct ContentIDs {
int title;
int body;
};
// Returns the string id of the body text for the Docking promo notification.
int DockingBodyID() {
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
return IDS_IOS_NOTIFICATIONS_TIPS_DOCKING_BODY_IPAD;
}
return IDS_IOS_NOTIFICATIONS_TIPS_DOCKING_BODY_IPHONE;
}
// Returns the ContentIDs for the given `type`.
ContentIDs ContentIDsForType(TipsNotificationType type) {
switch (type) {
case TipsNotificationType::kDefaultBrowser:
return {IDS_IOS_NOTIFICATIONS_TIPS_DEFAULT_BROWSER_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_DEFAULT_BROWSER_BODY};
case TipsNotificationType::kWhatsNew:
return {IDS_IOS_NOTIFICATIONS_TIPS_WHATS_NEW_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_WHATS_NEW_BODY};
case TipsNotificationType::kSignin:
return {IDS_IOS_NOTIFICATIONS_TIPS_SIGNIN_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_SIGNIN_BODY};
case TipsNotificationType::kSetUpListContinuation:
return {IDS_IOS_NOTIFICATIONS_TIPS_SETUPLIST_CONTINUATION_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_SETUPLIST_CONTINUATION_BODY};
case TipsNotificationType::kDocking:
return {IDS_IOS_NOTIFICATIONS_TIPS_DOCKING_TITLE, DockingBodyID()};
case TipsNotificationType::kOmniboxPosition:
return {IDS_IOS_NOTIFICATIONS_TIPS_OMNIBOX_POSITION_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_OMNIBOX_POSITION_BODY};
case TipsNotificationType::kLens:
return {IDS_IOS_NOTIFICATIONS_TIPS_LENS_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_LENS_BODY};
case TipsNotificationType::kEnhancedSafeBrowsing:
return {IDS_IOS_NOTIFICATIONS_TIPS_ENHANCED_SAFE_BROWSING_TITLE,
IDS_IOS_NOTIFICATIONS_TIPS_ENHANCED_SAFE_BROWSING_BODY};
case TipsNotificationType::kError:
NOTREACHED();
}
}
// Returns the default trigger TimeDelta for the given `user_type`.
base::TimeDelta DefaultTriggerDelta(TipsNotificationUserType user_type) {
switch (user_type) {
case TipsNotificationUserType::kUnknown:
return base::Days(3);
case TipsNotificationUserType::kLessEngaged:
return base::Days(21);
case TipsNotificationUserType::kActiveSeeker:
return base::Days(7);
}
}
// A bitfield with all notification types from the enum enabled.
constexpr int kAllNotificationBits =
(1 << (int(TipsNotificationType::kMaxValue) + 1)) - 1;
// A bitfield with all notification types from the enum enabled, except for
// kError.
constexpr int kEnableAllNotifications =
kAllNotificationBits - (1 << int(TipsNotificationType::kError));
} // namespace
NSString* const kTipsNotificationId = @"kTipsNotificationId";
NSString* const kTipsNotificationTypeKey = @"kTipsNotificationTypeKey";
const base::TimeDelta kTipsNotificationDefaultTriggerDelta = base::Hours(72);
const char kTipsNotificationsSentPref[] = "tips_notifications.sent_bitfield";
const char kTipsNotificationsLastSent[] = "tips_notifiations.last_sent";
const char kTipsNotificationsLastTriggered[] =
"tips_notifiations.last_triggered";
const char kTipsNotificationsLastRequestedTime[] =
"tips_notifications.last_requested.time";
const char kTipsNotificationsUserType[] = "tips_notifications.user_type";
bool IsTipsNotification(UNNotificationRequest* request) {
return [request.identifier isEqualToString:kTipsNotificationId];
}
NSDictionary* UserInfoForTipsNotificationType(TipsNotificationType type) {
return @{
kTipsNotificationId : @YES,
kTipsNotificationTypeKey : @(static_cast<int>(type)),
};
}
std::optional<TipsNotificationType> ParseTipsNotificationType(
UNNotificationRequest* request) {
NSDictionary* user_info = request.content.userInfo;
NSNumber* type = user_info[kTipsNotificationTypeKey];
if (type == nil) {
return std::nullopt;
}
return static_cast<TipsNotificationType>(type.integerValue);
}
UNNotificationRequest* TipsNotificationRequest(
TipsNotificationType type,
TipsNotificationUserType user_type) {
return [UNNotificationRequest
requestWithIdentifier:kTipsNotificationId
content:ContentForTipsNotificationType(type)
trigger:TipsNotificationTrigger(user_type)];
}
UNNotificationContent* ContentForTipsNotificationType(
TipsNotificationType type) {
UNMutableNotificationContent* content =
[[UNMutableNotificationContent alloc] init];
ContentIDs content_ids = ContentIDsForType(type);
content.title = l10n_util::GetNSString(content_ids.title);
content.body = l10n_util::GetNSString(content_ids.body);
content.userInfo = UserInfoForTipsNotificationType(type);
content.sound = UNNotificationSound.defaultSound;
return content;
}
base::TimeDelta TipsNotificationTriggerDelta(
TipsNotificationUserType user_type) {
base::TimeDelta default_trigger = DefaultTriggerDelta(user_type);
switch (user_type) {
case TipsNotificationUserType::kUnknown:
return GetFieldTrialParamByFeatureAsTimeDelta(
kIOSTipsNotifications, kIOSTipsNotificationsUnknownTriggerTimeParam,
default_trigger);
case TipsNotificationUserType::kLessEngaged:
return GetFieldTrialParamByFeatureAsTimeDelta(
kIOSTipsNotifications,
kIOSTipsNotificationsLessEngagedTriggerTimeParam, default_trigger);
case TipsNotificationUserType::kActiveSeeker:
return GetFieldTrialParamByFeatureAsTimeDelta(
kIOSTipsNotifications,
kIOSTipsNotificationsActiveSeekerTriggerTimeParam, default_trigger);
}
}
UNNotificationTrigger* TipsNotificationTrigger(
TipsNotificationUserType user_type) {
return [UNTimeIntervalNotificationTrigger
triggerWithTimeInterval:TipsNotificationTriggerDelta(user_type)
.InSecondsF()
repeats:NO];
}
int TipsNotificationsEnabledBitfield() {
return GetFieldTrialParamByFeatureAsInt(kIOSTipsNotifications,
kIOSTipsNotificationsEnabledParam,
kEnableAllNotifications);
}