chromium/ash/system/time/calendar_view_controller.cc

// Copyright 2021 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/time/calendar_view_controller.h"

#include <stdlib.h>
#include <cstddef>

#include "ash/calendar/calendar_client.h"
#include "ash/calendar/calendar_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_metrics.h"
#include "ash/system/time/calendar_model.h"
#include "ash/system/time/calendar_utils.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {

CalendarViewController::CalendarViewController()
    : currently_shown_date_(base::Time::Now()),
      calendar_open_time_(base::TimeTicks::Now()),
      month_dwell_time_(base::TimeTicks::Now()),
      first_shown_date_(base::Time::Now()) {
  std::set<base::Time> months = calendar_utils::GetSurroundingMonthsUTC(
      base::Time::Now() +
          calendar_utils::GetTimeDifference(currently_shown_date_),
      calendar_utils::kNumSurroundingMonthsCached);
  Shell::Get()->system_tray_model()->calendar_model()->AddNonPrunableMonths(
      months);
}

CalendarViewController::~CalendarViewController() {
  CalendarModel* calendar_model =
      Shell::Get()->system_tray_model()->calendar_model();
  DCHECK(calendar_model);
  calendar_model->UploadLifetimeMetrics();
  calendar_model->ClearAllPrunableEvents();

  calendar_metrics::RecordMonthDwellTime(base::TimeTicks::Now() -
                                         month_dwell_time_);

  if (user_journey_time_recorded_)
    return;

  base::UmaHistogramMediumTimes("Ash.Calendar.UserJourneyTime.EventNotLaunched",
                                base::TimeTicks::Now() - calendar_open_time_);
  base::UmaHistogramCounts100000("Ash.Calendar.MaxDistanceBrowsed",
                                 max_distance_browsed_);
}

void CalendarViewController::AddObserver(Observer* observer) {
  if (observer)
    observers_.AddObserver(observer);
}

void CalendarViewController::RemoveObserver(Observer* observer) {
  if (observer)
    observers_.RemoveObserver(observer);
}

void CalendarViewController::UpdateMonth(
    const base::Time current_month_first_date) {
  if (calendar_utils::GetMonthNameAndYear(currently_shown_date_) ==
      calendar_utils::GetMonthNameAndYear(current_month_first_date)) {
    return;
  }

  calendar_metrics::RecordMonthDwellTime(base::TimeTicks::Now() -
                                         month_dwell_time_);
  month_dwell_time_ = base::TimeTicks::Now();

  currently_shown_date_ = current_month_first_date;

  max_distance_browsed_ =
      std::max(max_distance_browsed_,
               static_cast<size_t>(abs(calendar_utils::GetMonthsBetween(
                   first_shown_date_, current_month_first_date))));

  for (auto& observer : observers_)
    observer.OnMonthChanged();
}

base::Time CalendarViewController::GetPreviousMonthFirstDayUTC(
    unsigned int num_months) {
  base::Time prev, current = ApplyTimeDifference(GetOnScreenMonthFirstDayUTC());

  DCHECK_GE(num_months, 1UL);

  for (unsigned int i = 0; i < num_months; i++, current = prev) {
    prev = calendar_utils::GetStartOfPreviousMonthLocal(current);
  }

  return prev - calendar_utils::GetTimeDifference(prev);
}

base::Time CalendarViewController::GetNextMonthFirstDayUTC(
    unsigned int num_months) {
  base::Time next, current = ApplyTimeDifference(GetOnScreenMonthFirstDayUTC());

  DCHECK_GE(num_months, 1UL);

  for (unsigned int i = 0; i < num_months; i++, current = next) {
    next = calendar_utils::GetStartOfNextMonthLocal(current);
  }

  return next - calendar_utils::GetTimeDifference(next);
}

std::u16string CalendarViewController::GetPreviousMonthName() {
  return calendar_utils::GetMonthName(GetPreviousMonthFirstDayUTC(1));
}

std::u16string CalendarViewController::GetNextMonthName(int num_months) {
  return calendar_utils::GetMonthName(GetNextMonthFirstDayUTC(num_months));
}

std::u16string CalendarViewController::GetOnScreenMonthName() const {
  return calendar_utils::GetMonthName(currently_shown_date_);
}

int CalendarViewController::GetExpandedRowIndex() const {
  DCHECK(is_event_list_showing_);
  return expanded_row_index_;
}

int CalendarViewController::GetTodayRowTopHeight() const {
  return (today_row_ - 1) * row_height_;
}

int CalendarViewController::GetTodayRowBottomHeight() const {
  return today_row_ * row_height_;
}

SingleDayEventList CalendarViewController::SelectedDateEvents() {
  if (!selected_date_.has_value())
    return std::list<google_apis::calendar::CalendarEvent>();

  return Shell::Get()->system_tray_model()->calendar_model()->FindEvents(
      ApplyTimeDifference(selected_date_.value()));
}

std::tuple<SingleDayEventList, SingleDayEventList>
CalendarViewController::SelectedDateEventsSplitByMultiDayAndSameDay() {
  if (!selected_date_.has_value())
    return std::make_tuple(std::list<google_apis::calendar::CalendarEvent>(),
                           std::list<google_apis::calendar::CalendarEvent>());

  return Shell::Get()
      ->system_tray_model()
      ->calendar_model()
      ->FindEventsSplitByMultiDayAndSameDay(
          ApplyTimeDifference(selected_date_.value()));
}

