1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/download/public/common/auto_resumption_handler.h"
6 
7 #include <vector>
8 
9 #include "base/bind.h"
10 #include "base/metrics/field_trial_params.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "components/download/public/task/task_scheduler.h"
14 #include "url/gurl.h"
15 
16 namespace {
17 
18 static download::AutoResumptionHandler* g_auto_resumption_handler = nullptr;
19 
20 // The delay to wait for after a chrome restart before resuming all pending
21 // downloads so that tab loading doesn't get impacted.
22 const base::TimeDelta kAutoResumeStartupDelay =
23     base::TimeDelta::FromSeconds(10);
24 
25 // The interval at which various download updates are grouped together for
26 // computing the params for the task scheduler.
27 const base::TimeDelta kBatchDownloadUpdatesInterval =
28     base::TimeDelta::FromSeconds(1);
29 
30 // The delay to wait for before immediately retrying a download after it got
31 // interrupted due to network reasons.
32 const base::TimeDelta kDownloadImmediateRetryDelay =
33     base::TimeDelta::FromSeconds(1);
34 
35 // The task type to use for scheduling a task.
36 const download::DownloadTaskType kResumptionTaskType =
37     download::DownloadTaskType::DOWNLOAD_AUTO_RESUMPTION_TASK;
38 
39 // The window start time after which the system should fire the task.
40 const int64_t kWindowStartTimeSeconds = 0;
41 
42 // The window end time before which the system should fire the task.
43 const int64_t kWindowEndTimeSeconds = 24 * 60 * 60;
44 
IsMetered(network::mojom::ConnectionType type)45 bool IsMetered(network::mojom::ConnectionType type) {
46   switch (type) {
47     case network::mojom::ConnectionType::CONNECTION_2G:
48     case network::mojom::ConnectionType::CONNECTION_3G:
49     case network::mojom::ConnectionType::CONNECTION_4G:
50       return true;
51     case network::mojom::ConnectionType::CONNECTION_ETHERNET:
52     case network::mojom::ConnectionType::CONNECTION_WIFI:
53     case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
54     case network::mojom::ConnectionType::CONNECTION_NONE:
55     case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
56       return false;
57   }
58   NOTREACHED();
59   return false;
60 }
61 
IsConnected(network::mojom::ConnectionType type)62 bool IsConnected(network::mojom::ConnectionType type) {
63   switch (type) {
64     case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
65     case network::mojom::ConnectionType::CONNECTION_NONE:
66     case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
67       return false;
68     default:
69       return true;
70   }
71 }
72 
73 }  // namespace
74 
75 namespace download {
76 
Config()77 AutoResumptionHandler::Config::Config()
78     : auto_resumption_size_limit(0),
79       is_auto_resumption_enabled_in_native(false) {}
80 
81 // static
Create(std::unique_ptr<download::NetworkStatusListener> network_listener,std::unique_ptr<download::TaskManager> task_manager,std::unique_ptr<Config> config)82 void AutoResumptionHandler::Create(
83     std::unique_ptr<download::NetworkStatusListener> network_listener,
84     std::unique_ptr<download::TaskManager> task_manager,
85     std::unique_ptr<Config> config) {
86   DCHECK(!g_auto_resumption_handler);
87   g_auto_resumption_handler = new AutoResumptionHandler(
88       std::move(network_listener), std::move(task_manager), std::move(config));
89 }
90 
91 // static
Get()92 AutoResumptionHandler* AutoResumptionHandler::Get() {
93   return g_auto_resumption_handler;
94 }
95 
AutoResumptionHandler(std::unique_ptr<download::NetworkStatusListener> network_listener,std::unique_ptr<download::TaskManager> task_manager,std::unique_ptr<Config> config)96 AutoResumptionHandler::AutoResumptionHandler(
97     std::unique_ptr<download::NetworkStatusListener> network_listener,
98     std::unique_ptr<download::TaskManager> task_manager,
99     std::unique_ptr<Config> config)
100     : network_listener_(std::move(network_listener)),
101       task_manager_(std::move(task_manager)),
102       config_(std::move(config)) {
103   network_listener_->Start(this);
104 }
105 
~AutoResumptionHandler()106 AutoResumptionHandler::~AutoResumptionHandler() {
107   network_listener_->Stop();
108 }
109 
SetResumableDownloads(const std::vector<download::DownloadItem * > & downloads)110 void AutoResumptionHandler::SetResumableDownloads(
111     const std::vector<download::DownloadItem*>& downloads) {
112   resumable_downloads_.clear();
113   for (auto* download : downloads) {
114     if (!IsAutoResumableDownload(download))
115       continue;
116     resumable_downloads_.insert(std::make_pair(download->GetGuid(), download));
117     download->RemoveObserver(this);
118     download->AddObserver(this);
119   }
120 
121   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
122       FROM_HERE,
123       base::BindOnce(&AutoResumptionHandler::ResumePendingDownloads,
124                      weak_factory_.GetWeakPtr()),
125       kAutoResumeStartupDelay);
126 }
127 
IsActiveNetworkMetered() const128 bool AutoResumptionHandler::IsActiveNetworkMetered() const {
129   return IsMetered(network_listener_->GetConnectionType());
130 }
131 
OnNetworkChanged(network::mojom::ConnectionType type)132 void AutoResumptionHandler::OnNetworkChanged(
133     network::mojom::ConnectionType type) {
134   if (!IsConnected(type))
135     return;
136 
137   ResumePendingDownloads();
138 }
139 
OnDownloadStarted(download::DownloadItem * item)140 void AutoResumptionHandler::OnDownloadStarted(download::DownloadItem* item) {
141   item->RemoveObserver(this);
142   item->AddObserver(this);
143 
144   OnDownloadUpdated(item);
145 }
146 
OnDownloadUpdated(download::DownloadItem * item)147 void AutoResumptionHandler::OnDownloadUpdated(download::DownloadItem* item) {
148   if (IsAutoResumableDownload(item))
149     resumable_downloads_[item->GetGuid()] = item;
150   else
151     resumable_downloads_.erase(item->GetGuid());
152 
153   if (item->GetState() == download::DownloadItem::INTERRUPTED &&
154       IsAutoResumableDownload(item) && SatisfiesNetworkRequirements(item)) {
155     downloads_to_retry_.insert(item);
156     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
157         FROM_HERE,
158         base::BindOnce(&AutoResumptionHandler::ResumeDownloadImmediately,
159                        weak_factory_.GetWeakPtr()),
160         kDownloadImmediateRetryDelay);
161     return;
162   }
163   RecomputeTaskParams();
164 }
165 
OnDownloadRemoved(download::DownloadItem * item)166 void AutoResumptionHandler::OnDownloadRemoved(download::DownloadItem* item) {
167   resumable_downloads_.erase(item->GetGuid());
168   RecomputeTaskParams();
169 }
170 
OnDownloadDestroyed(download::DownloadItem * item)171 void AutoResumptionHandler::OnDownloadDestroyed(download::DownloadItem* item) {
172   resumable_downloads_.erase(item->GetGuid());
173   downloads_to_retry_.erase(item);
174 }
175 
ResumeDownloadImmediately()176 void AutoResumptionHandler::ResumeDownloadImmediately() {
177   for (auto* download : std::move(downloads_to_retry_)) {
178     if (SatisfiesNetworkRequirements(download))
179       download->Resume(false);
180     else
181       RecomputeTaskParams();
182   }
183   downloads_to_retry_.clear();
184 }
185 
OnStartScheduledTask(download::TaskFinishedCallback callback)186 void AutoResumptionHandler::OnStartScheduledTask(
187     download::TaskFinishedCallback callback) {
188   task_manager_->OnStartScheduledTask(kResumptionTaskType, std::move(callback));
189   ResumePendingDownloads();
190 }
191 
OnStopScheduledTask()192 bool AutoResumptionHandler::OnStopScheduledTask() {
193   task_manager_->OnStopScheduledTask(kResumptionTaskType);
194   RescheduleTaskIfNecessary();
195   return false;
196 }
197 
RecomputeTaskParams()198 void AutoResumptionHandler::RecomputeTaskParams() {
199   if (recompute_task_params_scheduled_)
200     return;
201 
202   recompute_task_params_scheduled_ = true;
203   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
204       FROM_HERE,
205       base::BindOnce(&AutoResumptionHandler::RescheduleTaskIfNecessary,
206                      weak_factory_.GetWeakPtr()),
207       kBatchDownloadUpdatesInterval);
208 }
209 
RescheduleTaskIfNecessary()210 void AutoResumptionHandler::RescheduleTaskIfNecessary() {
211   if (!config_->is_auto_resumption_enabled_in_native)
212     return;
213 
214   recompute_task_params_scheduled_ = false;
215 
216   bool has_resumable_downloads = false;
217   bool has_actionable_downloads = false;
218   bool can_download_on_metered = false;
219   for (auto iter = resumable_downloads_.begin();
220        iter != resumable_downloads_.end(); ++iter) {
221     download::DownloadItem* download = iter->second;
222     if (!IsAutoResumableDownload(download))
223       continue;
224 
225     has_resumable_downloads = true;
226     has_actionable_downloads |= SatisfiesNetworkRequirements(download);
227     can_download_on_metered |= download->AllowMetered();
228     if (can_download_on_metered)
229       break;
230   }
231 
232   if (!has_actionable_downloads)
233     task_manager_->NotifyTaskFinished(kResumptionTaskType, false);
234 
235   if (!has_resumable_downloads) {
236     task_manager_->UnscheduleTask(kResumptionTaskType);
237     return;
238   }
239 
240   download::TaskManager::TaskParams task_params;
241   task_params.require_unmetered_network = !can_download_on_metered;
242   task_params.window_start_time_seconds = kWindowStartTimeSeconds;
243   task_params.window_end_time_seconds = kWindowEndTimeSeconds;
244   task_manager_->ScheduleTask(kResumptionTaskType, task_params);
245 }
246 
ResumePendingDownloads()247 void AutoResumptionHandler::ResumePendingDownloads() {
248   if (!config_->is_auto_resumption_enabled_in_native)
249     return;
250 
251   for (auto iter = resumable_downloads_.begin();
252        iter != resumable_downloads_.end(); ++iter) {
253     download::DownloadItem* download = iter->second;
254     if (!IsAutoResumableDownload(download))
255       continue;
256 
257     if (SatisfiesNetworkRequirements(download))
258       download->Resume(false);
259   }
260 }
261 
SatisfiesNetworkRequirements(download::DownloadItem * download)262 bool AutoResumptionHandler::SatisfiesNetworkRequirements(
263     download::DownloadItem* download) {
264   if (!IsConnected(network_listener_->GetConnectionType()))
265     return false;
266 
267   return download->AllowMetered() || !IsActiveNetworkMetered();
268 }
269 
IsAutoResumableDownload(download::DownloadItem * item)270 bool AutoResumptionHandler::IsAutoResumableDownload(
271     download::DownloadItem* item) {
272   if (!item || item->IsDangerous())
273     return false;
274 
275   switch (item->GetState()) {
276     case download::DownloadItem::IN_PROGRESS:
277       return !item->IsPaused();
278     case download::DownloadItem::COMPLETE:
279     case download::DownloadItem::CANCELLED:
280       return false;
281     case download::DownloadItem::INTERRUPTED:
282       return !item->IsPaused() &&
283              IsInterruptedDownloadAutoResumable(
284                  item, config_->auto_resumption_size_limit);
285     case download::DownloadItem::MAX_DOWNLOAD_STATE:
286       NOTREACHED();
287   }
288 
289   return false;
290 }
291 
292 // static
IsInterruptedDownloadAutoResumable(download::DownloadItem * download_item,int auto_resumption_size_limit)293 bool AutoResumptionHandler::IsInterruptedDownloadAutoResumable(
294     download::DownloadItem* download_item,
295     int auto_resumption_size_limit) {
296   DCHECK_EQ(download::DownloadItem::INTERRUPTED, download_item->GetState());
297   if (download_item->IsDangerous())
298     return false;
299 
300   if (!download_item->GetURL().SchemeIsHTTPOrHTTPS())
301     return false;
302 
303   if (download_item->GetBytesWasted() > auto_resumption_size_limit)
304     return false;
305 
306   int interrupt_reason = download_item->GetLastReason();
307   DCHECK_NE(interrupt_reason, download::DOWNLOAD_INTERRUPT_REASON_NONE);
308   return interrupt_reason ==
309              download::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT ||
310          interrupt_reason ==
311              download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED ||
312          interrupt_reason ==
313              download::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED ||
314          interrupt_reason == download::DOWNLOAD_INTERRUPT_REASON_CRASH;
315 }
316 
317 }  // namespace download
318