phone_hub_notification_controller.cc 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/phonehub/phone_hub_notification_controller.h"
  5. #include "ash/components/multidevice/logging/logging.h"
  6. #include "ash/components/phonehub/notification.h"
  7. #include "ash/components/phonehub/notification_interaction_handler.h"
  8. #include "ash/components/phonehub/phone_hub_manager.h"
  9. #include "ash/components/phonehub/phone_model.h"
  10. #include "ash/constants/ash_features.h"
  11. #include "ash/constants/notifier_catalogs.h"
  12. #include "ash/public/cpp/notification_utils.h"
  13. #include "ash/public/cpp/system_tray_client.h"
  14. #include "ash/resources/vector_icons/vector_icons.h"
  15. #include "ash/shell.h"
  16. #include "ash/strings/grit/ash_strings.h"
  17. #include "ash/system/message_center/ash_notification_view.h"
  18. #include "ash/system/message_center/message_view_factory.h"
  19. #include "ash/system/model/system_tray_model.h"
  20. #include "ash/system/phonehub/phone_hub_metrics.h"
  21. #include "ash/system/tray/tray_popup_utils.h"
  22. #include "base/bind.h"
  23. #include "base/containers/contains.h"
  24. #include "base/logging.h"
  25. #include "base/memory/weak_ptr.h"
  26. #include "base/strings/strcat.h"
  27. #include "base/strings/string_number_conversions.h"
  28. #include "base/strings/utf_string_conversions.h"
  29. #include "base/timer/timer.h"
  30. #include "ui/base/l10n/l10n_util.h"
  31. #include "ui/base/resource/resource_bundle.h"
  32. #include "ui/display/types/display_constants.h"
  33. #include "ui/gfx/text_elider.h"
  34. #include "ui/message_center/message_center.h"
  35. #include "ui/message_center/public/cpp/notification.h"
  36. #include "ui/message_center/public/cpp/notification_delegate.h"
  37. #include "ui/message_center/views/notification_header_view.h"
  38. #include "ui/message_center/views/notification_view.h"
  39. #include "ui/views/controls/label.h"
  40. #include "ui/views/controls/textfield/textfield.h"
  41. #include "ui/views/view.h"
  42. namespace ash {
  43. using phone_hub_metrics::NotificationInteraction;
  44. using phonehub::proto::CameraRollItemMetadata;
  45. namespace {
  46. const char kNotifierId[] = "chrome://phonehub";
  47. const char kNotifierIdSeparator[] = "-";
  48. const char kPhoneHubInstantTetherNotificationId[] =
  49. "chrome://phonehub-instant-tether";
  50. const char kPhoneHubCameraRollNotificationId[] =
  51. "chrome://phonehub-camera-roll";
  52. const char kNotificationCustomViewType[] = "phonehub";
  53. const char kNotificationCustomCallViewType[] = "phonehub-call";
  54. const int kReplyButtonIndex = 0;
  55. const int kNotificationHeaderTextWidth = 180;
  56. const int kNotificationAppNameMaxWidth = 140;
  57. // The max age since a notification's creation on the Android for it to get
  58. // shown in a heads-up pop up. Notifications older than this will only silently
  59. // get added.
  60. constexpr base::TimeDelta kMaxRecentNotificationAge = base::Seconds(15);
  61. // The amount of time the reply button is disabled after sending an inline
  62. // reply. This is used to make sure that all the replies are received by the
  63. // phone in a correct order (a reply sent right after another could cause it to
  64. // be received before the former one).
  65. constexpr base::TimeDelta kInlineReplyDisableTime = base::Seconds(1);
  66. class PhoneHubNotificationView : public message_center::NotificationView {
  67. public:
  68. explicit PhoneHubNotificationView(
  69. const message_center::Notification& notification,
  70. const std::u16string& phone_name)
  71. : message_center::NotificationView(notification) {
  72. // Add customized header.
  73. message_center::NotificationHeaderView* header_row =
  74. static_cast<message_center::NotificationHeaderView*>(
  75. GetViewByID(message_center::NotificationView::kHeaderRow));
  76. views::View* app_name_view =
  77. GetViewByID(message_center::NotificationView::kAppNameView);
  78. views::Label* summary_text_view = static_cast<views::Label*>(
  79. GetViewByID(message_center::NotificationView::kSummaryTextView));
  80. // The app name should be displayed in full, leaving the rest of the space
  81. // for device name. App name will only be truncated when it reached it
  82. // maximum width.
  83. int app_name_width = std::min(app_name_view->GetPreferredSize().width(),
  84. kNotificationAppNameMaxWidth);
  85. int device_name_width = kNotificationHeaderTextWidth - app_name_width;
  86. header_row->SetSummaryText(
  87. gfx::ElideText(phone_name, summary_text_view->font_list(),
  88. device_name_width, gfx::ELIDE_TAIL));
  89. custom_view_type_ = notification.custom_view_type();
  90. if (custom_view_type_ == kNotificationCustomCallViewType) {
  91. // Expand the action buttons row by default for Call Style notification.
  92. SetManuallyExpandedOrCollapsed(true);
  93. SetExpanded(true);
  94. return;
  95. }
  96. action_buttons_row_ =
  97. GetViewByID(message_center::NotificationView::kActionButtonsRow);
  98. if (!action_buttons_row_->children().empty())
  99. reply_button_ = static_cast<views::View*>(
  100. action_buttons_row_->children()[kReplyButtonIndex]);
  101. inline_reply_ = static_cast<message_center::NotificationInputContainer*>(
  102. GetViewByID(message_center::NotificationView::kInlineReply));
  103. }
  104. ~PhoneHubNotificationView() override = default;
  105. PhoneHubNotificationView(const PhoneHubNotificationView&) = delete;
  106. PhoneHubNotificationView& operator=(const PhoneHubNotificationView&) = delete;
  107. // message_center::NotificationViewBase
  108. void ActionButtonPressed(size_t index, const ui::Event& event) override {
  109. if (custom_view_type_ == kNotificationCustomCallViewType) {
  110. message_center::MessageCenter::Get()->ClickOnNotificationButton(
  111. notification_id(), static_cast<int>(index));
  112. } else {
  113. message_center::NotificationView::ActionButtonPressed(index, event);
  114. }
  115. }
  116. // message_center::NotificationView:
  117. void OnNotificationInputSubmit(size_t index,
  118. const std::u16string& text) override {
  119. message_center::NotificationView::OnNotificationInputSubmit(index, text);
  120. DCHECK(reply_button_);
  121. // After sending a reply, take the UI back to action buttons and clear out
  122. // text input.
  123. inline_reply_->SetVisible(false);
  124. action_buttons_row_->SetVisible(true);
  125. inline_reply_->textfield()->SetText(std::u16string());
  126. // Briefly disable reply button.
  127. reply_button_->SetEnabled(false);
  128. enable_reply_timer_ = std::make_unique<base::OneShotTimer>();
  129. enable_reply_timer_->Start(
  130. FROM_HERE, kInlineReplyDisableTime,
  131. base::BindOnce(&PhoneHubNotificationView::EnableReplyButton,
  132. base::Unretained(this)));
  133. }
  134. void EnableReplyButton() {
  135. reply_button_->SetEnabled(true);
  136. enable_reply_timer_.reset();
  137. }
  138. private:
  139. // Owned by view hierarchy.
  140. views::View* action_buttons_row_ = nullptr;
  141. views::View* reply_button_ = nullptr;
  142. message_center::NotificationInputContainer* inline_reply_ = nullptr;
  143. // Timer that fires to enable reply button after a brief period of time.
  144. std::unique_ptr<base::OneShotTimer> enable_reply_timer_;
  145. std::string custom_view_type_;
  146. };
  147. class PhoneHubAshNotificationView : public AshNotificationView {
  148. public:
  149. explicit PhoneHubAshNotificationView(
  150. const message_center::Notification& notification,
  151. bool shown_in_popup,
  152. const std::u16string& phone_name)
  153. : AshNotificationView(notification, shown_in_popup) {
  154. // Add customized header.
  155. message_center::NotificationHeaderView* header_row =
  156. static_cast<message_center::NotificationHeaderView*>(
  157. GetViewByID(message_center::NotificationView::kHeaderRow));
  158. views::View* app_name_view =
  159. GetViewByID(message_center::NotificationView::kAppNameView);
  160. views::Label* summary_text_view = static_cast<views::Label*>(
  161. GetViewByID(message_center::NotificationView::kSummaryTextView));
  162. // The app name should be displayed in full, leaving the rest of the space
  163. // for device name. App name will only be truncated when it reached it
  164. // maximum width.
  165. int app_name_width = std::min(app_name_view->GetPreferredSize().width(),
  166. kNotificationAppNameMaxWidth);
  167. int device_name_width = kNotificationHeaderTextWidth - app_name_width;
  168. header_row->SetSummaryText(
  169. gfx::ElideText(phone_name, summary_text_view->font_list(),
  170. device_name_width, gfx::ELIDE_TAIL));
  171. custom_view_type_ = notification.custom_view_type();
  172. if (custom_view_type_ == kNotificationCustomCallViewType) {
  173. // Expand the action buttons row by default for Call Style notification.
  174. SetManuallyExpandedOrCollapsed(true);
  175. SetExpanded(true);
  176. return;
  177. }
  178. action_buttons_row_ =
  179. GetViewByID(message_center::NotificationView::kActionButtonsRow);
  180. if (!action_buttons_row_->children().empty())
  181. reply_button_ = static_cast<views::View*>(
  182. action_buttons_row_->children()[kReplyButtonIndex]);
  183. inline_reply_ = static_cast<message_center::NotificationInputContainer*>(
  184. GetViewByID(message_center::NotificationView::kInlineReply));
  185. }
  186. ~PhoneHubAshNotificationView() override = default;
  187. PhoneHubAshNotificationView(const PhoneHubAshNotificationView&) = delete;
  188. PhoneHubAshNotificationView& operator=(const PhoneHubAshNotificationView&) =
  189. delete;
  190. // message_center::NotificationViewBase
  191. void ActionButtonPressed(size_t index, const ui::Event& event) override {
  192. if (custom_view_type_ == kNotificationCustomCallViewType) {
  193. message_center::MessageCenter::Get()->ClickOnNotificationButton(
  194. notification_id(), static_cast<int>(index));
  195. } else {
  196. AshNotificationView::ActionButtonPressed(index, event);
  197. }
  198. }
  199. // message_center::NotificationView:
  200. void OnNotificationInputSubmit(size_t index,
  201. const std::u16string& text) override {
  202. AshNotificationView::OnNotificationInputSubmit(index, text);
  203. DCHECK(reply_button_);
  204. // After sending a reply, take the UI back to action buttons and clear out
  205. // text input.
  206. inline_reply_->SetVisible(false);
  207. action_buttons_row_->SetVisible(true);
  208. inline_reply_->textfield()->SetText(std::u16string());
  209. // Briefly disable reply button.
  210. reply_button_->SetEnabled(false);
  211. enable_reply_timer_.Start(
  212. FROM_HERE, kInlineReplyDisableTime,
  213. base::BindOnce(&PhoneHubAshNotificationView::EnableReplyButton,
  214. base::Unretained(this)));
  215. }
  216. void EnableReplyButton() {
  217. reply_button_->SetEnabled(true);
  218. enable_reply_timer_.AbandonAndStop();
  219. }
  220. private:
  221. // Owned by view hierarchy.
  222. views::View* action_buttons_row_ = nullptr;
  223. views::View* reply_button_ = nullptr;
  224. message_center::NotificationInputContainer* inline_reply_ = nullptr;
  225. // Timer that fires to enable reply button after a brief period of time.
  226. base::OneShotTimer enable_reply_timer_;
  227. std::string custom_view_type_;
  228. };
  229. } // namespace
  230. // Delegate for the displayed ChromeOS notification.
  231. class PhoneHubNotificationController::NotificationDelegate
  232. : public message_center::NotificationObserver {
  233. public:
  234. NotificationDelegate(PhoneHubNotificationController* controller,
  235. int64_t phone_hub_id,
  236. const std::string& cros_id,
  237. phonehub::Notification::Category category)
  238. : controller_(controller),
  239. phone_hub_id_(phone_hub_id),
  240. cros_id_(cros_id),
  241. category_(category) {}
  242. virtual ~NotificationDelegate() { controller_ = nullptr; }
  243. NotificationDelegate(const NotificationDelegate&) = delete;
  244. NotificationDelegate& operator=(const NotificationDelegate&) = delete;
  245. // Returns a scoped_refptr that can be passed in the
  246. // message_center::Notification constructor.
  247. scoped_refptr<message_center::NotificationDelegate> AsScopedRefPtr() {
  248. return base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
  249. weak_ptr_factory_.GetWeakPtr());
  250. }
  251. // Called by the controller to remove the notification from the message
  252. // center.
  253. void Remove() {
  254. removed_by_phone_hub_ = true;
  255. message_center::MessageCenter::Get()->RemoveNotification(cros_id_,
  256. /*by_user=*/false);
  257. }
  258. // message_center::NotificationObserver:
  259. void Close(bool by_user) override {
  260. if (!controller_ || removed_by_phone_hub_)
  261. return;
  262. if (category_ == phonehub::Notification::Category::kIncomingCall ||
  263. category_ == phonehub::Notification::Category::kOngoingCall) {
  264. // TODO(b/203734343): Wait for UX confirm. Call notification is not
  265. // dismissible in android phone.
  266. PA_LOG(INFO)
  267. << "Can't dismiss an Incoming/Ongoing call notification with id: "
  268. << phone_hub_id_ << ".";
  269. return;
  270. }
  271. controller_->DismissNotification(phone_hub_id_);
  272. }
  273. void Click(const absl::optional<int>& button_index,
  274. const absl::optional<std::u16string>& reply) override {
  275. if (!controller_)
  276. return;
  277. if (!button_index.has_value()) {
  278. controller_->HandleNotificationBodyClick(
  279. phone_hub_id_, controller_->manager_->GetNotification(phone_hub_id_)
  280. ->app_metadata());
  281. return;
  282. }
  283. if (category_ == phonehub::Notification::Category::kIncomingCall) {
  284. // TODO(b/199223417): Implement actions.
  285. switch (*button_index) {
  286. case BUTTON_ANSWER:
  287. PA_LOG(INFO) << "answer button clicked";
  288. break;
  289. case BUTTON_DECLINE:
  290. PA_LOG(INFO) << "decline button clicked";
  291. break;
  292. }
  293. } else if (category_ == phonehub::Notification::Category::kOngoingCall) {
  294. switch (*button_index) {
  295. case BUTTON_HANGUP:
  296. PA_LOG(INFO) << "hangup button clicked";
  297. break;
  298. }
  299. } else if (button_index.value() == kReplyButtonIndex && reply.has_value()) {
  300. controller_->SendInlineReply(phone_hub_id_, reply.value());
  301. }
  302. }
  303. void SettingsClick() override {
  304. if (controller_)
  305. controller_->OpenSettings();
  306. }
  307. phonehub::Notification::Category Category() { return category_; }
  308. private:
  309. // Incoming call buttons that appear in notifications.
  310. enum IncomingCallButton { BUTTON_DECLINE, BUTTON_ANSWER };
  311. // Ongoing call buttons that appear in notifications.
  312. enum OngoingCallButton { BUTTON_HANGUP };
  313. // The parent controller, which owns this object.
  314. PhoneHubNotificationController* controller_ = nullptr;
  315. // The notification ID tracked by PhoneHub.
  316. const int64_t phone_hub_id_;
  317. // The notification ID tracked by the CrOS message center.
  318. const std::string cros_id_;
  319. // The category of the notification.
  320. phonehub::Notification::Category category_;
  321. // Flag set if the notification was removed by PhoneHub so we avoid a cycle.
  322. bool removed_by_phone_hub_ = false;
  323. base::WeakPtrFactory<NotificationDelegate> weak_ptr_factory_{this};
  324. };
  325. PhoneHubNotificationController::PhoneHubNotificationController() {
  326. if (!MessageViewFactory::HasCustomNotificationViewFactory(
  327. kNotificationCustomViewType)) {
  328. MessageViewFactory::SetCustomNotificationViewFactory(
  329. kNotificationCustomViewType,
  330. base::BindRepeating(
  331. &PhoneHubNotificationController::CreateCustomNotificationView,
  332. weak_ptr_factory_.GetWeakPtr()));
  333. }
  334. if (!MessageViewFactory::HasCustomNotificationViewFactory(
  335. kNotificationCustomCallViewType)) {
  336. MessageViewFactory::SetCustomNotificationViewFactory(
  337. kNotificationCustomCallViewType,
  338. base::BindRepeating(
  339. &PhoneHubNotificationController::CreateCustomActionNotificationView,
  340. weak_ptr_factory_.GetWeakPtr()));
  341. }
  342. }
  343. PhoneHubNotificationController::~PhoneHubNotificationController() {
  344. if (manager_)
  345. manager_->RemoveObserver(this);
  346. if (tether_controller_)
  347. tether_controller_->RemoveObserver(this);
  348. if (camera_roll_manager_)
  349. camera_roll_manager_->RemoveObserver(this);
  350. }
  351. void PhoneHubNotificationController::SetManager(
  352. phonehub::PhoneHubManager* phone_hub_manager) {
  353. if (manager_)
  354. manager_->RemoveObserver(this);
  355. if (phone_hub_manager) {
  356. manager_ = phone_hub_manager->GetNotificationManager();
  357. manager_->AddObserver(this);
  358. } else {
  359. manager_ = nullptr;
  360. }
  361. if (tether_controller_)
  362. tether_controller_->RemoveObserver(this);
  363. if (phone_hub_manager) {
  364. tether_controller_ = phone_hub_manager->GetTetherController();
  365. tether_controller_->AddObserver(this);
  366. } else {
  367. tether_controller_ = nullptr;
  368. }
  369. if (camera_roll_manager_)
  370. camera_roll_manager_->RemoveObserver(this);
  371. if (phone_hub_manager) {
  372. camera_roll_manager_ = phone_hub_manager->GetCameraRollManager();
  373. if (camera_roll_manager_) {
  374. camera_roll_manager_->AddObserver(this);
  375. } else {
  376. camera_roll_manager_ = nullptr;
  377. }
  378. } else {
  379. camera_roll_manager_ = nullptr;
  380. }
  381. if (phone_hub_manager)
  382. phone_model_ = phone_hub_manager->GetPhoneModel();
  383. else
  384. phone_model_ = nullptr;
  385. if (phone_hub_manager) {
  386. notification_interaction_handler_ =
  387. phone_hub_manager->GetNotificationInteractionHandler();
  388. } else {
  389. notification_interaction_handler_ = nullptr;
  390. }
  391. }
  392. const std::u16string PhoneHubNotificationController::GetPhoneName() const {
  393. if (!phone_model_)
  394. return std::u16string();
  395. return phone_model_->phone_name().value_or(std::u16string());
  396. }
  397. void PhoneHubNotificationController::OnNotificationsAdded(
  398. const base::flat_set<int64_t>& notification_ids) {
  399. for (int64_t id : notification_ids) {
  400. SetNotification(manager_->GetNotification(id),
  401. /*is_update=*/false);
  402. }
  403. LogNotificationCount();
  404. }
  405. void PhoneHubNotificationController::OnNotificationsUpdated(
  406. const base::flat_set<int64_t>& notification_ids) {
  407. for (int64_t id : notification_ids) {
  408. SetNotification(manager_->GetNotification(id),
  409. /*is_update=*/true);
  410. }
  411. }
  412. void PhoneHubNotificationController::OnNotificationsRemoved(
  413. const base::flat_set<int64_t>& notification_ids) {
  414. for (int64_t id : notification_ids) {
  415. auto it = notification_map_.find(id);
  416. if (it == notification_map_.end())
  417. continue;
  418. it->second->Remove();
  419. notification_map_.erase(it);
  420. }
  421. LogNotificationCount();
  422. }
  423. void PhoneHubNotificationController::OnAttemptConnectionScanFailed() {
  424. // Add a notification if tether failed.
  425. scoped_refptr<message_center::NotificationDelegate> delegate =
  426. base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
  427. base::BindRepeating([](absl::optional<int> button_index) {
  428. // When clicked, open Tether Settings page if we can open WebUI
  429. // settings, otherwise do nothing.
  430. if (TrayPopupUtils::CanOpenWebUISettings()) {
  431. Shell::Get()
  432. ->system_tray_model()
  433. ->client()
  434. ->ShowTetherNetworkSettings();
  435. } else {
  436. LOG(WARNING) << "Cannot open Tether Settings since it's not "
  437. "possible to opening WebUI settings";
  438. }
  439. }));
  440. std::unique_ptr<message_center::Notification> notification =
  441. CreateSystemNotification(
  442. message_center::NOTIFICATION_TYPE_SIMPLE,
  443. kPhoneHubInstantTetherNotificationId,
  444. l10n_util::GetStringUTF16(
  445. IDS_ASH_PHONE_HUB_NOTIFICATION_HOTSPOT_FAILED_TITLE),
  446. l10n_util::GetStringUTF16(
  447. IDS_ASH_PHONE_HUB_NOTIFICATION_HOTSPOT_FAILED_MESSAGE),
  448. std::u16string() /*display_source */, GURL() /* origin_url */,
  449. message_center::NotifierId(
  450. message_center::NotifierType::SYSTEM_COMPONENT,
  451. kPhoneHubInstantTetherNotificationId,
  452. NotificationCatalogName::kPhoneHubTetherFailed),
  453. message_center::RichNotificationData(), std::move(delegate),
  454. kPhoneHubEnableHotspotIcon,
  455. message_center::SystemNotificationWarningLevel::NORMAL);
  456. message_center::MessageCenter::Get()->AddNotification(
  457. std::move(notification));
  458. }
  459. void PhoneHubNotificationController::OnCameraRollDownloadError(
  460. DownloadErrorType error_type,
  461. const CameraRollItemMetadata& metadata) {
  462. std::unique_ptr<message_center::Notification> notification;
  463. switch (error_type) {
  464. case DownloadErrorType::kGenericError:
  465. notification = CreateCameraRollGenericNotification(metadata);
  466. break;
  467. case DownloadErrorType::kInsufficientStorage:
  468. notification = CreateCameraRollStorageNotification(metadata);
  469. break;
  470. case DownloadErrorType::kNetworkConnection:
  471. notification = CreateCameraRollNetworkNotification(metadata);
  472. break;
  473. }
  474. message_center::MessageCenter::Get()->AddNotification(
  475. std::move(notification));
  476. }
  477. std::unique_ptr<message_center::Notification>
  478. PhoneHubNotificationController::CreateCameraRollGenericNotification(
  479. const CameraRollItemMetadata& metadata) {
  480. scoped_refptr<message_center::NotificationDelegate> delegate =
  481. base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
  482. base::BindRepeating(
  483. [](phonehub::CameraRollManager* manager,
  484. const CameraRollItemMetadata& metadata,
  485. absl::optional<int> button_index) {
  486. // When button is clicked, close notification and retry the
  487. // download
  488. if (button_index.has_value()) {
  489. message_center::MessageCenter::Get()->RemoveNotification(
  490. kPhoneHubCameraRollNotificationId, /*by_user=*/true);
  491. manager->DownloadItem(metadata);
  492. }
  493. },
  494. camera_roll_manager_, metadata));
  495. message_center::NotifierId notifier_id(
  496. message_center::NotifierType::PHONE_HUB,
  497. kPhoneHubCameraRollNotificationId);
  498. message_center::RichNotificationData optional_fields;
  499. message_center::ButtonInfo button;
  500. button.title = l10n_util::GetStringUTF16(
  501. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_GENERIC_ACTION);
  502. optional_fields.buttons.push_back(button);
  503. return CreateSystemNotification(
  504. message_center::NOTIFICATION_TYPE_SIMPLE,
  505. kPhoneHubCameraRollNotificationId,
  506. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE),
  507. l10n_util::GetStringFUTF16(
  508. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_GENERIC_BODY,
  509. base::UTF8ToUTF16(metadata.file_name())),
  510. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME),
  511. /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate),
  512. kPhoneHubCameraRollMenuDownloadIcon,
  513. message_center::SystemNotificationWarningLevel::WARNING);
  514. }
  515. std::unique_ptr<message_center::Notification>
  516. PhoneHubNotificationController::CreateCameraRollStorageNotification(
  517. const CameraRollItemMetadata& metadata) {
  518. scoped_refptr<message_center::NotificationDelegate> delegate =
  519. base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
  520. base::BindRepeating([](absl::optional<int> button_index) {
  521. // When button is clicked, close notification and open Storage
  522. // Management Settings page if we can open WebUI settings.
  523. if (button_index.has_value()) {
  524. message_center::MessageCenter::Get()->RemoveNotification(
  525. kPhoneHubCameraRollNotificationId, /*by_user=*/true);
  526. if (TrayPopupUtils::CanOpenWebUISettings()) {
  527. Shell::Get()
  528. ->system_tray_model()
  529. ->client()
  530. ->ShowStorageSettings();
  531. } else {
  532. PA_LOG(WARNING)
  533. << "Cannot open Storage Management Settings since it's not "
  534. "possible to open WebUI settings";
  535. }
  536. }
  537. }));
  538. message_center::NotifierId notifier_id(
  539. message_center::NotifierType::PHONE_HUB,
  540. kPhoneHubCameraRollNotificationId);
  541. message_center::RichNotificationData optional_fields;
  542. message_center::ButtonInfo button;
  543. button.title = l10n_util::GetStringUTF16(
  544. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_STORAGE_ACTION);
  545. optional_fields.buttons.push_back(button);
  546. return CreateSystemNotification(
  547. message_center::NOTIFICATION_TYPE_SIMPLE,
  548. kPhoneHubCameraRollNotificationId,
  549. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE),
  550. l10n_util::GetStringFUTF16(
  551. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_STORAGE_BODY,
  552. base::UTF8ToUTF16(metadata.file_name())),
  553. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME),
  554. /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate),
  555. kPhoneHubCameraRollMenuDownloadIcon,
  556. message_center::SystemNotificationWarningLevel::WARNING);
  557. }
  558. std::unique_ptr<message_center::Notification>
  559. PhoneHubNotificationController::CreateCameraRollNetworkNotification(
  560. const CameraRollItemMetadata& metadata) {
  561. scoped_refptr<message_center::NotificationDelegate> delegate =
  562. base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
  563. base::BindRepeating([](absl::optional<int> button_index) {
  564. // When button is clicked, close notification and open Network
  565. // Settings page if we can open WebUI settings.
  566. if (button_index.has_value()) {
  567. message_center::MessageCenter::Get()->RemoveNotification(
  568. kPhoneHubCameraRollNotificationId, /*by_user=*/true);
  569. if (TrayPopupUtils::CanOpenWebUISettings()) {
  570. Shell::Get()->system_tray_model()->client()->ShowSettings(
  571. display::kInvalidDisplayId);
  572. } else {
  573. PA_LOG(WARNING)
  574. << "Cannot open Settings since it's not possible to open "
  575. "WebUI settings";
  576. }
  577. }
  578. }));
  579. message_center::NotifierId notifier_id(
  580. message_center::NotifierType::PHONE_HUB,
  581. kPhoneHubCameraRollNotificationId);
  582. message_center::RichNotificationData optional_fields;
  583. message_center::ButtonInfo button;
  584. button.title = l10n_util::GetStringUTF16(
  585. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_NETWORK_ACTION);
  586. optional_fields.buttons.push_back(button);
  587. return CreateSystemNotification(
  588. message_center::NOTIFICATION_TYPE_SIMPLE,
  589. kPhoneHubCameraRollNotificationId,
  590. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_TITLE),
  591. l10n_util::GetStringFUTF16(
  592. IDS_ASH_PHONE_HUB_CAMERA_ROLL_ERROR_NETWORK_BODY,
  593. base::UTF8ToUTF16(metadata.file_name())),
  594. l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME),
  595. /*origin_url=*/GURL(), notifier_id, optional_fields, std::move(delegate),
  596. kPhoneHubCameraRollMenuDownloadIcon,
  597. message_center::SystemNotificationWarningLevel::WARNING);
  598. }
  599. void PhoneHubNotificationController::OpenSettings() {
  600. DCHECK(TrayPopupUtils::CanOpenWebUISettings());
  601. Shell::Get()->system_tray_model()->client()->ShowConnectedDevicesSettings();
  602. }
  603. void PhoneHubNotificationController::DismissNotification(
  604. int64_t notification_id) {
  605. CHECK(manager_);
  606. manager_->DismissNotification(notification_id);
  607. phone_hub_metrics::LogNotificationInteraction(
  608. NotificationInteraction::kDismiss);
  609. }
  610. void PhoneHubNotificationController::HandleNotificationBodyClick(
  611. int64_t notification_id,
  612. const phonehub::Notification::AppMetadata& app_metadata) {
  613. CHECK(manager_);
  614. if (!notification_interaction_handler_)
  615. return;
  616. const phonehub::Notification* notification =
  617. manager_->GetNotification(notification_id);
  618. if (!notification)
  619. return;
  620. if (notification->interaction_behavior() ==
  621. phonehub::Notification::InteractionBehavior::kOpenable) {
  622. notification_interaction_handler_->HandleNotificationClicked(
  623. notification_id, app_metadata);
  624. }
  625. }
  626. void PhoneHubNotificationController::SendInlineReply(
  627. int64_t notification_id,
  628. const std::u16string& inline_reply_text) {
  629. CHECK(manager_);
  630. manager_->SendInlineReply(notification_id, inline_reply_text);
  631. phone_hub_metrics::LogNotificationInteraction(
  632. NotificationInteraction::kInlineReply);
  633. }
  634. void PhoneHubNotificationController::LogNotificationCount() {
  635. int count = notification_map_.size();
  636. phone_hub_metrics::LogNotificationCount(count);
  637. }
  638. void PhoneHubNotificationController::SetNotification(
  639. const phonehub::Notification* notification,
  640. bool is_update) {
  641. int64_t phone_hub_id = notification->id();
  642. std::string cros_id = base::StrCat(
  643. {kNotifierId, kNotifierIdSeparator, base::NumberToString(phone_hub_id)});
  644. bool notification_already_exists =
  645. base::Contains(notification_map_, phone_hub_id);
  646. if (!notification_already_exists) {
  647. notification_map_[phone_hub_id] = std::make_unique<NotificationDelegate>(
  648. this, phone_hub_id, cros_id, notification->category());
  649. }
  650. NotificationDelegate* delegate = notification_map_[phone_hub_id].get();
  651. auto cros_notification =
  652. CreateNotification(notification, cros_id, delegate, is_update);
  653. if (notification->category() ==
  654. phonehub::Notification::Category::kIncomingCall ||
  655. notification->category() ==
  656. phonehub::Notification::Category::kOngoingCall) {
  657. cros_notification->set_custom_view_type(kNotificationCustomCallViewType);
  658. } else {
  659. cros_notification->set_custom_view_type(kNotificationCustomViewType);
  660. }
  661. phone_hub_metrics::LogNotificationMessageLength(
  662. cros_notification->message().length());
  663. auto* message_center = message_center::MessageCenter::Get();
  664. if (notification_already_exists)
  665. message_center->UpdateNotification(cros_id, std::move(cros_notification));
  666. else
  667. message_center->AddNotification(std::move(cros_notification));
  668. }
  669. std::unique_ptr<message_center::Notification>
  670. PhoneHubNotificationController::CreateNotification(
  671. const phonehub::Notification* notification,
  672. const std::string& cros_id,
  673. NotificationDelegate* delegate,
  674. bool is_update) {
  675. message_center::NotifierId notifier_id(
  676. message_center::NotifierType::PHONE_HUB, kNotifierId);
  677. auto notification_type = message_center::NOTIFICATION_TYPE_CUSTOM;
  678. std::u16string title = notification->title().value_or(std::u16string());
  679. std::u16string message =
  680. notification->text_content().value_or(std::u16string());
  681. auto app_metadata = notification->app_metadata();
  682. std::u16string display_source = app_metadata.visible_app_name;
  683. message_center::RichNotificationData optional_fields;
  684. optional_fields.small_image = app_metadata.icon;
  685. optional_fields.timestamp = notification->timestamp();
  686. optional_fields.accessible_name = l10n_util::GetStringFUTF16(
  687. IDS_ASH_PHONE_HUB_NOTIFICATION_ACCESSIBLE_NAME, display_source, title,
  688. message, PhoneHubNotificationController::GetPhoneName());
  689. if (app_metadata.icon_is_monochrome) {
  690. optional_fields.accent_color = app_metadata.icon_color;
  691. if (features::IsNotificationsRefreshEnabled()) {
  692. optional_fields.ignore_accent_color_for_small_image = true;
  693. optional_fields.ignore_accent_color_for_text = false;
  694. optional_fields.small_image_needs_additional_masking = true;
  695. } else {
  696. optional_fields.ignore_accent_color_for_small_image = false;
  697. optional_fields.ignore_accent_color_for_text = true;
  698. optional_fields.small_image_needs_additional_masking = false;
  699. }
  700. } else {
  701. optional_fields.ignore_accent_color_for_small_image = true;
  702. optional_fields.ignore_accent_color_for_text = false;
  703. optional_fields.small_image_needs_additional_masking = false;
  704. }
  705. auto shared_image = notification->shared_image();
  706. if (shared_image.has_value())
  707. optional_fields.image = shared_image.value();
  708. const gfx::Image& icon = notification->contact_image().value_or(gfx::Image());
  709. optional_fields.priority =
  710. GetSystemPriorityForNotification(notification, is_update);
  711. // If the notification was updated, set renotify to true so that the
  712. // notification pops up again and is visible to the user. See
  713. // https://crbug.com/1159063.
  714. if (is_update)
  715. optional_fields.renotify = true;
  716. switch (notification->category()) {
  717. case phonehub::Notification::Category::kIncomingCall: {
  718. message_center::ButtonInfo decline_button;
  719. decline_button.title = l10n_util::GetStringUTF16(
  720. IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_DECLINE_BUTTON);
  721. optional_fields.buttons.push_back(decline_button);
  722. message_center::ButtonInfo answer_button;
  723. answer_button.title = l10n_util::GetStringUTF16(
  724. IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_ANSWER_BUTTON);
  725. optional_fields.buttons.push_back(answer_button);
  726. break;
  727. }
  728. case phonehub::Notification::Category::kOngoingCall: {
  729. message_center::ButtonInfo hangup_button;
  730. hangup_button.title = l10n_util::GetStringUTF16(
  731. IDS_ASH_PHONE_HUB_NOTIFICATION_CALL_HANGUP_BUTTON);
  732. optional_fields.buttons.push_back(hangup_button);
  733. break;
  734. }
  735. default: {
  736. message_center::ButtonInfo reply_button;
  737. reply_button.title = l10n_util::GetStringUTF16(
  738. IDS_ASH_PHONE_HUB_NOTIFICATION_INLINE_REPLY_BUTTON);
  739. // Setting a placeholder is needed to show the input field
  740. reply_button.placeholder = std::u16string();
  741. optional_fields.buttons.push_back(reply_button);
  742. break;
  743. }
  744. }
  745. if (TrayPopupUtils::CanOpenWebUISettings()) {
  746. optional_fields.settings_button_handler =
  747. message_center::SettingsButtonHandler::DELEGATE;
  748. }
  749. return std::make_unique<message_center::Notification>(
  750. notification_type, cros_id, title, message,
  751. ui::ImageModel::FromImage(icon), display_source,
  752. /*origin_url=*/GURL(), notifier_id, optional_fields,
  753. delegate->AsScopedRefPtr());
  754. }
  755. int PhoneHubNotificationController::GetSystemPriorityForNotification(
  756. const phonehub::Notification* notification,
  757. bool is_update) {
  758. bool is_recent = (notification->timestamp() + kMaxRecentNotificationAge) >
  759. base::Time::Now();
  760. // Use MAX_PRIORITY, which causes the notification to be shown in a popup
  761. // so that users can see new messages come in as they are chatting.
  762. if (is_recent || is_update)
  763. return message_center::MAX_PRIORITY;
  764. // Silently add older notifications that are likely to be stale.
  765. return message_center::LOW_PRIORITY;
  766. }
  767. std::u16string GetPhoneName(base::WeakPtr<ash::PhoneHubNotificationController>
  768. notification_controller) {
  769. return (notification_controller) ? notification_controller->GetPhoneName()
  770. : std::u16string();
  771. }
  772. // static
  773. std::unique_ptr<message_center::MessageView>
  774. PhoneHubNotificationController::CreateCustomNotificationView(
  775. base::WeakPtr<PhoneHubNotificationController> notification_controller,
  776. const message_center::Notification& notification,
  777. bool shown_in_popup) {
  778. DCHECK(notification.custom_view_type() == kNotificationCustomViewType);
  779. if (features::IsNotificationsRefreshEnabled()) {
  780. return std::make_unique<PhoneHubAshNotificationView>(
  781. notification, shown_in_popup,
  782. ash::GetPhoneName(notification_controller));
  783. }
  784. return std::make_unique<PhoneHubNotificationView>(
  785. notification, ash::GetPhoneName(notification_controller));
  786. }
  787. // static
  788. std::unique_ptr<message_center::MessageView>
  789. PhoneHubNotificationController::CreateCustomActionNotificationView(
  790. base::WeakPtr<PhoneHubNotificationController> notification_controller,
  791. const message_center::Notification& notification,
  792. bool shown_in_popup) {
  793. DCHECK(notification.custom_view_type() == kNotificationCustomCallViewType);
  794. if (features::IsNotificationsRefreshEnabled()) {
  795. return std::make_unique<PhoneHubAshNotificationView>(
  796. notification, shown_in_popup,
  797. ash::GetPhoneName(notification_controller));
  798. }
  799. return std::make_unique<PhoneHubNotificationView>(
  800. notification, ash::GetPhoneName(notification_controller));
  801. }
  802. } // namespace ash