chromium/ash/birch/birch_weather_provider.cc

// Copyright 2024 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/birch/birch_weather_provider.h"

#include <string>

#include "ash/ambient/ambient_controller.h"
#include "ash/birch/birch_item.h"
#include "ash/birch/birch_model.h"
#include "ash/birch/birch_ranker.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/weather_info.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "chromeos/ash/components/geolocation/simple_geolocation_provider.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_names.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace ash {

BirchWeatherProvider::BirchWeatherProvider(BirchModel* birch_model)
    : birch_model_(birch_model) {}

BirchWeatherProvider::~BirchWeatherProvider() = default;

void BirchWeatherProvider::RequestBirchDataFetch() {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kDisableBirchWeatherApiForTesting) &&
      !base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kEnableBirchWeatherApiForTestingOverride)) {
    // Avoid calling into the Weather API when the switch is set for testing.
    Shell::Get()->birch_model()->SetWeatherItems({});
    return;
  }
  const auto* pref_service =
      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  if (!pref_service ||
      !base::Contains(pref_service->GetList(
                          prefs::kContextualGoogleIntegrationsConfiguration),
                      prefs::kWeatherIntegrationName)) {
    // Weather integration is disabled by policy.
    Shell::Get()->birch_model()->SetWeatherItems({});
    return;
  }
  if (!SimpleGeolocationProvider::GetInstance()
           ->IsGeolocationUsageAllowedForSystem()) {
    // Weather is not allowed if geolocation is off.
    birch_model_->SetWeatherItems({});
    return;
  }
  const UserSession* session =
      Shell::Get()->session_controller()->GetUserSession(0);
  if (session->user_info.account_id == user_manager::StubAccountId()) {
    // Weather is not allowed for stub users, which don't have valid Gaia IDs.
    birch_model_->SetWeatherItems({});
    return;
  }
  // The ranker only shows weather in the mornings, so only fetch the data in
  // the mornings to limit QPS on the backend.
  BirchRanker ranker(base::Time::Now());
  if (!ranker.IsMorning()) {
    birch_model_->SetWeatherItems({});
    return;
  }

  // Use the cache if it has data and the last fetch was recent.
  if (last_weather_info_.has_value() &&
      base::Time::Now() < last_fetch_time_ + base::Minutes(5)) {
    OnWeatherInfoFetched(last_weather_info_);
    return;
  }

  // Only allow one fetch at a time.
  if (is_fetching_) {
    return;
  }
  is_fetching_ = true;

  if (!birch_model_->birch_client()) {
    // BirchClient may be null in tests.
    FetchWeather();
    return;
  }
  // Fetching weather requires auth, but early in startup refresh tokens may not
  // be loaded yet. Ensure refresh tokens are loaded before doing the fetch.
  birch_model_->birch_client()->WaitForRefreshTokens(base::BindOnce(
      &BirchWeatherProvider::FetchWeather, weak_factory_.GetWeakPtr()));
}

void BirchWeatherProvider::FetchWeather() {
  // Use the prod endpoint by default. This results in the alpha server being
  // used for canary/dev channel and the prod server being used for beta/stable.
  const bool prefer_prod_endpoint = base::GetFieldTrialParamByFeatureAsBool(
      features::kBirchWeather, "prod_weather_endpoint", true);
  Shell::Get()
      ->ambient_controller()
      ->ambient_backend_controller()
      ->FetchWeather("chromeos-system-ui",
                     /*prefer_alpha_endpoint=*/!prefer_prod_endpoint,
                     base::BindOnce(&BirchWeatherProvider::OnWeatherInfoFetched,
                                    weak_factory_.GetWeakPtr()));
}

void BirchWeatherProvider::OnWeatherInfoFetched(
    const std::optional<WeatherInfo>& weather_info) {
  is_fetching_ = false;

  // Check for partial data.
  if (!weather_info || !weather_info->temp_f.has_value() ||
      !weather_info->condition_icon_url ||
      !weather_info->condition_description ||
      weather_info->condition_icon_url->empty()) {
    last_weather_info_.reset();
    birch_model_->SetWeatherItems({});
    return;
  }

  // Cache for future requests.
  last_weather_info_ = weather_info;
  last_fetch_time_ = base::Time::Now();

  // Add the item to the model. Note that we ignore "show_celsius" in favor
  // of a client-side pref for temperature units.
  AddItemToBirchModel(base::UTF8ToUTF16(*weather_info->condition_description),
                      *weather_info->temp_f, *weather_info->condition_icon_url);
}

void BirchWeatherProvider::AddItemToBirchModel(
    const std::u16string& weather_description,
    float temp_f,
    const std::string& icon_url) {
  std::vector<BirchWeatherItem> items;
  items.emplace_back(weather_description, temp_f, GURL(icon_url));
  birch_model_->SetWeatherItems(std::move(items));
}

void BirchWeatherProvider::ResetCacheForTest() {
  last_weather_info_.reset();
  last_fetch_time_ = base::Time();
}

}  // namespace ash