| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/phonehub/phone_hub_notification_controller.h" |
| |
| #include "ash/constants/ash_features.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/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/message_center/ash_notification_view.h" |
| #include "ash/system/message_center/message_view_factory.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/phonehub/phone_hub_metrics.h" |
| #include "ash/system/tray/tray_popup_utils.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/ash/components/multidevice/logging/logging.h" |
| #include "chromeos/ash/components/phonehub/notification.h" |
| #include "chromeos/ash/components/phonehub/notification_interaction_handler.h" |
| #include "chromeos/ash/components/phonehub/phone_hub_manager.h" |
| #include "chromeos/ash/components/phonehub/phone_model.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/gfx/text_elider.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "ui/message_center/public/cpp/notification_delegate.h" |
| #include "ui/message_center/views/notification_header_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| |
| using phone_hub_metrics::NotificationInteraction; |
| using phonehub::proto::CameraRollItemMetadata; |
| |
| namespace { |
| const char kNotifierId[] = "chrome://phonehub"; |
| const char kNotifierIdSeparator[] = "-"; |
| const char kPhoneHubInstantTetherNotificationId[] = |
| "chrome://phonehub-instant-tether"; |
| const char kPhoneHubCameraRollNotificationId[] = |
| "chrome://phonehub-camera-roll"; |
| const char kNotificationCustomViewType[] = "phonehub"; |
| const char kNotificationCustomCallViewType[] = "phonehub-call"; |
| const int kReplyButtonIndex = 0; |
| const int kNotificationHeaderTextWidth = 180; |
| const int kNotificationAppNameMaxWidth = 140; |
| |
| // The max age since a notification's creation on the Android for it to get |
| // shown in a heads-up pop up. Notifications older than this will only silently |
| // get added. |
| constexpr base::TimeDelta kMaxRecentNotificationAge = base::Seconds(15); |
| |
| // The amount of time the reply button is disabled after sending an inline |
| // reply. This is used to make sure that all the replies are received by the |
| // phone in a correct order (a reply sent right after another could cause it to |
| // be received before the former one). |
| constexpr base::TimeDelta kInlineReplyDisableTime = base::Seconds(1); |
| |
| class PhoneHubAshNotificationView : public AshNotificationView { |
| public: |
| explicit PhoneHubAshNotificationView( |
| const message_center::Notification& notification, |
| bool shown_in_popup, |
| const std::u16string& phone_name) |
| : AshNotificationView(notification, shown_in_popup) { |
| // Add customized header. |
| message_center::NotificationHeaderView* header_row = |
| static_cast<message_center::NotificationHeaderView*>( |
| GetViewByID(message_center::NotificationView::kHeaderRow)); |
| views::View* app_name_view = |
| GetViewByID(message_center::NotificationView::kAppNameView); |
| views::Label* summary_text_view = static_cast<views::Label*>( |
| GetViewByID(message_center::NotificationView::kSummaryTextView)); |
| |
| // The app name should be displayed in full, leaving the rest of the space |
| // for device name. App name will only be truncated when it reached it |
| // maximum width. |
| int app_name_width = std::min(app_name_view->GetPreferredSize().width(), |
| kNotificationAppNameMaxWidth); |
| int device_name_width = kNotificationHeaderTextWidth - app_name_width; |
| header_row->SetSummaryText( |
| gfx::ElideText(phone_name, summary_text_view->font_list(), |
| device_name_width, gfx::ELIDE_TAIL)); |
| custom_view_type_ = notification.custom_view_type(); |
| if (custom_view_type_ == kNotificationCustomCallViewType) { |
| // Expand the action buttons row by default for Call Style notification. |
| SetManuallyExpandedOrCollapsed( |
| !IsExpanded() ? message_center::ExpandState::USER_EXPANDED |
| : message_center::ExpandState::USER_COLLAPSED); |
| SetExpanded(true); |
| return; |
| } |
| action_buttons_row_ = |
| GetViewByID(message_center::NotificationView::kActionButtonsRow); |
| if (!action_buttons_row_->children().empty()) |
| reply_button_ = static_cast<views::View*>( |
| action_buttons_row_->children()[kReplyButtonIndex]); |
| |
| inline_reply_ = static_cast<message_center::NotificationInputContainer*>( |
| GetViewByID(message_center::NotificationView::kInlineReply)); |
| } |
| |
| ~PhoneHubAshNotificationView() override = default; |
| PhoneHubAshNotificationView(const PhoneHubAshNotificationView&) = delete; |
| PhoneHubAshNotificationView& operator=(const PhoneHubAshNotificationView&) = |
| delete; |
| |
| // message_center::NotificationViewBase |
| void ActionButtonPressed(size_t index, const ui::Event& event) override { |
| if (custom_view_type_ == kNotificationCustomCallViewType) { |
| message_center::MessageCenter::Get()->ClickOnNotificationButton( |
| notification_id(), static_cast<int>(index)); |
| } else { |
| AshNotificationView::ActionButtonPressed(index, event); |
| } |
| } |
| |
| // message_center::NotificationView: |
| void OnNotificationInputSubmit(size_t index, |
| const std::u16string& text) override { |
| if (text.empty()) |
| return; |
| AshNotificationView::OnNotificationInputSubmit(index, text); |
| |
| DCHECK(reply_button_); |
| |
| // After sending a reply, take the UI back to action buttons and clear out |
| // text input. |
| inline_reply_->SetVisible(false); |
| action_buttons_row_->SetVisible(true); |
| inline_reply_->textfield()->SetText(std::u16string()); |
| |
| // Briefly disable reply button. |
| reply_button_->SetEnabled(false); |
| enable_reply_timer_.Start( |
| FROM_HERE, kInlineReplyDisableTime, |
| base::BindOnce(&PhoneHubAshNotificationView::EnableReplyButton, |
| base::Unretained(this))); |
| } |
| |
| void EnableReplyButton() { |
| reply_button_->SetEnabled(true); |
| enable_reply_timer_.AbandonAndStop(); |
| } |
| |
| private: |
| // Owned by view hierarchy. |
| raw_ptr<views::View, ExperimentalAsh> action_buttons_row_ = nullptr; |
| raw_ptr<views::View, ExperimentalAsh> reply_button_ = nullptr; |
| raw_ptr<message_center::NotificationInputContainer, ExperimentalAsh> |
| inline_reply_ = nullptr; |
| |
| // Timer that fires to enable reply button after a brief period of time. |
| base::OneShotTimer enable_reply_timer_; |
| std::string custom_view_type_; |
| }; |
| |
| } // namespace |
| |
| // Delegate for the displayed ChromeOS notification. |
| class PhoneHubNotificationController::NotificationDelegate |
| : public message_center::NotificationObserver { |
| public: |
| NotificationDelegate(PhoneHubNotificationController* controller, |
| int64_t phone_hub_id, |
| const std::string& cros_id, |
| phonehub::Notification::Category category) |
| : controller_(controller), |
| phone_hub_id_(phone_hub_id), |
| cros_id_(cros_id), |
| category_(category) {} |
| |
| virtual ~NotificationDelegate() { controller_ = nullptr; } |
| |
| NotificationDelegate(const NotificationDelegate&) = delete; |
| NotificationDelegate& operator=(const NotificationDelegate&) = delete; |
| |
| // Returns a scoped_refptr that can be passed in the |
| // message_center::Notification constructor. |
| scoped_refptr<message_center::NotificationDelegate> AsScopedRefPtr() { |
| return base::MakeRefCounted<message_center::ThunkNotificationDelegate>( |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| // Called by the controller to remove the notification from the message |
| // center. |
| void Remove() { |
| removed_by_phone_hub_ = true; |
| message_center::MessageCenter::Get()->RemoveNotification(cros_id_, |
| /*by_user=*/false); |
| } |
| |
| // message_center::NotificationObserver: |
| void Close(bool by_user) override { |
| if (!controller_ || removed_by_phone_hub_) |
| return; |
| |
| if (category_ == phonehub::Notification::Category::kIncomingCall || |
| category_ == phonehub::Notification::Category::kOngoingCall) { |
| // TODO(b/203734343): Wait for UX confirm. Call notification is not |
| // dismissible in android phone. |
| PA_LOG(INFO) |
| << "Can't dismiss an Incoming/Ongoing call notification with id: " |
| << phone_hub_id_ << "."; |
| return; |
| } |
| |
| controller_->DismissNotification(phone_hub_id_); |
| } |
| |
| void Click(const absl::optional<int>& button_index, |
| const absl::optional<std::u16string>& reply) override { |
| if (!controller_) |
| return; |
| |
| if (!button_index.has_value()) { |
| controller_->HandleNotificationBodyClick( |
| phone_hub_id_, controller_->manager_->GetNotification(phone_hub_id_) |
| ->app_metadata()); |
| return; |
| } |
| if (category_ == phonehub::Notification::Category::kIncomingCall) { |
| // TODO(b/199223417): Implement actions. |
| switch (*button_index) { |
| case BUTTON_ANSWER: |
| PA_LOG(INFO) << "answer button clicked"; |
| break; |
| case BUTTON_DECLINE: |
| PA_LOG(INFO) << "decline button clicked"; |
| break; |
| } |
| } else if (category_ == phonehub::Notification::Category::kOngoingCall) { |
| switch (*button_index) { |
| case BUTTON_HANGUP: |
| PA_LOG(INFO) << "hangup button clicked"; |
| break; |
| } |
| } else if (button_index.value() == kReplyButtonIndex && reply.has_value()) { |
| controller_->SendInlineReply(phone_hub_id_, reply.value()); |
| } |
| } |
| |
| void SettingsClick() override { |
| if (controller_) |
| controller_->OpenSettings(); |
| } |
| |
| phonehub::Notification::Category Category() { return category_; } |
| |
| private: |
| // Incoming call buttons that appear in notifications. |
| enum IncomingCallButton { BUTTON_DECLINE, BUTTON_ANSWER }; |
| // Ongoing call buttons that appear in notifications. |
| enum OngoingCallButton { BUTTON_HANGUP }; |
| |
| // The parent controller, which owns this object. |
| raw_ptr<PhoneHubNotificationController, ExperimentalAsh> controller_ = |
| nullptr; |
| |
| // The notification ID tracked by PhoneHub. |
| const int64_t phone_hub_id_; |
| |
| // The notification ID tracked by the CrOS message center. |
| const std::string cros_id_; |
| |
| // The category of the notification. |
| phonehub::Notification::Category category_; |
| |
| // Flag set if the notification was removed by PhoneHub so we avoid a cycle. |
| bool removed_by_phone_hub_ = false; |
| |
| base::WeakPtrFactory<NotificationDelegate> weak_ptr_factory_{this}; |
| }; |
| |
| PhoneHubNotificationController::PhoneHubNotificationController() { |
| if (!MessageViewFactory::HasCustomNotificationViewFactory( |
| kNotificationCustomViewType)) { |
| MessageViewFactory::SetCustomNotificationViewFactory( |
| kNotificationCustomViewType, |
| base::BindRepeating( |
| &PhoneHubNotificationController::CreateCustomNotificationView, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| if (!MessageViewFactory::HasCustomNotificationViewFactory( |
| kNotificationCustomCallViewType)) { |
| MessageViewFactory::SetCustomNotificationViewFactory( |
| kNotificationCustomCallViewType, |
| base::BindRepeating( |
| &PhoneHubNotificationController::CreateCustomActionNotificationView, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| PhoneHubNotificationController::~PhoneHubNotificationController() { |
| if (manager_) |
| manager_->RemoveObserver(this); |
| if (tether_controller_) |
| tether_controller_->RemoveObserver(this); |
| if (camera_roll_manager_) |
| camera_roll_manager_->RemoveObserver(this); |
| } |
| |
| void PhoneHubNotificationController::SetManager( |
| phonehub::PhoneHubManager* phone_hub_manager) { |
| if (manager_) |
| manager_->RemoveObserver(this); |
| if (phone_hub_manager) { |
| manager_ = phone_hub_manager->GetNotificationManager(); |
| manager_->AddObserver(this); |
| } else { |
| manager_ = nullptr; |
| } |
| |
| if (tether_controller_) |
| tether_controller_->RemoveObserver(this); |
| if (phone_hub_manager) { |
| tether_controller_ = phone_hub_manager->GetTetherController(); |
| tether_controller_->AddObserver(this); |
| } else { |
| tether_controller_ = nullptr; |
| } |
| |
| if (camera_roll_manager_) |
| camera_roll_manager_->RemoveObserver(this); |
| if (phone_hub_manager) { |
| camera_roll_manager_ = phone_hub_manager->GetCameraRollManager(); |
| if (camera_roll_manager_) { |
| camera_roll_manager_->AddObserver(this); |
| } else { |
| camera_roll_manager_ = nullptr; |
| } |
| } else { |
| camera_roll_manager_ = nullptr; |
| } |
| |
| if (phone_hub_manager) |
| phone_model_ = phone_hub_manager->GetPhoneModel(); |
| else |
| phone_model_ = nullptr; |
| |
| if (phone_hub_manager) { |
| notification_interaction_handler_ = |
| phone_hub_manager->GetNotificationInteractionHandler(); |
| } else { |
| notification_interaction_handler_ = nullptr; |
| } |
| } |
| |
| const std::u16string PhoneHubNotificationController::GetPhoneName() const { |
| if (!phone_model_) |
| return std::u16string(); |
| return phone_model_->phone_name().value_or(std::u16string()); |
| } |
| |
| void PhoneHubNotificationController::OnNotificationsAdded( |
| const base::flat_set<int64_t>& notification_ids) { |
| for (int64_t id : notification_ids) { |
| SetNotification(manager_->GetNotification(id), |
| /*is_update=*/false); |
| } |
| |
| LogNotificationCount(); |
| } |
| |
| void PhoneHubNotificationController::OnNotificationsUpdated( |
| const base::flat_set<int64_t>& notification_ids) { |
| for (int64_t id : notification_ids) { |
| SetNotification(manager_->GetNotification(id), |
| /*is_update=*/true); |
| } |
| } |
| |
| void PhoneHubNotificationController::OnNotificationsRemoved( |
| const base::flat_set<int64_t>& notification_ids) { |
| for (int64_t id : notification_ids) { |
| auto it = notification_map_.find(id); |
| if (it == notification_map_.end()) |
| continue; |
| it->second->Remove(); |
| notification_map_.erase(it); |
| } |
| |
| LogNotificationCount(); |
| } |
| |
| void PhoneHubNotificationController::OnAttemptConnectionScanFailed() { |
| // Add a notification if tether failed. |
| scoped_refptr<message_center::NotificationDelegate> delegate = |
| base::MakeRefCounted<message_center::HandleNotificationClickDelegate>( |
| base::BindRepeating([](absl::optional<int> button_index) { |
| // When clicked, open Tether Settings page if we can open WebUI |
| // settings, otherwise do nothing. |
| if (TrayPopupUtils::CanOpenWebUISettings()) { |
| Shell::Get() |
| ->system_tray_model() |
| ->client() |
| ->ShowTetherNetworkSettings(); |
| } else { |
| LOG(WARNING) << "Cannot open Tether Settings since it's not " |
| "possible to opening WebUI settings"; |
| } |
| })); |
| std::unique_ptr<message_center::Notification> notification = |
| CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kPhoneHubInstantTetherNotificationId, |
| l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_HOTSPOT_FAILED_TITLE), |
| l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_HOTSPOT_FAILED_MESSAGE), |
| std::u16string() /*display_source */, GURL() /* origin_url */, |
| message_center::NotifierId( |
| message_center::NotifierType::SYSTEM_COMPONENT, |
| kPhoneHubInstantTetherNotificationId, |
| NotificationCatalogName::kPhoneHubTetherFailed), |
| message_center::RichNotificationData(), std::move(delegate), |
| kPhoneHubEnableHotspotIcon, |
| message_center::SystemNotificationWarningLevel::NORMAL); |
| message_center::MessageCenter::Get()->AddNotification( |
| std::move(notification)); |
| } |
| |
| void PhoneHubNotificationController::OnCameraRollDownloadError( |
| DownloadErrorType error_type, |
| const CameraRollItemMetadata& metadata) { |
| std::unique_ptr<message_center::Notification> notification; |
| switch (error_type) { |
| case DownloadErrorType::kGenericError: |
| notification = CreateCameraRollGenericNotification(metadata); |
| break; |
| case DownloadErrorType::kInsufficientStorage: |
| notification = CreateCameraRollStorageNotification(metadata); |
| break; |
| case DownloadErrorType::kNetworkConnection: |
| notification = CreateCameraRollNetworkNotification(metadata); |
| break; |
| } |
| message_center::MessageCenter::Get()->AddNotification( |
| std::move(notification)); |
| } |
| |
| std::unique_ptr<message_center::Notification> |
| PhoneHubNotificationController::CreateCameraRollGenericNotification( |
| const CameraRollItemMetadata& metadata) { |
| scoped_refptr<message_center::NotificationDelegate> delegate = |
| base::MakeRefCounted<message_center::HandleNotificationClickDelegate>( |
| base::BindRepeating( |
| [](phonehub::CameraRollManager* manager, |
| const CameraRollItemMetadata& metadata, |
| absl::optional<int> button_index) { |
| // When button is clicked, close notification and retry the |
| // download |
| if (button_index.has_value()) { |
| message_center::MessageCenter::Get()->RemoveNotification( |
| kPhoneHubCameraRollNotificationId, /*by_user=*/true); |
| manager->DownloadItem(metadata); |
| } |
| }, |
| camera_roll_manager_, metadata)); |
| message_center::NotifierId notifier_id( |
| message_center::NotifierType::PHONE_HUB, |
| kPhoneHubCameraRollNotificationId); |
| message_center::RichNotificationData optional_fields; |
| message_center::ButtonInfo button; |
| button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_GENERIC_ACTION); |
| optional_fields.buttons.push_back(button); |
| return CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kPhoneHubCameraRollNotificationId, |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE), |
| l10n_util::GetStringFUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_GENERIC_BODY, |
| base::UTF8ToUTF16(metadata.file_name())), |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME), |
| /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate), |
| kPhoneHubCameraRollMenuDownloadIcon, |
| message_center::SystemNotificationWarningLevel::WARNING); |
| } |
| |
| std::unique_ptr<message_center::Notification> |
| PhoneHubNotificationController::CreateCameraRollStorageNotification( |
| const CameraRollItemMetadata& metadata) { |
| scoped_refptr<message_center::NotificationDelegate> delegate = |
| base::MakeRefCounted<message_center::HandleNotificationClickDelegate>( |
| base::BindRepeating([](absl::optional<int> button_index) { |
| // When button is clicked, close notification and open Storage |
| // Management Settings page if we can open WebUI settings. |
| if (button_index.has_value()) { |
| message_center::MessageCenter::Get()->RemoveNotification( |
| kPhoneHubCameraRollNotificationId, /*by_user=*/true); |
| if (TrayPopupUtils::CanOpenWebUISettings()) { |
| Shell::Get() |
| ->system_tray_model() |
| ->client() |
| ->ShowStorageSettings(); |
| } else { |
| PA_LOG(WARNING) |
| << "Cannot open Storage Management Settings since it's not " |
| "possible to open WebUI settings"; |
| } |
| } |
| })); |
| message_center::NotifierId notifier_id( |
| message_center::NotifierType::PHONE_HUB, |
| kPhoneHubCameraRollNotificationId); |
| message_center::RichNotificationData optional_fields; |
| message_center::ButtonInfo button; |
| button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_STORAGE_ACTION); |
| optional_fields.buttons.push_back(button); |
| return CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kPhoneHubCameraRollNotificationId, |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE), |
| l10n_util::GetStringFUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_STORAGE_BODY, |
| base::UTF8ToUTF16(metadata.file_name())), |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME), |
| /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate), |
| kPhoneHubCameraRollMenuDownloadIcon, |
| message_center::SystemNotificationWarningLevel::WARNING); |
| } |
| |
| std::unique_ptr<message_center::Notification> |
| PhoneHubNotificationController::CreateCameraRollNetworkNotification( |
| const CameraRollItemMetadata& metadata) { |
| scoped_refptr<message_center::NotificationDelegate> delegate = |
| base::MakeRefCounted<message_center::HandleNotificationClickDelegate>( |
| base::BindRepeating([](absl::optional<int> button_index) { |
| // When button is clicked, close notification and open Network |
| // Settings page if we can open WebUI settings. |
| if (button_index.has_value()) { |
| message_center::MessageCenter::Get()->RemoveNotification( |
| kPhoneHubCameraRollNotificationId, /*by_user=*/true); |
| if (TrayPopupUtils::CanOpenWebUISettings()) { |
| Shell::Get()->system_tray_model()->client()->ShowSettings( |
| display::kInvalidDisplayId); |
| } else { |
| PA_LOG(WARNING) |
| << "Cannot open Settings since it's not possible to open " |
| "WebUI settings"; |
| } |
| } |
| })); |
| message_center::NotifierId notifier_id( |
| message_center::NotifierType::PHONE_HUB, |
| kPhoneHubCameraRollNotificationId); |
| message_center::RichNotificationData optional_fields; |
| message_center::ButtonInfo button; |
| button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_NETWORK_ACTION); |
| optional_fields.buttons.push_back(button); |
| return CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kPhoneHubCameraRollNotificationId, |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE), |
| l10n_util::GetStringFUTF16( |
| IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_NETWORK_BODY, |
| base::UTF8ToUTF16(metadata.file_name())), |
| l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME), |
| /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate), |
| kPhoneHubCameraRollMenuDownloadIcon, |
| message_center::SystemNotificationWarningLevel::WARNING); |
| } |
| |
| void PhoneHubNotificationController::OpenSettings() { |
| DCHECK(TrayPopupUtils::CanOpenWebUISettings()); |
| Shell::Get()->system_tray_model()->client()->ShowConnectedDevicesSettings(); |
| } |
| |
| void PhoneHubNotificationController::DismissNotification( |
| int64_t notification_id) { |
| CHECK(manager_); |
| manager_->DismissNotification(notification_id); |
| phone_hub_metrics::LogNotificationInteraction( |
| NotificationInteraction::kDismiss); |
| } |
| |
| void PhoneHubNotificationController::HandleNotificationBodyClick( |
| int64_t notification_id, |
| const phonehub::Notification::AppMetadata& app_metadata) { |
| CHECK(manager_); |
| if (!notification_interaction_handler_) |
| return; |
| const phonehub::Notification* notification = |
| manager_->GetNotification(notification_id); |
| if (!notification) |
| return; |
| if (notification->interaction_behavior() == |
| phonehub::Notification::InteractionBehavior::kOpenable) { |
| notification_interaction_handler_->HandleNotificationClicked( |
| notification_id, app_metadata); |
| } |
| } |
| |
| void PhoneHubNotificationController::SendInlineReply( |
| int64_t notification_id, |
| const std::u16string& inline_reply_text) { |
| CHECK(manager_); |
| manager_->SendInlineReply(notification_id, inline_reply_text); |
| phone_hub_metrics::LogNotificationInteraction( |
| NotificationInteraction::kInlineReply); |
| } |
| |
| void PhoneHubNotificationController::LogNotificationCount() { |
| int count = notification_map_.size(); |
| phone_hub_metrics::LogNotificationCount(count); |
| } |
| |
| void PhoneHubNotificationController::SetNotification( |
| const phonehub::Notification* notification, |
| bool is_update) { |
| int64_t phone_hub_id = notification->id(); |
| std::string cros_id = base::StrCat( |
| {kNotifierId, kNotifierIdSeparator, base::NumberToString(phone_hub_id)}); |
| |
| bool notification_already_exists = |
| base::Contains(notification_map_, phone_hub_id); |
| if (!notification_already_exists) { |
| notification_map_[phone_hub_id] = std::make_unique<NotificationDelegate>( |
| this, phone_hub_id, cros_id, notification->category()); |
| } |
| NotificationDelegate* delegate = notification_map_[phone_hub_id].get(); |
| |
| auto cros_notification = |
| CreateNotification(notification, cros_id, delegate, is_update); |
| |
| if (notification->category() == |
| phonehub::Notification::Category::kIncomingCall || |
| notification->category() == |
| phonehub::Notification::Category::kOngoingCall) { |
| cros_notification->set_custom_view_type(kNotificationCustomCallViewType); |
| } else { |
| cros_notification->set_custom_view_type(kNotificationCustomViewType); |
| } |
| |
| phone_hub_metrics::LogNotificationMessageLength( |
| cros_notification->message().length()); |
| |
| auto* message_center = message_center::MessageCenter::Get(); |
| if (notification_already_exists) |
| message_center->UpdateNotification(cros_id, std::move(cros_notification)); |
| else |
| message_center->AddNotification(std::move(cros_notification)); |
| } |
| |
| std::unique_ptr<message_center::Notification> |
| PhoneHubNotificationController::CreateNotification( |
| const phonehub::Notification* notification, |
| const std::string& cros_id, |
| NotificationDelegate* delegate, |
| bool is_update) { |
| message_center::NotifierId notifier_id( |
| message_center::NotifierType::PHONE_HUB, kNotifierId); |
| |
| auto notification_type = message_center::NOTIFICATION_TYPE_CUSTOM; |
| |
| std::u16string title = notification->title().value_or(std::u16string()); |
| std::u16string message = |
| notification->text_content().value_or(std::u16string()); |
| |
| auto app_metadata = notification->app_metadata(); |
| std::u16string display_source = app_metadata.visible_app_name; |
| |
| message_center::RichNotificationData optional_fields; |
| optional_fields.small_image = app_metadata.monochrome_icon_mask.has_value() |
| ? app_metadata.monochrome_icon_mask.value() |
| : app_metadata.color_icon; |
| optional_fields.timestamp = notification->timestamp(); |
| optional_fields.accessible_name = l10n_util::GetStringFUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_ACCESSIBLE_NAME, display_source, title, |
| message, PhoneHubNotificationController::GetPhoneName()); |
| if (app_metadata.icon_is_monochrome) { |
| optional_fields.accent_color = app_metadata.icon_color; |
| optional_fields.ignore_accent_color_for_small_image = true; |
| optional_fields.ignore_accent_color_for_text = false; |
| optional_fields.small_image_needs_additional_masking = true; |
| } else { |
| optional_fields.ignore_accent_color_for_small_image = true; |
| optional_fields.ignore_accent_color_for_text = false; |
| optional_fields.small_image_needs_additional_masking = false; |
| } |
| |
| auto shared_image = notification->shared_image(); |
| if (shared_image.has_value()) |
| optional_fields.image = shared_image.value(); |
| |
| const gfx::Image& icon = notification->contact_image().value_or(gfx::Image()); |
| |
| optional_fields.priority = |
| GetSystemPriorityForNotification(notification, is_update); |
| |
| // If the notification was updated, set renotify to true so that the |
| // notification pops up again and is visible to the user. See |
| // https://crbug.com/1159063. |
| if (is_update) |
| optional_fields.renotify = true; |
| |
| switch (notification->category()) { |
| case phonehub::Notification::Category::kIncomingCall: { |
| message_center::ButtonInfo decline_button; |
| decline_button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_DECLINE_BUTTON); |
| optional_fields.buttons.push_back(decline_button); |
| |
| message_center::ButtonInfo answer_button; |
| answer_button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_ANSWER_BUTTON); |
| optional_fields.buttons.push_back(answer_button); |
| break; |
| } |
| case phonehub::Notification::Category::kOngoingCall: { |
| message_center::ButtonInfo hangup_button; |
| hangup_button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_HANGUP_BUTTON); |
| optional_fields.buttons.push_back(hangup_button); |
| break; |
| } |
| default: { |
| message_center::ButtonInfo reply_button; |
| reply_button.title = l10n_util::GetStringUTF16( |
| IDS_ASH_PHONE_HUB_NOTIFICATION_INLINE_REPLY_BUTTON); |
| // Setting a placeholder is needed to show the input field |
| reply_button.placeholder = std::u16string(); |
| optional_fields.buttons.push_back(reply_button); |
| break; |
| } |
| } |
| |
| if (TrayPopupUtils::CanOpenWebUISettings()) { |
| optional_fields.settings_button_handler = |
| message_center::SettingsButtonHandler::DELEGATE; |
| } |
| |
| return std::make_unique<message_center::Notification>( |
| notification_type, cros_id, title, message, |
| ui::ImageModel::FromImage(icon), display_source, |
| /*origin_url=*/GURL(), notifier_id, optional_fields, |
| delegate->AsScopedRefPtr()); |
| } |
| |
| int PhoneHubNotificationController::GetSystemPriorityForNotification( |
| const phonehub::Notification* notification, |
| bool is_update) { |
| bool is_recent = (notification->timestamp() + kMaxRecentNotificationAge) > |
| base::Time::Now(); |
| |
| // Use MAX_PRIORITY, which causes the notification to be shown in a popup |
| // so that users can see new messages come in as they are chatting. |
| if (is_recent || is_update) |
| return message_center::MAX_PRIORITY; |
| |
| // Silently add older notifications that are likely to be stale. |
| return message_center::LOW_PRIORITY; |
| } |
| |
| std::u16string GetPhoneName(base::WeakPtr<ash::PhoneHubNotificationController> |
| notification_controller) { |
| return (notification_controller) ? notification_controller->GetPhoneName() |
| : std::u16string(); |
| } |
| |
| // static |
| std::unique_ptr<message_center::MessageView> |
| PhoneHubNotificationController::CreateCustomNotificationView( |
| base::WeakPtr<PhoneHubNotificationController> notification_controller, |
| const message_center::Notification& notification, |
| bool shown_in_popup) { |
| DCHECK(notification.custom_view_type() == kNotificationCustomViewType); |
| |
| return std::make_unique<PhoneHubAshNotificationView>( |
| notification, shown_in_popup, ash::GetPhoneName(notification_controller)); |
| } |
| |
| // static |
| std::unique_ptr<message_center::MessageView> |
| PhoneHubNotificationController::CreateCustomActionNotificationView( |
| base::WeakPtr<PhoneHubNotificationController> notification_controller, |
| const message_center::Notification& notification, |
| bool shown_in_popup) { |
| DCHECK(notification.custom_view_type() == kNotificationCustomCallViewType); |
| |
| return std::make_unique<PhoneHubAshNotificationView>( |
| notification, shown_in_popup, ash::GetPhoneName(notification_controller)); |
| } |
| |
| } // namespace ash |