blob: 38c1a2c27afa3cb69c69574c4c9610943cd6de65 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/assistant/assistant_alarm_timer_controller.h"
#include <map>
#include <string>
#include <utility>
#include "ash/assistant/assistant_controller.h"
#include "ash/assistant/assistant_notification_controller.h"
#include "ash/assistant/util/deep_link_util.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/i18n/message_formatter.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/assistant/public/features.h"
#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
// Grouping key and ID prefix for timer notifications.
constexpr char kTimerNotificationGroupingKey[] = "assistant/timer";
constexpr char kTimerNotificationIdPrefix[] = "assistant/timer";
// Interval at which alarms/timers are ticked.
constexpr base::TimeDelta kTickInterval = base::TimeDelta::FromSeconds(1);
// Helpers ---------------------------------------------------------------------
// Creates a notification ID for the given |alarm_timer_id|. It is guaranteed
// that this method will always return the same notification ID given the same
// alarm/timer ID.
std::string CreateTimerNotificationId(const std::string& alarm_timer_id) {
return std::string(kTimerNotificationIdPrefix) + alarm_timer_id;
}
std::string CreateTimerNotificationMessage(const AlarmTimer& alarm_timer,
base::TimeDelta time_remaining) {
const int minutes_remaining = time_remaining.InMinutes();
const int seconds_remaining =
(time_remaining - base::TimeDelta::FromMinutes(minutes_remaining))
.InSeconds();
return base::UTF16ToUTF8(base::i18n::MessageFormatter::FormatWithNumberedArgs(
l10n_util::GetStringUTF16(IDS_ASSISTANT_TIMER_NOTIFICATION_MESSAGE),
alarm_timer.expired() ? "-" : "", minutes_remaining, seconds_remaining));
}
// TODO(llin): Migrate to use the AlarmManager API to better support multiple
// timers when the API is available.
chromeos::assistant::mojom::AssistantNotificationPtr CreateTimerNotification(
const AlarmTimer& alarm_timer,
base::TimeDelta time_remaining) {
using chromeos::assistant::mojom::AssistantNotification;
using chromeos::assistant::mojom::AssistantNotificationButton;
using chromeos::assistant::mojom::AssistantNotificationPtr;
using chromeos::assistant::mojom::AssistantNotificationType;
const std::string title =
l10n_util::GetStringUTF8(IDS_ASSISTANT_TIMER_NOTIFICATION_TITLE);
const std::string message =
CreateTimerNotificationMessage(alarm_timer, time_remaining);
const GURL action_url = assistant::util::CreateAssistantQueryDeepLink(
l10n_util::GetStringUTF8(IDS_ASSISTANT_TIMER_NOTIFICATION_STOP_QUERY));
AssistantNotificationPtr notification = AssistantNotification::New();
// If in-Assistant notifications are supported, we'll allow alarm/timer
// notifications to show in either Assistant UI or the Message Center.
// Otherwise, we'll only allow the notification to show in the Message Center.
notification->type =
chromeos::assistant::features::IsInAssistantNotificationsEnabled()
? AssistantNotificationType::kPreferInAssistant
: AssistantNotificationType::kSystem;
notification->title = title;
notification->message = message;
notification->action_url = action_url;
notification->client_id = CreateTimerNotificationId(alarm_timer.id);
notification->grouping_key = kTimerNotificationGroupingKey;
// This notification should be able to wake up the display if it was off.
notification->is_high_priority = true;
// "STOP" button.
notification->buttons.push_back(AssistantNotificationButton::New(
l10n_util::GetStringUTF8(IDS_ASSISTANT_TIMER_NOTIFICATION_STOP_BUTTON),
action_url));
// "ADD 1 MIN" button.
notification->buttons.push_back(
chromeos::assistant::mojom::AssistantNotificationButton::New(
l10n_util::GetStringUTF8(
IDS_ASSISTANT_TIMER_NOTIFICATION_ADD_1_MIN_BUTTON),
assistant::util::CreateAssistantQueryDeepLink(
l10n_util::GetStringUTF8(
IDS_ASSISTANT_TIMER_NOTIFICATION_ADD_1_MIN_QUERY))));
return notification;
}
} // namespace
// AssistantAlarmTimerController -----------------------------------------------
AssistantAlarmTimerController::AssistantAlarmTimerController(
AssistantController* assistant_controller)
: assistant_controller_(assistant_controller), binding_(this) {
AddModelObserver(this);
}
AssistantAlarmTimerController::~AssistantAlarmTimerController() {
RemoveModelObserver(this);
}
void AssistantAlarmTimerController::BindRequest(
mojom::AssistantAlarmTimerControllerRequest request) {
DCHECK(chromeos::assistant::features::IsTimerNotificationEnabled());
binding_.Bind(std::move(request));
}
void AssistantAlarmTimerController::AddModelObserver(
AssistantAlarmTimerModelObserver* observer) {
model_.AddObserver(observer);
}
void AssistantAlarmTimerController::RemoveModelObserver(
AssistantAlarmTimerModelObserver* observer) {
model_.RemoveObserver(observer);
}
// TODO(dmblack): Remove method when the LibAssistant Alarm/Timer API is ready.
void AssistantAlarmTimerController::OnTimerSoundingStarted() {
AlarmTimer timer;
timer.id = std::to_string(next_timer_id_++);
timer.type = AlarmTimerType::kTimer;
timer.end_time = base::TimeTicks::Now();
model_.AddAlarmTimer(timer);
}
// TODO(dmblack): Remove method when the LibAssistant Alarm/Timer API is ready.
void AssistantAlarmTimerController::OnTimerSoundingFinished() {
model_.RemoveAllAlarmsTimers();
}
void AssistantAlarmTimerController::OnAlarmTimerStateChanged(
mojom::AssistantAlarmTimerEventPtr event) {
if (!event) {
// Nothing is ringing. Remove all alarms and timers.
model_.RemoveAllAlarmsTimers();
return;
}
switch (event->type) {
case mojom::AssistantAlarmTimerEventType::kTimer:
if (event->data->get_timer_data()->state ==
mojom::AssistantTimerState::kFired) {
// Remove all timers/alarms since there will be only one timer/alarm
// firing.
// TODO(llin): Handle multiple timers firing when the API is supported.
model_.RemoveAllAlarmsTimers();
AlarmTimer timer;
timer.id = event->data->get_timer_data()->timer_id;
timer.type = AlarmTimerType::kTimer;
timer.end_time = base::TimeTicks::Now();
model_.AddAlarmTimer(timer);
}
break;
// TODO(llin): Handle alarm event.
}
}
void AssistantAlarmTimerController::OnAlarmTimerAdded(
const AlarmTimer& alarm_timer,
const base::TimeDelta& time_remaining) {
// Schedule a repeating timer to tick the tracked alarms/timers.
if (chromeos::assistant::features::IsTimerTicksEnabled() &&
!timer_.IsRunning()) {
timer_.Start(FROM_HERE, kTickInterval, &model_,
&AssistantAlarmTimerModel::Tick);
}
// Create a notification for the added alarm/timer.
DCHECK(chromeos::assistant::features::IsTimerNotificationEnabled());
if (chromeos::assistant::features::IsTimerNotificationEnabled()) {
assistant_controller_->notification_controller()->AddOrUpdateNotification(
CreateTimerNotification(alarm_timer, time_remaining));
}
}
void AssistantAlarmTimerController::OnAlarmsTimersTicked(
const std::map<std::string, base::TimeDelta>& times_remaining) {
// This code should only be called when timer notifications/ticks are enabled.
DCHECK(chromeos::assistant::features::IsTimerNotificationEnabled());
DCHECK(chromeos::assistant::features::IsTimerTicksEnabled());
// Update any existing notifications associated w/ our alarms/timers.
for (auto& pair : times_remaining) {
auto* notification_controller =
assistant_controller_->notification_controller();
if (notification_controller->model()->HasNotificationForId(
CreateTimerNotificationId(/*alarm_timer_id=*/pair.first))) {
notification_controller->AddOrUpdateNotification(CreateTimerNotification(
*model_.GetAlarmTimerById(pair.first), pair.second));
}
}
}
void AssistantAlarmTimerController::OnAllAlarmsTimersRemoved() {
if (chromeos::assistant::features::IsTimerTicksEnabled())
timer_.Stop();
// Remove any notifications associated w/ alarms/timers.
DCHECK(chromeos::assistant::features::IsTimerNotificationEnabled());
if (chromeos::assistant::features::IsTimerNotificationEnabled()) {
assistant_controller_->notification_controller()
->RemoveNotificationByGroupingKey(kTimerNotificationGroupingKey,
/*from_server=*/false);
}
}
} // namespace ash