// Copyright 2017 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/public/cpp/external_arc/message_center/arc_notification_item_impl.h"
#include <utility>
#include <vector>
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/external_arc/message_center/arc_notification_content_view.h"
#include "ash/public/cpp/external_arc/message_center/arc_notification_delegate.h"
#include "ash/public/cpp/external_arc/message_center/arc_notification_view.h"
#include "ash/public/cpp/external_arc/message_center/metadata_utils.h"
#include "ash/public/cpp/message_center/arc_notification_constants.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"
using arc::mojom::ArcNotificationExpandState;
using arc::mojom::ArcNotificationPriority;
namespace ash {
namespace {
// Converts from Android notification priority to Chrome notification priority.
// On Android, PRIORITY_DEFAULT does not pop up, so this maps PRIORITY_DEFAULT
// to Chrome's -1 to adapt that behavior. Also, this maps PRIORITY_LOW and
// _HIGH to -2 and 0 respectively to adjust the value with keeping the order
// among _LOW, _DEFAULT and _HIGH. static
// TODO(yoshiki): rewrite this conversion as typemap
int ConvertAndroidPriority(ArcNotificationPriority android_priority) {
switch (android_priority) {
case ArcNotificationPriority::NONE:
case ArcNotificationPriority::MIN:
return message_center::MIN_PRIORITY;
case ArcNotificationPriority::LOW:
case ArcNotificationPriority::DEFAULT:
return message_center::LOW_PRIORITY;
case ArcNotificationPriority::HIGH:
return message_center::HIGH_PRIORITY;
case ArcNotificationPriority::MAX:
return message_center::MAX_PRIORITY;
}
NOTREACHED() << "Invalid Priority: " << android_priority;
}
} // anonymous namespace
ArcNotificationItemImpl::ArcNotificationItemImpl(
ArcNotificationManager* manager,
message_center::MessageCenter* message_center,
const std::string& notification_key,
const AccountId& profile_id)
: manager_(manager),
message_center_(message_center),
profile_id_(profile_id),
notification_key_(notification_key),
notification_id_(kArcNotificationIdPrefix + notification_key_) {}
ArcNotificationItemImpl::~ArcNotificationItemImpl() {
for (auto& observer : observers_)
observer.OnItemDestroying();
}
void ArcNotificationItemImpl::OnUpdatedFromAndroid(
arc::mojom::ArcNotificationDataPtr data,
const std::string& app_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(notification_key_, data->key);
bool is_setting_shown =
((data->shown_contents ==
arc::mojom::ArcNotificationShownContents::SETTINGS_SHOWN) ||
(data->shown_contents ==
arc::mojom::ArcNotificationShownContents::SNOOZE_SHOWN));
message_center::RichNotificationData rich_data;
rich_data.pinned =
(data->no_clear || data->ongoing_event) // Unclosable notification
|| is_setting_shown; // Settings are unclosable
rich_data.priority = ConvertAndroidPriority(data->priority);
if (data->small_icon)
rich_data.small_image = gfx::Image::CreateFrom1xBitmap(*data->small_icon);
if (data->big_picture) {
rich_data.image = gfx::Image::CreateFrom1xBitmap(*data->big_picture);
}
if (data->accessible_name.has_value()) {
rich_data.accessible_name =
base::UTF8ToUTF16(data->accessible_name.value());
}
const bool render_on_chrome =
features::IsRenderArcNotificationsByChromeEnabled() &&
data->render_on_chrome;
if (render_on_chrome) {
rich_data.settings_button_handler =
message_center::SettingsButtonHandler::INLINE;
} else if (manager_->IsOpeningSettingsSupported() && !is_setting_shown) {
rich_data.settings_button_handler =
message_center::SettingsButtonHandler::DELEGATE;
} else {
rich_data.settings_button_handler =
message_center::SettingsButtonHandler::NONE;
}
// TODO(b/313723218): Enable snooze on Chrome rendered ARC notifications.
rich_data.should_show_snooze_button = false;
message_center::NotifierId notifier_id(
message_center::NotifierType::ARC_APPLICATION,
app_id.empty() ? kDefaultArcNotifierId : app_id);
notifier_id.profile_id = profile_id_.GetUserEmail();
if (data->group_key) {
notifier_id.group_key = data->group_key;
}
auto notification_type =
render_on_chrome
? (data->messages
? message_center::NOTIFICATION_TYPE_CONVERSATION
: ((data->indeterminate_progress || data->progress_max != -1)
? message_center::NOTIFICATION_TYPE_PROGRESS
: message_center::NOTIFICATION_TYPE_SIMPLE))
: message_center::NOTIFICATION_TYPE_CUSTOM;
auto notification = CreateNotificationFromArcNotificationData(
notification_type, notification_id_, data.get(), notifier_id, rich_data,
new ArcNotificationDelegate(weak_ptr_factory_.GetWeakPtr()));
notification->set_timestamp(
base::Time::FromMillisecondsSinceUnixEpoch(data->time));
if (notification_type == message_center::NOTIFICATION_TYPE_CUSTOM) {
notification->set_custom_view_type(kArcNotificationCustomViewType);
}
if (notification_type == message_center::NOTIFICATION_TYPE_PROGRESS) {
notification->set_progress_status(base::UTF8ToUTF16(data->message));
}
if (expand_state_ != ArcNotificationExpandState::FIXED_SIZE &&
data->expand_state != ArcNotificationExpandState::FIXED_SIZE &&
expand_state_ != data->expand_state) {
// Assuming changing the expand status on Android-side is manually triggered
// by user.
manually_expanded_or_collapsed_ = true;
}
type_ = data->type;
expand_state_ = data->expand_state;
if (shown_contents_ != data->shown_contents) {
for (auto& observer : observers_)
observer.OnItemContentChanged(data->shown_contents);
}
shown_contents_ = data->shown_contents;
swipe_input_rect_ =
data->swipe_input_rect ? *data->swipe_input_rect : gfx::Rect();
notification->set_never_timeout(
data->remote_input_state ==
arc::mojom::ArcNotificationRemoteInputState::OPENED);
if (!data->snapshot_image || data->snapshot_image->isNull()) {
snapshot_ = gfx::ImageSkia();
} else {
snapshot_ = gfx::ImageSkia(
gfx::ImageSkiaRep(*data->snapshot_image, data->snapshot_image_scale));
}
message_center_->AddNotification(std::move(notification));
}
void ArcNotificationItemImpl::OnClosedFromAndroid() {
being_removed_by_manager_ = true; // Closing is initiated by the manager.
message_center_->RemoveNotification(notification_id_, false /* by_user */);
}
void ArcNotificationItemImpl::Close(bool by_user) {
if (being_removed_by_manager_) {
// Closing is caused by the manager, so we don't need to nofify a close
// event to the manager.
return;
}
// Do not touch its any members afterwards, because this instance will be
// destroyed in the following call
manager_->SendNotificationRemovedFromChrome(notification_key_);
}
void ArcNotificationItemImpl::Click() {
manager_->SendNotificationClickedOnChrome(notification_key_);
// This is reached when user focuses on the notification and hits enter on
// keyboard. Mouse clicks and taps are handled separately in
// ArcNotificationContentView.
// TODO(b/185943161): Record this in arc::ArcMetricsService.
UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
arc::UserInteractionType::NOTIFICATION_INTERACTION);
}
void ArcNotificationItemImpl::OpenSettings() {
manager_->OpenNotificationSettings(notification_key_);
}
void ArcNotificationItemImpl::DisableNotification() {
manager_->DisableNotification(notification_key_);
}
void ArcNotificationItemImpl::OpenSnooze() {
manager_->OpenNotificationSnoozeSettings(notification_key_);
}
void ArcNotificationItemImpl::ClickButton(const int button_index,
const std::string& input) {
manager_->SendNotificationButtonClickedOnChrome(notification_key_,
button_index, input);
}
void ArcNotificationItemImpl::ToggleExpansion() {
switch (expand_state_) {
case ArcNotificationExpandState::EXPANDED:
expand_state_ = ArcNotificationExpandState::COLLAPSED;
break;
case ArcNotificationExpandState::COLLAPSED:
expand_state_ = ArcNotificationExpandState::EXPANDED;
break;
case ArcNotificationExpandState::FIXED_SIZE:
// Do not change the state.
break;
}
manager_->SendNotificationToggleExpansionOnChrome(notification_key_);
}
void ArcNotificationItemImpl::SetExpandState(bool expanded) {
if (expand_state_ == ArcNotificationExpandState::FIXED_SIZE) {
// Fixed size notification does not change state, therefore we do not
// need to set corresponding state in ARC through SetExpandState.
return;
}
if (expanded && expand_state_ == ArcNotificationExpandState::COLLAPSED) {
manager_->SendNotificationToggleExpansionOnChrome(notification_key_);
} else if (!expanded &&
expand_state_ == ArcNotificationExpandState::EXPANDED) {
manager_->SendNotificationToggleExpansionOnChrome(notification_key_);
}
}
void ArcNotificationItemImpl::OnWindowActivated(bool activated) {
manager_->SendNotificationActivatedInChrome(notification_key_, activated);
}
void ArcNotificationItemImpl::OnRemoteInputActivationChanged(bool activated) {
for (auto& observer : observers_)
observer.OnRemoteInputActivationChanged(activated);
}
void ArcNotificationItemImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void ArcNotificationItemImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void ArcNotificationItemImpl::IncrementWindowRefCount() {
++window_ref_count_;
if (window_ref_count_ == 1)
manager_->CreateNotificationWindow(notification_key_);
}
void ArcNotificationItemImpl::DecrementWindowRefCount() {
DCHECK_GT(window_ref_count_, 0);
--window_ref_count_;
if (window_ref_count_ == 0)
manager_->CloseNotificationWindow(notification_key_);
}
const gfx::ImageSkia& ArcNotificationItemImpl::GetSnapshot() const {
return snapshot_;
}
arc::mojom::ArcNotificationType ArcNotificationItemImpl::GetNotificationType()
const {
return type_;
}
ArcNotificationExpandState ArcNotificationItemImpl::GetExpandState() const {
return expand_state_;
}
bool ArcNotificationItemImpl::IsManuallyExpandedOrCollapsed() const {
return manually_expanded_or_collapsed_;
}
gfx::Rect ArcNotificationItemImpl::GetSwipeInputRect() const {
return swipe_input_rect_;
}
const std::string& ArcNotificationItemImpl::GetNotificationKey() const {
return notification_key_;
}
const std::string& ArcNotificationItemImpl::GetNotificationId() const {
return notification_id_;
}
void ArcNotificationItemImpl::CancelPress() {
manager_->CancelPress(notification_key_);
}
} // namespace ash