blob: 8dd03fd5a268d1c7afb1c110a4863671d313e53c [file] [log] [blame]
// Copyright 2018 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_ui_model.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/download/offline_item_utils.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "net/base/mime_util.h"
#include "third_party/blink/public/common/mime_util/mime_util.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"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/note_taking_helper.h"
#endif // defined(OS_CHROMEOS)
using base::TimeDelta;
using download::DownloadItem;
using safe_browsing::DownloadFileType;
using offline_items_collection::FailState;
namespace {
// TODO(qinmin): Migrate this description generator to OfflineItemUtils once
// that component gets used to build desktop UI.
base::string16 FailStateMessage(FailState fail_state) {
int string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
base::string16 status_text;
switch (fail_state) {
case FailState::FILE_ACCESS_DENIED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_ACCESS_DENIED;
break;
case FailState::FILE_NO_SPACE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_DISK_FULL;
break;
case FailState::FILE_NAME_TOO_LONG:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_PATH_TOO_LONG;
break;
case FailState::FILE_TOO_LARGE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_LARGE;
break;
case FailState::FILE_VIRUS_INFECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_VIRUS;
break;
case FailState::FILE_TRANSIENT_ERROR:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_TEMPORARY_PROBLEM;
break;
case FailState::FILE_BLOCKED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_BLOCKED;
break;
case FailState::FILE_SECURITY_CHECK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SECURITY_CHECK_FAILED;
break;
case FailState::FILE_TOO_SHORT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_SHORT;
break;
case FailState::FILE_SAME_AS_SOURCE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_SAME_AS_SOURCE;
break;
case FailState::NETWORK_INVALID_REQUEST:
case FailState::NETWORK_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_ERROR;
break;
case FailState::NETWORK_TIMEOUT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_TIMEOUT;
break;
case FailState::NETWORK_DISCONNECTED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_DISCONNECTED;
break;
case FailState::NETWORK_SERVER_DOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_DOWN;
break;
case FailState::SERVER_FAILED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_PROBLEM;
break;
case FailState::SERVER_BAD_CONTENT:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NO_FILE;
break;
case FailState::USER_CANCELED:
string_id = IDS_DOWNLOAD_STATUS_CANCELLED;
break;
case FailState::USER_SHUTDOWN:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SHUTDOWN;
break;
case FailState::CRASH:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_CRASH;
break;
case FailState::SERVER_UNAUTHORIZED:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_UNAUTHORIZED;
break;
case FailState::SERVER_CERT_PROBLEM:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_CERT_PROBLEM;
break;
case FailState::SERVER_FORBIDDEN:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FORBIDDEN;
break;
case FailState::SERVER_UNREACHABLE:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_UNREACHABLE;
break;
case FailState::SERVER_CONTENT_LENGTH_MISMATCH:
string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_CONTENT_LENGTH_MISMATCH;
break;
case FailState::NO_FAILURE:
NOTREACHED();
FALLTHROUGH;
// fallthrough
case FailState::CANNOT_DOWNLOAD:
case FailState::NETWORK_INSTABILITY:
case FailState::SERVER_NO_RANGE:
case FailState::SERVER_CROSS_ORIGIN_REDIRECT:
case FailState::FILE_FAILED:
case FailState::FILE_HASH_MISMATCH:
string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
}
status_text = l10n_util::GetStringUTF16(string_id);
return status_text;
}
} // namespace
DownloadUIModel::DownloadUIModel() = default;
DownloadUIModel::~DownloadUIModel() = default;
void DownloadUIModel::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DownloadUIModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool DownloadUIModel::HasSupportedImageMimeType() const {
if (blink::IsSupportedImageMimeType(GetMimeType()))
return true;
std::string mime;
base::FilePath::StringType extension_with_dot =
GetTargetFilePath().FinalExtension();
if (!extension_with_dot.empty() &&
net::GetWellKnownMimeTypeFromExtension(extension_with_dot.substr(1),
&mime) &&
blink::IsSupportedImageMimeType(mime)) {
return true;
}
return false;
}
base::string16 DownloadUIModel::GetProgressSizesString() const {
base::string16 size_ratio;
int64_t size = GetCompletedBytes();
int64_t 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 DownloadUIModel::GetInterruptReasonText() const {
if (GetState() != DownloadItem::INTERRUPTED ||
GetLastFailState() == FailState::USER_CANCELED) {
return base::string16();
}
return FailStateMessage(GetLastFailState());
}
base::string16 DownloadUIModel::GetStatusText() const {
base::string16 status_text;
switch (GetState()) {
case DownloadItem::IN_PROGRESS:
status_text = GetInProgressStatusString();
break;
case DownloadItem::COMPLETE:
if (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: {
FailState fail_state = GetLastFailState();
if (fail_state != FailState::USER_CANCELED) {
base::string16 message =
OfflineItemUtils::GetFailStateMessage(fail_state);
status_text = l10n_util::GetStringFUTF16(
IDS_DOWNLOAD_STATUS_INTERRUPTED, message);
} else {
// Same as DownloadItem::CANCELLED.
status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
}
break;
}
default:
NOTREACHED();
}
return status_text;
}
base::string16 DownloadUIModel::GetTooltipText(const gfx::FontList& font_list,
int max_width) const {
base::string16 tooltip = gfx::ElideFilename(
GetFileNameToReportUser(), font_list, max_width, gfx::Typesetter::NATIVE);
if (GetState() == DownloadItem::INTERRUPTED &&
GetLastFailState() != FailState::USER_CANCELED) {
tooltip += base::ASCIIToUTF16("\n");
tooltip += gfx::ElideText(
OfflineItemUtils::GetFailStateMessage(GetLastFailState()), font_list,
max_width, gfx::ELIDE_TAIL, gfx::Typesetter::NATIVE);
}
return tooltip;
}
base::string16 DownloadUIModel::GetWarningText(const gfx::FontList& font_list,
int base_width) const {
// Should only be called if IsDangerous().
DCHECK(IsDangerous());
base::string16 elided_filename =
gfx::ElideFilename(GetFileNameToReportUser(), font_list, base_width,
gfx::Typesetter::BROWSER);
switch (GetDangerType()) {
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: {
return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL);
}
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
if (IsExtensionDownload()) {
return l10n_util::GetStringUTF16(
IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
} else {
return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
elided_filename);
}
}
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
elided_filename);
}
case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
elided_filename);
}
case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
elided_filename);
}
case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
case download::DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY:
case download::DOWNLOAD_DANGER_TYPE_MAX: {
break;
}
}
NOTREACHED();
return base::string16();
}
base::string16 DownloadUIModel::GetWarningConfirmButtonText() const {
// Should only be called if IsDangerous()
DCHECK(IsDangerous());
if (GetDangerType() == download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE &&
IsExtensionDownload()) {
return l10n_util::GetStringUTF16(IDS_CONTINUE_EXTENSION_DOWNLOAD);
} else {
return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD);
}
}
ContentId DownloadUIModel::GetContentId() const {
NOTREACHED();
return ContentId();
}
Profile* DownloadUIModel::profile() const {
NOTREACHED();
return nullptr;
}
base::string16 DownloadUIModel::GetTabProgressStatusText() const {
return base::string16();
}
int64_t DownloadUIModel::GetCompletedBytes() const {
return 0;
}
int64_t DownloadUIModel::GetTotalBytes() const {
return 0;
}
int DownloadUIModel::PercentComplete() const {
return -1;
}
bool DownloadUIModel::IsDangerous() const {
return false;
}
bool DownloadUIModel::MightBeMalicious() const {
return false;
}
bool DownloadUIModel::IsMalicious() const {
return false;
}
bool DownloadUIModel::ShouldAllowDownloadFeedback() const {
return false;
}
bool DownloadUIModel::ShouldRemoveFromShelfWhenComplete() const {
return false;
}
bool DownloadUIModel::ShouldShowDownloadStartedAnimation() const {
return true;
}
bool DownloadUIModel::ShouldShowInShelf() const {
return true;
}
void DownloadUIModel::SetShouldShowInShelf(bool should_show) {}
bool DownloadUIModel::ShouldNotifyUI() const {
return true;
}
bool DownloadUIModel::WasUINotified() const {
return false;
}
void DownloadUIModel::SetWasUINotified(bool should_notify) {}
bool DownloadUIModel::ShouldPreferOpeningInBrowser() const {
return true;
}
void DownloadUIModel::SetShouldPreferOpeningInBrowser(bool preference) {}
DownloadFileType::DangerLevel DownloadUIModel::GetDangerLevel() const {
return DownloadFileType::NOT_DANGEROUS;
}
void DownloadUIModel::SetDangerLevel(
DownloadFileType::DangerLevel danger_level) {}
void DownloadUIModel::OpenUsingPlatformHandler() {}
bool DownloadUIModel::IsBeingRevived() const {
return true;
}
void DownloadUIModel::SetIsBeingRevived(bool is_being_revived) {}
DownloadItem* DownloadUIModel::download() {
return nullptr;
}
base::FilePath DownloadUIModel::GetFileNameToReportUser() const {
return base::FilePath();
}
base::FilePath DownloadUIModel::GetTargetFilePath() const {
return base::FilePath();
}
void DownloadUIModel::OpenDownload() {}
download::DownloadItem::DownloadState DownloadUIModel::GetState() const {
return download::DownloadItem::IN_PROGRESS;
}
bool DownloadUIModel::IsPaused() const {
return false;
}
download::DownloadDangerType DownloadUIModel::GetDangerType() const {
return download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
}
bool DownloadUIModel::GetOpenWhenComplete() const {
return false;
}
bool DownloadUIModel::TimeRemaining(base::TimeDelta* remaining) const {
return false;
}
bool DownloadUIModel::GetOpened() const {
return false;
}
void DownloadUIModel::SetOpened(bool opened) {}
bool DownloadUIModel::IsDone() const {
return false;
}
void DownloadUIModel::Pause() {}
void DownloadUIModel::Resume() {}
void DownloadUIModel::Cancel(bool user_cancel) {}
void DownloadUIModel::Remove() {}
void DownloadUIModel::SetOpenWhenComplete(bool open) {}
FailState DownloadUIModel::GetLastFailState() const {
return FailState::NO_FAILURE;
}
base::FilePath DownloadUIModel::GetFullPath() const {
return base::FilePath();
}
bool DownloadUIModel::CanResume() const {
return false;
}
bool DownloadUIModel::AllDataSaved() const {
return false;
}
bool DownloadUIModel::GetFileExternallyRemoved() const {
return false;
}
GURL DownloadUIModel::GetURL() const {
return GURL();
}
GURL DownloadUIModel::GetOriginalURL() const {
return GURL();
}
bool DownloadUIModel::ShouldPromoteOrigin() const {
return false;
}
#if !defined(OS_ANDROID)
bool DownloadUIModel::IsCommandEnabled(
const DownloadCommands* download_commands,
DownloadCommands::Command command) const {
switch (command) {
case DownloadCommands::SHOW_IN_FOLDER:
case DownloadCommands::OPEN_WHEN_COMPLETE:
case DownloadCommands::PLATFORM_OPEN:
case DownloadCommands::ALWAYS_OPEN_TYPE:
NOTREACHED();
return false;
case DownloadCommands::CANCEL:
return !IsDone();
case DownloadCommands::PAUSE:
return !IsDone() && !IsPaused() &&
GetState() == download::DownloadItem::IN_PROGRESS;
case DownloadCommands::RESUME:
return CanResume() &&
(IsPaused() || GetState() != download::DownloadItem::IN_PROGRESS);
case DownloadCommands::COPY_TO_CLIPBOARD:
return download_commands->CanBeCopiedToClipboard();
case DownloadCommands::ANNOTATE:
return GetState() == download::DownloadItem::COMPLETE;
case DownloadCommands::DISCARD:
case DownloadCommands::KEEP:
case DownloadCommands::LEARN_MORE_SCANNING:
case DownloadCommands::LEARN_MORE_INTERRUPTED:
return true;
}
NOTREACHED();
return false;
}
bool DownloadUIModel::IsCommandChecked(
const DownloadCommands* download_commands,
DownloadCommands::Command command) const {
switch (command) {
case DownloadCommands::OPEN_WHEN_COMPLETE:
case DownloadCommands::ALWAYS_OPEN_TYPE:
NOTREACHED();
return false;
case DownloadCommands::PAUSE:
case DownloadCommands::RESUME:
return IsPaused();
case DownloadCommands::SHOW_IN_FOLDER:
case DownloadCommands::PLATFORM_OPEN:
case DownloadCommands::CANCEL:
case DownloadCommands::DISCARD:
case DownloadCommands::KEEP:
case DownloadCommands::LEARN_MORE_SCANNING:
case DownloadCommands::LEARN_MORE_INTERRUPTED:
case DownloadCommands::COPY_TO_CLIPBOARD:
case DownloadCommands::ANNOTATE:
return false;
}
return false;
}
void DownloadUIModel::ExecuteCommand(DownloadCommands* download_commands,
DownloadCommands::Command command) {
switch (command) {
case DownloadCommands::SHOW_IN_FOLDER:
case DownloadCommands::OPEN_WHEN_COMPLETE:
case DownloadCommands::ALWAYS_OPEN_TYPE:
NOTREACHED();
break;
case DownloadCommands::PLATFORM_OPEN:
OpenUsingPlatformHandler();
break;
case DownloadCommands::CANCEL:
Cancel(true /* Cancelled by user */);
break;
case DownloadCommands::DISCARD:
Remove();
break;
case DownloadCommands::KEEP:
case DownloadCommands::LEARN_MORE_SCANNING:
NOTREACHED();
break;
case DownloadCommands::LEARN_MORE_INTERRUPTED:
download_commands->GetBrowser()->OpenURL(content::OpenURLParams(
download_commands->GetLearnMoreURLForInterruptedDownload(),
content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false));
break;
case DownloadCommands::PAUSE:
Pause();
break;
case DownloadCommands::RESUME:
Resume();
break;
case DownloadCommands::COPY_TO_CLIPBOARD:
download_commands->CopyFileAsImageToClipboard();
break;
case DownloadCommands::ANNOTATE:
#if defined(OS_CHROMEOS)
if (HasSupportedImageMimeType()) {
chromeos::NoteTakingHelper::Get()->LaunchAppForNewNote(
profile(), GetTargetFilePath());
}
#endif // defined(OS_CHROMEOS)
break;
}
}
#endif
std::string DownloadUIModel::GetMimeType() const {
return "text/html";
}
bool DownloadUIModel::IsExtensionDownload() const {
return false;
}
base::string16 DownloadUIModel::GetInProgressStatusString() const {
DCHECK_EQ(DownloadItem::IN_PROGRESS, GetState());
TimeDelta time_remaining;
// time_remaining is only known if the download isn't paused.
bool time_remaining_known = (!IsPaused() && 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 (AllDataSaved() && IsExtensionDownload()) {
return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING);
}
// A paused download: "100/120 MB, Paused"
if (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 (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::Simple(ui::TimeFormat::FORMAT_DURATION,
ui::TimeFormat::LENGTH_SHORT, 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::Simple(ui::TimeFormat::FORMAT_REMAINING,
ui::TimeFormat::LENGTH_SHORT, 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);
}