blob: f8f078e42e2b3ef1d606723692b4fcecd479ee3e [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/chromeos/night_light/night_light_client.h"
#include <algorithm>
#include "ash/public/interfaces/constants.mojom.h"
#include "base/logging.h"
#include "base/time/clock.h"
#include "content/public/common/service_manager_connection.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/service_manager/public/cpp/connector.h"
namespace {
// Delay to wait for a response to our geolocation request, if we get a response
// after which, we will consider the request a failure.
constexpr base::TimeDelta kGeolocationRequestTimeout =
base::TimeDelta::FromSeconds(60);
// Minimum delay to wait to fire a new request after a previous one failing.
constexpr base::TimeDelta kMinimumDelayAfterFailure =
base::TimeDelta::FromSeconds(60);
// Delay to wait to fire a new request after a previous one succeeding.
constexpr base::TimeDelta kNextRequestDelayAfterSuccess =
base::TimeDelta::FromDays(1);
} // namespace
NightLightClient::NightLightClient(
scoped_refptr<network::SharedURLLoaderFactory> factory)
: provider_(
std::move(factory),
chromeos::SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
binding_(this),
backoff_delay_(kMinimumDelayAfterFailure),
timer_(std::make_unique<base::OneShotTimer>()) {}
NightLightClient::~NightLightClient() {}
void NightLightClient::Start() {
if (!night_light_controller_) {
service_manager::Connector* connector =
content::ServiceManagerConnection::GetForProcess()->GetConnector();
connector->BindInterface(ash::mojom::kServiceName,
&night_light_controller_);
}
ash::mojom::NightLightClientPtr client;
binding_.Bind(mojo::MakeRequest(&client));
night_light_controller_->SetClient(std::move(client));
}
void NightLightClient::OnScheduleTypeChanged(
ash::mojom::NightLightController::ScheduleType new_type) {
if (new_type !=
ash::mojom::NightLightController::ScheduleType::kSunsetToSunrise) {
using_geoposition_ = false;
timer_->Stop();
return;
}
using_geoposition_ = true;
// No need to request a new position if we already have a valid one from a
// request less than kNextRequestDelayAfterSuccess ago.
base::Time now = GetNow();
if ((now - last_successful_geo_request_time_) <
kNextRequestDelayAfterSuccess) {
VLOG(1) << "Already has a recent valid geoposition. Using it instead of "
<< "requesting a new one.";
// Use the current valid position to update NightLightController.
SendCurrentGeoposition();
}
// Next request is either immediate or kNextRequestDelayAfterSuccess later
// than the last success time, whichever is greater.
ScheduleNextRequest(std::max(
base::TimeDelta::FromSeconds(0),
last_successful_geo_request_time_ + kNextRequestDelayAfterSuccess - now));
}
// static
base::TimeDelta NightLightClient::GetNextRequestDelayAfterSuccessForTesting() {
return kNextRequestDelayAfterSuccess;
}
void NightLightClient::SetNightLightControllerPtrForTesting(
ash::mojom::NightLightControllerPtr controller) {
night_light_controller_ = std::move(controller);
}
void NightLightClient::FlushNightLightControllerForTesting() {
night_light_controller_.FlushForTesting();
}
void NightLightClient::SetTimerForTesting(
std::unique_ptr<base::OneShotTimer> timer) {
timer_ = std::move(timer);
}
void NightLightClient::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}
void NightLightClient::OnGeoposition(const chromeos::Geoposition& position,
bool server_error,
const base::TimeDelta elapsed) {
if (!using_geoposition_) {
// A response might arrive after the schedule type is no longer "sunset to
// sunrise", which means we should not push any positions to the
// NightLightController.
return;
}
if (server_error || !position.Valid() ||
elapsed > kGeolocationRequestTimeout) {
VLOG(1) << "Failed to get a valid geoposition. Trying again later.";
// Don't send invalid positions to ash.
// On failure, we schedule another request after the current backoff delay.
ScheduleNextRequest(backoff_delay_);
// If another failure occurs next, our backoff delay should double.
backoff_delay_ *= 2;
return;
}
last_successful_geo_request_time_ = GetNow();
latitude_ = position.latitude;
longitude_ = position.longitude;
SendCurrentGeoposition();
// On success, reset the backoff delay to its minimum value, and schedule
// another request.
backoff_delay_ = kMinimumDelayAfterFailure;
ScheduleNextRequest(kNextRequestDelayAfterSuccess);
}
base::Time NightLightClient::GetNow() const {
return clock_ ? clock_->Now() : base::Time::Now();
}
void NightLightClient::SendCurrentGeoposition() {
night_light_controller_->SetCurrentGeoposition(
ash::mojom::SimpleGeoposition::New(latitude_, longitude_));
}
void NightLightClient::ScheduleNextRequest(base::TimeDelta delay) {
timer_->Start(FROM_HERE, delay, this, &NightLightClient::RequestGeoposition);
}
void NightLightClient::RequestGeoposition() {
VLOG(1) << "Requesting a new geoposition";
provider_.RequestGeolocation(
kGeolocationRequestTimeout, false /* send_wifi_access_points */,
false /* send_cell_towers */,
base::Bind(&NightLightClient::OnGeoposition, base::Unretained(this)));
}