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
35using base::android::ConvertUTF8ToJavaString;
36using base::android::ScopedJavaLocalRef;
37
38namespace content {
39
40// JNI methods
41static void Init(JNIEnv* env, jobject obj) {
42  DownloadControllerAndroidImpl::GetInstance()->Init(env, obj);
43}
44
45struct DownloadControllerAndroidImpl::JavaObject {
46  ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) {
47    return GetRealObject(env, obj);
48  }
49  jweak obj;
50};
51
52// static
53bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) {
54  return RegisterNativesImpl(env);
55}
56
57// static
58DownloadControllerAndroid* DownloadControllerAndroid::Get() {
59  return DownloadControllerAndroidImpl::GetInstance();
60}
61
62// static
63DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() {
64  return Singleton<DownloadControllerAndroidImpl>::get();
65}
66
67DownloadControllerAndroidImpl::DownloadControllerAndroidImpl()
68    : java_object_(NULL) {
69}
70
71DownloadControllerAndroidImpl::~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.
81void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) {
82  java_object_ = new JavaObject;
83  java_object_->obj = env->NewWeakGlobalRef(obj);
84}
85
86void 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
105void 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
137void 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
157void 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
178void 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
188void 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
196void 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
230void 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
256void 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
311void 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
324ScopedJavaLocalRef<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
338ScopedJavaLocalRef<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
349DownloadControllerAndroidImpl::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
363void 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
382void 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
397DownloadControllerAndroidImpl::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
415DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {}
416
417}  // namespace content
418