blob: 7a74fffa2f15f35e25ace0ae78229fc4243542a0 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/download/download_manager_service.h"
#include <memory>
#include "base/android/jni_string.h"
#include "base/location.h"
#include "base/metrics/field_trial_params.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/android/chrome_feature_list.h"
#include "chrome/browser/android/download/download_controller.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/download/public/common/download_item.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/notification_service.h"
#include "jni/DownloadInfo_jni.h"
#include "jni/DownloadItem_jni.h"
#include "jni/DownloadManagerService_jni.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
using base::android::JavaParamRef;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;
namespace {
// The remaining time for a download item if it cannot be calculated.
constexpr int64_t kUnknownRemainingTime = -1;
// Finch flag for controlling auto resumption limit.
int kDefaultAutoResumptionLimit = 5;
const char kAutoResumptionLimitParamName[] = "AutoResumptionLimit";
bool ShouldShowDownloadItem(download::DownloadItem* item) {
return !item->IsTemporary() && !item->IsTransient();
}
ScopedJavaLocalRef<jobject> JNI_DownloadManagerService_CreateJavaDownloadItem(
JNIEnv* env,
download::DownloadItem* item) {
DCHECK(!item->IsTransient());
return Java_DownloadItem_createDownloadItem(
env, DownloadManagerService::CreateJavaDownloadInfo(env, item),
item->GetStartTime().ToJavaTime(), item->GetFileExternallyRemoved());
}
class ServiceImpl : public service_manager::Service {
public:
ServiceImpl() = default;
~ServiceImpl() override = default;
private:
// service_manager::Service:
void OnStart() override {
DownloadManagerService::GetInstance()->NotifyServiceStarted(
context()->connector()->Clone());
}
DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
};
} // namespace
// static
void DownloadManagerService::OnDownloadCanceled(
download::DownloadItem* download,
DownloadController::DownloadCancelReason reason) {
if (download->IsTransient()) {
LOG(WARNING) << "Transient download should not have user interaction!";
return;
}
// Inform the user in Java UI about file writing failures.
bool has_no_external_storage =
(reason == DownloadController::CANCEL_REASON_NO_EXTERNAL_STORAGE);
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_item =
JNI_DownloadManagerService_CreateJavaDownloadItem(env, download);
Java_DownloadManagerService_onDownloadItemCanceled(env, j_item,
has_no_external_storage);
DownloadController::RecordDownloadCancelReason(reason);
}
// static
DownloadManagerService* DownloadManagerService::GetInstance() {
return base::Singleton<DownloadManagerService>::get();
}
// static
ScopedJavaLocalRef<jobject> DownloadManagerService::CreateJavaDownloadInfo(
JNIEnv* env,
download::DownloadItem* item) {
bool user_initiated =
(item->GetTransitionType() & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) ||
PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_TYPED) ||
PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_GENERATED) ||
PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_RELOAD) ||
PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_KEYWORD);
bool has_user_gesture = item->HasUserGesture() || user_initiated;
base::TimeDelta time_delta;
bool time_remaining_known = item->TimeRemaining(&time_delta);
std::string original_url = item->GetOriginalUrl().SchemeIs(url::kDataScheme)
? std::string() : item->GetOriginalUrl().spec();
content::BrowserContext* browser_context =
content::DownloadItemUtils::GetBrowserContext(item);
return Java_DownloadInfo_createDownloadInfo(
env, ConvertUTF8ToJavaString(env, item->GetGuid()),
ConvertUTF8ToJavaString(env, item->GetFileNameToReportUser().value()),
ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value()),
ConvertUTF8ToJavaString(env, item->GetTabUrl().spec()),
ConvertUTF8ToJavaString(env, item->GetMimeType()),
item->GetReceivedBytes(), item->GetTotalBytes(),
browser_context ? browser_context->IsOffTheRecord() : false,
item->GetState(), item->PercentComplete(), item->IsPaused(),
has_user_gesture, item->CanResume(), item->IsParallelDownload(),
ConvertUTF8ToJavaString(env, original_url),
ConvertUTF8ToJavaString(env, item->GetReferrerUrl().spec()),
time_remaining_known ? time_delta.InMilliseconds()
: kUnknownRemainingTime,
item->GetLastAccessTime().ToJavaTime(), item->IsDangerous());
}
static jlong JNI_DownloadManagerService_Init(JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jboolean is_full_browser_started) {
DownloadManagerService* service = DownloadManagerService::GetInstance();
service->Init(env, jobj);
if (is_full_browser_started)
service->OnFullBrowserStarted(env, jobj);
return reinterpret_cast<intptr_t>(service);
}
DownloadManagerService::DownloadManagerService()
: is_history_query_complete_(false),
pending_get_downloads_actions_(NONE) {
}
DownloadManagerService::~DownloadManagerService() {}
std::unique_ptr<service_manager::Service>
DownloadManagerService::CreateServiceManagerServiceInstance() {
return std::make_unique<ServiceImpl>();
}
void DownloadManagerService::NotifyServiceStarted(
std::unique_ptr<service_manager::Connector> connector) {
// TODO: Additional initialization, e.g. connect to Network Service using
// |connector| if the minimal download manager path is enabled by flag.
}
void DownloadManagerService::Init(
JNIEnv* env,
jobject obj) {
java_ref_.Reset(env, obj);
}
void DownloadManagerService::OnFullBrowserStarted(JNIEnv* env, jobject obj) {
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
Profile* profile = ProfileManager::GetActiveUserProfile();
DownloadCoreService* download_core_service =
DownloadCoreServiceFactory::GetForBrowserContext(profile);
DownloadHistory* history = download_core_service->GetDownloadHistory();
if (history)
history->AddObserver(this);
}
void DownloadManagerService::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_PROFILE_CREATED: {
Profile* profile = content::Source<Profile>(source).ptr();
content::DownloadManager* manager =
content::BrowserContext::GetDownloadManager(profile);
if (!manager)
break;
auto& notifier = profile->IsOffTheRecord() ? off_the_record_notifier_
: original_notifier_;
// Update notifiers to monitor any newly created DownloadManagers.
if (!notifier || notifier->GetManager() != manager) {
notifier =
std::make_unique<download::AllDownloadItemNotifier>(manager, this);
}
} break;
default:
NOTREACHED();
}
}
download::InProgressDownloadManager*
DownloadManagerService::RetriveInProgressDownloadManager(
content::BrowserContext* context) {
// TODO(qinmin): return pre-created InProgressDownloadManager here.
return nullptr;
}
void DownloadManagerService::ShowDownloadManager(bool show_prefetched_content) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_DownloadManagerService_showDownloadManager(
env, java_ref_, static_cast<jboolean>(show_prefetched_content));
}
void DownloadManagerService::OpenDownload(download::DownloadItem* download,
int source) {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_item =
JNI_DownloadManagerService_CreateJavaDownloadItem(env, download);
Java_DownloadManagerService_openDownloadItem(env, java_ref_, j_item, source);
}
void DownloadManagerService::OpenDownload(
JNIEnv* env,
jobject obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record,
jint source) {
if (!is_history_query_complete_)
return;
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (!item)
return;
OpenDownload(item, source);
}
void DownloadManagerService::ResumeDownload(
JNIEnv* env,
jobject obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record) {
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
if (is_history_query_complete_ || is_off_the_record)
ResumeDownloadInternal(download_guid, is_off_the_record);
else
EnqueueDownloadAction(download_guid, RESUME);
}
void DownloadManagerService::PauseDownload(
JNIEnv* env,
jobject obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record) {
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
if (is_history_query_complete_ || is_off_the_record)
PauseDownloadInternal(download_guid, is_off_the_record);
else
EnqueueDownloadAction(download_guid, PAUSE);
}
void DownloadManagerService::RemoveDownload(
JNIEnv* env,
jobject obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record) {
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
if (is_history_query_complete_ || is_off_the_record)
RemoveDownloadInternal(download_guid, is_off_the_record);
else
EnqueueDownloadAction(download_guid, REMOVE);
}
void DownloadManagerService::GetAllDownloads(JNIEnv* env,
const JavaParamRef<jobject>& obj,
bool is_off_the_record) {
if (is_history_query_complete_)
GetAllDownloadsInternal(is_off_the_record);
else if (is_off_the_record)
pending_get_downloads_actions_ |= OFF_THE_RECORD;
else
pending_get_downloads_actions_ |= REGULAR;
}
void DownloadManagerService::GetAllDownloadsInternal(bool is_off_the_record) {
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (java_ref_.is_null() || !manager)
return;
content::DownloadManager::DownloadVector all_items;
manager->GetAllDownloads(&all_items);
// Create a Java array of all of the visible DownloadItems.
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_download_item_list =
Java_DownloadManagerService_createDownloadItemList(env, java_ref_);
for (size_t i = 0; i < all_items.size(); i++) {
download::DownloadItem* item = all_items[i];
if (!ShouldShowDownloadItem(item))
continue;
ScopedJavaLocalRef<jobject> j_item =
JNI_DownloadManagerService_CreateJavaDownloadItem(env, item);
Java_DownloadManagerService_addDownloadItemToList(
env, java_ref_, j_download_item_list, j_item);
}
Java_DownloadManagerService_onAllDownloadsRetrieved(
env, java_ref_, j_download_item_list, is_off_the_record);
}
void DownloadManagerService::CheckForExternallyRemovedDownloads(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
bool is_off_the_record) {
// Once the history query is complete, download_history.cc will check for the
// removal of history files. If the history query is not yet complete, ignore
// requests to check for externally removed downloads.
if (!is_history_query_complete_)
return;
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
manager->CheckForHistoryFilesRemoval();
}
void DownloadManagerService::UpdateLastAccessTime(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record) {
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (item)
item->SetLastAccessTime(base::Time::Now());
}
void DownloadManagerService::CancelDownload(
JNIEnv* env,
jobject obj,
const JavaParamRef<jstring>& jdownload_guid,
bool is_off_the_record) {
std::string download_guid = ConvertJavaStringToUTF8(env, jdownload_guid);
DownloadController::RecordDownloadCancelReason(
DownloadController::CANCEL_REASON_ACTION_BUTTON);
if (is_history_query_complete_ || is_off_the_record)
CancelDownloadInternal(download_guid, is_off_the_record);
else
EnqueueDownloadAction(download_guid, CANCEL);
}
void DownloadManagerService::OnHistoryQueryComplete() {
is_history_query_complete_ = true;
for (auto iter = pending_actions_.begin(); iter != pending_actions_.end();
++iter) {
DownloadAction action = iter->second;
std::string download_guid = iter->first;
switch (action) {
case RESUME:
ResumeDownloadInternal(download_guid, false);
break;
case PAUSE:
PauseDownloadInternal(download_guid, false);
break;
case CANCEL:
CancelDownloadInternal(download_guid, false);
break;
default:
NOTREACHED();
break;
}
}
pending_actions_.clear();
// Respond to any requests to get all downloads.
if (pending_get_downloads_actions_ & REGULAR)
GetAllDownloadsInternal(false);
if (pending_get_downloads_actions_ & OFF_THE_RECORD)
GetAllDownloadsInternal(true);
}
void DownloadManagerService::OnDownloadCreated(
content::DownloadManager* manager,
download::DownloadItem* item) {
if (item->IsTransient())
return;
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_item =
JNI_DownloadManagerService_CreateJavaDownloadItem(env, item);
Java_DownloadManagerService_onDownloadItemCreated(env, java_ref_, j_item);
}
void DownloadManagerService::OnDownloadUpdated(
content::DownloadManager* manager,
download::DownloadItem* item) {
if (java_ref_.is_null())
return;
if (item->IsTemporary() || item->IsTransient())
return;
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_item =
JNI_DownloadManagerService_CreateJavaDownloadItem(env, item);
Java_DownloadManagerService_onDownloadItemUpdated(env, java_ref_, j_item);
}
void DownloadManagerService::OnDownloadRemoved(
content::DownloadManager* manager,
download::DownloadItem* item) {
if (java_ref_.is_null() || item->IsTransient())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_DownloadManagerService_onDownloadItemRemoved(
env, java_ref_, ConvertUTF8ToJavaString(env, item->GetGuid()),
content::DownloadItemUtils::GetBrowserContext(item)->IsOffTheRecord());
}
void DownloadManagerService::ResumeDownloadInternal(
const std::string& download_guid, bool is_off_the_record) {
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager) {
OnResumptionFailed(download_guid);
return;
}
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (!item) {
OnResumptionFailed(download_guid);
return;
}
if (!item->CanResume()) {
OnResumptionFailed(download_guid);
return;
}
DownloadControllerBase::Get()->AboutToResumeDownload(item);
item->Resume();
if (!resume_callback_for_testing_.is_null())
resume_callback_for_testing_.Run(true);
}
void DownloadManagerService::CancelDownloadInternal(
const std::string& download_guid, bool is_off_the_record) {
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (item) {
// Remove the observer first to avoid item->Cancel() causing re-entrance
// issue.
item->RemoveObserver(DownloadControllerBase::Get());
item->Cancel(true);
}
}
void DownloadManagerService::PauseDownloadInternal(
const std::string& download_guid, bool is_off_the_record) {
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (item) {
item->Pause();
item->RemoveObserver(DownloadControllerBase::Get());
}
}
void DownloadManagerService::RemoveDownloadInternal(
const std::string& download_guid, bool is_off_the_record) {
content::DownloadManager* manager = GetDownloadManager(is_off_the_record);
if (!manager)
return;
download::DownloadItem* item = manager->GetDownloadByGuid(download_guid);
if (item)
item->Remove();
}
void DownloadManagerService::EnqueueDownloadAction(
const std::string& download_guid,
DownloadAction action) {
auto iter = pending_actions_.find(download_guid);
if (iter == pending_actions_.end()) {
pending_actions_[download_guid] = action;
return;
}
switch (action) {
case RESUME:
if (iter->second == PAUSE)
iter->second = action;
break;
case PAUSE:
if (iter->second == RESUME)
iter->second = action;
break;
case CANCEL:
iter->second = action;
break;
case REMOVE:
iter->second = action;
break;
default:
NOTREACHED();
break;
}
}
void DownloadManagerService::OnResumptionFailed(
const std::string& download_guid) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&DownloadManagerService::OnResumptionFailedInternal,
base::Unretained(this), download_guid));
DownloadController::RecordDownloadCancelReason(
DownloadController::CANCEL_REASON_NOT_CANCELED);
}
void DownloadManagerService::OnResumptionFailedInternal(
const std::string& download_guid) {
if (!java_ref_.is_null()) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_DownloadManagerService_onResumptionFailed(
env, java_ref_, ConvertUTF8ToJavaString(env, download_guid));
}
if (!resume_callback_for_testing_.is_null())
resume_callback_for_testing_.Run(false);
}
content::DownloadManager* DownloadManagerService::GetDownloadManager(
bool is_off_the_record) {
Profile* profile = ProfileManager::GetActiveUserProfile();
if (is_off_the_record)
profile = profile->GetOffTheRecordProfile();
auto& notifier =
is_off_the_record ? off_the_record_notifier_ : original_notifier_;
content::DownloadManager* manager =
content::BrowserContext::GetDownloadManager(profile);
if (!manager) {
notifier.reset();
return nullptr;
}
// Update notifiers to monitor any newly created DownloadManagers.
if (!notifier || notifier->GetManager() != manager) {
notifier =
std::make_unique<download::AllDownloadItemNotifier>(manager, this);
}
return manager;
}
// static
jboolean JNI_DownloadManagerService_IsSupportedMimeType(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& jmime_type) {
std::string mime_type = ConvertJavaStringToUTF8(env, jmime_type);
return blink::IsSupportedMimeType(mime_type);
}
// static
jint JNI_DownloadManagerService_GetAutoResumptionLimit(
JNIEnv* env,
const JavaParamRef<jclass>& clazz) {
std::string value = base::GetFieldTrialParamValueByFeature(
chrome::android::kDownloadAutoResumptionThrottling,
kAutoResumptionLimitParamName);
int auto_resumption_limit;
return base::StringToInt(value, &auto_resumption_limit)
? auto_resumption_limit
: kDefaultAutoResumptionLimit;
}