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 "ash/system/tray_update.h"
6
7#include "ash/root_window_controller.h"
8#include "ash/shelf/shelf_layout_manager.h"
9#include "ash/shelf/shelf_widget.h"
10#include "ash/shell.h"
11#include "ash/system/status_area_widget.h"
12#include "ash/system/tray/fixed_sized_image_view.h"
13#include "ash/system/tray/system_tray.h"
14#include "ash/system/tray/system_tray_delegate.h"
15#include "ash/system/tray/system_tray_notifier.h"
16#include "ash/system/tray/tray_constants.h"
17#include "base/time/time.h"
18#include "base/timer/timer.h"
19#include "grit/ash_resources.h"
20#include "grit/ash_strings.h"
21#include "ui/aura/window.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "ui/compositor/layer.h"
24#include "ui/compositor/layer_animation_observer.h"
25#include "ui/compositor/layer_animation_sequence.h"
26#include "ui/gfx/image/image.h"
27#include "ui/views/controls/image_view.h"
28#include "ui/views/controls/label.h"
29#include "ui/views/layout/box_layout.h"
30#include "ui/views/widget/widget.h"
31
32namespace {
33
34// How many seconds should we wait before showing the nag reminder?
35const int kUpdateNaggingTimeSeconds = 24 * 60 * 60;
36
37// How long should the nag reminder be displayed?
38const int kShowUpdateNaggerForSeconds = 15;
39
40int DecideResource(ash::UpdateObserver::UpdateSeverity severity, bool dark) {
41  switch (severity) {
42    case ash::UpdateObserver::UPDATE_NORMAL:
43      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK:
44                    IDR_AURA_UBER_TRAY_UPDATE;
45
46    case ash::UpdateObserver::UPDATE_LOW_GREEN:
47      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_GREEN :
48                    IDR_AURA_UBER_TRAY_UPDATE_GREEN;
49
50    case ash::UpdateObserver::UPDATE_HIGH_ORANGE:
51      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_ORANGE :
52                    IDR_AURA_UBER_TRAY_UPDATE_ORANGE;
53
54    case ash::UpdateObserver::UPDATE_SEVERE_RED:
55      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_RED :
56                    IDR_AURA_UBER_TRAY_UPDATE_RED;
57  }
58
59  NOTREACHED() << "Unknown update severity level.";
60  return 0;
61}
62
63class UpdateView : public ash::ActionableView {
64 public:
65  explicit UpdateView(ash::UpdateObserver::UpdateSeverity severity) {
66    SetLayoutManager(new
67        views::BoxLayout(views::BoxLayout::kHorizontal,
68        ash::kTrayPopupPaddingHorizontal, 0,
69        ash::kTrayPopupPaddingBetweenItems));
70
71    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
72    views::ImageView* image =
73        new ash::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
74    image->SetImage(bundle.GetImageNamed(DecideResource(severity, true)).
75        ToImageSkia());
76
77    AddChildView(image);
78    AddChildView(new views::Label(
79        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE)));
80    SetAccessibleName(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE));
81  }
82
83  virtual ~UpdateView() {}
84
85 private:
86  // Overridden from ActionableView.
87  virtual bool PerformAction(const ui::Event& event) OVERRIDE {
88    ash::Shell::GetInstance()->
89        system_tray_delegate()->RequestRestartForUpdate();
90    return true;
91  }
92
93  DISALLOW_COPY_AND_ASSIGN(UpdateView);
94};
95
96}
97
98namespace ash {
99namespace tray {
100
101class UpdateNagger : public ui::LayerAnimationObserver {
102 public:
103  explicit UpdateNagger(SystemTrayItem* owner)
104      : owner_(owner) {
105    RestartTimer();
106    owner_->system_tray()->GetWidget()->GetNativeView()->layer()->
107        GetAnimator()->AddObserver(this);
108  }
109
110  virtual ~UpdateNagger() {
111    StatusAreaWidget* status_area =
112        Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
113    if (status_area) {
114      status_area->system_tray()->GetWidget()->GetNativeView()->layer()->
115          GetAnimator()->RemoveObserver(this);
116    }
117  }
118
119  void RestartTimer() {
120    timer_.Stop();
121    timer_.Start(FROM_HERE,
122                 base::TimeDelta::FromSeconds(kUpdateNaggingTimeSeconds),
123                 this,
124                 &UpdateNagger::Nag);
125  }
126
127 private:
128  void Nag() {
129    owner_->PopupDetailedView(kShowUpdateNaggerForSeconds, false);
130  }
131
132  // Overridden from ui::LayerAnimationObserver.
133  virtual void OnLayerAnimationEnded(
134      ui::LayerAnimationSequence* sequence) OVERRIDE {
135    // TODO(oshima): Find out if the updator will be shown on non
136    // primary display.
137    if (Shell::GetPrimaryRootWindowController()->shelf()->IsVisible())
138      timer_.Stop();
139    else if (!timer_.IsRunning())
140      RestartTimer();
141  }
142
143  virtual void OnLayerAnimationAborted(
144      ui::LayerAnimationSequence* sequence) OVERRIDE {}
145
146  virtual void OnLayerAnimationScheduled(
147      ui::LayerAnimationSequence* sequence) OVERRIDE {}
148
149  SystemTrayItem* owner_;
150  base::OneShotTimer<UpdateNagger> timer_;
151
152  DISALLOW_COPY_AND_ASSIGN(UpdateNagger);
153};
154
155}  // namespace tray
156
157TrayUpdate::TrayUpdate(SystemTray* system_tray)
158    : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_UPDATE),
159      severity_(UpdateObserver::UPDATE_NORMAL) {
160  Shell::GetInstance()->system_tray_notifier()->AddUpdateObserver(this);
161}
162
163TrayUpdate::~TrayUpdate() {
164  Shell::GetInstance()->system_tray_notifier()->RemoveUpdateObserver(this);
165}
166
167bool TrayUpdate::GetInitialVisibility() {
168  return Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade();
169}
170
171views::View* TrayUpdate::CreateDefaultView(user::LoginStatus status) {
172  if (!Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade())
173    return NULL;
174  return new UpdateView(severity_);
175}
176
177views::View* TrayUpdate::CreateDetailedView(user::LoginStatus status) {
178  return CreateDefaultView(status);
179}
180
181void TrayUpdate::DestroyDetailedView() {
182  if (nagger_) {
183    // The nagger was being displayed. Now that the detailed view is being
184    // closed, that means either the user clicks on it to restart, or the user
185    // didn't click on it to restart. In either case, start the timer to show
186    // the nag reminder again after the specified time.
187    nagger_->RestartTimer();
188  }
189}
190
191void TrayUpdate::OnUpdateRecommended(UpdateObserver::UpdateSeverity severity) {
192  severity_ = severity;
193  SetImageFromResourceId(DecideResource(severity_, false));
194  tray_view()->SetVisible(true);
195  if (!Shell::GetPrimaryRootWindowController()->shelf()->IsVisible() &&
196      !nagger_.get()) {
197    // The shelf is not visible, and there is no nagger scheduled.
198    nagger_.reset(new tray::UpdateNagger(this));
199  }
200}
201
202}  // namespace ash
203