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 34namespace { 35 36// Time in milliseconds to delay showing of promo 37// notification when Chrome window is not on screen. 38const int kPromoShowDelayMs = 10000; 39 40const int kNotificationCountPrefDefault = -1; 41 42bool 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 52int 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 62void 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 71void 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. 82bool ShouldShow3gPromoNotification() { 83 return GetBooleanPref(prefs::kShow3gPromoNotification); 84} 85 86void 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. 92int GetCarrierDealPromoShown() { 93 return GetIntegerPref(prefs::kCarrierDealPromoShown); 94} 95 96void SetCarrierDealPromoShown(int value) { 97 SetIntegerPref(prefs::kCarrierDealPromoShown, value); 98} 99 100} // namespace 101 102namespace chromeos { 103 104//////////////////////////////////////////////////////////////////////////////// 105// NetworkMenuButton 106 107// static 108const int NetworkMenuButton::kThrobDuration = 750; 109 110NetworkMenuButton::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 135NetworkMenuButton::~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 149void 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 163void 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 184void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) { 185 OnNetworkChanged(cros, cros->active_network()); 186 ShowOptionalMobileDataPromoNotification(cros); 187} 188 189//////////////////////////////////////////////////////////////////////////////// 190// NetworkMenuButton, NetworkLibrary::NetworkObserver implementation: 191void 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 202void NetworkMenuButton::OnCellularDataPlanChanged(NetworkLibrary* cros) { 203 // Call OnNetworkManagerChanged which will update the icon. 204 OnNetworkManagerChanged(cros); 205} 206 207//////////////////////////////////////////////////////////////////////////////// 208// NetworkMenuButton, NetworkMenu implementation: 209 210bool NetworkMenuButton::IsBrowserMode() const { 211 return host_->GetScreenMode() == StatusAreaHost::kBrowserMode; 212} 213 214gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const { 215 return host_->GetNativeWindow(); 216} 217 218void NetworkMenuButton::OpenButtonOptions() { 219 host_->OpenButtonOptions(this); 220} 221 222bool NetworkMenuButton::ShouldOpenButtonOptions() const { 223 return host_->ShouldOpenButtonOptions(this); 224} 225 226//////////////////////////////////////////////////////////////////////////////// 227// NetworkMenuButton, views::View implementation: 228 229void NetworkMenuButton::OnLocaleChanged() { 230 NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary(); 231 SetNetworkIcon(lib, lib->active_network()); 232} 233 234//////////////////////////////////////////////////////////////////////////////// 235// MessageBubbleDelegate implementation: 236 237void 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 259const ServicesCustomizationDocument::CarrierDeal* 260NetworkMenuButton::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 289void 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 299void NetworkMenuButton::SetIconOnly(const SkBitmap* icon) { 300 icon_ = icon; 301 SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/, 302 left_badge_)); 303} 304 305void 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 313void 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 393void 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 407void 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 423void 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