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
32 namespace {
33
34 // How many seconds should we wait before showing the nag reminder?
35 const int kUpdateNaggingTimeSeconds = 24 * 60 * 60;
36
37 // How long should the nag reminder be displayed?
38 const int kShowUpdateNaggerForSeconds = 15;
39
DecideResource(ash::UpdateObserver::UpdateSeverity severity,bool dark)40 int 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
63 class UpdateView : public ash::ActionableView {
64 public:
UpdateView(ash::UpdateObserver::UpdateSeverity severity)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
~UpdateView()83 virtual ~UpdateView() {}
84
85 private:
86 // Overridden from ActionableView.
PerformAction(const ui::Event & event)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
98 namespace ash {
99 namespace tray {
100
101 class UpdateNagger : public ui::LayerAnimationObserver {
102 public:
UpdateNagger(SystemTrayItem * owner)103 explicit UpdateNagger(SystemTrayItem* owner)
104 : owner_(owner) {
105 RestartTimer();
106 owner_->system_tray()->GetWidget()->GetNativeView()->layer()->
107 GetAnimator()->AddObserver(this);
108 }
109
~UpdateNagger()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
RestartTimer()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:
Nag()128 void Nag() {
129 owner_->PopupDetailedView(kShowUpdateNaggerForSeconds, false);
130 }
131
132 // Overridden from ui::LayerAnimationObserver.
OnLayerAnimationEnded(ui::LayerAnimationSequence * sequence)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
OnLayerAnimationAborted(ui::LayerAnimationSequence * sequence)143 virtual void OnLayerAnimationAborted(
144 ui::LayerAnimationSequence* sequence) OVERRIDE {}
145
OnLayerAnimationScheduled(ui::LayerAnimationSequence * sequence)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
TrayUpdate(SystemTray * system_tray)157 TrayUpdate::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
~TrayUpdate()163 TrayUpdate::~TrayUpdate() {
164 Shell::GetInstance()->system_tray_notifier()->RemoveUpdateObserver(this);
165 }
166
GetInitialVisibility()167 bool TrayUpdate::GetInitialVisibility() {
168 return Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade();
169 }
170
CreateDefaultView(user::LoginStatus status)171 views::View* TrayUpdate::CreateDefaultView(user::LoginStatus status) {
172 if (!Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade())
173 return NULL;
174 return new UpdateView(severity_);
175 }
176
CreateDetailedView(user::LoginStatus status)177 views::View* TrayUpdate::CreateDetailedView(user::LoginStatus status) {
178 return CreateDefaultView(status);
179 }
180
DestroyDetailedView()181 void 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
OnUpdateRecommended(UpdateObserver::UpdateSeverity severity)191 void 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