// 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/notification_center/views/ash_notification_view.h"
#include "ash/system/notification_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());
// Since the focus may still be on the now-hidden buttons used to send a
// message, refocus on the entire notification.
CHECK(GetFocusManager());
GetFocusManager()->SetFocusedView(this);
// 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> action_buttons_row_ = nullptr;
raw_ptr<views::View> reply_button_ = nullptr;
raw_ptr<message_center::NotificationInputContainer> 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 std::optional<int>& button_index,
const std::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> 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([](std::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,
std::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([](std::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([](std::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