1 // Copyright (c) 2012 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.
5 #include "content/browser/android/download_controller_android_impl.h"
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_string.h"
10 #include "base/lazy_instance.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/synchronization/lock.h"
14 #include "base/time/time.h"
15 #include "content/browser/android/content_view_core_impl.h"
16 #include "content/browser/android/deferred_download_observer.h"
17 #include "content/browser/download/download_item_impl.h"
18 #include "content/browser/download/download_manager_impl.h"
19 #include "content/browser/loader/resource_dispatcher_host_impl.h"
20 #include "content/browser/renderer_host/render_process_host_impl.h"
21 #include "content/browser/renderer_host/render_view_host_delegate.h"
22 #include "content/browser/renderer_host/render_view_host_impl.h"
23 #include "content/browser/web_contents/web_contents_impl.h"
24 #include "content/public/browser/browser_context.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/download_url_parameters.h"
27 #include "content/public/browser/global_request_id.h"
28 #include "content/public/browser/resource_request_info.h"
29 #include "content/public/common/content_client.h"
30 #include "content/public/common/referrer.h"
31 #include "jni/DownloadController_jni.h"
32 #include "net/cookies/cookie_options.h"
33 #include "net/cookies/cookie_store.h"
34 #include "net/http/http_content_disposition.h"
35 #include "net/http/http_request_headers.h"
36 #include "net/http/http_response_headers.h"
37 #include "net/url_request/url_request.h"
38 #include "net/url_request/url_request_context.h"
40 using base::android::ConvertUTF8ToJavaString
;
41 using base::android::ScopedJavaLocalRef
;
44 // Guards download_controller_
45 base::LazyInstance
<base::Lock
> g_download_controller_lock_
;
47 content::WebContents
* GetWebContents(int render_process_id
,
49 content::RenderViewHost
* render_view_host
=
50 content::RenderViewHost::FromID(render_process_id
, render_view_id
);
52 if (!render_view_host
)
55 return content::WebContents::FromRenderViewHost(render_view_host
);
58 void CreateContextMenuDownload(int render_process_id
,
60 const content::ContextMenuParams
& params
,
62 const std::string
& extra_headers
,
67 content::WebContents
* web_contents
=
68 GetWebContents(render_process_id
, render_view_id
);
72 const GURL
& url
= is_link
? params
.link_url
: params
.src_url
;
73 const GURL
& referring_url
=
74 params
.frame_url
.is_empty() ? params
.page_url
: params
.frame_url
;
75 content::DownloadManagerImpl
* dlm
=
76 static_cast<content::DownloadManagerImpl
*>(
77 content::BrowserContext::GetDownloadManager(
78 web_contents
->GetBrowserContext()));
79 scoped_ptr
<content::DownloadUrlParameters
> dl_params(
80 content::DownloadUrlParameters::FromWebContents(web_contents
, url
));
81 content::Referrer referrer
= content::Referrer::SanitizeForRequest(
83 content::Referrer(referring_url
.GetAsReferrer(), params
.referrer_policy
));
84 dl_params
->set_referrer(referrer
);
86 dl_params
->set_referrer_encoding(params
.frame_charset
);
87 net::HttpRequestHeaders headers
;
88 headers
.AddHeadersFromString(extra_headers
);
89 for (net::HttpRequestHeaders::Iterator
it(headers
); it
.GetNext();)
90 dl_params
->add_request_header(it
.name(), it
.value());
91 if (!is_link
&& extra_headers
.empty())
92 dl_params
->set_prefer_cache(true);
93 dl_params
->set_prompt(false);
94 dlm
->DownloadUrl(dl_params
.Pass());
102 static void Init(JNIEnv
* env
, const JavaParamRef
<jobject
>& obj
) {
103 DownloadControllerAndroidImpl::GetInstance()->Init(env
, obj
);
106 static void OnRequestFileAccessResult(JNIEnv
* env
,
107 const JavaParamRef
<jobject
>& obj
,
110 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
113 // Convert java long long int to c++ pointer, take ownership.
114 scoped_ptr
<DownloadControllerAndroid::AcquireFileAccessPermissionCallback
> cb(
116 DownloadControllerAndroid::AcquireFileAccessPermissionCallback
*>(
121 struct DownloadControllerAndroidImpl::JavaObject
{
122 ScopedJavaLocalRef
<jobject
> Controller(JNIEnv
* env
) {
123 return GetRealObject(env
, obj
);
129 bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv
* env
) {
130 return RegisterNativesImpl(env
);
134 DownloadControllerAndroid
* DownloadControllerAndroid::Get() {
135 base::AutoLock
lock(g_download_controller_lock_
.Get());
136 if (!DownloadControllerAndroid::download_controller_
)
137 download_controller_
= DownloadControllerAndroidImpl::GetInstance();
138 return DownloadControllerAndroid::download_controller_
;
142 void DownloadControllerAndroid::SetDownloadControllerAndroid(
143 DownloadControllerAndroid
* download_controller
) {
144 base::AutoLock
lock(g_download_controller_lock_
.Get());
145 DownloadControllerAndroid::download_controller_
= download_controller
;
149 DownloadControllerAndroidImpl
* DownloadControllerAndroidImpl::GetInstance() {
150 return base::Singleton
<DownloadControllerAndroidImpl
>::get();
153 DownloadControllerAndroidImpl::DownloadControllerAndroidImpl()
154 : java_object_(NULL
) {
157 DownloadControllerAndroidImpl::~DownloadControllerAndroidImpl() {
159 JNIEnv
* env
= base::android::AttachCurrentThread();
160 env
->DeleteWeakGlobalRef(java_object_
->obj
);
162 base::android::CheckException(env
);
166 // Initialize references to Java object.
167 void DownloadControllerAndroidImpl::Init(JNIEnv
* env
, jobject obj
) {
168 java_object_
= new JavaObject
;
169 java_object_
->obj
= env
->NewWeakGlobalRef(obj
);
172 void DownloadControllerAndroidImpl::CancelDeferredDownload(
173 DeferredDownloadObserver
* observer
) {
174 for (auto iter
= deferred_downloads_
.begin();
175 iter
!= deferred_downloads_
.end(); ++iter
) {
176 if (*iter
== observer
) {
177 deferred_downloads_
.erase(iter
);
183 void DownloadControllerAndroidImpl::AcquireFileAccessPermission(
184 int render_process_id
,
186 const DownloadControllerAndroid::AcquireFileAccessPermissionCallback
& cb
) {
187 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
188 WebContents
* web_contents
= GetWebContents(render_process_id
, render_view_id
);
190 BrowserThread::PostTask(
191 BrowserThread::UI
, FROM_HERE
, base::Bind(cb
, false));
195 ScopedJavaLocalRef
<jobject
> view
=
196 GetContentViewCoreFromWebContents(web_contents
);
197 if (view
.is_null()) {
198 BrowserThread::PostTask(
199 BrowserThread::UI
, FROM_HERE
, base::Bind(cb
, false));
203 if (HasFileAccessPermission(view
)) {
204 BrowserThread::PostTask(
205 BrowserThread::UI
, FROM_HERE
, base::Bind(cb
, true));
209 JNIEnv
* env
= base::android::AttachCurrentThread();
210 // Make copy on the heap so we can pass the pointer through JNI.
211 intptr_t callback_id
= reinterpret_cast<intptr_t>(
212 new DownloadControllerAndroid::AcquireFileAccessPermissionCallback(cb
));
213 Java_DownloadController_requestFileAccess(
214 env
, GetJavaObject()->Controller(env
).obj(), view
.obj(), callback_id
);
217 bool DownloadControllerAndroidImpl::HasFileAccessPermission(
218 ScopedJavaLocalRef
<jobject
> j_content_view_core
) {
219 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
220 DCHECK(!j_content_view_core
.is_null());
222 JNIEnv
* env
= base::android::AttachCurrentThread();
223 return Java_DownloadController_hasFileAccess(
224 env
, GetJavaObject()->Controller(env
).obj(), j_content_view_core
.obj());
227 void DownloadControllerAndroidImpl::CreateGETDownload(
228 int render_process_id
, int render_view_id
, int request_id
) {
229 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
230 GlobalRequestID
global_id(render_process_id
, request_id
);
232 // We are yielding the UI thread and render_view_host may go away by
233 // the time we come back. Pass along render_process_id and render_view_id
234 // to retrieve it later (if it still exists).
235 GetDownloadInfoCB cb
= base::Bind(
236 &DownloadControllerAndroidImpl::StartAndroidDownload
,
237 base::Unretained(this), render_process_id
,
242 base::Bind(&DownloadControllerAndroidImpl::StartDownloadOnUIThread
,
243 base::Unretained(this), cb
));
246 void DownloadControllerAndroidImpl::PrepareDownloadInfo(
247 const GlobalRequestID
& global_id
,
248 const GetDownloadInfoCB
& callback
) {
249 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
251 net::URLRequest
* request
=
252 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id
);
254 LOG(ERROR
) << "Request to download not found.";
258 DownloadInfoAndroid
info_android(request
);
260 net::CookieStore
* cookie_store
= request
->context()->cookie_store();
262 net::CookieMonster
* cookie_monster
= cookie_store
->GetCookieMonster();
263 if (cookie_monster
) {
264 cookie_monster
->GetAllCookiesForURLAsync(
266 base::Bind(&DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies
,
267 base::Unretained(this), info_android
, callback
,
270 DoLoadCookies(info_android
, callback
, global_id
);
273 // Can't get any cookies, start android download.
274 callback
.Run(info_android
);
278 void DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies(
279 const DownloadInfoAndroid
& info
, const GetDownloadInfoCB
& callback
,
280 const GlobalRequestID
& global_id
, const net::CookieList
& cookie_list
) {
281 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
283 net::URLRequest
* request
=
284 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id
);
286 LOG(ERROR
) << "Request to download not found.";
290 if (request
->context()->network_delegate()->CanGetCookies(
291 *request
, cookie_list
)) {
292 DoLoadCookies(info
, callback
, global_id
);
298 void DownloadControllerAndroidImpl::DoLoadCookies(
299 const DownloadInfoAndroid
& info
, const GetDownloadInfoCB
& callback
,
300 const GlobalRequestID
& global_id
) {
301 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
303 net::CookieOptions options
;
304 options
.set_include_httponly();
306 net::URLRequest
* request
=
307 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id
);
309 LOG(ERROR
) << "Request to download not found.";
313 request
->context()->cookie_store()->GetCookiesWithOptionsAsync(
315 base::Bind(&DownloadControllerAndroidImpl::OnCookieResponse
,
316 base::Unretained(this), info
, callback
));
319 void DownloadControllerAndroidImpl::OnCookieResponse(
320 DownloadInfoAndroid download_info
,
321 const GetDownloadInfoCB
& callback
,
322 const std::string
& cookie
) {
323 download_info
.cookie
= cookie
;
325 // We have everything we need, start Android download.
326 callback
.Run(download_info
);
329 void DownloadControllerAndroidImpl::StartDownloadOnUIThread(
330 const GetDownloadInfoCB
& callback
,
331 const DownloadInfoAndroid
& info
) {
332 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
333 BrowserThread::PostTask(
334 BrowserThread::UI
, FROM_HERE
, base::Bind(callback
, info
));
337 void DownloadControllerAndroidImpl::StartAndroidDownload(
338 int render_process_id
, int render_view_id
,
339 const DownloadInfoAndroid
& info
) {
340 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
342 WebContents
* web_contents
= GetWebContents(render_process_id
, render_view_id
);
344 // The view went away. Can't proceed.
345 LOG(ERROR
) << "Download failed on URL:" << info
.url
.spec();
348 ScopedJavaLocalRef
<jobject
> view
=
349 GetContentViewCoreFromWebContents(web_contents
);
350 if (view
.is_null()) {
351 // ContentViewCore might not have been created yet, pass a callback to
352 // DeferredDownloadTaskManager so that the download can restart when
353 // ContentViewCore is created.
354 deferred_downloads_
.push_back(new DeferredDownloadObserver(
356 base::Bind(&DownloadControllerAndroidImpl::StartAndroidDownload
,
357 base::Unretained(this), render_process_id
, render_view_id
,
362 AcquireFileAccessPermission(
363 render_process_id
, render_view_id
,
364 base::Bind(&DownloadControllerAndroidImpl::StartAndroidDownloadInternal
,
365 base::Unretained(this), render_process_id
, render_view_id
,
369 void DownloadControllerAndroidImpl::StartAndroidDownloadInternal(
370 int render_process_id
, int render_view_id
,
371 const DownloadInfoAndroid
& info
, bool allowed
) {
372 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
376 // Call newHttpGetDownload
377 WebContents
* web_contents
= GetWebContents(render_process_id
, render_view_id
);
378 // The view went away. Can't proceed.
381 ScopedJavaLocalRef
<jobject
> view
=
382 GetContentViewCoreFromWebContents(web_contents
);
386 JNIEnv
* env
= base::android::AttachCurrentThread();
387 ScopedJavaLocalRef
<jstring
> jurl
=
388 ConvertUTF8ToJavaString(env
, info
.url
.spec());
389 ScopedJavaLocalRef
<jstring
> juser_agent
=
390 ConvertUTF8ToJavaString(env
, info
.user_agent
);
391 ScopedJavaLocalRef
<jstring
> jcontent_disposition
=
392 ConvertUTF8ToJavaString(env
, info
.content_disposition
);
393 ScopedJavaLocalRef
<jstring
> jmime_type
=
394 ConvertUTF8ToJavaString(env
, info
.original_mime_type
);
395 ScopedJavaLocalRef
<jstring
> jcookie
=
396 ConvertUTF8ToJavaString(env
, info
.cookie
);
397 ScopedJavaLocalRef
<jstring
> jreferer
=
398 ConvertUTF8ToJavaString(env
, info
.referer
);
400 // Try parsing the content disposition header to get a
401 // explicitly specified filename if available.
402 net::HttpContentDisposition
header(info
.content_disposition
, "");
403 ScopedJavaLocalRef
<jstring
> jfilename
=
404 ConvertUTF8ToJavaString(env
, header
.filename());
406 Java_DownloadController_newHttpGetDownload(
407 env
, GetJavaObject()->Controller(env
).obj(), view
.obj(), jurl
.obj(),
408 juser_agent
.obj(), jcontent_disposition
.obj(), jmime_type
.obj(),
409 jcookie
.obj(), jreferer
.obj(), info
.has_user_gesture
, jfilename
.obj(),
413 void DownloadControllerAndroidImpl::OnDownloadStarted(
414 DownloadItem
* download_item
) {
415 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
416 if (!download_item
->GetWebContents())
419 JNIEnv
* env
= base::android::AttachCurrentThread();
421 // Register for updates to the DownloadItem.
422 download_item
->AddObserver(this);
424 ScopedJavaLocalRef
<jobject
> view
=
425 GetContentViewCoreFromWebContents(download_item
->GetWebContents());
426 // The view went away. Can't proceed.
430 ScopedJavaLocalRef
<jstring
> jmime_type
=
431 ConvertUTF8ToJavaString(env
, download_item
->GetMimeType());
432 ScopedJavaLocalRef
<jstring
> jfilename
= ConvertUTF8ToJavaString(
433 env
, download_item
->GetTargetFilePath().BaseName().value());
434 Java_DownloadController_onDownloadStarted(
435 env
, GetJavaObject()->Controller(env
).obj(), view
.obj(), jfilename
.obj(),
439 void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem
* item
) {
440 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
441 if (item
->IsDangerous() && (item
->GetState() != DownloadItem::CANCELLED
))
442 OnDangerousDownload(item
);
444 JNIEnv
* env
= base::android::AttachCurrentThread();
445 ScopedJavaLocalRef
<jstring
> jurl
=
446 ConvertUTF8ToJavaString(env
, item
->GetURL().spec());
447 ScopedJavaLocalRef
<jstring
> jmime_type
=
448 ConvertUTF8ToJavaString(env
, item
->GetMimeType());
449 ScopedJavaLocalRef
<jstring
> jpath
=
450 ConvertUTF8ToJavaString(env
, item
->GetTargetFilePath().value());
451 ScopedJavaLocalRef
<jstring
> jfilename
= ConvertUTF8ToJavaString(
452 env
, item
->GetTargetFilePath().BaseName().value());
454 switch (item
->GetState()) {
455 case DownloadItem::IN_PROGRESS
: {
456 base::TimeDelta time_delta
;
457 item
->TimeRemaining(&time_delta
);
458 Java_DownloadController_onDownloadUpdated(
459 env
, GetJavaObject()->Controller(env
).obj(),
460 base::android::GetApplicationContext(), jurl
.obj(), jmime_type
.obj(),
461 jfilename
.obj(), jpath
.obj(), item
->GetReceivedBytes(), true,
462 item
->GetId(), item
->PercentComplete(), time_delta
.InMilliseconds(),
463 item
->HasUserGesture());
466 case DownloadItem::COMPLETE
:
467 // Multiple OnDownloadUpdated() notifications may be issued while the
468 // download is in the COMPLETE state. Only handle one.
469 item
->RemoveObserver(this);
471 // Call onDownloadCompleted
472 Java_DownloadController_onDownloadCompleted(
473 env
, GetJavaObject()->Controller(env
).obj(),
474 base::android::GetApplicationContext(), jurl
.obj(), jmime_type
.obj(),
475 jfilename
.obj(), jpath
.obj(), item
->GetReceivedBytes(), true,
476 item
->GetId(), item
->HasUserGesture());
478 case DownloadItem::CANCELLED
:
479 // TODO(shashishekhar): An interrupted download can be resumed. Android
480 // currently does not support resumable downloads. Add handling for
481 // interrupted case based on item->CanResume().
482 case DownloadItem::INTERRUPTED
:
483 // Call onDownloadCompleted with success = false.
484 Java_DownloadController_onDownloadCompleted(
485 env
, GetJavaObject()->Controller(env
).obj(),
486 base::android::GetApplicationContext(), jurl
.obj(), jmime_type
.obj(),
487 jfilename
.obj(), jpath
.obj(), item
->GetReceivedBytes(), false,
488 item
->GetId(), item
->HasUserGesture());
490 case DownloadItem::MAX_DOWNLOAD_STATE
:
495 void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem
* item
) {
496 JNIEnv
* env
= base::android::AttachCurrentThread();
497 ScopedJavaLocalRef
<jstring
> jfilename
= ConvertUTF8ToJavaString(
498 env
, item
->GetTargetFilePath().BaseName().value());
499 ScopedJavaLocalRef
<jobject
> view_core
= GetContentViewCoreFromWebContents(
500 item
->GetWebContents());
501 if (!view_core
.is_null()) {
502 Java_DownloadController_onDangerousDownload(
503 env
, GetJavaObject()->Controller(env
).obj(), view_core
.obj(),
504 jfilename
.obj(), item
->GetId());
508 ScopedJavaLocalRef
<jobject
>
509 DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents(
510 WebContents
* web_contents
) {
512 return ScopedJavaLocalRef
<jobject
>();
514 ContentViewCore
* view_core
= ContentViewCore::FromWebContents(web_contents
);
515 return view_core
? view_core
->GetJavaObject() :
516 ScopedJavaLocalRef
<jobject
>();
519 DownloadControllerAndroidImpl::JavaObject
*
520 DownloadControllerAndroidImpl::GetJavaObject() {
522 // Initialize Java DownloadController by calling
523 // DownloadController.getInstance(), which will call Init()
524 // if Java DownloadController is not instantiated already.
525 JNIEnv
* env
= base::android::AttachCurrentThread();
526 Java_DownloadController_getInstance(env
);
529 DCHECK(java_object_
);
533 void DownloadControllerAndroidImpl::StartContextMenuDownload(
534 const ContextMenuParams
& params
, WebContents
* web_contents
, bool is_link
,
535 const std::string
& extra_headers
) {
536 int process_id
= web_contents
->GetRenderProcessHost()->GetID();
537 int routing_id
= web_contents
->GetRoutingID();
538 AcquireFileAccessPermission(
539 process_id
, routing_id
,
540 base::Bind(&CreateContextMenuDownload
, process_id
, routing_id
, params
,
541 is_link
, extra_headers
));
544 void DownloadControllerAndroidImpl::DangerousDownloadValidated(
545 WebContents
* web_contents
, int download_id
, bool accept
) {
548 DownloadManagerImpl
* dlm
= static_cast<DownloadManagerImpl
*>(
549 BrowserContext::GetDownloadManager(web_contents
->GetBrowserContext()));
550 DownloadItem
* item
= dlm
->GetDownload(download_id
);
554 item
->ValidateDangerousDownload();
559 DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid(
560 net::URLRequest
* request
)
561 : has_user_gesture(false) {
562 request
->GetResponseHeaderByName("content-disposition", &content_disposition
);
564 if (request
->response_headers())
565 request
->response_headers()->GetMimeType(&original_mime_type
);
567 request
->extra_request_headers().GetHeader(
568 net::HttpRequestHeaders::kUserAgent
, &user_agent
);
569 if (user_agent
.empty())
570 user_agent
= GetContentClient()->GetUserAgent();
571 GURL
referer_url(request
->referrer());
572 if (referer_url
.is_valid())
573 referer
= referer_url
.spec();
574 if (!request
->url_chain().empty()) {
575 original_url
= request
->url_chain().front();
576 url
= request
->url_chain().back();
579 const ResourceRequestInfo
* info
= ResourceRequestInfo::ForRequest(request
);
581 has_user_gesture
= info
->HasUserGesture();
584 DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {}
586 } // namespace content