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 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 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 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 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. 82 bool ShouldShow3gPromoNotification() { 83 return GetBooleanPref(prefs::kShow3gPromoNotification); 84 } 85 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. 92 int GetCarrierDealPromoShown() { 93 return GetIntegerPref(prefs::kCarrierDealPromoShown); 94 } 95 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 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 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 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 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 184 void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) { 185 OnNetworkChanged(cros, cros->active_network()); 186 ShowOptionalMobileDataPromoNotification(cros); 187 } 188 189 //////////////////////////////////////////////////////////////////////////////// 190 // NetworkMenuButton, NetworkLibrary::NetworkObserver implementation: 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 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 210 bool NetworkMenuButton::IsBrowserMode() const { 211 return host_->GetScreenMode() == StatusAreaHost::kBrowserMode; 212 } 213 214 gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const { 215 return host_->GetNativeWindow(); 216 } 217 218 void NetworkMenuButton::OpenButtonOptions() { 219 host_->OpenButtonOptions(this); 220 } 221 222 bool NetworkMenuButton::ShouldOpenButtonOptions() const { 223 return host_->ShouldOpenButtonOptions(this); 224 } 225 226 //////////////////////////////////////////////////////////////////////////////// 227 // NetworkMenuButton, views::View implementation: 228 229 void NetworkMenuButton::OnLocaleChanged() { 230 NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary(); 231 SetNetworkIcon(lib, lib->active_network()); 232 } 233 234 //////////////////////////////////////////////////////////////////////////////// 235 // MessageBubbleDelegate implementation: 236 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* 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 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 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 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 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 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 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 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