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. 4 5 #include "content/browser/android/download_controller_android_impl.h" 6 7 #include "base/android/jni_android.h" 8 #include "base/android/jni_string.h" 9 #include "base/bind.h" 10 #include "base/logging.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "base/time/time.h" 13 #include "content/browser/android/content_view_core_impl.h" 14 #include "content/browser/download/download_item_impl.h" 15 #include "content/browser/download/download_manager_impl.h" 16 #include "content/browser/loader/resource_dispatcher_host_impl.h" 17 #include "content/browser/renderer_host/render_process_host_impl.h" 18 #include "content/browser/renderer_host/render_view_host_delegate.h" 19 #include "content/browser/renderer_host/render_view_host_impl.h" 20 #include "content/browser/web_contents/web_contents_impl.h" 21 #include "content/public/browser/browser_context.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "content/public/browser/download_url_parameters.h" 24 #include "content/public/browser/global_request_id.h" 25 #include "content/public/common/referrer.h" 26 #include "jni/DownloadController_jni.h" 27 #include "net/cookies/cookie_options.h" 28 #include "net/cookies/cookie_store.h" 29 #include "net/http/http_content_disposition.h" 30 #include "net/http/http_request_headers.h" 31 #include "net/http/http_response_headers.h" 32 #include "net/url_request/url_request.h" 33 #include "net/url_request/url_request_context.h" 34 35 using base::android::ConvertUTF8ToJavaString; 36 using base::android::ScopedJavaLocalRef; 37 38 namespace content { 39 40 // JNI methods 41 static void Init(JNIEnv* env, jobject obj) { 42 DownloadControllerAndroidImpl::GetInstance()->Init(env, obj); 43 } 44 45 struct DownloadControllerAndroidImpl::JavaObject { 46 ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) { 47 return GetRealObject(env, obj); 48 } 49 jweak obj; 50 }; 51 52 // static 53 bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) { 54 return RegisterNativesImpl(env); 55 } 56 57 // static 58 DownloadControllerAndroid* DownloadControllerAndroid::Get() { 59 return DownloadControllerAndroidImpl::GetInstance(); 60 } 61 62 // static 63 DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() { 64 return Singleton<DownloadControllerAndroidImpl>::get(); 65 } 66 67 DownloadControllerAndroidImpl::DownloadControllerAndroidImpl() 68 : java_object_(NULL) { 69 } 70 71 DownloadControllerAndroidImpl::~DownloadControllerAndroidImpl() { 72 if (java_object_) { 73 JNIEnv* env = base::android::AttachCurrentThread(); 74 env->DeleteWeakGlobalRef(java_object_->obj); 75 delete java_object_; 76 base::android::CheckException(env); 77 } 78 } 79 80 // Initialize references to Java object. 81 void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) { 82 java_object_ = new JavaObject; 83 java_object_->obj = env->NewWeakGlobalRef(obj); 84 } 85 86 void DownloadControllerAndroidImpl::CreateGETDownload( 87 int render_process_id, int render_view_id, int request_id) { 88 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 89 GlobalRequestID global_id(render_process_id, request_id); 90 91 // We are yielding the UI thread and render_view_host may go away by 92 // the time we come back. Pass along render_process_id and render_view_id 93 // to retrieve it later (if it still exists). 94 GetDownloadInfoCB cb = base::Bind( 95 &DownloadControllerAndroidImpl::StartAndroidDownload, 96 base::Unretained(this), render_process_id, 97 render_view_id); 98 99 PrepareDownloadInfo( 100 global_id, 101 base::Bind(&DownloadControllerAndroidImpl::StartDownloadOnUIThread, 102 base::Unretained(this), cb)); 103 } 104 105 void DownloadControllerAndroidImpl::PrepareDownloadInfo( 106 const GlobalRequestID& global_id, 107 const GetDownloadInfoCB& callback) { 108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 109 110 net::URLRequest* request = 111 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); 112 if (!request) { 113 LOG(ERROR) << "Request to download not found."; 114 return; 115 } 116 117 DownloadInfoAndroid info_android(request); 118 119 net::CookieStore* cookie_store = request->context()->cookie_store(); 120 if (cookie_store) { 121 net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster(); 122 if (cookie_monster) { 123 cookie_monster->GetAllCookiesForURLAsync( 124 request->url(), 125 base::Bind(&DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies, 126 base::Unretained(this), info_android, callback, 127 global_id)); 128 } else { 129 DoLoadCookies(info_android, callback, global_id); 130 } 131 } else { 132 // Can't get any cookies, start android download. 133 callback.Run(info_android); 134 } 135 } 136 137 void DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies( 138 const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback, 139 const GlobalRequestID& global_id, const net::CookieList& cookie_list) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 141 142 net::URLRequest* request = 143 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); 144 if (!request) { 145 LOG(ERROR) << "Request to download not found."; 146 return; 147 } 148 149 if (request->context()->network_delegate()->CanGetCookies( 150 *request, cookie_list)) { 151 DoLoadCookies(info, callback, global_id); 152 } else { 153 callback.Run(info); 154 } 155 } 156 157 void DownloadControllerAndroidImpl::DoLoadCookies( 158 const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback, 159 const GlobalRequestID& global_id) { 160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 161 162 net::CookieOptions options; 163 options.set_include_httponly(); 164 165 net::URLRequest* request = 166 ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id); 167 if (!request) { 168 LOG(ERROR) << "Request to download not found."; 169 return; 170 } 171 172 request->context()->cookie_store()->GetCookiesWithOptionsAsync( 173 info.url, options, 174 base::Bind(&DownloadControllerAndroidImpl::OnCookieResponse, 175 base::Unretained(this), info, callback)); 176 } 177 178 void DownloadControllerAndroidImpl::OnCookieResponse( 179 DownloadInfoAndroid download_info, 180 const GetDownloadInfoCB& callback, 181 const std::string& cookie) { 182 download_info.cookie = cookie; 183 184 // We have everything we need, start Android download. 185 callback.Run(download_info); 186 } 187 188 void DownloadControllerAndroidImpl::StartDownloadOnUIThread( 189 const GetDownloadInfoCB& callback, 190 const DownloadInfoAndroid& info) { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 192 BrowserThread::PostTask( 193 BrowserThread::UI, FROM_HERE, base::Bind(callback, info)); 194 } 195 196 void DownloadControllerAndroidImpl::StartAndroidDownload( 197 int render_process_id, int render_view_id, 198 const DownloadInfoAndroid& info) { 199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 200 JNIEnv* env = base::android::AttachCurrentThread(); 201 202 // Call newHttpGetDownload 203 ScopedJavaLocalRef<jobject> view = GetContentView(render_process_id, 204 render_view_id); 205 if (view.is_null()) { 206 // The view went away. Can't proceed. 207 LOG(ERROR) << "Download failed on URL:" << info.url.spec(); 208 return; 209 } 210 211 ScopedJavaLocalRef<jstring> jurl = 212 ConvertUTF8ToJavaString(env, info.url.spec()); 213 ScopedJavaLocalRef<jstring> juser_agent = 214 ConvertUTF8ToJavaString(env, info.user_agent); 215 ScopedJavaLocalRef<jstring> jcontent_disposition = 216 ConvertUTF8ToJavaString(env, info.content_disposition); 217 ScopedJavaLocalRef<jstring> jmime_type = 218 ConvertUTF8ToJavaString(env, info.original_mime_type); 219 ScopedJavaLocalRef<jstring> jcookie = 220 ConvertUTF8ToJavaString(env, info.cookie); 221 ScopedJavaLocalRef<jstring> jreferer = 222 ConvertUTF8ToJavaString(env, info.referer); 223 224 // Try parsing the content disposition header to get a 225 // explicitly specified filename if available. 226 net::HttpContentDisposition header(info.content_disposition, ""); 227 ScopedJavaLocalRef<jstring> jfilename = 228 ConvertUTF8ToJavaString(env, header.filename()); 229 230 Java_DownloadController_newHttpGetDownload( 231 env, GetJavaObject()->Controller(env).obj(), view.obj(), jurl.obj(), 232 juser_agent.obj(), jcontent_disposition.obj(), jmime_type.obj(), 233 jcookie.obj(), jreferer.obj(), jfilename.obj(), info.total_bytes); 234 } 235 236 void DownloadControllerAndroidImpl::OnDownloadStarted( 237 DownloadItem* download_item) { 238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 239 if (!download_item->GetWebContents()) 240 return; 241 242 JNIEnv* env = base::android::AttachCurrentThread(); 243 244 // Register for updates to the DownloadItem. 245 download_item->AddObserver(this); 246 247 ScopedJavaLocalRef<jobject> view = 248 GetContentViewCoreFromWebContents(download_item->GetWebContents()); 249 // The view went away. Can't proceed. 250 if (view.is_null()) 251 return; 252 253 ScopedJavaLocalRef<jstring> jmime_type = 254 ConvertUTF8ToJavaString(env, download_item->GetMimeType()); 255 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 256 env, download_item->GetTargetFilePath().BaseName().value()); 257 Java_DownloadController_onDownloadStarted( 258 env, GetJavaObject()->Controller(env).obj(), view.obj(), jfilename.obj(), 259 jmime_type.obj()); 260 } 261 262 void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) { 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 264 if (item->IsDangerous() && (item->GetState() != DownloadItem::CANCELLED)) 265 OnDangerousDownload(item); 266 267 JNIEnv* env = base::android::AttachCurrentThread(); 268 ScopedJavaLocalRef<jstring> jurl = 269 ConvertUTF8ToJavaString(env, item->GetURL().spec()); 270 ScopedJavaLocalRef<jstring> jmime_type = 271 ConvertUTF8ToJavaString(env, item->GetMimeType()); 272 ScopedJavaLocalRef<jstring> jpath = 273 ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value()); 274 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 275 env, item->GetTargetFilePath().BaseName().value()); 276 277 switch (item->GetState()) { 278 case DownloadItem::IN_PROGRESS: { 279 base::TimeDelta time_delta; 280 item->TimeRemaining(&time_delta); 281 Java_DownloadController_onDownloadUpdated( 282 env, GetJavaObject()->Controller(env).obj(), 283 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 284 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, 285 item->GetId(), item->PercentComplete(), time_delta.InMilliseconds()); 286 break; 287 } 288 case DownloadItem::COMPLETE: 289 // Multiple OnDownloadUpdated() notifications may be issued while the 290 // download is in the COMPLETE state. Only handle one. 291 item->RemoveObserver(this); 292 293 // Call onDownloadCompleted 294 Java_DownloadController_onDownloadCompleted( 295 env, GetJavaObject()->Controller(env).obj(), 296 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 297 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, 298 item->GetId()); 299 break; 300 case DownloadItem::CANCELLED: 301 // TODO(shashishekhar): An interrupted download can be resumed. Android 302 // currently does not support resumable downloads. Add handling for 303 // interrupted case based on item->CanResume(). 304 case DownloadItem::INTERRUPTED: 305 // Call onDownloadCompleted with success = false. 306 Java_DownloadController_onDownloadCompleted( 307 env, GetJavaObject()->Controller(env).obj(), 308 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 309 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), false, 310 item->GetId()); 311 break; 312 case DownloadItem::MAX_DOWNLOAD_STATE: 313 NOTREACHED(); 314 } 315 } 316 317 void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem* item) { 318 JNIEnv* env = base::android::AttachCurrentThread(); 319 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 320 env, item->GetTargetFilePath().BaseName().value()); 321 ScopedJavaLocalRef<jobject> view_core = GetContentViewCoreFromWebContents( 322 item->GetWebContents()); 323 if (!view_core.is_null()) { 324 Java_DownloadController_onDangerousDownload( 325 env, GetJavaObject()->Controller(env).obj(), view_core.obj(), 326 jfilename.obj(), item->GetId()); 327 } 328 } 329 330 ScopedJavaLocalRef<jobject> DownloadControllerAndroidImpl::GetContentView( 331 int render_process_id, int render_view_id) { 332 RenderViewHost* render_view_host = 333 RenderViewHost::FromID(render_process_id, render_view_id); 334 335 if (!render_view_host) 336 return ScopedJavaLocalRef<jobject>(); 337 338 WebContents* web_contents = 339 render_view_host->GetDelegate()->GetAsWebContents(); 340 341 return GetContentViewCoreFromWebContents(web_contents); 342 } 343 344 ScopedJavaLocalRef<jobject> 345 DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents( 346 WebContents* web_contents) { 347 if (!web_contents) 348 return ScopedJavaLocalRef<jobject>(); 349 350 ContentViewCore* view_core = ContentViewCore::FromWebContents(web_contents); 351 return view_core ? view_core->GetJavaObject() : 352 ScopedJavaLocalRef<jobject>(); 353 } 354 355 DownloadControllerAndroidImpl::JavaObject* 356 DownloadControllerAndroidImpl::GetJavaObject() { 357 if (!java_object_) { 358 // Initialize Java DownloadController by calling 359 // DownloadController.getInstance(), which will call Init() 360 // if Java DownloadController is not instantiated already. 361 JNIEnv* env = base::android::AttachCurrentThread(); 362 Java_DownloadController_getInstance(env); 363 } 364 365 DCHECK(java_object_); 366 return java_object_; 367 } 368 369 void DownloadControllerAndroidImpl::StartContextMenuDownload( 370 const ContextMenuParams& params, WebContents* web_contents, bool is_link) { 371 const GURL& url = is_link ? params.link_url : params.src_url; 372 const GURL& referrer = params.frame_url.is_empty() ? 373 params.page_url : params.frame_url; 374 DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( 375 BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); 376 scoped_ptr<DownloadUrlParameters> dl_params( 377 DownloadUrlParameters::FromWebContents(web_contents, url)); 378 dl_params->set_referrer( 379 Referrer(referrer, params.referrer_policy)); 380 if (is_link) 381 dl_params->set_referrer_encoding(params.frame_charset); 382 else 383 dl_params->set_prefer_cache(true); 384 dl_params->set_prompt(false); 385 dlm->DownloadUrl(dl_params.Pass()); 386 } 387 388 void DownloadControllerAndroidImpl::DangerousDownloadValidated( 389 WebContents* web_contents, int download_id, bool accept) { 390 if (!web_contents) 391 return; 392 DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( 393 BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); 394 DownloadItem* item = dlm->GetDownload(download_id); 395 if (!item) 396 return; 397 if (accept) 398 item->ValidateDangerousDownload(); 399 else 400 item->Remove(); 401 } 402 403 DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid( 404 net::URLRequest* request) { 405 request->GetResponseHeaderByName("content-disposition", &content_disposition); 406 407 if (request->response_headers()) 408 request->response_headers()->GetMimeType(&original_mime_type); 409 410 request->extra_request_headers().GetHeader( 411 net::HttpRequestHeaders::kUserAgent, &user_agent); 412 GURL referer_url(request->referrer()); 413 if (referer_url.is_valid()) 414 referer = referer_url.spec(); 415 if (!request->url_chain().empty()) { 416 original_url = request->url_chain().front(); 417 url = request->url_chain().back(); 418 } 419 } 420 421 DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {} 422 423 } // namespace content 424