// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/status/network_menu_button.h" #include <algorithm> #include <limits> #include "base/logging.h" #include "base/message_loop.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/login/helper.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/chromeos/options/network_config_view.h" #include "chrome/browser/chromeos/sim_dialog_delegate.h" #include "chrome/browser/chromeos/status/status_area_host.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/pref_names.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas_skia.h" #include "views/window/window.h" namespace { // Time in milliseconds to delay showing of promo // notification when Chrome window is not on screen. const int kPromoShowDelayMs = 10000; const int kNotificationCountPrefDefault = -1; bool GetBooleanPref(const char* pref_name) { Browser* browser = BrowserList::GetLastActive(); // Default to safe value which is false (not to show bubble). if (!browser || !browser->profile()) return false; PrefService* prefs = browser->profile()->GetPrefs(); return prefs->GetBoolean(pref_name); } int GetIntegerPref(const char* pref_name) { Browser* browser = BrowserList::GetLastActive(); // Default to "safe" value. if (!browser || !browser->profile()) return kNotificationCountPrefDefault; PrefService* prefs = browser->profile()->GetPrefs(); return prefs->GetInteger(pref_name); } void SetBooleanPref(const char* pref_name, bool value) { Browser* browser = BrowserList::GetLastActive(); if (!browser || !browser->profile()) return; PrefService* prefs = browser->profile()->GetPrefs(); prefs->SetBoolean(pref_name, value); } void SetIntegerPref(const char* pref_name, int value) { Browser* browser = BrowserList::GetLastActive(); if (!browser || !browser->profile()) return; PrefService* prefs = browser->profile()->GetPrefs(); prefs->SetInteger(pref_name, value); } // Returns prefs::kShow3gPromoNotification or false // if there's no active browser. bool ShouldShow3gPromoNotification() { return GetBooleanPref(prefs::kShow3gPromoNotification); } void SetShow3gPromoNotification(bool value) { SetBooleanPref(prefs::kShow3gPromoNotification, value); } // Returns prefs::kCarrierDealPromoShown which is number of times // carrier deal notification has been shown to user or -1 // if there's no active browser. int GetCarrierDealPromoShown() { return GetIntegerPref(prefs::kCarrierDealPromoShown); } void SetCarrierDealPromoShown(int value) { SetIntegerPref(prefs::kCarrierDealPromoShown, value); } } // namespace namespace chromeos { //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton // static const int NetworkMenuButton::kThrobDuration = 750; NetworkMenuButton::NetworkMenuButton(StatusAreaHost* host) : StatusAreaButton(host, this), NetworkMenu(), icon_(NULL), right_badge_(NULL), left_badge_(NULL), mobile_data_bubble_(NULL), check_for_promo_(true), was_sim_locked_(false), ALLOW_THIS_IN_INITIALIZER_LIST(animation_connecting_(this)), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { animation_connecting_.SetThrobDuration(kThrobDuration); animation_connecting_.SetTweenType(ui::Tween::LINEAR); NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary(); OnNetworkManagerChanged(network_library); network_library->AddNetworkManagerObserver(this); network_library->AddCellularDataPlanObserver(this); const NetworkDevice* cellular = network_library->FindCellularDevice(); if (cellular) { cellular_device_path_ = cellular->device_path(); was_sim_locked_ = cellular->is_sim_locked(); network_library->AddNetworkDeviceObserver(cellular_device_path_, this); } } NetworkMenuButton::~NetworkMenuButton() { NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary(); netlib->RemoveNetworkManagerObserver(this); netlib->RemoveObserverForAllNetworks(this); netlib->RemoveCellularDataPlanObserver(this); if (!cellular_device_path_.empty()) netlib->RemoveNetworkDeviceObserver(cellular_device_path_, this); if (mobile_data_bubble_) mobile_data_bubble_->Close(); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, ui::AnimationDelegate implementation: void NetworkMenuButton::AnimationProgressed(const ui::Animation* animation) { if (animation == &animation_connecting_) { SetIconOnly(IconForNetworkConnecting( animation_connecting_.GetCurrentValue(), false)); // No need to set the badge here, because it should already be set. SchedulePaint(); } else { MenuButton::AnimationProgressed(animation); } } //////////////////////////////////////////////////////////////////////////////// // NetworkLibrary::NetworkDeviceObserver implementation: void NetworkMenuButton::OnNetworkDeviceChanged(NetworkLibrary* cros, const NetworkDevice* device) { // Device status, such as SIMLock may have changed. OnNetworkChanged(cros, cros->active_network()); const NetworkDevice* cellular = cros->FindCellularDevice(); if (cellular) { // We make an assumption (which is valid for now) that the SIM // unlock dialog is put up only when the user is trying to enable // mobile data. So if the SIM is now unlocked, initiate the // enable operation that the user originally requested. if (was_sim_locked_ && !cellular->is_sim_locked() && !cros->cellular_enabled()) { cros->EnableCellularNetworkDevice(true); } was_sim_locked_ = cellular->is_sim_locked(); } } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, NetworkLibrary::NetworkManagerObserver implementation: void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) { OnNetworkChanged(cros, cros->active_network()); ShowOptionalMobileDataPromoNotification(cros); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, NetworkLibrary::NetworkObserver implementation: void NetworkMenuButton::OnNetworkChanged(NetworkLibrary* cros, const Network* network) { // This gets called on initialization, so any changes should be reflected // in CrosMock::SetNetworkLibraryStatusAreaExpectations(). SetNetworkIcon(cros, network); RefreshNetworkObserver(cros); RefreshNetworkDeviceObserver(cros); SchedulePaint(); UpdateMenu(); } void NetworkMenuButton::OnCellularDataPlanChanged(NetworkLibrary* cros) { // Call OnNetworkManagerChanged which will update the icon. OnNetworkManagerChanged(cros); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, NetworkMenu implementation: bool NetworkMenuButton::IsBrowserMode() const { return host_->GetScreenMode() == StatusAreaHost::kBrowserMode; } gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const { return host_->GetNativeWindow(); } void NetworkMenuButton::OpenButtonOptions() { host_->OpenButtonOptions(this); } bool NetworkMenuButton::ShouldOpenButtonOptions() const { return host_->ShouldOpenButtonOptions(this); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, views::View implementation: void NetworkMenuButton::OnLocaleChanged() { NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary(); SetNetworkIcon(lib, lib->active_network()); } //////////////////////////////////////////////////////////////////////////////// // MessageBubbleDelegate implementation: void NetworkMenuButton::OnHelpLinkActivated() { // mobile_data_bubble_ will be set to NULL in callback. if (mobile_data_bubble_) mobile_data_bubble_->Close(); if (!deal_url_.empty()) { Browser* browser = BrowserList::GetLastActive(); if (!browser) return; browser->ShowSingletonTab(GURL(deal_url_)); deal_url_.clear(); } else { const Network* cellular = CrosLibrary::Get()->GetNetworkLibrary()->cellular_network(); if (!cellular) return; ShowTabbedNetworkSettings(cellular); } } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuButton, private methods const ServicesCustomizationDocument::CarrierDeal* NetworkMenuButton::GetCarrierDeal( NetworkLibrary* cros) { std::string carrier_id = cros->GetCellularHomeCarrierId(); if (carrier_id.empty()) { LOG(ERROR) << "Empty carrier ID with a cellular connected."; return NULL; } ServicesCustomizationDocument* customization = ServicesCustomizationDocument::GetInstance(); if (!customization->IsReady()) return NULL; const ServicesCustomizationDocument::CarrierDeal* deal = customization->GetCarrierDeal(carrier_id, true); if (deal) { // Check deal for validity. int carrier_deal_promo_pref = GetCarrierDealPromoShown(); if (carrier_deal_promo_pref >= deal->notification_count) return NULL; const std::string locale = g_browser_process->GetApplicationLocale(); std::string deal_text = deal->GetLocalizedString(locale, "notification_text"); if (deal_text.empty()) return NULL; } return deal; } void NetworkMenuButton::SetIconAndBadges(const SkBitmap* icon, const SkBitmap* right_badge, const SkBitmap* left_badge) { icon_ = icon; right_badge_ = right_badge; left_badge_ = left_badge; SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/, left_badge_)); } void NetworkMenuButton::SetIconOnly(const SkBitmap* icon) { icon_ = icon; SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/, left_badge_)); } void NetworkMenuButton::SetBadgesOnly(const SkBitmap* right_badge, const SkBitmap* left_badge) { right_badge_ = right_badge; left_badge_ = left_badge; SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/, left_badge_)); } void NetworkMenuButton::SetNetworkIcon(NetworkLibrary* cros, const Network* network) { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); if (!cros || !CrosLibrary::Get()->EnsureLoaded()) { SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0), rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_WARNING), NULL); SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16( IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP))); return; } if (!cros->Connected() && !cros->Connecting()) { animation_connecting_.Stop(); SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0), rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_DISCONNECTED), NULL); SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16( IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP))); return; } if (cros->wifi_connecting() || cros->cellular_connecting()) { // Start the connecting animation if not running. if (!animation_connecting_.is_animating()) { animation_connecting_.Reset(); animation_connecting_.StartThrobbing(-1); SetIconOnly(IconForNetworkConnecting(0, false)); } const WirelessNetwork* wireless = NULL; if (cros->wifi_connecting()) { wireless = cros->wifi_network(); SetBadgesOnly(NULL, NULL); } else { // cellular_connecting wireless = cros->cellular_network(); SetBadgesOnly(BadgeForNetworkTechnology(cros->cellular_network()), NULL); } SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16( wireless->configuring() ? IDS_STATUSBAR_NETWORK_CONFIGURING_TOOLTIP : IDS_STATUSBAR_NETWORK_CONNECTING_TOOLTIP, UTF8ToUTF16(wireless->name())))); } else { // Stop connecting animation since we are not connecting. animation_connecting_.Stop(); // Only set the icon, if it is an active network that changed. if (network && network->is_active()) { const SkBitmap* right_badge(NULL); const SkBitmap* left_badge(NULL); if (cros->virtual_network()) left_badge = rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_SECURE); if (network->type() == TYPE_ETHERNET) { SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_WIRED), right_badge, left_badge); SetTooltipText( UTF16ToWide(l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP, l10n_util::GetStringUTF16( IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET)))); } else if (network->type() == TYPE_WIFI) { const WifiNetwork* wifi = static_cast<const WifiNetwork*>(network); SetIconAndBadges(IconForNetworkStrength(wifi, false), right_badge, left_badge); SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP, UTF8ToUTF16(wifi->name())))); } else if (network->type() == TYPE_CELLULAR) { const CellularNetwork* cellular = static_cast<const CellularNetwork*>(network); right_badge = BadgeForNetworkTechnology(cellular); SetIconAndBadges(IconForNetworkStrength(cellular, false), right_badge, left_badge); SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP, UTF8ToUTF16(cellular->name())))); } } } } void NetworkMenuButton::RefreshNetworkObserver(NetworkLibrary* cros) { const Network* network = cros->active_network(); std::string new_network = network ? network->service_path() : std::string(); if (active_network_ != new_network) { if (!active_network_.empty()) { cros->RemoveNetworkObserver(active_network_, this); } if (!new_network.empty()) { cros->AddNetworkObserver(new_network, this); } active_network_ = new_network; } } void NetworkMenuButton::RefreshNetworkDeviceObserver(NetworkLibrary* cros) { const NetworkDevice* cellular = cros->FindCellularDevice(); std::string new_cellular_device_path = cellular ? cellular->device_path() : std::string(); if (cellular_device_path_ != new_cellular_device_path) { if (!cellular_device_path_.empty()) { cros->RemoveNetworkDeviceObserver(cellular_device_path_, this); } if (!new_cellular_device_path.empty()) { was_sim_locked_ = cellular->is_sim_locked(); cros->AddNetworkDeviceObserver(new_cellular_device_path, this); } cellular_device_path_ = new_cellular_device_path; } } void NetworkMenuButton::ShowOptionalMobileDataPromoNotification( NetworkLibrary* cros) { // Display one-time notification for non-Guest users on first use // of Mobile Data connection or if there's a carrier deal defined // show that even if user has already seen generic promo. if (IsBrowserMode() && !UserManager::Get()->IsLoggedInAsGuest() && check_for_promo_ && BrowserList::GetLastActive() && cros->cellular_connected() && !cros->ethernet_connected() && !cros->wifi_connected()) { const ServicesCustomizationDocument::CarrierDeal* deal = GetCarrierDeal(cros); std::string deal_text; int carrier_deal_promo_pref = -1; if (deal) { carrier_deal_promo_pref = GetCarrierDealPromoShown(); const std::string locale = g_browser_process->GetApplicationLocale(); deal_text = deal->GetLocalizedString(locale, "notification_text"); deal_url_ = deal->top_up_url; } else if (!ShouldShow3gPromoNotification()) { check_for_promo_ = false; return; } gfx::Rect button_bounds = GetScreenBounds(); // StatusArea button Y position is usually -1, fix it so that // Contains() method for screen bounds works correctly. button_bounds.set_y(button_bounds.y() + 1); gfx::Rect screen_bounds(chromeos::CalculateScreenBounds(gfx::Size())); // Chrome window is initialized in visible state off screen and then is // moved into visible screen area. Make sure that we're on screen // so that bubble is shown correctly. if (!screen_bounds.Contains(button_bounds)) { // If we're not on screen yet, delay notification display. // It may be shown earlier, on next NetworkLibrary callback processing. if (method_factory_.empty()) { MessageLoop::current()->PostDelayedTask(FROM_HERE, method_factory_.NewRunnableMethod( &NetworkMenuButton::ShowOptionalMobileDataPromoNotification, cros), kPromoShowDelayMs); } return; } // Add deal text if it's defined. std::wstring notification_text; std::wstring default_text = UTF16ToWide(l10n_util::GetStringUTF16(IDS_3G_NOTIFICATION_MESSAGE)); if (!deal_text.empty()) { notification_text = StringPrintf(L"%ls\n\n%ls", UTF8ToWide(deal_text).c_str(), default_text.c_str()); } else { notification_text = default_text; } // Use deal URL if it's defined or general "Network Settings" URL. int link_message_id; if (deal_url_.empty()) link_message_id = IDS_OFFLINE_NETWORK_SETTINGS; else link_message_id = IDS_STATUSBAR_NETWORK_VIEW_ACCOUNT; mobile_data_bubble_ = MessageBubble::Show( GetWidget(), button_bounds, BubbleBorder::TOP_RIGHT , ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_NOTIFICATION_3G), notification_text, UTF16ToWide(l10n_util::GetStringUTF16(link_message_id)), this); check_for_promo_ = false; SetShow3gPromoNotification(false); if (carrier_deal_promo_pref != kNotificationCountPrefDefault) SetCarrierDealPromoShown(carrier_deal_promo_pref + 1); } } } // namespace chromeos