text: // 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/network_message_observer.h"
  
  #include "base/callback.h"
  #include "base/stl_util-inl.h"
  #include "base/string_number_conversions.h"
  #include "base/utf_string_conversions.h"
  #include "chrome/browser/chromeos/cros/cros_library.h"
  #include "chrome/browser/chromeos/cros/network_library.h"
  #include "chrome/browser/chromeos/notifications/balloon_view_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 "chrome/common/time_format.h"
  #include "grit/generated_resources.h"
  #include "grit/theme_resources.h"
  #include "ui/base/l10n/l10n_util.h"
  
  namespace {
  
  // Returns prefs::kShowPlanNotifications in the profile of the last active
  // browser. If there is no active browser, returns true.
  bool ShouldShowMobilePlanNotifications() {
    Browser* browser = BrowserList::GetLastActive();
    if (!browser || !browser->profile())
      return true;
  
    PrefService* prefs = browser->profile()->GetPrefs();
    return prefs->GetBoolean(prefs::kShowPlanNotifications);
  }
  
  }  // namespace
  
  namespace chromeos {
  
  NetworkMessageObserver::NetworkMessageObserver(Profile* profile)
      : notification_connection_error_(profile, "network_connection.chromeos",
            IDR_NOTIFICATION_NETWORK_FAILED,
            l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE)),
        notification_low_data_(profile, "network_low_data.chromeos",
            IDR_NOTIFICATION_BARS_CRITICAL,
            l10n_util::GetStringUTF16(IDS_NETWORK_LOW_DATA_TITLE)),
        notification_no_data_(profile, "network_no_data.chromeos",
            IDR_NOTIFICATION_BARS_EMPTY,
            l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE)) {
    NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
    OnNetworkManagerChanged(netlib);
    // Note that this gets added as a NetworkManagerObserver,
    // CellularDataPlanObserver, and UserActionObserver in browser_init.cc
  }
  
  NetworkMessageObserver::~NetworkMessageObserver() {
    NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
    netlib->RemoveNetworkManagerObserver(this);
    netlib->RemoveCellularDataPlanObserver(this);
    netlib->RemoveUserActionObserver(this);
    notification_connection_error_.Hide();
    notification_low_data_.Hide();
    notification_no_data_.Hide();
  }
  
  // static
  bool NetworkMessageObserver::IsApplicableBackupPlan(
      const CellularDataPlan* plan, const CellularDataPlan* other_plan) {
    // By applicable plan, we mean that the other plan has data AND the timeframe
    // will apply: (unlimited OR used bytes < max bytes) AND
    //   ((start time - 1 sec) <= end time of currently active plan).
    // In other words, there is data available and there is no gap of more than a
    // second in time between the old plan and the new plan.
    bool has_data = other_plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED ||
        other_plan->remaining_data() > 0;
    bool will_apply =
        (other_plan->plan_start_time - plan->plan_end_time).InSeconds() <= 1;
    return has_data && will_apply;
  }
  
  void NetworkMessageObserver::OpenMobileSetupPage(const ListValue* args) {
    Browser* browser = BrowserList::GetLastActive();
    if (browser)
      browser->OpenMobilePlanTabAndActivate();
  }
  
  void NetworkMessageObserver::OpenMoreInfoPage(const ListValue* args) {
    Browser* browser = BrowserList::GetLastActive();
    if (!browser)
      return;
    chromeos::NetworkLibrary* lib =
        chromeos::CrosLibrary::Get()->GetNetworkLibrary();
    const chromeos::CellularNetwork* cellular = lib->cellular_network();
    if (!cellular)
      return;
    browser->ShowSingletonTab(GURL(cellular->payment_url()));
  }
  
  void NetworkMessageObserver::InitNewPlan(const CellularDataPlan* plan) {
    notification_low_data_.Hide();
    notification_no_data_.Hide();
    if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
      notification_no_data_.set_title(
          l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_EXPIRED_TITLE,
                                     ASCIIToUTF16(plan->plan_name)));
      notification_low_data_.set_title(
          l10n_util::GetStringFUTF16(IDS_NETWORK_NEARING_EXPIRATION_TITLE,
                                     ASCIIToUTF16(plan->plan_name)));
    } else {
      notification_no_data_.set_title(
          l10n_util::GetStringFUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE,
                                     ASCIIToUTF16(plan->plan_name)));
      notification_low_data_.set_title(
          l10n_util::GetStringFUTF16(IDS_NETWORK_LOW_DATA_TITLE,
                                     ASCIIToUTF16(plan->plan_name)));
    }
  }
  
  void NetworkMessageObserver::ShowNeedsPlanNotification(
      const CellularNetwork* cellular) {
    notification_no_data_.set_title(
        l10n_util::GetStringFUTF16(IDS_NETWORK_NO_DATA_PLAN_TITLE,
                                   UTF8ToUTF16(cellular->name())));
    notification_no_data_.Show(
        l10n_util::GetStringFUTF16(
            IDS_NETWORK_NO_DATA_PLAN_MESSAGE,
            UTF8ToUTF16(cellular->name())),
        l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
        NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
        false, false);
  }
  
  void NetworkMessageObserver::ShowNoDataNotification(
      CellularDataPlanType plan_type) {
    notification_low_data_.Hide();  // Hide previous low data notification.
    string16 message = plan_type == CELLULAR_DATA_PLAN_UNLIMITED ?
        TimeFormat::TimeRemaining(base::TimeDelta()) :
        l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
                                   ASCIIToUTF16("0"));
    notification_no_data_.Show(message,
        l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
        NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
        false, false);
  }
  
  void NetworkMessageObserver::ShowLowDataNotification(
      const CellularDataPlan* plan) {
    string16 message;
    if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
      message = plan->GetPlanExpiration();
    } else {
      int64 remaining_mbytes = plan->remaining_data() / (1024 * 1024);
      message = l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
          UTF8ToUTF16(base::Int64ToString(remaining_mbytes)));
    }
    notification_low_data_.Show(message,
        l10n_util::GetStringUTF16(IDS_NETWORK_MORE_INFO_MESSAGE),
        NewCallback(this, &NetworkMessageObserver::OpenMoreInfoPage),
        false, false);
  }
  
  bool NetworkMessageObserver::CheckNetworkFailed(const Network* network) {
    if (network->failed()) {
      NetworkStateMap::iterator iter =
          network_states_.find(network->service_path());
      // If the network did not previously exist, then don't do anything.
      // For example, if the user travels to a location and finds a service
      // that has previously failed, we don't want to show a notification.
      if (iter == network_states_.end())
        return false;
      // If network connection failed, display a notification.
      // We only do this if we were trying to make a new connection.
      // So if a previously connected network got disconnected for any reason,
      // we don't display notification.
      ConnectionState prev_state = iter->second;
      if (Network::IsConnectingState(prev_state))
        return true;
    }
    return false;
  }
  
  void NetworkMessageObserver::OnNetworkManagerChanged(NetworkLibrary* cros) {
    const Network* new_failed_network = NULL;
    // Check to see if we have any newly failed networks.
    for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
         it != cros->wifi_networks().end(); it++) {
      const WifiNetwork* net = *it;
      if (CheckNetworkFailed(net)) {
        new_failed_network = net;
        break;  // There should only be one failed network.
      }
    }
  
    if (!new_failed_network) {
      for (CellularNetworkVector::const_iterator it =
               cros->cellular_networks().begin();
           it != cros->cellular_networks().end(); it++) {
        const CellularNetwork* net = *it;
        if (CheckNetworkFailed(net)) {
          new_failed_network = net;
          break;  // There should only be one failed network.
        }
      }
    }
  
    if (!new_failed_network) {
      for (VirtualNetworkVector::const_iterator it =
               cros->virtual_networks().begin();
           it != cros->virtual_networks().end(); it++) {
        const VirtualNetwork* net = *it;
        if (CheckNetworkFailed(net)) {
          new_failed_network = net;
          break;  // There should only be one failed network.
        }
      }
    }
  
    network_states_.clear();
    for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
         it != cros->wifi_networks().end(); it++)
      network_states_[(*it)->service_path()] = (*it)->state();
    for (CellularNetworkVector::const_iterator it =
             cros->cellular_networks().begin();
         it != cros->cellular_networks().end(); it++)
      network_states_[(*it)->service_path()] = (*it)->state();
    for (VirtualNetworkVector::const_iterator it =
             cros->virtual_networks().begin();
         it != cros->virtual_networks().end(); it++)
      network_states_[(*it)->service_path()] = (*it)->state();
  
    // Show connection error notification if necessary.
    if (new_failed_network) {
      // Hide if already shown to force show it in case user has closed it.
      if (notification_connection_error_.visible())
        notification_connection_error_.Hide();
      notification_connection_error_.Show(l10n_util::GetStringFUTF16(
          IDS_NETWORK_CONNECTION_ERROR_MESSAGE,
          UTF8ToUTF16(new_failed_network->name())), false, false);
    }
  }
  
  void NetworkMessageObserver::OnCellularDataPlanChanged(NetworkLibrary* cros) {
    if (!ShouldShowMobilePlanNotifications())
      return;
    const CellularNetwork* cellular = cros->cellular_network();
    if (!cellular || !cellular->SupportsDataPlan())
      return;
  
    const CellularDataPlanVector* plans =
        cros->GetDataPlans(cellular->service_path());
    // If no plans available, check to see if we need a new plan.
    if (!plans || plans->empty()) {
      // If previously, we had low data, we know that a plan was near expiring.
      // In that case, because the plan disappeared, we assume that it expired.
      if (cellular_data_left_ == CellularNetwork::DATA_LOW) {
        ShowNoDataNotification(cellular_data_plan_type_);
      } else if (cellular->needs_new_plan()) {
        ShowNeedsPlanNotification(cellular);
      }
      SaveLastCellularInfo(cellular, NULL);
      return;
    }
  
    CellularDataPlanVector::const_iterator iter = plans->begin();
    const CellularDataPlan* current_plan = *iter;
  
    // If current plan is not the last plan (there is another backup plan),
    // then we do not show notifications for this plan.
    // For example, if there is another data plan available when this runs out.
    for (++iter; iter != plans->end(); ++iter) {
      if (IsApplicableBackupPlan(current_plan, *iter)) {
        SaveLastCellularInfo(cellular, current_plan);
        return;
      }
    }
  
    // If connected cellular network changed, or data plan is different, then
    // it's a new network. Then hide all previous notifications.
    bool new_plan = cellular->service_path() != cellular_service_path_ ||
        current_plan->GetUniqueIdentifier() != cellular_data_plan_unique_id_;
  
    if (new_plan) {
      InitNewPlan(current_plan);
    }
  
    if (cellular->data_left() == CellularNetwork::DATA_NONE) {
      ShowNoDataNotification(current_plan->plan_type);
    } else if (cellular->data_left() == CellularNetwork::DATA_VERY_LOW) {
      // Only show low data notification if we transition to very low data
      // and we are on the same plan. This is so that users don't get a
      // notification whenever they connect to a low data 3g network.
      if (!new_plan && (cellular_data_left_ != CellularNetwork::DATA_VERY_LOW))
        ShowLowDataNotification(current_plan);
    }
  
    SaveLastCellularInfo(cellular, current_plan);
  }
  
  void NetworkMessageObserver::OnConnectionInitiated(NetworkLibrary* cros,
                                                     const Network* network) {
    // If user initiated any network connection, we hide the error notification.
    notification_connection_error_.Hide();
  }
  
  void NetworkMessageObserver::SaveLastCellularInfo(
      const CellularNetwork* cellular, const CellularDataPlan* plan) {
    DCHECK(cellular);
    cellular_service_path_ = cellular->service_path();
    cellular_data_left_ = cellular->data_left();
    if (plan) {
      cellular_data_plan_unique_id_ = plan->GetUniqueIdentifier();
      cellular_data_plan_type_ = plan->plan_type;
    } else {
      cellular_data_plan_unique_id_ = std::string();
      cellular_data_plan_type_ = CELLULAR_DATA_PLAN_UNKNOWN;
    }
  }
  
  }  // namespace chromeos
  