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/web_contents_view.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_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 Java_DownloadController_newHttpGetDownload( 225 env, GetJavaObject()->Controller(env).obj(), view.obj(), jurl.obj(), 226 juser_agent.obj(), jcontent_disposition.obj(), jmime_type.obj(), 227 jcookie.obj(), jreferer.obj(), info.total_bytes); 228 } 229 230 void DownloadControllerAndroidImpl::OnDownloadStarted( 231 DownloadItem* download_item) { 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 233 if (!download_item->GetWebContents()) 234 return; 235 236 JNIEnv* env = base::android::AttachCurrentThread(); 237 238 // Register for updates to the DownloadItem. 239 download_item->AddObserver(this); 240 241 ScopedJavaLocalRef<jobject> view = 242 GetContentViewCoreFromWebContents(download_item->GetWebContents()); 243 // The view went away. Can't proceed. 244 if (view.is_null()) 245 return; 246 247 ScopedJavaLocalRef<jstring> jmime_type = 248 ConvertUTF8ToJavaString(env, download_item->GetMimeType()); 249 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 250 env, download_item->GetTargetFilePath().BaseName().value()); 251 Java_DownloadController_onDownloadStarted( 252 env, GetJavaObject()->Controller(env).obj(), view.obj(), jfilename.obj(), 253 jmime_type.obj()); 254 } 255 256 void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) { 257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 258 if (item->IsDangerous() && (item->GetState() != DownloadItem::CANCELLED)) 259 OnDangerousDownload(item); 260 261 JNIEnv* env = base::android::AttachCurrentThread(); 262 ScopedJavaLocalRef<jstring> jurl = 263 ConvertUTF8ToJavaString(env, item->GetURL().spec()); 264 ScopedJavaLocalRef<jstring> jmime_type = 265 ConvertUTF8ToJavaString(env, item->GetMimeType()); 266 ScopedJavaLocalRef<jstring> jpath = 267 ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value()); 268 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 269 env, item->GetTargetFilePath().BaseName().value()); 270 271 switch (item->GetState()) { 272 case DownloadItem::IN_PROGRESS: { 273 base::TimeDelta time_delta; 274 item->TimeRemaining(&time_delta); 275 Java_DownloadController_onDownloadUpdated( 276 env, GetJavaObject()->Controller(env).obj(), 277 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 278 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, 279 item->GetId(), item->PercentComplete(), time_delta.InMilliseconds()); 280 break; 281 } 282 case DownloadItem::COMPLETE: 283 // Multiple OnDownloadUpdated() notifications may be issued while the 284 // download is in the COMPLETE state. Only handle one. 285 item->RemoveObserver(this); 286 287 // Call onDownloadCompleted 288 Java_DownloadController_onDownloadCompleted( 289 env, GetJavaObject()->Controller(env).obj(), 290 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 291 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true, 292 item->GetId()); 293 break; 294 case DownloadItem::CANCELLED: 295 // TODO(shashishekhar): An interrupted download can be resumed. Android 296 // currently does not support resumable downloads. Add handling for 297 // interrupted case based on item->CanResume(). 298 case DownloadItem::INTERRUPTED: 299 // Call onDownloadCompleted with success = false. 300 Java_DownloadController_onDownloadCompleted( 301 env, GetJavaObject()->Controller(env).obj(), 302 base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(), 303 jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), false, 304 item->GetId()); 305 break; 306 case DownloadItem::MAX_DOWNLOAD_STATE: 307 NOTREACHED(); 308 } 309 } 310 311 void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem* item) { 312 JNIEnv* env = base::android::AttachCurrentThread(); 313 ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString( 314 env, item->GetTargetFilePath().BaseName().value()); 315 ScopedJavaLocalRef<jobject> view_core = GetContentViewCoreFromWebContents( 316 item->GetWebContents()); 317 if (!view_core.is_null()) { 318 Java_DownloadController_onDangerousDownload( 319 env, GetJavaObject()->Controller(env).obj(), view_core.obj(), 320 jfilename.obj(), item->GetId()); 321 } 322 } 323 324 ScopedJavaLocalRef<jobject> DownloadControllerAndroidImpl::GetContentView( 325 int render_process_id, int render_view_id) { 326 RenderViewHost* render_view_host = 327 RenderViewHost::FromID(render_process_id, render_view_id); 328 329 if (!render_view_host) 330 return ScopedJavaLocalRef<jobject>(); 331 332 WebContents* web_contents = 333 render_view_host->GetDelegate()->GetAsWebContents(); 334 335 return GetContentViewCoreFromWebContents(web_contents); 336 } 337 338 ScopedJavaLocalRef<jobject> 339 DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents( 340 WebContents* web_contents) { 341 if (!web_contents) 342 return ScopedJavaLocalRef<jobject>(); 343 344 ContentViewCore* view_core = ContentViewCore::FromWebContents(web_contents); 345 return view_core ? view_core->GetJavaObject() : 346 ScopedJavaLocalRef<jobject>(); 347 } 348 349 DownloadControllerAndroidImpl::JavaObject* 350 DownloadControllerAndroidImpl::GetJavaObject() { 351 if (!java_object_) { 352 // Initialize Java DownloadController by calling 353 // DownloadController.getInstance(), which will call Init() 354 // if Java DownloadController is not instantiated already. 355 JNIEnv* env = base::android::AttachCurrentThread(); 356 Java_DownloadController_getInstance(env); 357 } 358 359 DCHECK(java_object_); 360 return java_object_; 361 } 362 363 void DownloadControllerAndroidImpl::StartContextMenuDownload( 364 const ContextMenuParams& params, WebContents* web_contents, bool is_link) { 365 const GURL& url = is_link ? params.link_url : params.src_url; 366 const GURL& referrer = params.frame_url.is_empty() ? 367 params.page_url : params.frame_url; 368 DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( 369 BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); 370 scoped_ptr<DownloadUrlParameters> dl_params( 371 DownloadUrlParameters::FromWebContents(web_contents, url)); 372 dl_params->set_referrer( 373 Referrer(referrer, params.referrer_policy)); 374 if (is_link) 375 dl_params->set_referrer_encoding(params.frame_charset); 376 else 377 dl_params->set_prefer_cache(true); 378 dl_params->set_prompt(false); 379 dlm->DownloadUrl(dl_params.Pass()); 380 } 381 382 void DownloadControllerAndroidImpl::DangerousDownloadValidated( 383 WebContents* web_contents, int download_id, bool accept) { 384 if (!web_contents) 385 return; 386 DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>( 387 BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())); 388 DownloadItem* item = dlm->GetDownload(download_id); 389 if (!item) 390 return; 391 if (accept) 392 item->ValidateDangerousDownload(); 393 else 394 item->Remove(); 395 } 396 397 DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid( 398 net::URLRequest* request) { 399 request->GetResponseHeaderByName("content-disposition", &content_disposition); 400 401 if (request->response_headers()) 402 request->response_headers()->GetMimeType(&original_mime_type); 403 404 request->extra_request_headers().GetHeader( 405 net::HttpRequestHeaders::kUserAgent, &user_agent); 406 GURL referer_url(request->referrer()); 407 if (referer_url.is_valid()) 408 referer = referer_url.spec(); 409 if (!request->url_chain().empty()) { 410 original_url = request->url_chain().front(); 411 url = request->url_chain().back(); 412 } 413 } 414 415 DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {} 416 417 } // namespace content 418