1 // Copyright (c) 2011 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/chromeos/status/network_menu_button.h"
6
7 #include <algorithm>
8 #include <limits>
9
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "base/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chromeos/cros/cros_library.h"
17 #include "chrome/browser/chromeos/login/helper.h"
18 #include "chrome/browser/chromeos/login/user_manager.h"
19 #include "chrome/browser/chromeos/options/network_config_view.h"
20 #include "chrome/browser/chromeos/sim_dialog_delegate.h"
21 #include "chrome/browser/chromeos/status/status_area_host.h"
22 #include "chrome/browser/prefs/pref_service.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_list.h"
26 #include "chrome/common/pref_names.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/gfx/canvas_skia.h"
32 #include "views/window/window.h"
33
34 namespace {
35
36 // Time in milliseconds to delay showing of promo
37 // notification when Chrome window is not on screen.
38 const int kPromoShowDelayMs = 10000;
39
40 const int kNotificationCountPrefDefault = -1;
41
GetBooleanPref(const char * pref_name)42 bool GetBooleanPref(const char* pref_name) {
43 Browser* browser = BrowserList::GetLastActive();
44 // Default to safe value which is false (not to show bubble).
45 if (!browser || !browser->profile())
46 return false;
47
48 PrefService* prefs = browser->profile()->GetPrefs();
49 return prefs->GetBoolean(pref_name);
50 }
51
GetIntegerPref(const char * pref_name)52 int GetIntegerPref(const char* pref_name) {
53 Browser* browser = BrowserList::GetLastActive();
54 // Default to "safe" value.
55 if (!browser || !browser->profile())
56 return kNotificationCountPrefDefault;
57
58 PrefService* prefs = browser->profile()->GetPrefs();
59 return prefs->GetInteger(pref_name);
60 }
61
SetBooleanPref(const char * pref_name,bool value)62 void SetBooleanPref(const char* pref_name, bool value) {
63 Browser* browser = BrowserList::GetLastActive();
64 if (!browser || !browser->profile())
65 return;
66
67 PrefService* prefs = browser->profile()->GetPrefs();
68 prefs->SetBoolean(pref_name, value);
69 }
70
SetIntegerPref(const char * pref_name,int value)71 void SetIntegerPref(const char* pref_name, int value) {
72 Browser* browser = BrowserList::GetLastActive();
73 if (!browser || !browser->profile())
74 return;
75
76 PrefService* prefs = browser->profile()->GetPrefs();
77 prefs->SetInteger(pref_name, value);
78 }
79
80 // Returns prefs::kShow3gPromoNotification or false
81 // if there's no active browser.
ShouldShow3gPromoNotification()82 bool ShouldShow3gPromoNotification() {
83 return GetBooleanPref(prefs::kShow3gPromoNotification);
84 }
85
SetShow3gPromoNotification(bool value)86 void SetShow3gPromoNotification(bool value) {
87 SetBooleanPref(prefs::kShow3gPromoNotification, value);
88 }
89 // Returns prefs::kCarrierDealPromoShown which is number of times
90 // carrier deal notification has been shown to user or -1
91 // if there's no active browser.
GetCarrierDealPromoShown()92 int GetCarrierDealPromoShown() {
93 return GetIntegerPref(prefs::kCarrierDealPromoShown);
94 }
95
SetCarrierDealPromoShown(int value)96 void SetCarrierDealPromoShown(int value) {
97 SetIntegerPref(prefs::kCarrierDealPromoShown, value);
98 }
99
100 } // namespace
101
102 namespace chromeos {
103
104 ////////////////////////////////////////////////////////////////////////////////
105 // NetworkMenuButton
106
107 // static
108 const int NetworkMenuButton::kThrobDuration = 750;
109
NetworkMenuButton(StatusAreaHost * host)110 NetworkMenuButton::NetworkMenuButton(StatusAreaHost* host)
111 : StatusAreaButton(host, this),
112 NetworkMenu(),
113 icon_(NULL),
114 right_badge_(NULL),
115 left_badge_(NULL),
116 mobile_data_bubble_(NULL),
117 check_for_promo_(true),
118 was_sim_locked_(false),
119 ALLOW_THIS_IN_INITIALIZER_LIST(animation_connecting_(this)),
120 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
121 animation_connecting_.SetThrobDuration(kThrobDuration);
122 animation_connecting_.SetTweenType(ui::Tween::LINEAR);
123 NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
124 OnNetworkManagerChanged(network_library);
125 network_library->AddNetworkManagerObserver(this);
126 network_library->AddCellularDataPlanObserver(this);
127 const NetworkDevice* cellular = network_library->FindCellularDevice();
128 if (cellular) {
129 cellular_device_path_ = cellular->device_path();
130 was_sim_locked_ = cellular->is_sim_locked();
131 network_library->AddNetworkDeviceObserver(cellular_device_path_, this);
132 }
133 }
134
~NetworkMenuButton()135 NetworkMenuButton::~NetworkMenuButton() {
136 NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
137 netlib->RemoveNetworkManagerObserver(this);
138 netlib->RemoveObserverForAllNetworks(this);
139 netlib->RemoveCellularDataPlanObserver(this);
140 if (!cellular_device_path_.empty())
141 netlib->RemoveNetworkDeviceObserver(cellular_device_path_, this);
142 if (mobile_data_bubble_)
143 mobile_data_bubble_->Close();
144 }
145
146 ////////////////////////////////////////////////////////////////////////////////
147 // NetworkMenuButton, ui::AnimationDelegate implementation:
148
AnimationProgressed(const ui::Animation * animation)149 void NetworkMenuButton::AnimationProgressed(const ui::Animation* animation) {
150 if (animation == &animation_connecting_) {
151 SetIconOnly(IconForNetworkConnecting(
152 animation_connecting_.GetCurrentValue(), false));
153 // No need to set the badge here, because it should already be set.
154 SchedulePaint();
155 } else {
156 MenuButton::AnimationProgressed(animation);
157 }
158 }
159
160 ////////////////////////////////////////////////////////////////////////////////
161 // NetworkLibrary::NetworkDeviceObserver implementation:
162
OnNetworkDeviceChanged(NetworkLibrary * cros,const NetworkDevice * device)163 void NetworkMenuButton::OnNetworkDeviceChanged(NetworkLibrary* cros,
164 const NetworkDevice* device) {
165 // Device status, such as SIMLock may have changed.
166 OnNetworkChanged(cros, cros->active_network());
167 const NetworkDevice* cellular = cros->FindCellularDevice();
168 if (cellular) {
169 // We make an assumption (which is valid for now) that the SIM
170 // unlock dialog is put up only when the user is trying to enable
171 // mobile data. So if the SIM is now unlocked, initiate the
172 // enable operation that the user originally requested.
173 if (was_sim_locked_ && !cellular->is_sim_locked() &&
174 !cros->cellular_enabled()) {
175 cros->EnableCellularNetworkDevice(true);
176 }
177 was_sim_locked_ = cellular->is_sim_locked();
178 }
179 }
180
181 ////////////////////////////////////////////////////////////////////////////////
182 // NetworkMenuButton, NetworkLibrary::NetworkManagerObserver implementation:
183
OnNetworkManagerChanged(NetworkLibrary * cros)184 void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) {
185 OnNetworkChanged(cros, cros->active_network());
186 ShowOptionalMobileDataPromoNotification(cros);
187 }
188
189 ////////////////////////////////////////////////////////////////////////////////
190 // NetworkMenuButton, NetworkLibrary::NetworkObserver implementation:
OnNetworkChanged(NetworkLibrary * cros,const Network * network)191 void NetworkMenuButton::OnNetworkChanged(NetworkLibrary* cros,
192 const Network* network) {
193 // This gets called on initialization, so any changes should be reflected
194 // in CrosMock::SetNetworkLibraryStatusAreaExpectations().
195 SetNetworkIcon(cros, network);
196 RefreshNetworkObserver(cros);
197 RefreshNetworkDeviceObserver(cros);
198 SchedulePaint();
199 UpdateMenu();
200 }
201
OnCellularDataPlanChanged(NetworkLibrary * cros)202 void NetworkMenuButton::OnCellularDataPlanChanged(NetworkLibrary* cros) {
203 // Call OnNetworkManagerChanged which will update the icon.
204 OnNetworkManagerChanged(cros);
205 }
206
207 ////////////////////////////////////////////////////////////////////////////////
208 // NetworkMenuButton, NetworkMenu implementation:
209
IsBrowserMode() const210 bool NetworkMenuButton::IsBrowserMode() const {
211 return host_->GetScreenMode() == StatusAreaHost::kBrowserMode;
212 }
213
GetNativeWindow() const214 gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const {
215 return host_->GetNativeWindow();
216 }
217
OpenButtonOptions()218 void NetworkMenuButton::OpenButtonOptions() {
219 host_->OpenButtonOptions(this);
220 }
221
ShouldOpenButtonOptions() const222 bool NetworkMenuButton::ShouldOpenButtonOptions() const {
223 return host_->ShouldOpenButtonOptions(this);
224 }
225
226 ////////////////////////////////////////////////////////////////////////////////
227 // NetworkMenuButton, views::View implementation:
228
OnLocaleChanged()229 void NetworkMenuButton::OnLocaleChanged() {
230 NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary();
231 SetNetworkIcon(lib, lib->active_network());
232 }
233
234 ////////////////////////////////////////////////////////////////////////////////
235 // MessageBubbleDelegate implementation:
236
OnHelpLinkActivated()237 void NetworkMenuButton::OnHelpLinkActivated() {
238 // mobile_data_bubble_ will be set to NULL in callback.
239 if (mobile_data_bubble_)
240 mobile_data_bubble_->Close();
241 if (!deal_url_.empty()) {
242 Browser* browser = BrowserList::GetLastActive();
243 if (!browser)
244 return;
245 browser->ShowSingletonTab(GURL(deal_url_));
246 deal_url_.clear();
247 } else {
248 const Network* cellular =
249 CrosLibrary::Get()->GetNetworkLibrary()->cellular_network();
250 if (!cellular)
251 return;
252 ShowTabbedNetworkSettings(cellular);
253 }
254 }
255
256 ////////////////////////////////////////////////////////////////////////////////
257 // NetworkMenuButton, private methods
258
259 const ServicesCustomizationDocument::CarrierDeal*
GetCarrierDeal(NetworkLibrary * cros)260 NetworkMenuButton::GetCarrierDeal(
261 NetworkLibrary* cros) {
262 std::string carrier_id = cros->GetCellularHomeCarrierId();
263 if (carrier_id.empty()) {
264 LOG(ERROR) << "Empty carrier ID with a cellular connected.";
265 return NULL;
266 }
267
268 ServicesCustomizationDocument* customization =
269 ServicesCustomizationDocument::GetInstance();
270 if (!customization->IsReady())
271 return NULL;
272
273 const ServicesCustomizationDocument::CarrierDeal* deal =
274 customization->GetCarrierDeal(carrier_id, true);
275 if (deal) {
276 // Check deal for validity.
277 int carrier_deal_promo_pref = GetCarrierDealPromoShown();
278 if (carrier_deal_promo_pref >= deal->notification_count)
279 return NULL;
280 const std::string locale = g_browser_process->GetApplicationLocale();
281 std::string deal_text = deal->GetLocalizedString(locale,
282 "notification_text");
283 if (deal_text.empty())
284 return NULL;
285 }
286 return deal;
287 }
288
SetIconAndBadges(const SkBitmap * icon,const SkBitmap * right_badge,const SkBitmap * left_badge)289 void NetworkMenuButton::SetIconAndBadges(const SkBitmap* icon,
290 const SkBitmap* right_badge,
291 const SkBitmap* left_badge) {
292 icon_ = icon;
293 right_badge_ = right_badge;
294 left_badge_ = left_badge;
295 SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
296 left_badge_));
297 }
298
SetIconOnly(const SkBitmap * icon)299 void NetworkMenuButton::SetIconOnly(const SkBitmap* icon) {
300 icon_ = icon;
301 SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
302 left_badge_));
303 }
304
SetBadgesOnly(const SkBitmap * right_badge,const SkBitmap * left_badge)305 void NetworkMenuButton::SetBadgesOnly(const SkBitmap* right_badge,
306 const SkBitmap* left_badge) {
307 right_badge_ = right_badge;
308 left_badge_ = left_badge;
309 SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
310 left_badge_));
311 }
312
SetNetworkIcon(NetworkLibrary * cros,const Network * network)313 void NetworkMenuButton::SetNetworkIcon(NetworkLibrary* cros,
314 const Network* network) {
315 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
316
317 if (!cros || !CrosLibrary::Get()->EnsureLoaded()) {
318 SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
319 rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_WARNING),
320 NULL);
321 SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
322 IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
323 return;
324 }
325
326 if (!cros->Connected() && !cros->Connecting()) {
327 animation_connecting_.Stop();
328 SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
329 rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_DISCONNECTED),
330 NULL);
331 SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
332 IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
333 return;
334 }
335
336 if (cros->wifi_connecting() || cros->cellular_connecting()) {
337 // Start the connecting animation if not running.
338 if (!animation_connecting_.is_animating()) {
339 animation_connecting_.Reset();
340 animation_connecting_.StartThrobbing(-1);
341 SetIconOnly(IconForNetworkConnecting(0, false));
342 }
343 const WirelessNetwork* wireless = NULL;
344 if (cros->wifi_connecting()) {
345 wireless = cros->wifi_network();
346 SetBadgesOnly(NULL, NULL);
347 } else { // cellular_connecting
348 wireless = cros->cellular_network();
349 SetBadgesOnly(BadgeForNetworkTechnology(cros->cellular_network()), NULL);
350 }
351 SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
352 wireless->configuring() ? IDS_STATUSBAR_NETWORK_CONFIGURING_TOOLTIP
353 : IDS_STATUSBAR_NETWORK_CONNECTING_TOOLTIP,
354 UTF8ToUTF16(wireless->name()))));
355 } else {
356 // Stop connecting animation since we are not connecting.
357 animation_connecting_.Stop();
358 // Only set the icon, if it is an active network that changed.
359 if (network && network->is_active()) {
360 const SkBitmap* right_badge(NULL);
361 const SkBitmap* left_badge(NULL);
362 if (cros->virtual_network())
363 left_badge = rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_SECURE);
364 if (network->type() == TYPE_ETHERNET) {
365 SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_WIRED),
366 right_badge, left_badge);
367 SetTooltipText(
368 UTF16ToWide(l10n_util::GetStringFUTF16(
369 IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
370 l10n_util::GetStringUTF16(
371 IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET))));
372 } else if (network->type() == TYPE_WIFI) {
373 const WifiNetwork* wifi = static_cast<const WifiNetwork*>(network);
374 SetIconAndBadges(IconForNetworkStrength(wifi, false),
375 right_badge, left_badge);
376 SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
377 IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
378 UTF8ToUTF16(wifi->name()))));
379 } else if (network->type() == TYPE_CELLULAR) {
380 const CellularNetwork* cellular =
381 static_cast<const CellularNetwork*>(network);
382 right_badge = BadgeForNetworkTechnology(cellular);
383 SetIconAndBadges(IconForNetworkStrength(cellular, false),
384 right_badge, left_badge);
385 SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
386 IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
387 UTF8ToUTF16(cellular->name()))));
388 }
389 }
390 }
391 }
392
RefreshNetworkObserver(NetworkLibrary * cros)393 void NetworkMenuButton::RefreshNetworkObserver(NetworkLibrary* cros) {
394 const Network* network = cros->active_network();
395 std::string new_network = network ? network->service_path() : std::string();
396 if (active_network_ != new_network) {
397 if (!active_network_.empty()) {
398 cros->RemoveNetworkObserver(active_network_, this);
399 }
400 if (!new_network.empty()) {
401 cros->AddNetworkObserver(new_network, this);
402 }
403 active_network_ = new_network;
404 }
405 }
406
RefreshNetworkDeviceObserver(NetworkLibrary * cros)407 void NetworkMenuButton::RefreshNetworkDeviceObserver(NetworkLibrary* cros) {
408 const NetworkDevice* cellular = cros->FindCellularDevice();
409 std::string new_cellular_device_path = cellular ?
410 cellular->device_path() : std::string();
411 if (cellular_device_path_ != new_cellular_device_path) {
412 if (!cellular_device_path_.empty()) {
413 cros->RemoveNetworkDeviceObserver(cellular_device_path_, this);
414 }
415 if (!new_cellular_device_path.empty()) {
416 was_sim_locked_ = cellular->is_sim_locked();
417 cros->AddNetworkDeviceObserver(new_cellular_device_path, this);
418 }
419 cellular_device_path_ = new_cellular_device_path;
420 }
421 }
422
ShowOptionalMobileDataPromoNotification(NetworkLibrary * cros)423 void NetworkMenuButton::ShowOptionalMobileDataPromoNotification(
424 NetworkLibrary* cros) {
425 // Display one-time notification for non-Guest users on first use
426 // of Mobile Data connection or if there's a carrier deal defined
427 // show that even if user has already seen generic promo.
428 if (IsBrowserMode() && !UserManager::Get()->IsLoggedInAsGuest() &&
429 check_for_promo_ && BrowserList::GetLastActive() &&
430 cros->cellular_connected() && !cros->ethernet_connected() &&
431 !cros->wifi_connected()) {
432 const ServicesCustomizationDocument::CarrierDeal* deal =
433 GetCarrierDeal(cros);
434 std::string deal_text;
435 int carrier_deal_promo_pref = -1;
436 if (deal) {
437 carrier_deal_promo_pref = GetCarrierDealPromoShown();
438 const std::string locale = g_browser_process->GetApplicationLocale();
439 deal_text = deal->GetLocalizedString(locale, "notification_text");
440 deal_url_ = deal->top_up_url;
441 } else if (!ShouldShow3gPromoNotification()) {
442 check_for_promo_ = false;
443 return;
444 }
445
446 gfx::Rect button_bounds = GetScreenBounds();
447 // StatusArea button Y position is usually -1, fix it so that
448 // Contains() method for screen bounds works correctly.
449 button_bounds.set_y(button_bounds.y() + 1);
450 gfx::Rect screen_bounds(chromeos::CalculateScreenBounds(gfx::Size()));
451
452 // Chrome window is initialized in visible state off screen and then is
453 // moved into visible screen area. Make sure that we're on screen
454 // so that bubble is shown correctly.
455 if (!screen_bounds.Contains(button_bounds)) {
456 // If we're not on screen yet, delay notification display.
457 // It may be shown earlier, on next NetworkLibrary callback processing.
458 if (method_factory_.empty()) {
459 MessageLoop::current()->PostDelayedTask(FROM_HERE,
460 method_factory_.NewRunnableMethod(
461 &NetworkMenuButton::ShowOptionalMobileDataPromoNotification,
462 cros),
463 kPromoShowDelayMs);
464 }
465 return;
466 }
467
468 // Add deal text if it's defined.
469 std::wstring notification_text;
470 std::wstring default_text =
471 UTF16ToWide(l10n_util::GetStringUTF16(IDS_3G_NOTIFICATION_MESSAGE));
472 if (!deal_text.empty()) {
473 notification_text = StringPrintf(L"%ls\n\n%ls",
474 UTF8ToWide(deal_text).c_str(),
475 default_text.c_str());
476 } else {
477 notification_text = default_text;
478 }
479
480 // Use deal URL if it's defined or general "Network Settings" URL.
481 int link_message_id;
482 if (deal_url_.empty())
483 link_message_id = IDS_OFFLINE_NETWORK_SETTINGS;
484 else
485 link_message_id = IDS_STATUSBAR_NETWORK_VIEW_ACCOUNT;
486
487 mobile_data_bubble_ = MessageBubble::Show(
488 GetWidget(),
489 button_bounds,
490 BubbleBorder::TOP_RIGHT ,
491 ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_NOTIFICATION_3G),
492 notification_text,
493 UTF16ToWide(l10n_util::GetStringUTF16(link_message_id)),
494 this);
495
496 check_for_promo_ = false;
497 SetShow3gPromoNotification(false);
498 if (carrier_deal_promo_pref != kNotificationCountPrefDefault)
499 SetCarrierDealPromoShown(carrier_deal_promo_pref + 1);
500 }
501 }
502
503 } // namespace chromeos
504