普通文本  |  1134行  |  40.99 KB

// 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