|  | // Copyright (c) 2012 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 "content/browser/android/download_controller_android_impl.h" | 
|  |  | 
|  | #include "base/android/jni_android.h" | 
|  | #include "base/android/jni_string.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/scoped_ptr.h" | 
|  | #include "base/time/time.h" | 
|  | #include "content/browser/android/content_view_core_impl.h" | 
|  | #include "content/browser/download/download_item_impl.h" | 
|  | #include "content/browser/download/download_manager_impl.h" | 
|  | #include "content/browser/loader/resource_dispatcher_host_impl.h" | 
|  | #include "content/browser/renderer_host/render_process_host_impl.h" | 
|  | #include "content/browser/renderer_host/render_view_host_delegate.h" | 
|  | #include "content/browser/renderer_host/render_view_host_impl.h" | 
|  | #include "content/browser/web_contents/web_contents_impl.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/download_url_parameters.h" | 
|  | #include "content/public/browser/global_request_id.h" | 
|  | #include "content/public/browser/resource_request_info.h" | 
|  | #include "content/public/common/referrer.h" | 
|  | #include "jni/DownloadController_jni.h" | 
|  | #include "net/cookies/cookie_options.h" | 
|  | #include "net/cookies/cookie_store.h" | 
|  | #include "net/http/http_content_disposition.h" | 
|  | #include "net/http/http_request_headers.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "net/url_request/url_request_context.h" | 
|  |  | 
|  | using base::android::ConvertUTF8ToJavaString; | 
|  | using base::android::ScopedJavaLocalRef; | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | // JNI methods | 
|  | static void Init(JNIEnv* env, jobject obj) { | 
|  | DownloadControllerAndroidImpl::GetInstance()->Init(env, obj); | 
|  | } | 
|  |  | 
|  | struct DownloadControllerAndroidImpl::JavaObject { | 
|  | ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) { | 
|  | return GetRealObject(env, obj); | 
|  | } | 
|  | jweak obj; | 
|  | }; | 
|  |  | 
|  | // static | 
|  | bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) { | 
|  | return RegisterNativesImpl(env); | 
|  | } | 
|  |  | 
|  | // static | 
|  | DownloadControllerAndroid* DownloadControllerAndroid::Get() { | 
|  | return DownloadControllerAndroidImpl::GetInstance(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() { | 
|  | return Singleton<DownloadControllerAndroidImpl>::get(); | 
|  | } | 
|  |  | 
|  | DownloadControllerAndroidImpl::DownloadControllerAndroidImpl() | 
|  | : java_object_(NULL) { | 
|  | } | 
|  |  | 
|  | DownloadControllerAndroidImpl::~DownloadControllerAndroidImpl() { | 
|  | if (java_object_) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | env->DeleteWeakGlobalRef(java_object_->obj); | 
|  | delete java_object_; | 
|  | base::android::CheckException(env); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Initialize references to Java object. | 
|  | void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) { | 
|  | java_object_ = new JavaObject; | 
|  | java_object_->obj = env->NewWeakGlobalRef(obj); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::CreateGETDownload( | 
|  | int render_process_id, int render_view_id, int request_id) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 
|  | GlobalRequestID global_id(render_process_id, request_id); | 
|  |  | 
|  | // We are yielding the UI thread and render_view_host may go away by | 
|  | // the time we come back. Pass along render_process_id and render_view_id | 
|  | // to retrieve it later (if it still exists). | 
|  | GetDownloadInfoCB cb = base::Bind( | 
|  | &DownloadControllerAndroidImpl::StartAndroidDownload, | 
|  | base::Unretained(this), render_process_id, | 
|  | render_view_id); | 
|  |  | 
|  | PrepareDownloadInfo( | 
|  | global_id, | 
|  | base::Bind(&DownloadControllerAndroidImpl::StartDownloadOnUIThread, | 
|  | base::Unretained(this), cb)); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::PrepareDownloadInfo( | 
|  | const GlobalRequestID& global_id, | 
|  | const GetDownloadInfoCB& callback) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 
|  |  | 
|  | net::URLRequest* request = | 
|  | ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); | 
|  | if (!request) { | 
|  | LOG(ERROR) << "Request to download not found."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | DownloadInfoAndroid info_android(request); | 
|  |  | 
|  | net::CookieStore* cookie_store = request->context()->cookie_store(); | 
|  | if (cookie_store) { | 
|  | net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster(); | 
|  | if (cookie_monster) { | 
|  | cookie_monster->GetAllCookiesForURLAsync( | 
|  | request->url(), | 
|  | base::Bind(&DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies, | 
|  | base::Unretained(this), info_android, callback, | 
|  | global_id)); | 
|  | } else { | 
|  | DoLoadCookies(info_android, callback, global_id); | 
|  | } | 
|  | } else { | 
|  | // Can't get any cookies, start android download. | 
|  | callback.Run(info_android); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies( | 
|  | const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback, | 
|  | const GlobalRequestID& global_id, const net::CookieList& cookie_list) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 
|  |  | 
|  | net::URLRequest* request = | 
|  | ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); | 
|  | if (!request) { | 
|  | LOG(ERROR) << "Request to download not found."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (request->context()->network_delegate()->CanGetCookies( | 
|  | *request, cookie_list)) { | 
|  | DoLoadCookies(info, callback, global_id); | 
|  | } else { | 
|  | callback.Run(info); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::DoLoadCookies( | 
|  | const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback, | 
|  | const GlobalRequestID& global_id) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 
|  |  | 
|  | net::CookieOptions options; | 
|  | options.set_include_httponly(); | 
|  |  | 
|  | net::URLRequest* request = | 
|  | ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); | 
|  | if (!request) { | 
|  | LOG(ERROR) << "Request to download not found."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | request->context()->cookie_store()->GetCookiesWithOptionsAsync( | 
|  | info.url, options, | 
|  | base::Bind(&DownloadControllerAndroidImpl::OnCookieResponse, | 
|  | base::Unretained(this), info, callback)); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::OnCookieResponse( | 
|  | DownloadInfoAndroid download_info, | 
|  | const GetDownloadInfoCB& callback, | 
|  | const std::string& cookie) { | 
|  | download_info.cookie = cookie; | 
|  |  | 
|  | // We have everything we need, start Android download. | 
|  | callback.Run(download_info); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::StartDownloadOnUIThread( | 
|  | const GetDownloadInfoCB& callback, | 
|  | const DownloadInfoAndroid& info) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::UI, FROM_HERE, base::Bind(callback, info)); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::StartAndroidDownload( | 
|  | int render_process_id, int render_view_id, | 
|  | const DownloadInfoAndroid& info) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Call newHttpGetDownload | 
|  | ScopedJavaLocalRef<jobject> view = GetContentView(render_process_id, | 
|  | render_view_id); | 
|  | if (view.is_null()) { | 
|  | // The view went away. Can't proceed. | 
|  | LOG(ERROR) << "Download failed on URL:" << info.url.spec(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ScopedJavaLocalRef<jstring> jurl = | 
|  | ConvertUTF8ToJavaString(env, info.url.spec()); | 
|  | ScopedJavaLocalRef<jstring> juser_agent = | 
|  | ConvertUTF8ToJavaString(env, info.user_agent); | 
|  | ScopedJavaLocalRef<jstring> jcontent_disposition = | 
|  | ConvertUTF8ToJavaString(env, info.content_disposition); | 
|  | ScopedJavaLocalRef<jstring> jmime_type = | 
|  | ConvertUTF8ToJavaString(env, info.original_mime_type); | 
|  | ScopedJavaLocalRef<jstring> jcookie = | 
|  | ConvertUTF8ToJavaString(env, info.cookie); | 
|  | ScopedJavaLocalRef<jstring> jreferer = | 
|  | ConvertUTF8ToJavaString(env, info.referer); | 
|  |  | 
|  | // Try parsing the content disposition header to get a | 
|  | // explicitly specified filename if available. | 
|  | net::HttpContentDisposition header(info.content_disposition, ""); | 
|  | ScopedJavaLocalRef<jstring> jfilename = | 
|  | ConvertUTF8ToJavaString(env, header.filename()); | 
|  |  | 
|  | Java_DownloadController_newHttpGetDownload( | 
|  | env, GetJavaObject()->Controller(env).obj(), view.obj(), jurl.obj(), | 
|  | juser_agent.obj(), jcontent_disposition.obj(), jmime_type.obj(), | 
|  | jcookie.obj(), jreferer.obj(), info.has_user_gesture, jfilename.obj(), | 
|  | info.total_bytes); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::OnDownloadStarted( | 
|  | DownloadItem* download_item) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 
|  | if (!download_item->GetWebContents()) | 
|  | return; | 
|  |  | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  |  | 
|  | // Register for updates to the DownloadItem. | 
|  | download_item->AddObserver(this); | 
|  |  | 
|  | ScopedJavaLocalRef<jobject> view = | 
|  | GetContentViewCoreFromWebContents(download_item->GetWebContents()); | 
|  | // The view went away. Can't proceed. | 
|  | if (view.is_null()) | 
|  | return; | 
|  |  | 
|  | ScopedJavaLocalRef<jstring> jmime_type = | 
|  | ConvertUTF8ToJavaString(env, download_item->GetMimeType()); | 
|  | ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( | 
|  | env, download_item->GetTargetFilePath().BaseName().value()); | 
|  | Java_DownloadController_onDownloadStarted( | 
|  | env, GetJavaObject()->Controller(env).obj(), view.obj(), jfilename.obj(), | 
|  | jmime_type.obj()); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) { | 
|  | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 
|  | if (item->IsDangerous() && (item->GetState() != DownloadItem::CANCELLED)) | 
|  | OnDangerousDownload(item); | 
|  |  | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | ScopedJavaLocalRef<jstring> jurl = | 
|  | ConvertUTF8ToJavaString(env, item->GetURL().spec()); | 
|  | ScopedJavaLocalRef<jstring> jmime_type = | 
|  | ConvertUTF8ToJavaString(env, item->GetMimeType()); | 
|  | ScopedJavaLocalRef<jstring> jpath = | 
|  | ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value()); | 
|  | ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( | 
|  | env, item->GetTargetFilePath().BaseName().value()); | 
|  |  | 
|  | switch (item->GetState()) { | 
|  | case DownloadItem::IN_PROGRESS: { | 
|  | base::TimeDelta time_delta; | 
|  | item->TimeRemaining(&time_delta); | 
|  | Java_DownloadController_onDownloadUpdated( | 
|  | env, GetJavaObject()->Controller(env).obj(), | 
|  | base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), | 
|  | jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, | 
|  | item->GetId(), item->PercentComplete(), time_delta.InMilliseconds()); | 
|  | break; | 
|  | } | 
|  | case DownloadItem::COMPLETE: | 
|  | // Multiple OnDownloadUpdated() notifications may be issued while the | 
|  | // download is in the COMPLETE state. Only handle one. | 
|  | item->RemoveObserver(this); | 
|  |  | 
|  | // Call onDownloadCompleted | 
|  | Java_DownloadController_onDownloadCompleted( | 
|  | env, GetJavaObject()->Controller(env).obj(), | 
|  | base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), | 
|  | jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, | 
|  | item->GetId()); | 
|  | break; | 
|  | case DownloadItem::CANCELLED: | 
|  | // TODO(shashishekhar): An interrupted download can be resumed. Android | 
|  | // currently does not support resumable downloads. Add handling for | 
|  | // interrupted case based on item->CanResume(). | 
|  | case DownloadItem::INTERRUPTED: | 
|  | // Call onDownloadCompleted with success = false. | 
|  | Java_DownloadController_onDownloadCompleted( | 
|  | env, GetJavaObject()->Controller(env).obj(), | 
|  | base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), | 
|  | jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), false, | 
|  | item->GetId()); | 
|  | break; | 
|  | case DownloadItem::MAX_DOWNLOAD_STATE: | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem* item) { | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( | 
|  | env, item->GetTargetFilePath().BaseName().value()); | 
|  | ScopedJavaLocalRef<jobject> view_core = GetContentViewCoreFromWebContents( | 
|  | item->GetWebContents()); | 
|  | if (!view_core.is_null()) { | 
|  | Java_DownloadController_onDangerousDownload( | 
|  | env, GetJavaObject()->Controller(env).obj(), view_core.obj(), | 
|  | jfilename.obj(), item->GetId()); | 
|  | } | 
|  | } | 
|  |  | 
|  | ScopedJavaLocalRef<jobject> DownloadControllerAndroidImpl::GetContentView( | 
|  | int render_process_id, int render_view_id) { | 
|  | RenderViewHost* render_view_host = | 
|  | RenderViewHost::FromID(render_process_id, render_view_id); | 
|  |  | 
|  | if (!render_view_host) | 
|  | return ScopedJavaLocalRef<jobject>(); | 
|  |  | 
|  | WebContents* web_contents = | 
|  | render_view_host->GetDelegate()->GetAsWebContents(); | 
|  |  | 
|  | return GetContentViewCoreFromWebContents(web_contents); | 
|  | } | 
|  |  | 
|  | ScopedJavaLocalRef<jobject> | 
|  | DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents( | 
|  | WebContents* web_contents) { | 
|  | if (!web_contents) | 
|  | return ScopedJavaLocalRef<jobject>(); | 
|  |  | 
|  | ContentViewCore* view_core = ContentViewCore::FromWebContents(web_contents); | 
|  | return view_core ? view_core->GetJavaObject() : | 
|  | ScopedJavaLocalRef<jobject>(); | 
|  | } | 
|  |  | 
|  | DownloadControllerAndroidImpl::JavaObject* | 
|  | DownloadControllerAndroidImpl::GetJavaObject() { | 
|  | if (!java_object_) { | 
|  | // Initialize Java DownloadController by calling | 
|  | // DownloadController.getInstance(), which will call Init() | 
|  | // if Java DownloadController is not instantiated already. | 
|  | JNIEnv* env = base::android::AttachCurrentThread(); | 
|  | Java_DownloadController_getInstance(env); | 
|  | } | 
|  |  | 
|  | DCHECK(java_object_); | 
|  | return java_object_; | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::StartContextMenuDownload( | 
|  | const ContextMenuParams& params, WebContents* web_contents, bool is_link) { | 
|  | const GURL& url = is_link ? params.link_url : params.src_url; | 
|  | const GURL& referrer = params.frame_url.is_empty() ? | 
|  | params.page_url : params.frame_url; | 
|  | DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( | 
|  | BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); | 
|  | scoped_ptr<DownloadUrlParameters> dl_params( | 
|  | DownloadUrlParameters::FromWebContents(web_contents, url)); | 
|  | dl_params->set_referrer( | 
|  | Referrer(referrer, params.referrer_policy)); | 
|  | if (is_link) | 
|  | dl_params->set_referrer_encoding(params.frame_charset); | 
|  | else | 
|  | dl_params->set_prefer_cache(true); | 
|  | dl_params->set_prompt(false); | 
|  | dlm->DownloadUrl(dl_params.Pass()); | 
|  | } | 
|  |  | 
|  | void DownloadControllerAndroidImpl::DangerousDownloadValidated( | 
|  | WebContents* web_contents, int download_id, bool accept) { | 
|  | if (!web_contents) | 
|  | return; | 
|  | DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( | 
|  | BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); | 
|  | DownloadItem* item = dlm->GetDownload(download_id); | 
|  | if (!item) | 
|  | return; | 
|  | if (accept) | 
|  | item->ValidateDangerousDownload(); | 
|  | else | 
|  | item->Remove(); | 
|  | } | 
|  |  | 
|  | DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid( | 
|  | net::URLRequest* request) | 
|  | : has_user_gesture(false) { | 
|  | request->GetResponseHeaderByName("content-disposition", &content_disposition); | 
|  |  | 
|  | if (request->response_headers()) | 
|  | request->response_headers()->GetMimeType(&original_mime_type); | 
|  |  | 
|  | request->extra_request_headers().GetHeader( | 
|  | net::HttpRequestHeaders::kUserAgent, &user_agent); | 
|  | GURL referer_url(request->referrer()); | 
|  | if (referer_url.is_valid()) | 
|  | referer = referer_url.spec(); | 
|  | if (!request->url_chain().empty()) { | 
|  | original_url = request->url_chain().front(); | 
|  | url = request->url_chain().back(); | 
|  | } | 
|  |  | 
|  | const content::ResourceRequestInfo* info = | 
|  | content::ResourceRequestInfo::ForRequest(request); | 
|  | if (info) | 
|  | has_user_gesture = info->HasUserGesture(); | 
|  | } | 
|  |  | 
|  | DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {} | 
|  |  | 
|  | }  // namespace content |