blob: eca40b4bfb17729fb4d30545f8b1bb6366333873 [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 "components/download/public/common/auto_resumption_handler.h"
#include <vector>
#include "base/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/download/public/task/task_scheduler.h"
#include "url/gurl.h"
namespace {
static download::AutoResumptionHandler* g_auto_resumption_handler = nullptr;
// The delay to wait for after a chrome restart before resuming all pending
// downloads so that tab loading doesn't get impacted.
const base::TimeDelta kAutoResumeStartupDelay =
base::TimeDelta::FromSeconds(10);
// The interval at which various download updates are grouped together for
// computing the params for the task scheduler.
const base::TimeDelta kBatchDownloadUpdatesInterval =
base::TimeDelta::FromSeconds(1);
// The delay to wait for before immediately retrying a download after it got
// interrupted due to network reasons.
const base::TimeDelta kDownloadImmediateRetryDelay =
base::TimeDelta::FromSeconds(1);
// The task type to use for scheduling a task.
const download::DownloadTaskType kResumptionTaskType =
download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK;
// The window start time after which the system should fire the task.
const int64_t kWindowStartTimeSeconds = 0;
// The window end time before which the system should fire the task.
const int64_t kWindowEndTimeSeconds = 24 * 60 * 60;
bool IsMetered(network::mojom::ConnectionType type) {
switch (type) {
case network::mojom::ConnectionType::CONNECTION_2G:
case network::mojom::ConnectionType::CONNECTION_3G:
case network::mojom::ConnectionType::CONNECTION_4G:
return true;
case network::mojom::ConnectionType::CONNECTION_ETHERNET:
case network::mojom::ConnectionType::CONNECTION_WIFI:
case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
case network::mojom::ConnectionType::CONNECTION_NONE:
case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
return false;
}
NOTREACHED();
return false;
}
bool IsConnected(network::mojom::ConnectionType type) {
switch (type) {
case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
case network::mojom::ConnectionType::CONNECTION_NONE:
case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
return false;
default:
return true;
}
}
bool IsInterruptedDownloadAutoResumable(download::DownloadItem* download_item,
int auto_resumption_size_limit) {
if (!download_item->GetURL().SchemeIsHTTPOrHTTPS())
return false;
if (download_item->GetBytesWasted() > auto_resumption_size_limit)
return false;
int interrupt_reason = download_item->GetLastReason();
DCHECK_NE(interrupt_reason, download::DOWNLOAD_INTERRUPT_REASON_NONE);
return interrupt_reason ==
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT ||
interrupt_reason ==
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED ||
interrupt_reason ==
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED ||
interrupt_reason == download::DOWNLOAD_INTERRUPT_REASON_CRASH;
}
} // namespace
namespace download {
AutoResumptionHandler::Config::Config()
: auto_resumption_size_limit(0),
is_auto_resumption_enabled_in_native(false) {}
// static
void AutoResumptionHandler::Create(
std::unique_ptr<download::NetworkStatusListener> network_listener,
std::unique_ptr<download::TaskManager> task_manager,
std::unique_ptr<Config> config) {
DCHECK(!g_auto_resumption_handler);
g_auto_resumption_handler = new AutoResumptionHandler(
std::move(network_listener), std::move(task_manager), std::move(config));
}
// static
AutoResumptionHandler* AutoResumptionHandler::Get() {
return g_auto_resumption_handler;
}
AutoResumptionHandler::AutoResumptionHandler(
std::unique_ptr<download::NetworkStatusListener> network_listener,
std::unique_ptr<download::TaskManager> task_manager,
std::unique_ptr<Config> config)
: network_listener_(std::move(network_listener)),
task_manager_(std::move(task_manager)),
config_(std::move(config)),
weak_factory_(this) {
network_listener_->Start(this);
}
AutoResumptionHandler::~AutoResumptionHandler() {
network_listener_->Stop();
}
void AutoResumptionHandler::SetResumableDownloads(
const std::vector<download::DownloadItem*>& downloads) {
resumable_downloads_.clear();
for (auto* download : downloads) {
if (!IsAutoResumableDownload(download))
continue;
resumable_downloads_.insert(std::make_pair(download->GetGuid(), download));
download->RemoveObserver(this);
download->AddObserver(this);
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AutoResumptionHandler::ResumePendingDownloads,
weak_factory_.GetWeakPtr()),
kAutoResumeStartupDelay);
}
bool AutoResumptionHandler::IsActiveNetworkMetered() const {
return IsMetered(network_listener_->GetConnectionType());
}
void AutoResumptionHandler::OnNetworkChanged(
network::mojom::ConnectionType type) {
if (!IsConnected(type))
return;
ResumePendingDownloads();
}
void AutoResumptionHandler::OnDownloadStarted(download::DownloadItem* item) {
item->RemoveObserver(this);
item->AddObserver(this);
OnDownloadUpdated(item);
}
void AutoResumptionHandler::OnDownloadUpdated(download::DownloadItem* item) {
if (IsAutoResumableDownload(item))
resumable_downloads_[item->GetGuid()] = item;
else
resumable_downloads_.erase(item->GetGuid());
if (item->GetState() == download::DownloadItem::INTERRUPTED &&
IsAutoResumableDownload(item) && SatisfiesNetworkRequirements(item)) {
downloads_to_retry_.insert(item);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AutoResumptionHandler::ResumeDownloadImmediately,
weak_factory_.GetWeakPtr()),
kDownloadImmediateRetryDelay);
return;
}
RecomputeTaskParams();
}
void AutoResumptionHandler::OnDownloadRemoved(download::DownloadItem* item) {
resumable_downloads_.erase(item->GetGuid());
RecomputeTaskParams();
}
void AutoResumptionHandler::OnDownloadDestroyed(download::DownloadItem* item) {
downloads_to_retry_.erase(item);
}
void AutoResumptionHandler::ResumeDownloadImmediately() {
for (auto* download : std::move(downloads_to_retry_)) {
if (SatisfiesNetworkRequirements(download))
download->Resume(false);
else
RecomputeTaskParams();
}
downloads_to_retry_.clear();
}
void AutoResumptionHandler::OnStartScheduledTask(
download::TaskFinishedCallback callback) {
task_manager_->OnStartScheduledTask(kResumptionTaskType, std::move(callback));
ResumePendingDownloads();
}
bool AutoResumptionHandler::OnStopScheduledTask() {
task_manager_->OnStopScheduledTask(kResumptionTaskType);
RescheduleTaskIfNecessary();
return false;
}
void AutoResumptionHandler::RecomputeTaskParams() {
if (recompute_task_params_scheduled_)
return;
recompute_task_params_scheduled_ = true;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AutoResumptionHandler::RescheduleTaskIfNecessary,
weak_factory_.GetWeakPtr()),
kBatchDownloadUpdatesInterval);
}
void AutoResumptionHandler::RescheduleTaskIfNecessary() {
if (!config_->is_auto_resumption_enabled_in_native)
return;
recompute_task_params_scheduled_ = false;
bool has_resumable_downloads = false;
bool has_actionable_downloads = false;
bool can_download_on_metered = false;
for (auto iter = resumable_downloads_.begin();
iter != resumable_downloads_.end(); ++iter) {
download::DownloadItem* download = iter->second;
if (!IsAutoResumableDownload(download))
continue;
has_resumable_downloads = true;
has_actionable_downloads |= SatisfiesNetworkRequirements(download);
can_download_on_metered |= download->AllowMetered();
if (can_download_on_metered)
break;
}
if (!has_actionable_downloads)
task_manager_->NotifyTaskFinished(kResumptionTaskType, false);
if (!has_resumable_downloads) {
task_manager_->UnscheduleTask(kResumptionTaskType);
return;
}
download::TaskManager::TaskParams task_params;
task_params.require_unmetered_network = !can_download_on_metered;
task_params.window_start_time_seconds = kWindowStartTimeSeconds;
task_params.window_end_time_seconds = kWindowEndTimeSeconds;
task_manager_->ScheduleTask(kResumptionTaskType, task_params);
}
void AutoResumptionHandler::ResumePendingDownloads() {
if (!config_->is_auto_resumption_enabled_in_native)
return;
for (auto iter = resumable_downloads_.begin();
iter != resumable_downloads_.end(); ++iter) {
download::DownloadItem* download = iter->second;
if (!IsAutoResumableDownload(download))
continue;
if (SatisfiesNetworkRequirements(download))
download->Resume(false);
}
}
bool AutoResumptionHandler::SatisfiesNetworkRequirements(
download::DownloadItem* download) {
if (!IsConnected(network_listener_->GetConnectionType()))
return false;
return download->AllowMetered() || !IsActiveNetworkMetered();
}
bool AutoResumptionHandler::IsAutoResumableDownload(
download::DownloadItem* item) {
if (item->IsDangerous())
return false;
switch (item->GetState()) {
case download::DownloadItem::IN_PROGRESS:
return !item->IsPaused();
case download::DownloadItem::COMPLETE:
case download::DownloadItem::CANCELLED:
return false;
case download::DownloadItem::INTERRUPTED:
return !item->IsPaused() &&
IsInterruptedDownloadAutoResumable(
item, config_->auto_resumption_size_limit);
case download::DownloadItem::MAX_DOWNLOAD_STATE:
NOTREACHED();
}
return false;
}
} // namespace download