// Copyright (c) 2012 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/download/download_item_model.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/rtl.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string16.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_crx_util.h"
#include "chrome/browser/download/download_service.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/safe_browsing/download_feedback_service.h"
#include "content/public/browser/download_danger_type.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_item.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "ui/base/text/bytes_formatting.h"
#include "ui/gfx/text_elider.h"
using base::TimeDelta;
using content::DownloadItem;
namespace {
// Per DownloadItem data used by DownloadItemModel. The model doesn't keep any
// state since there could be multiple models associated with a single
// DownloadItem, and the lifetime of the model is shorter than the DownloadItem.
class DownloadItemModelData : public base::SupportsUserData::Data {
public:
// Get the DownloadItemModelData object for |download|. Returns NULL if
// there's no model data.
static const DownloadItemModelData* Get(const DownloadItem* download);
// Get the DownloadItemModelData object for |download|. Creates a model data
// object if not found. Always returns a non-NULL pointer, unless OOM.
static DownloadItemModelData* GetOrCreate(DownloadItem* download);
bool should_show_in_shelf() const { return should_show_in_shelf_; }
void set_should_show_in_shelf(bool should_show_in_shelf) {
should_show_in_shelf_ = should_show_in_shelf;
}
bool should_notify_ui() const { return should_notify_ui_; }
void set_should_notify_ui(bool should_notify_ui) {
should_notify_ui_ = should_notify_ui;
}
bool should_prefer_opening_in_browser() const {
return should_prefer_opening_in_browser_;
}
void set_should_prefer_opening_in_browser(bool preference) {
should_prefer_opening_in_browser_ = preference;
}
private:
DownloadItemModelData();
virtual ~DownloadItemModelData() {}
static const char kKey[];
// Whether the download should be displayed in the download shelf. True by
// default.
bool should_show_in_shelf_;
// Whether the UI should be notified when the download is ready to be
// presented.
bool should_notify_ui_;
// Whether the download should be opened in the browser vs. the system handler
// for the file type.
bool should_prefer_opening_in_browser_;
};
// static
const char DownloadItemModelData::kKey[] = "DownloadItemModelData key";
// static
const DownloadItemModelData* DownloadItemModelData::Get(
const DownloadItem* download) {
return static_cast<const DownloadItemModelData*>(download->GetUserData(kKey));
}
// static
DownloadItemModelData* DownloadItemModelData::GetOrCreate(
DownloadItem* download) {
DownloadItemModelData* data =
static_cast<DownloadItemModelData*>(download->GetUserData(kKey));
if (data == NULL) {
data = new DownloadItemModelData();
download->SetUserData(kKey, data);
}
return data;
}
DownloadItemModelData::DownloadItemModelData()
: should_show_in_shelf_(true),
should_notify_ui_(false),
should_prefer_opening_in_browser_(false) {
}
base::string16 InterruptReasonStatusMessage(int reason) {
int string_id = 0;
switch (reason) {
case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_ACCESS_DENIED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_DISK_FULL;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_PATH_TOO_LONG;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_LARGE;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_VIRUS;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_TEMPORARY_PROBLEM;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_BLOCKED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SECURITY_CHECK_FAILED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_SHORT;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_ERROR;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_TIMEOUT;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_DISCONNECTED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_DOWN;
break;
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_PROBLEM;
break;
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NO_FILE;
break;
case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
string_id = IDS_DOWNLOAD_STATUS_CANCELLED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SHUTDOWN;
break;
case content::DOWNLOAD_INTERRUPT_REASON_CRASH:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_CRASH;
break;
default:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
break;
}
return l10n_util::GetStringUTF16(string_id);
}
base::string16 InterruptReasonMessage(int reason) {
int string_id = 0;
base::string16 status_text;
switch (reason) {
case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_ACCESS_DENIED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_DISK_FULL;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_PATH_TOO_LONG;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_LARGE;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_VIRUS;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_TEMPORARY_PROBLEM;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_BLOCKED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SECURITY_CHECK_FAILED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_SHORT;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_ERROR;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_TIMEOUT;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_DISCONNECTED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_DOWN;
break;
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_PROBLEM;
break;
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NO_FILE;
break;
case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
string_id = IDS_DOWNLOAD_STATUS_CANCELLED;
break;
case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SHUTDOWN;
break;
case content::DOWNLOAD_INTERRUPT_REASON_CRASH:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_CRASH;
break;
default:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
break;
}
status_text = l10n_util::GetStringUTF16(string_id);
return status_text;
}
} // namespace
// -----------------------------------------------------------------------------
// DownloadItemModel
DownloadItemModel::DownloadItemModel(DownloadItem* download)
: download_(download) {}
DownloadItemModel::~DownloadItemModel() {}
base::string16 DownloadItemModel::GetInterruptReasonText() const {
if (download_->GetState() != DownloadItem::INTERRUPTED ||
download_->GetLastReason() ==
content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
return base::string16();
}
return InterruptReasonMessage(download_->GetLastReason());
}
base::string16 DownloadItemModel::GetStatusText() const {
base::string16 status_text;
switch (download_->GetState()) {
case DownloadItem::IN_PROGRESS:
status_text = GetInProgressStatusString();
break;
case DownloadItem::COMPLETE:
if (download_->GetFileExternallyRemoved()) {
status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED);
} else {
status_text.clear();
}
break;
case DownloadItem::CANCELLED:
status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
break;
case DownloadItem::INTERRUPTED: {
content::DownloadInterruptReason reason = download_->GetLastReason();
if (reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
base::string16 interrupt_reason = InterruptReasonStatusMessage(reason);
status_text = l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_STATUS_INTERRUPTED, interrupt_reason);
} else {
// Same as DownloadItem::CANCELLED.
status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
}
break;
}
default:
NOTREACHED();
}
return status_text;
}
base::string16 DownloadItemModel::GetTabProgressStatusText() const {
int64 total = GetTotalBytes();
int64 size = download_->GetReceivedBytes();
base::string16 received_size = ui::FormatBytes(size);
base::string16 amount = received_size;
// Adjust both strings for the locale direction since we don't yet know which
// string we'll end up using for constructing the final progress string.
base::i18n::AdjustStringForLocaleDirection(&amount);
if (total) {
base::string16 total_text = ui::FormatBytes(total);
base::i18n::AdjustStringForLocaleDirection(&total_text);
base::i18n::AdjustStringForLocaleDirection(&received_size);
amount = l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_TAB_PROGRESS_SIZE, received_size, total_text);
} else {
amount.assign(received_size);
}
int64 current_speed = download_->CurrentSpeed();
base::string16 speed_text = ui::FormatSpeed(current_speed);
base::i18n::AdjustStringForLocaleDirection(&speed_text);
base::TimeDelta remaining;
base::string16 time_remaining;
if (download_->IsPaused())
time_remaining = l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED);
else if (download_->TimeRemaining(&remaining))
time_remaining = ui::TimeFormat::TimeRemaining(remaining);
if (time_remaining.empty()) {
base::i18n::AdjustStringForLocaleDirection(&amount);
return l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN, speed_text, amount);
}
return l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_TAB_PROGRESS_STATUS, speed_text, amount, time_remaining);
}
base::string16 DownloadItemModel::GetTooltipText(const gfx::FontList& font_list,
int max_width) const {
base::string16 tooltip = gfx::ElideFilename(
download_->GetFileNameToReportUser(), font_list, max_width);
content::DownloadInterruptReason reason = download_->GetLastReason();
if (download_->GetState() == DownloadItem::INTERRUPTED &&
reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
tooltip += ASCIIToUTF16("\n");
tooltip += gfx::ElideText(InterruptReasonStatusMessage(reason),
font_list, max_width, gfx::ELIDE_AT_END);
}
return tooltip;
}
base::string16 DownloadItemModel::GetWarningText(const gfx::FontList& font_list,
int base_width) const {
// Should only be called if IsDangerous().
DCHECK(IsDangerous());
base::string16 elided_filename =
gfx::ElideFilename(download_->GetFileNameToReportUser(), font_list,
base_width);
switch (download_->GetDangerType()) {
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: {
return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL);
}
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
if (download_crx_util::IsExtensionDownload(*download_)) {
return l10n_util::GetStringUTF16(
IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
} else {
return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
elided_filename);
}
}
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
elided_filename);
}
case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
elided_filename);
}
case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
return l10n_util::GetStringFUTF16(
IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, elided_filename);
}
case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
case content::DOWNLOAD_DANGER_TYPE_MAX: {
break;
}
}
NOTREACHED();
return base::string16();
}
base::string16 DownloadItemModel::GetWarningConfirmButtonText() const {
// Should only be called if IsDangerous()
DCHECK(IsDangerous());
if (download_->GetDangerType() ==
content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE &&
download_crx_util::IsExtensionDownload(*download_)) {
return l10n_util::GetStringUTF16(IDS_CONTINUE_EXTENSION_DOWNLOAD);
} else {
return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD);
}
}
int64 DownloadItemModel::GetCompletedBytes() const {
return download_->GetReceivedBytes();
}
int64 DownloadItemModel::GetTotalBytes() const {
return download_->AllDataSaved() ? download_->GetReceivedBytes() :
download_->GetTotalBytes();
}
// TODO(asanka,rdsmith): Once 'open' moves exclusively to the
// ChromeDownloadManagerDelegate, we should calculate the percentage here
// instead of calling into the DownloadItem.
int DownloadItemModel::PercentComplete() const {
return download_->PercentComplete();
}
bool DownloadItemModel::IsDangerous() const {
return download_->IsDangerous();
}
bool DownloadItemModel::MightBeMalicious() const {
if (!IsDangerous())
return false;
switch (download_->GetDangerType()) {
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
return true;
case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
case content::DOWNLOAD_DANGER_TYPE_MAX:
// We shouldn't get any of these due to the IsDangerous() test above.
NOTREACHED();
// Fallthrough.
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
return false;
}
NOTREACHED();
return false;
}
// If you change this definition of malicious, also update
// DownloadManagerImpl::NonMaliciousInProgressCount.
bool DownloadItemModel::IsMalicious() const {
if (!MightBeMalicious())
return false;
switch (download_->GetDangerType()) {
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
return true;
case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
case content::DOWNLOAD_DANGER_TYPE_MAX:
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
// We shouldn't get any of these due to the MightBeMalicious() test above.
NOTREACHED();
// Fallthrough.
case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
return false;
}
NOTREACHED();
return false;
}
bool DownloadItemModel::ShouldAllowDownloadFeedback() const {
#if defined(FULL_SAFE_BROWSING)
if (!IsDangerous())
return false;
return safe_browsing::DownloadFeedbackService::IsEnabledForDownload(
*download_);
#else
return false;
#endif
}
bool DownloadItemModel::ShouldRemoveFromShelfWhenComplete() const {
switch (download_->GetState()) {
case DownloadItem::IN_PROGRESS:
// If the download is dangerous or malicious, we should display a warning
// on the shelf until the user accepts the download.
if (IsDangerous())
return false;
// If the download is an extension, temporary, or will be opened
// automatically, then it should be removed from the shelf on completion.
// TODO(asanka): The logic for deciding opening behavior should be in a
// central location. http://crbug.com/167702
return (download_crx_util::IsExtensionDownload(*download_) ||
download_->IsTemporary() ||
download_->GetOpenWhenComplete() ||
download_->ShouldOpenFileBasedOnExtension());
case DownloadItem::COMPLETE:
// If the download completed, then rely on GetAutoOpened() to check for
// opening behavior. This should accurately reflect whether the download
// was successfully opened. Extensions, for example, may fail to open.
return download_->GetAutoOpened() || download_->IsTemporary();
case DownloadItem::CANCELLED:
case DownloadItem::INTERRUPTED:
// Interrupted or cancelled downloads should remain on the shelf.
return false;
case DownloadItem::MAX_DOWNLOAD_STATE:
NOTREACHED();
}
NOTREACHED();
return false;
}
bool DownloadItemModel::ShouldShowDownloadStartedAnimation() const {
return !download_->IsSavePackageDownload() &&
!download_crx_util::IsExtensionDownload(*download_);
}
bool DownloadItemModel::ShouldShowInShelf() const {
const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
return !data || data->should_show_in_shelf();
}
void DownloadItemModel::SetShouldShowInShelf(bool should_show) {
DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
data->set_should_show_in_shelf(should_show);
}
bool DownloadItemModel::ShouldNotifyUI() const {
const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
return data && data->should_notify_ui();
}
void DownloadItemModel::SetShouldNotifyUI(bool should_notify) {
DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
data->set_should_notify_ui(should_notify);
}
bool DownloadItemModel::ShouldPreferOpeningInBrowser() const {
const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
return data && data->should_prefer_opening_in_browser();
}
void DownloadItemModel::SetShouldPreferOpeningInBrowser(bool preference) {
DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
data->set_should_prefer_opening_in_browser(preference);
}
base::string16 DownloadItemModel::GetProgressSizesString() const {
base::string16 size_ratio;
int64 size = GetCompletedBytes();
int64 total = GetTotalBytes();
if (total > 0) {
ui::DataUnits amount_units = ui::GetByteDisplayUnits(total);
base::string16 simple_size = ui::FormatBytesWithUnits(size, amount_units, false);
// In RTL locales, we render the text "size/total" in an RTL context. This
// is problematic since a string such as "123/456 MB" is displayed
// as "MB 123/456" because it ends with an LTR run. In order to solve this,
// we mark the total string as an LTR string if the UI layout is
// right-to-left so that the string "456 MB" is treated as an LTR run.
base::string16 simple_total = base::i18n::GetDisplayStringInLTRDirectionality(
ui::FormatBytesWithUnits(total, amount_units, true));
size_ratio = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_SIZES,
simple_size, simple_total);
} else {
size_ratio = ui::FormatBytes(size);
}
return size_ratio;
}
base::string16 DownloadItemModel::GetInProgressStatusString() const {
DCHECK_EQ(DownloadItem::IN_PROGRESS, download_->GetState());
TimeDelta time_remaining;
// time_remaining is only known if the download isn't paused.
bool time_remaining_known = (!download_->IsPaused() &&
download_->TimeRemaining(&time_remaining));
// Indication of progress. (E.g.:"100/200 MB" or "100MB")
base::string16 size_ratio = GetProgressSizesString();
// The download is a CRX (app, extension, theme, ...) and it is being unpacked
// and validated.
if (download_->AllDataSaved() &&
download_crx_util::IsExtensionDownload(*download_)) {
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING);
}
// A paused download: "100/120 MB, Paused"
if (download_->IsPaused()) {
return l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio,
l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED));
}
// A download scheduled to be opened when complete: "Opening in 10 secs"
if (download_->GetOpenWhenComplete()) {
if (!time_remaining_known)
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE);
return l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_STATUS_OPEN_IN,
ui::TimeFormat::TimeRemainingShort(time_remaining));
}
// In progress download with known time left: "100/120 MB, 10 secs left"
if (time_remaining_known) {
return l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio,
ui::TimeFormat::TimeRemaining(time_remaining));
}
// In progress download with no known time left and non-zero completed bytes:
// "100/120 MB" or "100 MB"
if (GetCompletedBytes() > 0)
return size_ratio;
// Instead of displaying "0 B" we say "Starting..."
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING);
}
void DownloadItemModel::OpenUsingPlatformHandler() {
DownloadService* download_service =
DownloadServiceFactory::GetForBrowserContext(
download_->GetBrowserContext());
if (!download_service)
return;
ChromeDownloadManagerDelegate* delegate =
download_service->GetDownloadManagerDelegate();
if (!delegate)
return;
delegate->OpenDownloadUsingPlatformHandler(download_);
RecordDownloadOpenMethod(DOWNLOAD_OPEN_METHOD_USER_PLATFORM);
}