// Copyright 2018 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. #include "ash/multi_device_setup/multi_device_notification_presenter.h" #include #include #include "ash/components/multidevice/logging/logging.h" #include "ash/constants/notifier_catalogs.h" #include "ash/public/cpp/notification_utils.h" #include "ash/public/cpp/system_tray_client.h" #include "ash/resources/vector_icons/vector_icons.h" #include "ash/session/session_controller_impl.h" #include "ash/shell.h" #include "ash/shell_delegate.h" #include "ash/strings/grit/ash_strings.h" #include "ash/system/model/system_tray_model.h" #include "base/callback_helpers.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_functions.h" #include "base/strings/utf_string_conversions.h" #include "ui/base/l10n/l10n_util.h" #include "ui/chromeos/devicetype_utils.h" #include "ui/message_center/message_center.h" #include "ui/message_center/public/cpp/notifier_id.h" namespace ash { namespace { bool g_disable_notifications_for_test_ = false; const char kNotifierMultiDevice[] = "ash.multi_device_setup"; } // namespace // static const char MultiDeviceNotificationPresenter::kSetupNotificationId[] = "cros_multi_device_setup_notification_id"; // static const char MultiDeviceNotificationPresenter::kWifiSyncNotificationId[] = "cros_wifi_sync_announcement_notification_id"; // static std::string MultiDeviceNotificationPresenter::GetNotificationDescriptionForLogging( Status notification_status) { switch (notification_status) { case Status::kNewUserNotificationVisible: return "notification to prompt setup"; case Status::kExistingUserHostSwitchedNotificationVisible: return "notification of switch to new host"; case Status::kExistingUserNewChromebookNotificationVisible: return "notification of new Chromebook added"; case Status::kNoNotificationVisible: return "no notification"; } NOTREACHED(); } // static MultiDeviceNotificationPresenter::NotificationType MultiDeviceNotificationPresenter::GetMetricValueForNotification( Status notification_status) { switch (notification_status) { case Status::kNewUserNotificationVisible: return NotificationType::kNewUserPotentialHostExists; case Status::kExistingUserHostSwitchedNotificationVisible: return NotificationType::kExistingUserHostSwitched; case Status::kExistingUserNewChromebookNotificationVisible: return NotificationType::kExistingUserNewChromebookAdded; case Status::kNoNotificationVisible: NOTREACHED(); return NotificationType::kErrorUnknown; } } MultiDeviceNotificationPresenter::MultiDeviceNotificationPresenter( message_center::MessageCenter* message_center) : message_center_(message_center) { DCHECK(message_center_); Shell::Get()->session_controller()->AddObserver(this); // If the constructor is called after the session state has already been set // (e.g., if recovering from a crash), handle that now. If the user has not // yet logged in, this will be a no-op. ObserveMultiDeviceSetupIfPossible(); } MultiDeviceNotificationPresenter::~MultiDeviceNotificationPresenter() { message_center_->RemoveObserver(this); Shell::Get()->session_controller()->RemoveObserver(this); } void MultiDeviceNotificationPresenter::OnPotentialHostExistsForNewUser() { std::u16string title = l10n_util::GetStringUTF16( IDS_ASH_MULTI_DEVICE_SETUP_NEW_USER_POTENTIAL_HOST_EXISTS_TITLE); std::u16string message = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_SETUP_NEW_USER_POTENTIAL_HOST_EXISTS_MESSAGE, ui::GetChromeOSDeviceName()); ShowSetupNotification(Status::kNewUserNotificationVisible, title, message); } void MultiDeviceNotificationPresenter::OnNoLongerNewUser() { if (notification_status_ != Status::kNewUserNotificationVisible) return; RemoveMultiDeviceSetupNotification(); } void MultiDeviceNotificationPresenter::OnConnectedHostSwitchedForExistingUser( const std::string& new_host_device_name) { std::u16string title = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_HOST_SWITCHED_TITLE, base::ASCIIToUTF16(new_host_device_name)); std::u16string message = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_HOST_SWITCHED_MESSAGE, ui::GetChromeOSDeviceName()); ShowSetupNotification(Status::kExistingUserHostSwitchedNotificationVisible, title, message); } void MultiDeviceNotificationPresenter::OnNewChromebookAddedForExistingUser( const std::string& new_host_device_name) { std::u16string title = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_NEW_CHROME_DEVICE_ADDED_TITLE, base::ASCIIToUTF16(new_host_device_name)); std::u16string message = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_NEW_CHROME_DEVICE_ADDED_MESSAGE, ui::GetChromeOSDeviceName()); ShowSetupNotification(Status::kExistingUserNewChromebookNotificationVisible, title, message); } void MultiDeviceNotificationPresenter::OnBecameEligibleForWifiSync() { std::u16string title = l10n_util::GetStringUTF16(IDS_ASH_MULTI_DEVICE_WIFI_SYNC_AVAILABLE_TITLE); std::u16string message = l10n_util::GetStringFUTF16( IDS_ASH_MULTI_DEVICE_WIFI_SYNC_AVAILABLE_MESSAGE, ui::GetChromeOSDeviceName()); message_center::RichNotificationData optional_fields; optional_fields.buttons.push_back( message_center::ButtonInfo(l10n_util::GetStringUTF16( IDS_ASH_MULTI_DEVICE_WIFI_SYNC_AVAILABLE_TURN_ON_BUTTON))); optional_fields.buttons.push_back( message_center::ButtonInfo(l10n_util::GetStringUTF16( IDS_ASH_MULTI_DEVICE_WIFI_SYNC_AVAILABLE_CANCEL_BUTTON))); ShowNotification(kWifiSyncNotificationId, title, message, optional_fields); base::UmaHistogramEnumeration("MultiDeviceSetup_NotificationShown", NotificationType::kWifiSyncAnnouncement); } // static std::unique_ptr> MultiDeviceNotificationPresenter::DisableNotificationsForTesting() { return std::make_unique>( &g_disable_notifications_for_test_, true); } void MultiDeviceNotificationPresenter::RemoveMultiDeviceSetupNotification() { notification_status_ = Status::kNoNotificationVisible; message_center_->RemoveNotification(kSetupNotificationId, /* by_user */ false); } void MultiDeviceNotificationPresenter::OnUserSessionAdded( const AccountId& account_id) { ObserveMultiDeviceSetupIfPossible(); } void MultiDeviceNotificationPresenter::OnSessionStateChanged( session_manager::SessionState state) { ObserveMultiDeviceSetupIfPossible(); } void MultiDeviceNotificationPresenter::OnNotificationRemoved( const std::string& notification_id, bool by_user) { if (!by_user) { return; } if (notification_id == kSetupNotificationId) { base::UmaHistogramEnumeration( "MultiDeviceSetup_NotificationDismissed", GetMetricValueForNotification(notification_status_)); return; } if (notification_id == kWifiSyncNotificationId) { base::UmaHistogramEnumeration("MultiDeviceSetup_NotificationDismissed", NotificationType::kWifiSyncAnnouncement); return; } } void MultiDeviceNotificationPresenter::OnNotificationClicked( const std::string& notification_id, const absl::optional& button_index, const absl::optional& reply) { if (notification_id == kWifiSyncNotificationId) { message_center_->RemoveNotification(kWifiSyncNotificationId, /* by_user */ false); if (button_index) { switch (*button_index) { case 0: // "Turn on" button PA_LOG(INFO) << "Enabling Wi-Fi Sync."; multidevice_setup_remote_->SetFeatureEnabledState( multidevice_setup::mojom::Feature::kWifiSync, /*enabled=*/true, /*auth_token=*/absl::nullopt, /*callback=*/base::DoNothing()); break; case 1: // "Cancel" button base::UmaHistogramEnumeration( "MultiDeviceSetup_NotificationDismissed", NotificationType::kWifiSyncAnnouncement); return; } } Shell::Get()->system_tray_model()->client()->ShowWifiSyncSettings(); base::UmaHistogramEnumeration("MultiDeviceSetup_NotificationClicked", NotificationType::kWifiSyncAnnouncement); return; } if (notification_id != kSetupNotificationId) return; DCHECK(notification_status_ != Status::kNoNotificationVisible); PA_LOG(VERBOSE) << "User clicked " << GetNotificationDescriptionForLogging(notification_status_) << "."; base::UmaHistogramEnumeration( "MultiDeviceSetup_NotificationClicked", GetMetricValueForNotification(notification_status_)); switch (notification_status_) { case Status::kNewUserNotificationVisible: Shell::Get()->system_tray_model()->client()->ShowMultiDeviceSetup(); break; case Status::kExistingUserHostSwitchedNotificationVisible: // Clicks on the 'host switched' and 'Chromebook added' notifications have // the same effect, i.e. opening the Settings subpage. [[fallthrough]]; case Status::kExistingUserNewChromebookNotificationVisible: Shell::Get() ->system_tray_model() ->client() ->ShowConnectedDevicesSettings(); break; case Status::kNoNotificationVisible: NOTREACHED(); } RemoveMultiDeviceSetupNotification(); } void MultiDeviceNotificationPresenter::ObserveMultiDeviceSetupIfPossible() { // If already the delegate, there is nothing else to do. if (multidevice_setup_remote_) return; const SessionControllerImpl* session_controller = Shell::Get()->session_controller(); if (session_controller->GetSessionState() != session_manager::SessionState::ACTIVE) { return; } const UserSession* user_session = session_controller->GetPrimaryUserSession(); // The primary user session may be unavailable (e.g., for test/guest users). if (!user_session) return; Shell::Get()->shell_delegate()->BindMultiDeviceSetup( multidevice_setup_remote_.BindNewPipeAndPassReceiver()); // Add this object as the delegate of the MultiDeviceSetup Service. multidevice_setup_remote_->SetAccountStatusChangeDelegate( receiver_.BindNewPipeAndPassRemote()); message_center_->AddObserver(this); } void MultiDeviceNotificationPresenter::ShowSetupNotification( const Status notification_status, const std::u16string& title, const std::u16string& message) { PA_LOG(VERBOSE) << "Showing " << GetNotificationDescriptionForLogging(notification_status) << "."; base::UmaHistogramEnumeration( "MultiDeviceSetup_NotificationShown", GetMetricValueForNotification(notification_status)); ShowNotification(kSetupNotificationId, title, message, message_center::RichNotificationData()); notification_status_ = notification_status; } void MultiDeviceNotificationPresenter::ShowNotification( const std::string& id, const std::u16string& title, const std::u16string& message, message_center::RichNotificationData optional_fields) { if (g_disable_notifications_for_test_) return; std::unique_ptr notification = CreateSystemNotification( message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE, id, title, message, std::u16string() /* display_source */, GURL() /* origin_url */, message_center::NotifierId( message_center::NotifierType::SYSTEM_COMPONENT, kNotifierMultiDevice, NotificationCatalogName::kMultiDevice), optional_fields, nullptr /* delegate */, kNotificationMultiDeviceSetupIcon, message_center::SystemNotificationWarningLevel::NORMAL); if (message_center_->FindVisibleNotificationById(kSetupNotificationId)) { message_center_->UpdateNotification(id, std::move(notification)); return; } message_center_->AddNotification(std::move(notification)); } void MultiDeviceNotificationPresenter::FlushForTesting() { if (multidevice_setup_remote_) multidevice_setup_remote_.FlushForTesting(); } } // namespace ash