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
Init(JNIEnv * env,jobject obj)42 static void Init(JNIEnv* env, jobject obj) {
43 DownloadControllerAndroidImpl::GetInstance()->Init(env, obj);
44 }
45
46 struct DownloadControllerAndroidImpl::JavaObject {
Controllercontent::DownloadControllerAndroidImpl::JavaObject47 ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) {
48 return GetRealObject(env, obj);
49 }
50 jweak obj;
51 };
52
53 // static
RegisterDownloadController(JNIEnv * env)54 bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) {
55 return RegisterNativesImpl(env);
56 }
57
58 // static
Get()59 DownloadControllerAndroid* DownloadControllerAndroid::Get() {
60 return DownloadControllerAndroidImpl::GetInstance();
61 }
62
63 // static
GetInstance()64 DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() {
65 return Singleton<DownloadControllerAndroidImpl>::get();
66 }
67
DownloadControllerAndroidImpl()68 DownloadControllerAndroidImpl::DownloadControllerAndroidImpl()
69 : java_object_(NULL) {
70 }
71
~DownloadControllerAndroidImpl()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.
Init(JNIEnv * env,jobject obj)82 void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) {
83 java_object_ = new JavaObject;
84 java_object_->obj = env->NewWeakGlobalRef(obj);
85 }
86
CreateGETDownload(int render_process_id,int render_view_id,int request_id)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
PrepareDownloadInfo(const GlobalRequestID & global_id,const GetDownloadInfoCB & callback)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
CheckPolicyAndLoadCookies(const DownloadInfoAndroid & info,const GetDownloadInfoCB & callback,const GlobalRequestID & global_id,const net::CookieList & cookie_list)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
DoLoadCookies(const DownloadInfoAndroid & info,const GetDownloadInfoCB & callback,const GlobalRequestID & global_id)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
OnCookieResponse(DownloadInfoAndroid download_info,const GetDownloadInfoCB & callback,const std::string & cookie)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
StartDownloadOnUIThread(const GetDownloadInfoCB & callback,const DownloadInfoAndroid & info)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
StartAndroidDownload(int render_process_id,int render_view_id,const DownloadInfoAndroid & info)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
OnDownloadStarted(DownloadItem * download_item)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
OnDownloadUpdated(DownloadItem * item)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
OnDangerousDownload(DownloadItem * item)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
GetContentView(int render_process_id,int render_view_id)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>
GetContentViewCoreFromWebContents(WebContents * web_contents)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*
GetJavaObject()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
StartContextMenuDownload(const ContextMenuParams & params,WebContents * web_contents,bool is_link)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
DangerousDownloadValidated(WebContents * web_contents,int download_id,bool accept)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
DownloadInfoAndroid(net::URLRequest * request)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
~DownloadInfoAndroid()429 DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {}
430
431 } // namespace content
432