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 "chrome/browser/download/download_item_model.h"
6
7#include "base/i18n/number_formatting.h"
8#include "base/i18n/rtl.h"
9#include "base/metrics/field_trial.h"
10#include "base/strings/string16.h"
11#include "base/strings/sys_string_conversions.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/supports_user_data.h"
14#include "base/time/time.h"
15#include "chrome/browser/download/chrome_download_manager_delegate.h"
16#include "chrome/browser/download/download_crx_util.h"
17#include "chrome/browser/download/download_service.h"
18#include "chrome/browser/download/download_service_factory.h"
19#include "chrome/browser/download/download_stats.h"
20#include "chrome/browser/safe_browsing/download_feedback_service.h"
21#include "content/public/browser/download_danger_type.h"
22#include "content/public/browser/download_interrupt_reasons.h"
23#include "content/public/browser/download_item.h"
24#include "grit/chromium_strings.h"
25#include "grit/generated_resources.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/base/l10n/time_format.h"
28#include "ui/base/text/bytes_formatting.h"
29#include "ui/gfx/text_elider.h"
30
31using base::TimeDelta;
32using content::DownloadItem;
33
34namespace {
35
36// Per DownloadItem data used by DownloadItemModel. The model doesn't keep any
37// state since there could be multiple models associated with a single
38// DownloadItem, and the lifetime of the model is shorter than the DownloadItem.
39class DownloadItemModelData : public base::SupportsUserData::Data {
40 public:
41  // Get the DownloadItemModelData object for |download|. Returns NULL if
42  // there's no model data.
43  static const DownloadItemModelData* Get(const DownloadItem* download);
44
45  // Get the DownloadItemModelData object for |download|. Creates a model data
46  // object if not found. Always returns a non-NULL pointer, unless OOM.
47  static DownloadItemModelData* GetOrCreate(DownloadItem* download);
48
49  bool should_show_in_shelf() const { return should_show_in_shelf_; }
50  void set_should_show_in_shelf(bool should_show_in_shelf) {
51    should_show_in_shelf_ = should_show_in_shelf;
52  }
53
54  bool should_notify_ui() const { return should_notify_ui_; }
55  void set_should_notify_ui(bool should_notify_ui) {
56    should_notify_ui_ = should_notify_ui;
57  }
58
59  bool should_prefer_opening_in_browser() const {
60    return should_prefer_opening_in_browser_;
61  }
62  void set_should_prefer_opening_in_browser(bool preference) {
63    should_prefer_opening_in_browser_ = preference;
64  }
65
66 private:
67  DownloadItemModelData();
68  virtual ~DownloadItemModelData() {}
69
70  static const char kKey[];
71
72  // Whether the download should be displayed in the download shelf. True by
73  // default.
74  bool should_show_in_shelf_;
75
76  // Whether the UI should be notified when the download is ready to be
77  // presented.
78  bool should_notify_ui_;
79
80  // Whether the download should be opened in the browser vs. the system handler
81  // for the file type.
82  bool should_prefer_opening_in_browser_;
83};
84
85// static
86const char DownloadItemModelData::kKey[] = "DownloadItemModelData key";
87
88// static
89const DownloadItemModelData* DownloadItemModelData::Get(
90    const DownloadItem* download) {
91  return static_cast<const DownloadItemModelData*>(download->GetUserData(kKey));
92}
93
94// static
95DownloadItemModelData* DownloadItemModelData::GetOrCreate(
96    DownloadItem* download) {
97  DownloadItemModelData* data =
98      static_cast<DownloadItemModelData*>(download->GetUserData(kKey));
99  if (data == NULL) {
100    data = new DownloadItemModelData();
101    download->SetUserData(kKey, data);
102  }
103  return data;
104}
105
106DownloadItemModelData::DownloadItemModelData()
107    : should_show_in_shelf_(true),
108      should_notify_ui_(false),
109      should_prefer_opening_in_browser_(false) {
110}
111
112base::string16 InterruptReasonStatusMessage(int reason) {
113  int string_id = 0;
114
115  switch (reason) {
116    case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
117      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_ACCESS_DENIED;
118      break;
119    case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
120      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_DISK_FULL;
121      break;
122    case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
123      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_PATH_TOO_LONG;
124      break;
125    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
126      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_LARGE;
127      break;
128    case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
129      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_VIRUS;
130      break;
131    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
132      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_TEMPORARY_PROBLEM;
133      break;
134    case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
135      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_BLOCKED;
136      break;
137    case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
138      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SECURITY_CHECK_FAILED;
139      break;
140    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
141      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_FILE_TOO_SHORT;
142      break;
143    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
144      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_ERROR;
145      break;
146    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
147      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_TIMEOUT;
148      break;
149    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
150      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NETWORK_DISCONNECTED;
151      break;
152    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
153      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_DOWN;
154      break;
155    case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
156      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SERVER_PROBLEM;
157      break;
158    case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
159      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_NO_FILE;
160      break;
161    case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
162      string_id = IDS_DOWNLOAD_STATUS_CANCELLED;
163      break;
164    case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
165      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_SHUTDOWN;
166      break;
167    case content::DOWNLOAD_INTERRUPT_REASON_CRASH:
168      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS_CRASH;
169      break;
170    default:
171      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
172      break;
173  }
174
175  return l10n_util::GetStringUTF16(string_id);
176}
177
178base::string16 InterruptReasonMessage(int reason) {
179  int string_id = 0;
180  base::string16 status_text;
181
182  switch (reason) {
183    case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
184      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_ACCESS_DENIED;
185      break;
186    case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
187      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_DISK_FULL;
188      break;
189    case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
190      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_PATH_TOO_LONG;
191      break;
192    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
193      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_LARGE;
194      break;
195    case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
196      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_VIRUS;
197      break;
198    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
199      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_TEMPORARY_PROBLEM;
200      break;
201    case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
202      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_BLOCKED;
203      break;
204    case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
205      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SECURITY_CHECK_FAILED;
206      break;
207    case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
208      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_FILE_TOO_SHORT;
209      break;
210    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
211      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_ERROR;
212      break;
213    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
214      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_TIMEOUT;
215      break;
216    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
217      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NETWORK_DISCONNECTED;
218      break;
219    case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
220      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_DOWN;
221      break;
222    case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
223      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SERVER_PROBLEM;
224      break;
225    case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
226      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_NO_FILE;
227      break;
228    case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
229      string_id = IDS_DOWNLOAD_STATUS_CANCELLED;
230      break;
231    case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
232      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_SHUTDOWN;
233      break;
234    case content::DOWNLOAD_INTERRUPT_REASON_CRASH:
235      string_id = IDS_DOWNLOAD_INTERRUPTED_DESCRIPTION_CRASH;
236      break;
237    default:
238      string_id = IDS_DOWNLOAD_INTERRUPTED_STATUS;
239      break;
240  }
241
242  status_text = l10n_util::GetStringUTF16(string_id);
243
244  return status_text;
245}
246
247} // namespace
248
249// -----------------------------------------------------------------------------
250// DownloadItemModel
251
252DownloadItemModel::DownloadItemModel(DownloadItem* download)
253    : download_(download) {}
254
255DownloadItemModel::~DownloadItemModel() {}
256
257base::string16 DownloadItemModel::GetInterruptReasonText() const {
258  if (download_->GetState() != DownloadItem::INTERRUPTED ||
259      download_->GetLastReason() ==
260      content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
261    return base::string16();
262  }
263  return InterruptReasonMessage(download_->GetLastReason());
264}
265
266base::string16 DownloadItemModel::GetStatusText() const {
267  base::string16 status_text;
268  switch (download_->GetState()) {
269    case DownloadItem::IN_PROGRESS:
270      status_text = GetInProgressStatusString();
271      break;
272    case DownloadItem::COMPLETE:
273      if (download_->GetFileExternallyRemoved()) {
274        status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED);
275      } else {
276        status_text.clear();
277      }
278      break;
279    case DownloadItem::CANCELLED:
280      status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
281      break;
282    case DownloadItem::INTERRUPTED: {
283      content::DownloadInterruptReason reason = download_->GetLastReason();
284      if (reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
285        base::string16 interrupt_reason = InterruptReasonStatusMessage(reason);
286        status_text = l10n_util::GetStringFUTF16(
287            IDS_DOWNLOAD_STATUS_INTERRUPTED, interrupt_reason);
288      } else {
289        // Same as DownloadItem::CANCELLED.
290        status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
291      }
292      break;
293    }
294    default:
295      NOTREACHED();
296  }
297
298  return status_text;
299}
300
301base::string16 DownloadItemModel::GetTabProgressStatusText() const {
302  int64 total = GetTotalBytes();
303  int64 size = download_->GetReceivedBytes();
304  base::string16 received_size = ui::FormatBytes(size);
305  base::string16 amount = received_size;
306
307  // Adjust both strings for the locale direction since we don't yet know which
308  // string we'll end up using for constructing the final progress string.
309  base::i18n::AdjustStringForLocaleDirection(&amount);
310
311  if (total) {
312    base::string16 total_text = ui::FormatBytes(total);
313    base::i18n::AdjustStringForLocaleDirection(&total_text);
314
315    base::i18n::AdjustStringForLocaleDirection(&received_size);
316    amount = l10n_util::GetStringFUTF16(
317        IDS_DOWNLOAD_TAB_PROGRESS_SIZE, received_size, total_text);
318  } else {
319    amount.assign(received_size);
320  }
321  int64 current_speed = download_->CurrentSpeed();
322  base::string16 speed_text = ui::FormatSpeed(current_speed);
323  base::i18n::AdjustStringForLocaleDirection(&speed_text);
324
325  base::TimeDelta remaining;
326  base::string16 time_remaining;
327  if (download_->IsPaused())
328    time_remaining = l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED);
329  else if (download_->TimeRemaining(&remaining))
330    time_remaining = ui::TimeFormat::TimeRemaining(remaining);
331
332  if (time_remaining.empty()) {
333    base::i18n::AdjustStringForLocaleDirection(&amount);
334    return l10n_util::GetStringFUTF16(
335        IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN, speed_text, amount);
336  }
337  return l10n_util::GetStringFUTF16(
338      IDS_DOWNLOAD_TAB_PROGRESS_STATUS, speed_text, amount, time_remaining);
339}
340
341base::string16 DownloadItemModel::GetTooltipText(const gfx::FontList& font_list,
342                                                 int max_width) const {
343  base::string16 tooltip = gfx::ElideFilename(
344      download_->GetFileNameToReportUser(), font_list, max_width);
345  content::DownloadInterruptReason reason = download_->GetLastReason();
346  if (download_->GetState() == DownloadItem::INTERRUPTED &&
347      reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) {
348    tooltip += ASCIIToUTF16("\n");
349    tooltip += gfx::ElideText(InterruptReasonStatusMessage(reason),
350                             font_list, max_width, gfx::ELIDE_AT_END);
351  }
352  return tooltip;
353}
354
355base::string16 DownloadItemModel::GetWarningText(const gfx::FontList& font_list,
356                                                 int base_width) const {
357  // Should only be called if IsDangerous().
358  DCHECK(IsDangerous());
359  base::string16 elided_filename =
360      gfx::ElideFilename(download_->GetFileNameToReportUser(), font_list,
361                        base_width);
362  switch (download_->GetDangerType()) {
363    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: {
364      return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL);
365    }
366    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
367      if (download_crx_util::IsExtensionDownload(*download_)) {
368        return l10n_util::GetStringUTF16(
369            IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
370      } else {
371        return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
372                                          elided_filename);
373      }
374    }
375    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
376    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
377      return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
378                                        elided_filename);
379    }
380    case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
381      return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
382                                        elided_filename);
383    }
384    case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
385      return l10n_util::GetStringFUTF16(
386          IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, elided_filename);
387    }
388    case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
389    case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
390    case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
391    case content::DOWNLOAD_DANGER_TYPE_MAX: {
392      break;
393    }
394  }
395  NOTREACHED();
396  return base::string16();
397}
398
399base::string16 DownloadItemModel::GetWarningConfirmButtonText() const {
400  // Should only be called if IsDangerous()
401  DCHECK(IsDangerous());
402  if (download_->GetDangerType() ==
403          content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE &&
404      download_crx_util::IsExtensionDownload(*download_)) {
405    return l10n_util::GetStringUTF16(IDS_CONTINUE_EXTENSION_DOWNLOAD);
406  } else {
407    return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD);
408  }
409}
410
411int64 DownloadItemModel::GetCompletedBytes() const {
412  return download_->GetReceivedBytes();
413}
414
415int64 DownloadItemModel::GetTotalBytes() const {
416  return download_->AllDataSaved() ? download_->GetReceivedBytes() :
417                                     download_->GetTotalBytes();
418}
419
420// TODO(asanka,rdsmith): Once 'open' moves exclusively to the
421//     ChromeDownloadManagerDelegate, we should calculate the percentage here
422//     instead of calling into the DownloadItem.
423int DownloadItemModel::PercentComplete() const {
424  return download_->PercentComplete();
425}
426
427bool DownloadItemModel::IsDangerous() const {
428  return download_->IsDangerous();
429}
430
431bool DownloadItemModel::MightBeMalicious() const {
432  if (!IsDangerous())
433    return false;
434  switch (download_->GetDangerType()) {
435    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
436    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
437    case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
438    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
439    case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
440      return true;
441
442    case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
443    case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
444    case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
445    case content::DOWNLOAD_DANGER_TYPE_MAX:
446      // We shouldn't get any of these due to the IsDangerous() test above.
447      NOTREACHED();
448      // Fallthrough.
449    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
450      return false;
451  }
452  NOTREACHED();
453  return false;
454}
455
456// If you change this definition of malicious, also update
457// DownloadManagerImpl::NonMaliciousInProgressCount.
458bool DownloadItemModel::IsMalicious() const {
459  if (!MightBeMalicious())
460    return false;
461  switch (download_->GetDangerType()) {
462    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
463    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
464    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
465    case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
466      return true;
467
468    case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
469    case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
470    case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
471    case content::DOWNLOAD_DANGER_TYPE_MAX:
472    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
473      // We shouldn't get any of these due to the MightBeMalicious() test above.
474      NOTREACHED();
475      // Fallthrough.
476    case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
477      return false;
478  }
479  NOTREACHED();
480  return false;
481}
482
483bool DownloadItemModel::ShouldAllowDownloadFeedback() const {
484#if defined(FULL_SAFE_BROWSING)
485  if (!IsDangerous())
486    return false;
487  return safe_browsing::DownloadFeedbackService::IsEnabledForDownload(
488      *download_);
489#else
490  return false;
491#endif
492}
493
494bool DownloadItemModel::ShouldRemoveFromShelfWhenComplete() const {
495  switch (download_->GetState()) {
496    case DownloadItem::IN_PROGRESS:
497      // If the download is dangerous or malicious, we should display a warning
498      // on the shelf until the user accepts the download.
499      if (IsDangerous())
500        return false;
501
502      // If the download is an extension, temporary, or will be opened
503      // automatically, then it should be removed from the shelf on completion.
504      // TODO(asanka): The logic for deciding opening behavior should be in a
505      //               central location. http://crbug.com/167702
506      return (download_crx_util::IsExtensionDownload(*download_) ||
507              download_->IsTemporary() ||
508              download_->GetOpenWhenComplete() ||
509              download_->ShouldOpenFileBasedOnExtension());
510
511    case DownloadItem::COMPLETE:
512      // If the download completed, then rely on GetAutoOpened() to check for
513      // opening behavior. This should accurately reflect whether the download
514      // was successfully opened.  Extensions, for example, may fail to open.
515      return download_->GetAutoOpened() || download_->IsTemporary();
516
517    case DownloadItem::CANCELLED:
518    case DownloadItem::INTERRUPTED:
519      // Interrupted or cancelled downloads should remain on the shelf.
520      return false;
521
522    case DownloadItem::MAX_DOWNLOAD_STATE:
523      NOTREACHED();
524  }
525
526  NOTREACHED();
527  return false;
528}
529
530bool DownloadItemModel::ShouldShowDownloadStartedAnimation() const {
531  return !download_->IsSavePackageDownload() &&
532      !download_crx_util::IsExtensionDownload(*download_);
533}
534
535bool DownloadItemModel::ShouldShowInShelf() const {
536  const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
537  return !data || data->should_show_in_shelf();
538}
539
540void DownloadItemModel::SetShouldShowInShelf(bool should_show) {
541  DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
542  data->set_should_show_in_shelf(should_show);
543}
544
545bool DownloadItemModel::ShouldNotifyUI() const {
546  const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
547  return data && data->should_notify_ui();
548}
549
550void DownloadItemModel::SetShouldNotifyUI(bool should_notify) {
551  DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
552  data->set_should_notify_ui(should_notify);
553}
554
555bool DownloadItemModel::ShouldPreferOpeningInBrowser() const {
556  const DownloadItemModelData* data = DownloadItemModelData::Get(download_);
557  return data && data->should_prefer_opening_in_browser();
558}
559
560void DownloadItemModel::SetShouldPreferOpeningInBrowser(bool preference) {
561  DownloadItemModelData* data = DownloadItemModelData::GetOrCreate(download_);
562  data->set_should_prefer_opening_in_browser(preference);
563}
564
565base::string16 DownloadItemModel::GetProgressSizesString() const {
566  base::string16 size_ratio;
567  int64 size = GetCompletedBytes();
568  int64 total = GetTotalBytes();
569  if (total > 0) {
570    ui::DataUnits amount_units = ui::GetByteDisplayUnits(total);
571    base::string16 simple_size = ui::FormatBytesWithUnits(size, amount_units, false);
572
573    // In RTL locales, we render the text "size/total" in an RTL context. This
574    // is problematic since a string such as "123/456 MB" is displayed
575    // as "MB 123/456" because it ends with an LTR run. In order to solve this,
576    // we mark the total string as an LTR string if the UI layout is
577    // right-to-left so that the string "456 MB" is treated as an LTR run.
578    base::string16 simple_total = base::i18n::GetDisplayStringInLTRDirectionality(
579        ui::FormatBytesWithUnits(total, amount_units, true));
580    size_ratio = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_SIZES,
581                                            simple_size, simple_total);
582  } else {
583    size_ratio = ui::FormatBytes(size);
584  }
585  return size_ratio;
586}
587
588base::string16 DownloadItemModel::GetInProgressStatusString() const {
589  DCHECK_EQ(DownloadItem::IN_PROGRESS, download_->GetState());
590
591  TimeDelta time_remaining;
592  // time_remaining is only known if the download isn't paused.
593  bool time_remaining_known = (!download_->IsPaused() &&
594                               download_->TimeRemaining(&time_remaining));
595
596  // Indication of progress. (E.g.:"100/200 MB" or "100MB")
597  base::string16 size_ratio = GetProgressSizesString();
598
599  // The download is a CRX (app, extension, theme, ...) and it is being unpacked
600  // and validated.
601  if (download_->AllDataSaved() &&
602      download_crx_util::IsExtensionDownload(*download_)) {
603    return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING);
604  }
605
606  // A paused download: "100/120 MB, Paused"
607  if (download_->IsPaused()) {
608    return l10n_util::GetStringFUTF16(
609        IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio,
610        l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED));
611  }
612
613  // A download scheduled to be opened when complete: "Opening in 10 secs"
614  if (download_->GetOpenWhenComplete()) {
615    if (!time_remaining_known)
616      return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE);
617
618    return l10n_util::GetStringFUTF16(
619        IDS_DOWNLOAD_STATUS_OPEN_IN,
620        ui::TimeFormat::TimeRemainingShort(time_remaining));
621  }
622
623  // In progress download with known time left: "100/120 MB, 10 secs left"
624  if (time_remaining_known) {
625    return l10n_util::GetStringFUTF16(
626        IDS_DOWNLOAD_STATUS_IN_PROGRESS, size_ratio,
627        ui::TimeFormat::TimeRemaining(time_remaining));
628  }
629
630  // In progress download with no known time left and non-zero completed bytes:
631  // "100/120 MB" or "100 MB"
632  if (GetCompletedBytes() > 0)
633    return size_ratio;
634
635  // Instead of displaying "0 B" we say "Starting..."
636  return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING);
637}
638
639void DownloadItemModel::OpenUsingPlatformHandler() {
640  DownloadService* download_service =
641      DownloadServiceFactory::GetForBrowserContext(
642          download_->GetBrowserContext());
643  if (!download_service)
644    return;
645
646  ChromeDownloadManagerDelegate* delegate =
647      download_service->GetDownloadManagerDelegate();
648  if (!delegate)
649    return;
650  delegate->OpenDownloadUsingPlatformHandler(download_);
651  RecordDownloadOpenMethod(DOWNLOAD_OPEN_METHOD_USER_PLATFORM);
652}
653