// 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/ui/push_notification/notifications_opt_in_alert_coordinator.h"
#import "base/check.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/ui/push_notification/metrics.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 gaia id used for `browser_state`.
NSString* GetGaiaIdForBrowserState(ChromeBrowserState* browser_state) {
const ProfileAttributesIOS attributes =
GetApplicationContext()
->GetProfileManager()
->GetProfileAttributesStorage()
->GetAttributesForProfileWithName(
browser_state->GetBrowserStateName());
return base::SysUTF8ToNSString(attributes.GetGaiaId());
}
} // namespace
@implementation NotificationsOptInAlertCoordinator {
SEQUENCE_CHECKER(sequence_checker_);
// The coordinator used to present the alert used when permission has
// previously been denied.
AlertCoordinator* _alertCoordinator;
}
- (void)start {
CHECK(self.clientIds.has_value());
[self requestPushNotificationPermission];
}
- (void)stop {
[_alertCoordinator stop];
_alertCoordinator = nil;
}
#pragma mark - Private methods
// Asks iOS to request permission from the user to receive notifications. If
// the request has already happened and permission was not granted, an alert
// will be presented to ask the user if they want to enable the permission in
// the iOS settings app.
- (void)requestPushNotificationPermission {
__weak __typeof(self) weakSelf = self;
scoped_refptr<base::TaskRunner> taskRunner =
base::SequencedTaskRunner::GetCurrentDefault();
[PushNotificationUtil requestPushNotificationPermission:^(
BOOL granted, BOOL promptShown, NSError* error) {
taskRunner->PostTask(FROM_HERE, base::BindOnce(^{
[weakSelf onRequestPermissionResult:granted
promptShown:promptShown
error:error];
}));
}];
}
// Handles the response from requesting notification authorization.
- (void)onRequestPermissionResult:(BOOL)granted
promptShown:(BOOL)promptShown
error:(NSError*)error {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error) {
[self setResult:NotificationsOptInAlertResult::kError];
} else if (!granted) {
if (!promptShown) {
[self presentNotificationPermissionAlert];
} else {
[self setResult:NotificationsOptInAlertResult::kPermissionDenied];
}
} else {
// Permission has been granted!
[self enableNotifications];
if (self.confirmationMessage) {
[self showConfirmationSnackbar];
}
[self setResult:NotificationsOptInAlertResult::kPermissionGranted];
}
}
// Presents an alert view to ask the user to enable notifications via iOS
// settings.
- (void)presentNotificationPermissionAlert {
NSString* alertTitle =
l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_ALERT_TITLE);
NSString* alertMessage =
self.alertMessage
? self.alertMessage
: l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_ALERT_MESSAGE);
NSString* cancelTitle =
l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_ALERT_CANCEL);
NSString* settingsTitle =
l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_ALERT_GO_TO_SETTINGS);
[_alertCoordinator stop];
_alertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
title:alertTitle
message:alertMessage];
__weak __typeof(self) weakSelf = self;
[_alertCoordinator addItemWithTitle:cancelTitle
action:^{
[weakSelf didCancelAlert];
}
style:UIAlertActionStyleCancel];
[_alertCoordinator addItemWithTitle:settingsTitle
action:^{
[weakSelf openSettings];
}
style:UIAlertActionStyleDefault];
[_alertCoordinator start];
}
// Enables notifications in prefs for the client with `clientID`.
- (void)enableNotifications {
NSString* gaiaID = GetGaiaIdForBrowserState(self.browser->GetBrowserState());
std::vector<PushNotificationClientId> clientIDs = self.clientIds.value();
for (PushNotificationClientId clientID : clientIDs) {
GetApplicationContext()->GetPushNotificationService()->SetPreference(
gaiaID, clientID, true);
}
}
// Shows a snackbar message indicating that notifications are enabled.
- (void)showConfirmationSnackbar {
NSString* buttonText =
l10n_util::GetNSString(IDS_IOS_NOTIFICATIONS_MANAGE_SETTINGS);
// Show snackbar confirmation.
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
id<SnackbarCommands> snackbarHandler =
HandlerForProtocol(dispatcher, SnackbarCommands);
__weak id<SettingsCommands> weakSettingsHandler =
HandlerForProtocol(dispatcher, SettingsCommands);
__weak id<ApplicationCommands> weakApplicationHandler =
HandlerForProtocol(dispatcher, ApplicationCommands);
[snackbarHandler
showSnackbarWithMessage:self.confirmationMessage
buttonText:buttonText
messageAction:^{
[weakApplicationHandler prepareToPresentModal:^{
[weakSettingsHandler showNotificationsSettings];
}];
}
completionAction:nil];
}
// Opens the iOS settings app to the app's Notification permissions.
- (void)openSettings {
NSURL* url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if (@available(iOS 15.4, *)) {
url = [NSURL URLWithString:UIApplicationOpenNotificationSettingsURLString];
}
[[UIApplication sharedApplication] openURL:url
options:@{}
completionHandler:nil];
[self setResult:NotificationsOptInAlertResult::kOpenedSettings];
}
// Called when the user taps the alert's "cancel" action.
- (void)didCancelAlert {
[self setResult:NotificationsOptInAlertResult::kCanceled];
}
// Tells the delegate the result of the UI flow.
- (void)setResult:(NotificationsOptInAlertResult)result {
[self.delegate notificationsOptInAlertCoordinator:self result:result];
switch (result) {
case NotificationsOptInAlertResult::kPermissionGranted:
base::RecordAction(
base::UserMetricsAction(kNotificationsOptInAlertPermissionGranted));
break;
case NotificationsOptInAlertResult::kPermissionDenied:
base::RecordAction(
base::UserMetricsAction(kNotificationsOptInAlertPermissionDenied));
break;
case NotificationsOptInAlertResult::kOpenedSettings:
base::RecordAction(
base::UserMetricsAction(kNotificationsOptInAlertOpenedSettings));
break;
case NotificationsOptInAlertResult::kCanceled:
base::RecordAction(
base::UserMetricsAction(kNotificationsOptInAlertCancelled));
break;
case NotificationsOptInAlertResult::kError:
base::RecordAction(
base::UserMetricsAction(kNotificationsOptInAlertError));
break;
}
}
@end