Home | History | Annotate | Download | only in android
      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