SingleDayEventList CalendarViewController::UpcomingEvents() {
  return Shell::Get()
      ->system_tray_model()
      ->calendar_model()
      ->FindUpcomingEvents(
          ApplyTimeDifference(base::Time::NowFromSystemTime()));
}

int CalendarViewController::GetEventNumber(base::Time date) {
  return Shell::Get()->system_tray_model()->calendar_model()->EventsNumberOfDay(
      ApplyTimeDifference(date),
      /*events =*/nullptr);
}

void CalendarViewController::ShowEventListView(
    CalendarDateCellView* selected_calendar_date_cell_view,
    base::Time selected_date,
    int row_index) {
  // Do nothing if selecting on the same date.
  if (is_event_list_showing_ &&
      selected_calendar_date_cell_view == selected_date_cell_view_) {
    return;
  }
  selected_date_ = selected_date;
  set_selected_date_cell_view(selected_calendar_date_cell_view);
  selected_date_row_index_ = row_index;
  expanded_row_index_ = row_index;

  std::tie(selected_date_midnight_, selected_date_midnight_utc_) =
      calendar_utils::GetMidnight(selected_date);

  // Notify observers.
  for (auto& observer : observers_)
    observer.OnSelectedDateUpdated();

  if (!is_event_list_showing_) {
    for (auto& observer : observers_)
      observer.OpenEventList();
  }
}

void CalendarViewController::CloseEventListView() {
  for (auto& observer : observers_)
    observer.CloseEventList();
}

void CalendarViewController::OnEventListOpened() {
  is_event_list_showing_ = true;
}

void CalendarViewController::OnEventListClosed() {
  is_event_list_showing_ = false;
  selected_date_ = std::nullopt;
}

void CalendarViewController::CalendarLoaded() {
  for (auto& observer : observers_) {
    observer.OnCalendarLoaded();
  }
}

void CalendarViewController::RecordEventListItemActivated(
    const ui::Event& event) {
  // The EventListItemView is used by both the event list view and the up next
  // view. So if the event list view is not showing, then it's in the up next
  // view.
  if (is_event_list_showing_) {
    calendar_metrics::RecordEventListItemActivated(event);
    return;
  }

  calendar_metrics::RecordEventListItemInUpNextLaunched(event);
}

void CalendarViewController::RecordJoinMeetingButtonPressed(
    const ui::Event& event) {
  // The EventListItemView is used by both the event list view and the up next
  // view. So if the event list view is not showing, then it's in the up next
  // view.
  if (is_event_list_showing_) {
    calendar_metrics::RecordJoinButtonPressedFromEventListView(event);
    return;
  }

  calendar_metrics::RecordJoinButtonPressedFromUpNextView(event);
}

void CalendarViewController::OnCalendarEventWillLaunch() {
  UmaHistogramMediumTimes("Ash.Calendar.UserJourneyTime.EventLaunched",
                          base::TimeTicks::Now() - calendar_open_time_);
  user_journey_time_recorded_ = true;
}

void CalendarViewController::OnTodaysEventFetchComplete() {
  // Only record this once per lifetime of the CalendarView (and therefore the
  // controller).
  if (todays_date_cell_fetch_recorded_)
    return;

  calendar_metrics::RecordTimeToSeeTodaysEventDots(
      base::TimeTicks::Now() - calendar_open_time_,
      /*multi_calendar_enabled=*/calendar_utils::IsMultiCalendarEnabled());
  todays_date_cell_fetch_recorded_ = true;
}

void CalendarViewController::EventsDisplayedToUser() {
  // Only record this once per lifetime of the `CalendarView` (and therefore the
  // controller).
  if (events_shown_to_user_recorded_) {
    return;
  }

  calendar_metrics::RecordEventsDisplayedToUser();
  events_shown_to_user_recorded_ = true;
}

bool CalendarViewController::IsSelectedDateInCurrentMonth() {
  if (!selected_date_.has_value())
    return false;

  return calendar_utils::GetMonthNameAndYear(currently_shown_date_) ==
         calendar_utils::GetMonthNameAndYear(selected_date_.value());
}

base::Time CalendarViewController::GetOnScreenMonthFirstDayUTC() {
  base::TimeDelta time_difference =
      calendar_utils::GetTimeDifference(currently_shown_date_);
  return calendar_utils::GetFirstDayOfMonth(currently_shown_date_ +
                                            time_difference) -
         time_difference;
}

bool CalendarViewController::IsSuccessfullyFetched(base::Time start_of_month) {
  auto fetch_status =
      Shell::Get()->system_tray_model()->calendar_model()->FindFetchingStatus(
          start_of_month);
  return fetch_status == CalendarModel::kSuccess ||
         fetch_status == CalendarModel::kRefetching;
}

base::Time CalendarViewController::ApplyTimeDifference(base::Time date) {
  return date + calendar_utils::GetTimeDifference(date);
}

}  // namespace ash