// 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.h" #include <algorithm> #include "base/logging.h" #include "base/command_line.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chromeos/choose_mobile_network_dialog.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/customization_document.h" #include "chrome/browser/chromeos/sim_dialog_delegate.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/views/window.h" #include "chrome/common/url_constants.h" #include "chrome/common/chrome_switches.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "net/base/escape.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas_skia.h" #include "ui/gfx/skbitmap_operations.h" #include "views/controls/menu/menu_2.h" #include "views/window/window.h" namespace { // Amount to fade icons while connecting. const double kConnectingImageAlpha = 0.5; // Replace '&' in a string with "&&" to allow it to be a menu item label. std::string EscapeAmpersands(const std::string& input) { std::string str = input; size_t found = str.find('&'); while (found != std::string::npos) { str.replace(found, 1, "&&"); found = str.find('&', found + 2); } return str; } } // namespace namespace chromeos { class MoreMenuModel : public NetworkMenuModel { public: explicit MoreMenuModel(NetworkMenu* owner); virtual ~MoreMenuModel() {} // NetworkMenuModel implementation. virtual void InitMenuItems(bool is_browser_mode, bool should_open_button_options); private: friend class MainMenuModel; DISALLOW_COPY_AND_ASSIGN(MoreMenuModel); }; class VPNMenuModel : public NetworkMenuModel { public: explicit VPNMenuModel(NetworkMenu* owner); virtual ~VPNMenuModel() {} // NetworkMenuModel implementation. virtual void InitMenuItems(bool is_browser_mode, bool should_open_button_options); static SkBitmap IconForDisplay(const Network* network); private: DISALLOW_COPY_AND_ASSIGN(VPNMenuModel); }; class MainMenuModel : public NetworkMenuModel { public: explicit MainMenuModel(NetworkMenu* owner); virtual ~MainMenuModel() {} // NetworkMenuModel implementation. virtual void InitMenuItems(bool is_browser_mode, bool should_open_button_options); private: scoped_ptr<NetworkMenuModel> vpn_menu_model_; scoped_ptr<MoreMenuModel> more_menu_model_; DISALLOW_COPY_AND_ASSIGN(MainMenuModel); }; //////////////////////////////////////////////////////////////////////////////// // NetworkMenuModel, public methods: bool NetworkMenuModel::ConnectToNetworkAt(int index, const std::string& passphrase, const std::string& ssid, int auto_connect) const { int flags = menu_items_[index].flags; NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); const std::string& service_path = menu_items_[index].service_path; if (flags & FLAG_WIFI) { WifiNetwork* wifi = cros->FindWifiNetworkByPath(service_path); if (wifi) { // Connect or reconnect. if (auto_connect >= 0) wifi->SetAutoConnect(auto_connect ? true : false); if (wifi->connecting_or_connected()) { // Show the config settings for the active network. owner_->ShowTabbedNetworkSettings(wifi); return true; } if (wifi->IsPassphraseRequired()) { // Show the connection UI if we require a passphrase. ShowNetworkConfigView(new NetworkConfigView(wifi)); return true; } else { cros->ConnectToWifiNetwork(wifi); // Connection failures are responsible for updating the UI, including // reopening dialogs. return true; } } else { // If we are attempting to connect to a network that no longer exists, // display a notification. LOG(WARNING) << "Wi-fi network does not exist to connect to: " << service_path; // TODO(stevenjb): Show notification. } } else if (flags & FLAG_CELLULAR) { CellularNetwork* cellular = cros->FindCellularNetworkByPath( service_path); if (cellular) { if ((cellular->activation_state() != ACTIVATION_STATE_ACTIVATED && cellular->activation_state() != ACTIVATION_STATE_UNKNOWN) || cellular->needs_new_plan()) { ActivateCellular(cellular); return true; } else if (cellular->connecting_or_connected()) { // Cellular network is connecting or connected, // so we show the config settings for the cellular network. owner_->ShowTabbedNetworkSettings(cellular); return true; } // Clicked on a disconnected cellular network, so connect to it. cros->ConnectToCellularNetwork(cellular); } else { // If we are attempting to connect to a network that no longer exists, // display a notification. LOG(WARNING) << "Cellular network does not exist to connect to: " << service_path; // TODO(stevenjb): Show notification. } } else if (flags & FLAG_ADD_WIFI) { ShowOther(TYPE_WIFI); } else if (flags & FLAG_ADD_CELLULAR) { ShowOtherCellular(); } else if (flags & FLAG_ADD_VPN) { ShowOther(TYPE_VPN); } else if (flags & FLAG_VPN) { VirtualNetwork* vpn = cros->FindVirtualNetworkByPath(service_path); if (vpn) { // Connect or reconnect. if (vpn->connecting_or_connected()) { // Show the config settings for the connected network. if (cros->connected_network()) owner_->ShowTabbedNetworkSettings(cros->connected_network()); return true; } // Show the connection UI if info for a field is missing. if (vpn->NeedMoreInfoToConnect()) { ShowNetworkConfigView(new NetworkConfigView(vpn)); return true; } cros->ConnectToVirtualNetwork(vpn); // Connection failures are responsible for updating the UI, including // reopening dialogs. return true; } else { // If we are attempting to connect to a network that no longer exists, // display a notification. LOG(WARNING) << "VPN does not exist to connect to: " << service_path; // TODO(stevenjb): Show notification. } } return true; } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuModel, ui::MenuModel implementation: int NetworkMenuModel::GetItemCount() const { return static_cast<int>(menu_items_.size()); } ui::MenuModel::ItemType NetworkMenuModel::GetTypeAt(int index) const { return menu_items_[index].type; } string16 NetworkMenuModel::GetLabelAt(int index) const { return menu_items_[index].label; } const gfx::Font* NetworkMenuModel::GetLabelFontAt(int index) const { return (menu_items_[index].flags & FLAG_ASSOCIATED) ? &ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BoldFont) : NULL; } bool NetworkMenuModel::IsItemCheckedAt(int index) const { // All ui::MenuModel::TYPE_CHECK menu items are checked. return true; } bool NetworkMenuModel::GetIconAt(int index, SkBitmap* icon) { if (!menu_items_[index].icon.empty()) { *icon = menu_items_[index].icon; return true; } return false; } bool NetworkMenuModel::IsEnabledAt(int index) const { return !(menu_items_[index].flags & FLAG_DISABLED); } ui::MenuModel* NetworkMenuModel::GetSubmenuModelAt(int index) const { return menu_items_[index].sub_menu_model; } void NetworkMenuModel::ActivatedAt(int index) { // When we are refreshing the menu, ignore menu item activation. if (owner_->refreshing_menu_) return; NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); int flags = menu_items_[index].flags; if (flags & FLAG_OPTIONS) { owner_->OpenButtonOptions(); } else if (flags & FLAG_TOGGLE_ETHERNET) { cros->EnableEthernetNetworkDevice(!cros->ethernet_enabled()); } else if (flags & FLAG_TOGGLE_WIFI) { cros->EnableWifiNetworkDevice(!cros->wifi_enabled()); } else if (flags & FLAG_TOGGLE_CELLULAR) { const NetworkDevice* cellular = cros->FindCellularDevice(); if (!cellular) { LOG(ERROR) << "No cellular device found, it should be available."; cros->EnableCellularNetworkDevice(!cros->cellular_enabled()); } else if (cellular->sim_lock_state() == SIM_UNLOCKED || cellular->sim_lock_state() == SIM_UNKNOWN) { cros->EnableCellularNetworkDevice(!cros->cellular_enabled()); } else { SimDialogDelegate::ShowDialog(owner_->GetNativeWindow(), SimDialogDelegate::SIM_DIALOG_UNLOCK); } } else if (flags & FLAG_TOGGLE_OFFLINE) { cros->EnableOfflineMode(!cros->offline_mode()); } else if (flags & FLAG_ETHERNET) { if (cros->ethernet_connected()) { owner_->ShowTabbedNetworkSettings(cros->ethernet_network()); } } else if (flags & (FLAG_WIFI | FLAG_ADD_WIFI | FLAG_CELLULAR | FLAG_ADD_CELLULAR | FLAG_VPN | FLAG_ADD_VPN)) { ConnectToNetworkAt(index, std::string(), std::string(), -1); } else if (flags & FLAG_DISCONNECT_VPN) { const VirtualNetwork* active_vpn = cros->virtual_network(); if (active_vpn) cros->DisconnectFromNetwork(active_vpn); } else if (flags & FLAG_VIEW_ACCOUNT) { Browser* browser = BrowserList::GetLastActive(); if (browser) browser->ShowSingletonTab(GURL(top_up_url_)); } } //////////////////////////////////////////////////////////////////////////////// // NetworkMenuModel, private methods: // TODO(stevenjb): deprecate this once we've committed to tabbed settings // and the embedded menu UI (and fully deprecated NetworkConfigView). // Meanwhile, if MenuUI::IsEnabled() is true, always show the settings UI, // otherwise show NetworkConfigView only to get passwords when not connected. void NetworkMenuModel::ShowNetworkConfigView(NetworkConfigView* view) const { view->set_browser_mode(owner_->IsBrowserMode()); views::Window* window = browser::CreateViewsWindow( owner_->GetNativeWindow(), gfx::Rect(), view); window->SetIsAlwaysOnTop(true); window->Show(); } void NetworkMenuModel::ActivateCellular(const CellularNetwork* cellular) const { DCHECK(cellular); Browser* browser = BrowserList::GetLastActive(); if (!browser) return; browser->OpenMobilePlanTabAndActivate(); } void NetworkMenuModel::ShowOther(ConnectionType type) const { ShowNetworkConfigView(new NetworkConfigView(type)); } void NetworkMenuModel::ShowOtherCellular() const { ChooseMobileNetworkDialog::ShowDialog(owner_->GetNativeWindow()); } //////////////////////////////////////////////////////////////////////////////// // MainMenuModel MainMenuModel::MainMenuModel(NetworkMenu* owner) : NetworkMenuModel(owner), vpn_menu_model_(new VPNMenuModel(owner)), more_menu_model_(new MoreMenuModel(owner)) { } void MainMenuModel::InitMenuItems(bool is_browser_mode, bool should_open_button_options) { // This gets called on initialization, so any changes should be reflected // in CrosMock::SetNetworkLibraryStatusAreaExpectations(). menu_items_.clear(); NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); if (cros->IsLocked()) { menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_LOCKED), SkBitmap(), std::string(), FLAG_DISABLED)); return; } // Populate our MenuItems with the current list of networks. ResourceBundle& rb = ResourceBundle::GetSharedInstance(); string16 label; // Ethernet bool ethernet_available = cros->ethernet_available(); bool ethernet_enabled = cros->ethernet_enabled(); if (ethernet_available && ethernet_enabled) { bool ethernet_connected = cros->ethernet_connected(); bool ethernet_connecting = cros->ethernet_connecting(); if (ethernet_connecting) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_STATUS, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET), l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING)); } else { label = l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET); } const SkBitmap* icon = rb.GetBitmapNamed(IDR_STATUSBAR_WIRED_BLACK); const SkBitmap* badge = ethernet_connecting || ethernet_connected ? NULL : rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_DISCONNECTED); int flag = FLAG_ETHERNET; if (ethernet_connecting || ethernet_connected) flag |= FLAG_ASSOCIATED; menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, label, NetworkMenu::IconForDisplay(icon, badge), std::string(), flag)); } // Wifi Networks bool wifi_available = cros->wifi_available(); bool wifi_enabled = cros->wifi_enabled(); if (wifi_available && wifi_enabled) { const WifiNetworkVector& wifi_networks = cros->wifi_networks(); const WifiNetwork* active_wifi = cros->wifi_network(); bool separator_added = false; // List Wifi networks. for (size_t i = 0; i < wifi_networks.size(); ++i) { // Ampersand is a valid character in an SSID, but menu2 uses it to mark // "mnemonics" for keyboard shortcuts. std::string wifi_name = EscapeAmpersands(wifi_networks[i]->name()); if (wifi_networks[i]->connecting()) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_STATUS, UTF8ToUTF16(wifi_name), l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING)); } else { label = UTF8ToUTF16(wifi_name); } // First add a separator if necessary. if (!separator_added) { separator_added = true; if (!menu_items_.empty()) { // Don't add if first menu item. menu_items_.push_back(MenuItem()); // Separator } } const SkBitmap* icon = NetworkMenu::IconForNetworkStrength( wifi_networks[i], true); const SkBitmap* badge = wifi_networks[i]->encrypted() ? rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_SECURE) : NULL; int flag = FLAG_WIFI; // If a network is not connectable from login/oobe, we disable it. // We do not allow configuring a network (e.g. 802.1x) from login/oobe. if (!owner_->IsBrowserMode() && !wifi_networks[i]->connectable()) flag |= FLAG_DISABLED; if (active_wifi && wifi_networks[i]->service_path() == active_wifi->service_path()) flag |= FLAG_ASSOCIATED; menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, label, NetworkMenu::IconForDisplay(icon, badge), wifi_networks[i]->service_path(), flag)); } if (!separator_added && !menu_items_.empty()) menu_items_.push_back(MenuItem()); menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_OTHER_WIFI_NETWORKS), *rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0_BLACK), std::string(), FLAG_ADD_WIFI)); } // Cellular Networks bool cellular_available = cros->cellular_available(); bool cellular_enabled = cros->cellular_enabled(); if (cellular_available && cellular_enabled) { const CellularNetworkVector& cell_networks = cros->cellular_networks(); const CellularNetwork* active_cellular = cros->cellular_network(); bool separator_added = false; // List Cellular networks. for (size_t i = 0; i < cell_networks.size(); ++i) { chromeos::ActivationState activation_state = cell_networks[i]->activation_state(); // If we are on the OOBE/login screen, do not show activating 3G option. if (!is_browser_mode && activation_state != ACTIVATION_STATE_ACTIVATED) continue; // Ampersand is a valid character in a network name, but menu2 uses it // to mark "mnemonics" for keyboard shortcuts. http://crosbug.com/14697 std::string network_name = EscapeAmpersands(cell_networks[i]->name()); if (activation_state == ACTIVATION_STATE_NOT_ACTIVATED || activation_state == ACTIVATION_STATE_PARTIALLY_ACTIVATED) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_ACTIVATE, UTF8ToUTF16(network_name)); } else if (activation_state == ACTIVATION_STATE_ACTIVATING) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_STATUS, UTF8ToUTF16(network_name), l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_ACTIVATING)); } else if (cell_networks[i]->connecting()) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_STATUS, UTF8ToUTF16(network_name), l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING)); } else { label = UTF8ToUTF16(network_name); } // First add a separator if necessary. if (!separator_added) { separator_added = true; if (!menu_items_.empty()) { // Don't add if first menu item. menu_items_.push_back(MenuItem()); // Separator } } const SkBitmap* icon = NetworkMenu::IconForNetworkStrength( cell_networks[i], true); const SkBitmap* badge = NetworkMenu::BadgeForNetworkTechnology( cell_networks[i]); const SkBitmap* roaming_badge = NetworkMenu::BadgeForRoamingStatus( cell_networks[i]); int flag = FLAG_CELLULAR; bool isActive = active_cellular && cell_networks[i]->service_path() == active_cellular->service_path() && (cell_networks[i]->connecting() || cell_networks[i]->connected()); bool supports_data_plan = active_cellular && active_cellular->SupportsDataPlan(); if (isActive) flag |= FLAG_ASSOCIATED; menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, label, NetworkMenu::IconForDisplay(icon, badge, roaming_badge, NULL), cell_networks[i]->service_path(), flag)); if (isActive && supports_data_plan) { label.clear(); if (active_cellular->needs_new_plan()) { label = l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_NO_PLAN_LABEL); } else { const chromeos::CellularDataPlan* plan = cros->GetSignificantDataPlan(active_cellular->service_path()); if (plan) label = plan->GetUsageInfo(); } if (label.length()) { menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_DISABLED)); } } } const NetworkDevice* cellular_device = cros->FindCellularDevice(); if (cellular_device) { // Add "View Account" with top up URL if we know that. ServicesCustomizationDocument* customization = ServicesCustomizationDocument::GetInstance(); if (is_browser_mode && customization->IsReady()) { std::string carrier_id = cros->GetCellularHomeCarrierId(); // If we don't have top up URL cached. if (carrier_id != carrier_id_) { // Mark that we've checked this carrier ID. carrier_id_ = carrier_id; top_up_url_.clear(); // Ignoring deal restrictions, use any carrier information available. const ServicesCustomizationDocument::CarrierDeal* deal = customization->GetCarrierDeal(carrier_id, false); if (deal && !deal->top_up_url.empty()) top_up_url_ = deal->top_up_url; } if (!top_up_url_.empty()) { menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_VIEW_ACCOUNT), SkBitmap(), std::string(), FLAG_VIEW_ACCOUNT)); } } if (cellular_device->support_network_scan()) { // For GSM add mobile network scan. if (!separator_added && !menu_items_.empty()) menu_items_.push_back(MenuItem()); menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16( IDS_OPTIONS_SETTINGS_OTHER_CELLULAR_NETWORKS), *rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0_BLACK), std::string(), FLAG_ADD_CELLULAR)); } } } // No networks available message. if (menu_items_.empty()) { label = l10n_util::GetStringFUTF16(IDS_STATUSBAR_NETWORK_MENU_ITEM_INDENT, l10n_util::GetStringUTF16(IDS_STATUSBAR_NO_NETWORKS_MESSAGE)); menu_items_.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_DISABLED)); } if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableVPN)) { // If there's a connected network, add submenu for Private Networks. const Network* connected_network = cros->connected_network(); if (connected_network) { menu_items_.push_back(MenuItem()); // Separator menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_SUBMENU, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_PRIVATE_NETWORKS), VPNMenuModel::IconForDisplay(connected_network), vpn_menu_model_.get(), FLAG_NONE)); vpn_menu_model_->InitMenuItems( is_browser_mode, should_open_button_options); } } // Enable / disable wireless. if (wifi_available || cellular_available) { menu_items_.push_back(MenuItem()); // Separator if (wifi_available) { // Add 'Scanning...' if (cros->wifi_scanning()) { label = l10n_util::GetStringUTF16(IDS_STATUSBAR_WIFI_SCANNING_MESSAGE); menu_items_.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_DISABLED)); } int id = wifi_enabled ? IDS_STATUSBAR_NETWORK_DEVICE_DISABLE : IDS_STATUSBAR_NETWORK_DEVICE_ENABLE; label = l10n_util::GetStringFUTF16(id, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_WIFI)); menu_items_.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_TOGGLE_WIFI)); } if (cellular_available) { const NetworkDevice* cellular = cros->FindCellularDevice(); bool is_locked = false; if (!cellular) { LOG(ERROR) << "Didn't find cellular device."; } else { // If cellular is SIM locked then show "Enable" action. is_locked = cellular->sim_lock_state() == SIM_LOCKED_PIN || cellular->sim_lock_state() == SIM_LOCKED_PUK; } int id; if (cellular_enabled && !is_locked) id = IDS_STATUSBAR_NETWORK_DEVICE_DISABLE; else id = IDS_STATUSBAR_NETWORK_DEVICE_ENABLE; label = l10n_util::GetStringFUTF16(id, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CELLULAR)); menu_items_.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_TOGGLE_CELLULAR)); } } // Offline mode. // TODO(chocobo): Uncomment once we figure out how to do offline mode. // menu_items_.push_back(MenuItem(cros->offline_mode() ? // ui::MenuModel::TYPE_CHECK : ui::MenuModel::TYPE_COMMAND, // l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_OFFLINE_MODE), // SkBitmap(), std::string(), FLAG_TOGGLE_OFFLINE)); // Additional links like: // * Network settings; // * IP Address on active interface; // * Hardware addresses for wifi and ethernet. menu_items_.push_back(MenuItem()); // Separator more_menu_model_->InitMenuItems(is_browser_mode, should_open_button_options); if (is_browser_mode) { // In browser mode we do not want separate submenu, inline items. menu_items_.insert( menu_items_.end(), more_menu_model_->menu_items_.begin(), more_menu_model_->menu_items_.end()); } else { if (!more_menu_model_->menu_items_.empty()) { menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_SUBMENU, l10n_util::GetStringUTF16(IDS_LANGUAGES_MORE), SkBitmap(), more_menu_model_.get(), FLAG_NONE)); } } } //////////////////////////////////////////////////////////////////////////////// // VPNMenuModel VPNMenuModel::VPNMenuModel(NetworkMenu* owner) : NetworkMenuModel(owner) { } void VPNMenuModel::InitMenuItems(bool is_browser_mode, bool should_open_button_options) { // This gets called on initialization, so any changes should be reflected // in CrosMock::SetNetworkLibraryStatusAreaExpectations(). menu_items_.clear(); // VPN only applies if there's a connected underlying network. NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); const Network* connected_network = cros->connected_network(); if (!connected_network) return; // Populate our MenuItems with the current list of virtual networks. const VirtualNetworkVector& virtual_networks = cros->virtual_networks(); const VirtualNetwork* active_vpn = cros->virtual_network(); SkBitmap icon = VPNMenuModel::IconForDisplay(connected_network); bool separator_added = false; string16 label; for (size_t i = 0; i < virtual_networks.size(); ++i) { const VirtualNetwork* vpn = virtual_networks[i]; if (vpn->connecting()) { label = l10n_util::GetStringFUTF16( IDS_STATUSBAR_NETWORK_DEVICE_STATUS, UTF8ToUTF16(vpn->name()), l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DEVICE_CONNECTING)); } else { label = UTF8ToUTF16(vpn->name()); } // First add a separator if necessary. if (!separator_added) { separator_added = true; if (!menu_items_.empty()) { // Don't add if first menu item. menu_items_.push_back(MenuItem()); // Separator } } int flag = FLAG_VPN; if (!vpn->connectable()) flag |= FLAG_DISABLED; if (active_vpn && vpn->service_path() == active_vpn->service_path()) flag |= FLAG_ASSOCIATED; menu_items_.push_back( MenuItem(ui::MenuModel::TYPE_COMMAND, label, icon, vpn->service_path(), flag)); } // Add option to add/disconnect from vpn. if (!menu_items_.empty()) { // Add separator if menu is not empty. menu_items_.push_back(MenuItem()); } menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_ADD_VPN), SkBitmap(), std::string(), FLAG_ADD_VPN)); if (active_vpn) { menu_items_.push_back(MenuItem( ui::MenuModel::TYPE_COMMAND, l10n_util::GetStringUTF16(IDS_STATUSBAR_NETWORK_DISCONNECT_VPN), SkBitmap(), std::string(), FLAG_DISCONNECT_VPN)); } } // static SkBitmap VPNMenuModel::IconForDisplay(const Network* network) { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); const SkBitmap* icon = NULL; const SkBitmap* bottom_right_badge = NULL; const SkBitmap* top_left_badge = NULL; // We know for sure |network| is the active network, so no more checking // is needed by BadgeForPrivateNetworkStatus, hence pass in NULL. const SkBitmap* bottom_left_badge = NetworkMenu::BadgeForPrivateNetworkStatus(NULL); switch (network->type()) { case TYPE_ETHERNET : icon = rb.GetBitmapNamed(IDR_STATUSBAR_WIRED_BLACK); break; case TYPE_WIFI : icon = NetworkMenu::IconForNetworkStrength( static_cast<const WifiNetwork*>(network), true); break; case TYPE_CELLULAR : { const CellularNetwork* cellular = static_cast<const CellularNetwork*>(network); icon = NetworkMenu::IconForNetworkStrength(cellular, true); bottom_right_badge = NetworkMenu::BadgeForNetworkTechnology(cellular); top_left_badge = NetworkMenu::BadgeForRoamingStatus(cellular); break; } default: LOG(WARNING) << "VPN not handled for connection type " << network->type(); return SkBitmap(); } return NetworkMenu::IconForDisplay(icon, bottom_right_badge, top_left_badge, bottom_left_badge); } //////////////////////////////////////////////////////////////////////////////// // MoreMenuModel MoreMenuModel::MoreMenuModel(NetworkMenu* owner) : NetworkMenuModel(owner) { } void MoreMenuModel::InitMenuItems( bool is_browser_mode, bool should_open_button_options) { // This gets called on initialization, so any changes should be reflected // in CrosMock::SetNetworkLibraryStatusAreaExpectations(). menu_items_.clear(); MenuItemVector link_items; MenuItemVector address_items; NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); bool oobe = !should_open_button_options; // we don't show options for OOBE. if (!oobe) { string16 label = l10n_util::GetStringUTF16(is_browser_mode ? IDS_STATUSBAR_NETWORK_OPEN_OPTIONS_DIALOG : IDS_STATUSBAR_NETWORK_OPEN_PROXY_SETTINGS_DIALOG); link_items.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, label, SkBitmap(), std::string(), FLAG_OPTIONS)); } bool connected = cros->Connected(); // always call for test expectations. if (connected) { std::string ip_address = cros->IPAddress(); if (!ip_address.empty()) { address_items.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, ASCIIToUTF16(cros->IPAddress()), SkBitmap(), std::string(), FLAG_DISABLED)); } } if (!is_browser_mode) { const NetworkDevice* ether = cros->FindEthernetDevice(); if (ether) { std::string hardware_address; cros->GetIPConfigs(ether->device_path(), &hardware_address, NetworkLibrary::FORMAT_COLON_SEPARATED_HEX); if (!hardware_address.empty()) { std::string label = l10n_util::GetStringUTF8( IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET) + " " + hardware_address; address_items.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, UTF8ToUTF16(label), SkBitmap(), std::string(), FLAG_DISABLED)); } } if (cros->wifi_enabled()) { const NetworkDevice* wifi = cros->FindWifiDevice(); if (wifi) { std::string hardware_address; cros->GetIPConfigs(wifi->device_path(), &hardware_address, NetworkLibrary::FORMAT_COLON_SEPARATED_HEX); if (!hardware_address.empty()) { std::string label = l10n_util::GetStringUTF8( IDS_STATUSBAR_NETWORK_DEVICE_WIFI) + " " + hardware_address; address_items.push_back(MenuItem(ui::MenuModel::TYPE_COMMAND, UTF8ToUTF16(label), SkBitmap(), std::string(), FLAG_DISABLED)); } } } } menu_items_ = link_items; if (!menu_items_.empty() && address_items.size() > 1) menu_items_.push_back(MenuItem()); // Separator menu_items_.insert(menu_items_.end(), address_items.begin(), address_items.end()); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenu // static const int NetworkMenu::kNumBarsImages = 4; // NOTE: Use an array rather than just calculating a resource number to avoid // creating implicit ordering dependencies on the resource values. // static const int NetworkMenu::kBarsImages[kNumBarsImages] = { IDR_STATUSBAR_NETWORK_BARS1, IDR_STATUSBAR_NETWORK_BARS2, IDR_STATUSBAR_NETWORK_BARS3, IDR_STATUSBAR_NETWORK_BARS4, }; // static const int NetworkMenu::kBarsImagesBlack[kNumBarsImages] = { IDR_STATUSBAR_NETWORK_BARS1_BLACK, IDR_STATUSBAR_NETWORK_BARS2_BLACK, IDR_STATUSBAR_NETWORK_BARS3_BLACK, IDR_STATUSBAR_NETWORK_BARS4_BLACK, }; // static const int NetworkMenu::kBarsImagesOrange[kNumBarsImages] = { IDR_STATUSBAR_NETWORK_BARS1_ORANGE, IDR_STATUSBAR_NETWORK_BARS2_ORANGE, IDR_STATUSBAR_NETWORK_BARS3_ORANGE, IDR_STATUSBAR_NETWORK_BARS4_ORANGE, }; #if 0 // static const int NetworkMenu::kBarsImagesVLowData[kNumBarsImages] = { IDR_STATUSBAR_NETWORK_BARS1_RED, IDR_STATUSBAR_NETWORK_BARS2_RED, IDR_STATUSBAR_NETWORK_BARS3_RED, IDR_STATUSBAR_NETWORK_BARS4_RED, }; #endif // static SkBitmap NetworkMenu::kAnimatingImages[kNumBarsImages]; // static SkBitmap NetworkMenu::kAnimatingImagesBlack[kNumBarsImages]; NetworkMenu::NetworkMenu() : min_width_(-1) { main_menu_model_.reset(new MainMenuModel(this)); network_menu_.reset(new views::Menu2(main_menu_model_.get())); } NetworkMenu::~NetworkMenu() { } void NetworkMenu::SetFirstLevelMenuWidth(int width) { min_width_ = width; // This actually has no effect since menu is rebuilt before showing. network_menu_->SetMinimumWidth(width); } void NetworkMenu::CancelMenu() { network_menu_->CancelMenu(); } void NetworkMenu::UpdateMenu() { refreshing_menu_ = true; main_menu_model_->InitMenuItems(IsBrowserMode(), ShouldOpenButtonOptions()); network_menu_->Rebuild(); refreshing_menu_ = false; } // static const SkBitmap* NetworkMenu::IconForNetworkStrength(const WifiNetwork* wifi, bool black) { DCHECK(wifi); if (wifi->strength() == 0) { return ResourceBundle::GetSharedInstance().GetBitmapNamed( black ? IDR_STATUSBAR_NETWORK_BARS0_BLACK : IDR_STATUSBAR_NETWORK_BARS0); } int index = static_cast<int>(wifi->strength() / 100.0 * nextafter(static_cast<float>(kNumBarsImages), 0)); index = std::max(std::min(index, kNumBarsImages - 1), 0); const int* images = black ? kBarsImagesBlack : kBarsImages; return ResourceBundle::GetSharedInstance().GetBitmapNamed(images[index]); } // static const SkBitmap* NetworkMenu::IconForNetworkStrength( const CellularNetwork* cellular, bool black) { DCHECK(cellular); // If no data, then we show 0 bars. if (cellular->strength() == 0 || cellular->data_left() == CellularNetwork::DATA_NONE) { return ResourceBundle::GetSharedInstance().GetBitmapNamed( black ? IDR_STATUSBAR_NETWORK_BARS0_BLACK : IDR_STATUSBAR_NETWORK_BARS0); } int index = static_cast<int>(cellular->strength() / 100.0 * nextafter(static_cast<float>(kNumBarsImages), 0)); index = std::max(std::min(index, kNumBarsImages - 1), 0); const int* images = black ? kBarsImagesBlack : kBarsImages; return ResourceBundle::GetSharedInstance().GetBitmapNamed(images[index]); } // static const SkBitmap* NetworkMenu::IconForNetworkConnecting(double animation_value, bool black) { // Fade bars a bit and show the different bar states. const int* source_image_ids = black ? kBarsImagesBlack : kBarsImages; SkBitmap* images = black ? kAnimatingImagesBlack : kAnimatingImages; int index = static_cast<int>(animation_value * nextafter(static_cast<float>(kNumBarsImages), 0)); index = std::max(std::min(index, kNumBarsImages - 1), 0); // Lazily cache images. if (images[index].empty()) { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); SkBitmap source = *rb.GetBitmapNamed(source_image_ids[index]); // Create an empty image to fade against. SkBitmap empty_image; empty_image.setConfig(SkBitmap::kARGB_8888_Config, source.width(), source.height(), 0); empty_image.allocPixels(); empty_image.eraseARGB(0, 0, 0, 0); images[index] = SkBitmapOperations::CreateBlendedBitmap( empty_image, source, kConnectingImageAlpha); } return &images[index]; } // static const SkBitmap* NetworkMenu::BadgeForNetworkTechnology( const CellularNetwork* cellular) { if (!cellular) return NULL; int id = -1; switch (cellular->network_technology()) { case NETWORK_TECHNOLOGY_EVDO: switch (cellular->data_left()) { case CellularNetwork::DATA_NONE: id = IDR_STATUSBAR_NETWORK_3G_ERROR; break; case CellularNetwork::DATA_VERY_LOW: case CellularNetwork::DATA_LOW: case CellularNetwork::DATA_NORMAL: id = IDR_STATUSBAR_NETWORK_3G; break; case CellularNetwork::DATA_UNKNOWN: id = IDR_STATUSBAR_NETWORK_3G_UNKNOWN; break; } break; case NETWORK_TECHNOLOGY_1XRTT: switch (cellular->data_left()) { case CellularNetwork::DATA_NONE: id = IDR_STATUSBAR_NETWORK_1X_ERROR; break; case CellularNetwork::DATA_VERY_LOW: case CellularNetwork::DATA_LOW: case CellularNetwork::DATA_NORMAL: id = IDR_STATUSBAR_NETWORK_1X; break; case CellularNetwork::DATA_UNKNOWN: id = IDR_STATUSBAR_NETWORK_1X_UNKNOWN; break; } break; // Note: we may not be able to obtain data usage info // from GSM carriers, so there may not be a reason // to create _ERROR or _UNKNOWN versions of the following // icons. case NETWORK_TECHNOLOGY_GPRS: id = IDR_STATUSBAR_NETWORK_GPRS; break; case NETWORK_TECHNOLOGY_EDGE: id = IDR_STATUSBAR_NETWORK_EDGE; break; case NETWORK_TECHNOLOGY_UMTS: id = IDR_STATUSBAR_NETWORK_3G; break; case NETWORK_TECHNOLOGY_HSPA: id = IDR_STATUSBAR_NETWORK_HSPA; break; case NETWORK_TECHNOLOGY_HSPA_PLUS: id = IDR_STATUSBAR_NETWORK_HSPA_PLUS; break; case NETWORK_TECHNOLOGY_LTE: id = IDR_STATUSBAR_NETWORK_LTE; break; case NETWORK_TECHNOLOGY_LTE_ADVANCED: id = IDR_STATUSBAR_NETWORK_LTE_ADVANCED; break; case NETWORK_TECHNOLOGY_UNKNOWN: break; } if (id == -1) return NULL; else return ResourceBundle::GetSharedInstance().GetBitmapNamed(id); } // static const SkBitmap* NetworkMenu::BadgeForRoamingStatus( const CellularNetwork* cellular) { if (cellular->roaming_state() == ROAMING_STATE_ROAMING) return ResourceBundle::GetSharedInstance().GetBitmapNamed( IDR_STATUSBAR_NETWORK_ROAMING); else return NULL; } const SkBitmap* NetworkMenu::BadgeForPrivateNetworkStatus( const Network* network) { // If network is not null, check if it's the active network with vpn on it. if (network) { NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); if (!(cros->virtual_network() && network == cros->connected_network())) return NULL; } // TODO(kuan): yellow lock icon not ready yet; for now, return the black one // used by secure wifi network. return ResourceBundle::GetSharedInstance().GetBitmapNamed( IDR_STATUSBAR_NETWORK_SECURE); } // static SkBitmap NetworkMenu::IconForDisplay(const SkBitmap* icon, const SkBitmap* badge) { return IconForDisplay(icon, badge, NULL, NULL); } // static SkBitmap NetworkMenu::IconForDisplay(const SkBitmap* icon, const SkBitmap* bottom_right_badge, const SkBitmap* top_left_badge, const SkBitmap* bottom_left_badge) { DCHECK(icon); if (bottom_right_badge == NULL && top_left_badge == NULL && bottom_left_badge == NULL) return *icon; static const int kTopLeftBadgeX = 0; static const int kTopLeftBadgeY = 0; static const int kBottomRightBadgeX = 14; static const int kBottomRightBadgeY = 14; static const int kBottomLeftBadgeX = 0; static const int kBottomLeftBadgeY = 14; gfx::CanvasSkia canvas(icon->width(), icon->height(), false); canvas.DrawBitmapInt(*icon, 0, 0); if (bottom_right_badge != NULL) canvas.DrawBitmapInt(*bottom_right_badge, kBottomRightBadgeX, kBottomRightBadgeY); if (top_left_badge != NULL) canvas.DrawBitmapInt(*top_left_badge, kTopLeftBadgeX, kTopLeftBadgeY); if (bottom_left_badge != NULL) canvas.DrawBitmapInt(*bottom_left_badge, kBottomLeftBadgeX, kBottomLeftBadgeY); return canvas.ExtractBitmap(); } void NetworkMenu::ShowTabbedNetworkSettings(const Network* network) const { DCHECK(network); Browser* browser = BrowserList::GetLastActive(); if (!browser) return; std::string page = StringPrintf("%s?servicePath=%s&networkType=%d", chrome::kInternetOptionsSubPage, EscapeUrlEncodedData(network->service_path()).c_str(), network->type()); browser->ShowOptionsTab(page); } //////////////////////////////////////////////////////////////////////////////// // NetworkMenu, views::ViewMenuDelegate implementation: void NetworkMenu::RunMenu(views::View* source, const gfx::Point& pt) { refreshing_menu_ = true; NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); cros->RequestNetworkScan(); // Build initial menu items. They will be updated when UpdateMenu is // called from NetworkChanged. main_menu_model_->InitMenuItems(IsBrowserMode(), ShouldOpenButtonOptions()); network_menu_->Rebuild(); // Restore menu width, if it was set up. // NOTE: width isn't checked for correctness here since all width-related // logic implemented inside |network_menu_|. if (min_width_ != -1) network_menu_->SetMinimumWidth(min_width_); refreshing_menu_ = false; network_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); } } // namespace chromeos