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