1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/status/network_menu_button.h"
6
7#include <algorithm>
8#include <limits>
9
10#include "base/logging.h"
11#include "base/message_loop.h"
12#include "base/string_util.h"
13#include "base/stringprintf.h"
14#include "base/utf_string_conversions.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/chromeos/cros/cros_library.h"
17#include "chrome/browser/chromeos/login/helper.h"
18#include "chrome/browser/chromeos/login/user_manager.h"
19#include "chrome/browser/chromeos/options/network_config_view.h"
20#include "chrome/browser/chromeos/sim_dialog_delegate.h"
21#include "chrome/browser/chromeos/status/status_area_host.h"
22#include "chrome/browser/prefs/pref_service.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/ui/browser.h"
25#include "chrome/browser/ui/browser_list.h"
26#include "chrome/common/pref_names.h"
27#include "grit/generated_resources.h"
28#include "grit/theme_resources.h"
29#include "ui/base/l10n/l10n_util.h"
30#include "ui/base/resource/resource_bundle.h"
31#include "ui/gfx/canvas_skia.h"
32#include "views/window/window.h"
33
34namespace {
35
36// Time in milliseconds to delay showing of promo
37// notification when Chrome window is not on screen.
38const int kPromoShowDelayMs = 10000;
39
40const int kNotificationCountPrefDefault = -1;
41
42bool GetBooleanPref(const char* pref_name) {
43  Browser* browser = BrowserList::GetLastActive();
44  // Default to safe value which is false (not to show bubble).
45  if (!browser || !browser->profile())
46    return false;
47
48  PrefService* prefs = browser->profile()->GetPrefs();
49  return prefs->GetBoolean(pref_name);
50}
51
52int GetIntegerPref(const char* pref_name) {
53  Browser* browser = BrowserList::GetLastActive();
54  // Default to "safe" value.
55  if (!browser || !browser->profile())
56    return kNotificationCountPrefDefault;
57
58  PrefService* prefs = browser->profile()->GetPrefs();
59  return prefs->GetInteger(pref_name);
60}
61
62void SetBooleanPref(const char* pref_name, bool value) {
63  Browser* browser = BrowserList::GetLastActive();
64  if (!browser || !browser->profile())
65    return;
66
67  PrefService* prefs = browser->profile()->GetPrefs();
68  prefs->SetBoolean(pref_name, value);
69}
70
71void SetIntegerPref(const char* pref_name, int value) {
72  Browser* browser = BrowserList::GetLastActive();
73  if (!browser || !browser->profile())
74    return;
75
76  PrefService* prefs = browser->profile()->GetPrefs();
77  prefs->SetInteger(pref_name, value);
78}
79
80// Returns prefs::kShow3gPromoNotification or false
81// if there's no active browser.
82bool ShouldShow3gPromoNotification() {
83  return GetBooleanPref(prefs::kShow3gPromoNotification);
84}
85
86void SetShow3gPromoNotification(bool value) {
87  SetBooleanPref(prefs::kShow3gPromoNotification, value);
88}
89// Returns prefs::kCarrierDealPromoShown which is number of times
90// carrier deal notification has been shown to user or -1
91// if there's no active browser.
92int GetCarrierDealPromoShown() {
93  return GetIntegerPref(prefs::kCarrierDealPromoShown);
94}
95
96void SetCarrierDealPromoShown(int value) {
97  SetIntegerPref(prefs::kCarrierDealPromoShown, value);
98}
99
100}  // namespace
101
102namespace chromeos {
103
104////////////////////////////////////////////////////////////////////////////////
105// NetworkMenuButton
106
107// static
108const int NetworkMenuButton::kThrobDuration = 750;
109
110NetworkMenuButton::NetworkMenuButton(StatusAreaHost* host)
111    : StatusAreaButton(host, this),
112      NetworkMenu(),
113      icon_(NULL),
114      right_badge_(NULL),
115      left_badge_(NULL),
116      mobile_data_bubble_(NULL),
117      check_for_promo_(true),
118      was_sim_locked_(false),
119      ALLOW_THIS_IN_INITIALIZER_LIST(animation_connecting_(this)),
120      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
121  animation_connecting_.SetThrobDuration(kThrobDuration);
122  animation_connecting_.SetTweenType(ui::Tween::LINEAR);
123  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
124  OnNetworkManagerChanged(network_library);
125  network_library->AddNetworkManagerObserver(this);
126  network_library->AddCellularDataPlanObserver(this);
127  const NetworkDevice* cellular = network_library->FindCellularDevice();
128  if (cellular) {
129    cellular_device_path_ = cellular->device_path();
130    was_sim_locked_ = cellular->is_sim_locked();
131    network_library->AddNetworkDeviceObserver(cellular_device_path_, this);
132  }
133}
134
135NetworkMenuButton::~NetworkMenuButton() {
136  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
137  netlib->RemoveNetworkManagerObserver(this);
138  netlib->RemoveObserverForAllNetworks(this);
139  netlib->RemoveCellularDataPlanObserver(this);
140  if (!cellular_device_path_.empty())
141    netlib->RemoveNetworkDeviceObserver(cellular_device_path_, this);
142  if (mobile_data_bubble_)
143    mobile_data_bubble_->Close();
144}
145
146////////////////////////////////////////////////////////////////////////////////
147// NetworkMenuButton, ui::AnimationDelegate implementation:
148
149void NetworkMenuButton::AnimationProgressed(const ui::Animation* animation) {
150  if (animation == &animation_connecting_) {
151    SetIconOnly(IconForNetworkConnecting(
152        animation_connecting_.GetCurrentValue(), false));
153    // No need to set the badge here, because it should already be set.
154    SchedulePaint();
155  } else {
156    MenuButton::AnimationProgressed(animation);
157  }
158}
159
160////////////////////////////////////////////////////////////////////////////////
161// NetworkLibrary::NetworkDeviceObserver implementation:
162
163void NetworkMenuButton::OnNetworkDeviceChanged(NetworkLibrary* cros,
164                                               const NetworkDevice* device) {
165  // Device status, such as SIMLock may have changed.
166  OnNetworkChanged(cros, cros->active_network());
167  const NetworkDevice* cellular = cros->FindCellularDevice();
168  if (cellular) {
169    // We make an assumption (which is valid for now) that the SIM
170    // unlock dialog is put up only when the user is trying to enable
171    // mobile data. So if the SIM is now unlocked, initiate the
172    // enable operation that the user originally requested.
173    if (was_sim_locked_ && !cellular->is_sim_locked() &&
174        !cros->cellular_enabled()) {
175      cros->EnableCellularNetworkDevice(true);
176    }
177    was_sim_locked_ = cellular->is_sim_locked();
178  }
179}
180
181////////////////////////////////////////////////////////////////////////////////
182// NetworkMenuButton, NetworkLibrary::NetworkManagerObserver implementation:
183
184void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) {
185  OnNetworkChanged(cros, cros->active_network());
186  ShowOptionalMobileDataPromoNotification(cros);
187}
188
189////////////////////////////////////////////////////////////////////////////////
190// NetworkMenuButton, NetworkLibrary::NetworkObserver implementation:
191void NetworkMenuButton::OnNetworkChanged(NetworkLibrary* cros,
192                                         const Network* network) {
193  // This gets called on initialization, so any changes should be reflected
194  // in CrosMock::SetNetworkLibraryStatusAreaExpectations().
195  SetNetworkIcon(cros, network);
196  RefreshNetworkObserver(cros);
197  RefreshNetworkDeviceObserver(cros);
198  SchedulePaint();
199  UpdateMenu();
200}
201
202void NetworkMenuButton::OnCellularDataPlanChanged(NetworkLibrary* cros) {
203  // Call OnNetworkManagerChanged which will update the icon.
204  OnNetworkManagerChanged(cros);
205}
206
207////////////////////////////////////////////////////////////////////////////////
208// NetworkMenuButton, NetworkMenu implementation:
209
210bool NetworkMenuButton::IsBrowserMode() const {
211  return host_->GetScreenMode() == StatusAreaHost::kBrowserMode;
212}
213
214gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const {
215  return host_->GetNativeWindow();
216}
217
218void NetworkMenuButton::OpenButtonOptions() {
219  host_->OpenButtonOptions(this);
220}
221
222bool NetworkMenuButton::ShouldOpenButtonOptions() const {
223  return host_->ShouldOpenButtonOptions(this);
224}
225
226////////////////////////////////////////////////////////////////////////////////
227// NetworkMenuButton, views::View implementation:
228
229void NetworkMenuButton::OnLocaleChanged() {
230  NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary();
231  SetNetworkIcon(lib, lib->active_network());
232}
233
234////////////////////////////////////////////////////////////////////////////////
235// MessageBubbleDelegate implementation:
236
237void NetworkMenuButton::OnHelpLinkActivated() {
238  // mobile_data_bubble_ will be set to NULL in callback.
239  if (mobile_data_bubble_)
240    mobile_data_bubble_->Close();
241  if (!deal_url_.empty()) {
242    Browser* browser = BrowserList::GetLastActive();
243    if (!browser)
244      return;
245    browser->ShowSingletonTab(GURL(deal_url_));
246    deal_url_.clear();
247  } else {
248    const Network* cellular =
249        CrosLibrary::Get()->GetNetworkLibrary()->cellular_network();
250    if (!cellular)
251      return;
252    ShowTabbedNetworkSettings(cellular);
253  }
254}
255
256////////////////////////////////////////////////////////////////////////////////
257// NetworkMenuButton, private methods
258
259const ServicesCustomizationDocument::CarrierDeal*
260NetworkMenuButton::GetCarrierDeal(
261    NetworkLibrary* cros) {
262  std::string carrier_id = cros->GetCellularHomeCarrierId();
263  if (carrier_id.empty()) {
264    LOG(ERROR) << "Empty carrier ID with a cellular connected.";
265    return NULL;
266  }
267
268  ServicesCustomizationDocument* customization =
269      ServicesCustomizationDocument::GetInstance();
270  if (!customization->IsReady())
271    return NULL;
272
273  const ServicesCustomizationDocument::CarrierDeal* deal =
274      customization->GetCarrierDeal(carrier_id, true);
275  if (deal) {
276    // Check deal for validity.
277    int carrier_deal_promo_pref = GetCarrierDealPromoShown();
278    if (carrier_deal_promo_pref >= deal->notification_count)
279      return NULL;
280    const std::string locale = g_browser_process->GetApplicationLocale();
281    std::string deal_text = deal->GetLocalizedString(locale,
282                                                     "notification_text");
283    if (deal_text.empty())
284      return NULL;
285  }
286  return deal;
287}
288
289void NetworkMenuButton::SetIconAndBadges(const SkBitmap* icon,
290                                         const SkBitmap* right_badge,
291                                         const SkBitmap* left_badge) {
292  icon_ = icon;
293  right_badge_ = right_badge;
294  left_badge_ = left_badge;
295  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
296                         left_badge_));
297}
298
299void NetworkMenuButton::SetIconOnly(const SkBitmap* icon) {
300  icon_ = icon;
301  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
302                         left_badge_));
303}
304
305void NetworkMenuButton::SetBadgesOnly(const SkBitmap* right_badge,
306                                      const SkBitmap* left_badge) {
307  right_badge_ = right_badge;
308  left_badge_ = left_badge;
309  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
310                         left_badge_));
311}
312
313void NetworkMenuButton::SetNetworkIcon(NetworkLibrary* cros,
314                                       const Network* network) {
315  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
316
317  if (!cros || !CrosLibrary::Get()->EnsureLoaded()) {
318    SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
319                     rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_WARNING),
320                     NULL);
321    SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
322        IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
323    return;
324  }
325
326  if (!cros->Connected() && !cros->Connecting()) {
327    animation_connecting_.Stop();
328    SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
329                     rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_DISCONNECTED),
330                     NULL);
331    SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
332        IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
333    return;
334  }
335
336  if (cros->wifi_connecting() || cros->cellular_connecting()) {
337    // Start the connecting animation if not running.
338    if (!animation_connecting_.is_animating()) {
339      animation_connecting_.Reset();
340      animation_connecting_.StartThrobbing(-1);
341      SetIconOnly(IconForNetworkConnecting(0, false));
342    }
343    const WirelessNetwork* wireless = NULL;
344    if (cros->wifi_connecting()) {
345      wireless = cros->wifi_network();
346      SetBadgesOnly(NULL, NULL);
347    } else {  // cellular_connecting
348      wireless = cros->cellular_network();
349      SetBadgesOnly(BadgeForNetworkTechnology(cros->cellular_network()), NULL);
350    }
351    SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
352        wireless->configuring() ? IDS_STATUSBAR_NETWORK_CONFIGURING_TOOLTIP
353                                : IDS_STATUSBAR_NETWORK_CONNECTING_TOOLTIP,
354        UTF8ToUTF16(wireless->name()))));
355  } else {
356    // Stop connecting animation since we are not connecting.
357    animation_connecting_.Stop();
358    // Only set the icon, if it is an active network that changed.
359    if (network && network->is_active()) {
360      const SkBitmap* right_badge(NULL);
361      const SkBitmap* left_badge(NULL);
362      if (cros->virtual_network())
363        left_badge = rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_SECURE);
364      if (network->type() == TYPE_ETHERNET) {
365        SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_WIRED),
366                         right_badge, left_badge);
367        SetTooltipText(
368            UTF16ToWide(l10n_util::GetStringFUTF16(
369                IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
370                l10n_util::GetStringUTF16(
371                    IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET))));
372      } else if (network->type() == TYPE_WIFI) {
373        const WifiNetwork* wifi = static_cast<const WifiNetwork*>(network);
374        SetIconAndBadges(IconForNetworkStrength(wifi, false),
375                         right_badge, left_badge);
376        SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
377            IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
378            UTF8ToUTF16(wifi->name()))));
379      } else if (network->type() == TYPE_CELLULAR) {
380        const CellularNetwork* cellular =
381            static_cast<const CellularNetwork*>(network);
382        right_badge = BadgeForNetworkTechnology(cellular);
383        SetIconAndBadges(IconForNetworkStrength(cellular, false),
384                         right_badge, left_badge);
385        SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
386            IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
387            UTF8ToUTF16(cellular->name()))));
388      }
389    }
390  }
391}
392
393void NetworkMenuButton::RefreshNetworkObserver(NetworkLibrary* cros) {
394  const Network* network = cros->active_network();
395  std::string new_network = network ? network->service_path() : std::string();
396  if (active_network_ != new_network) {
397    if (!active_network_.empty()) {
398      cros->RemoveNetworkObserver(active_network_, this);
399    }
400    if (!new_network.empty()) {
401      cros->AddNetworkObserver(new_network, this);
402    }
403    active_network_ = new_network;
404  }
405}
406
407void NetworkMenuButton::RefreshNetworkDeviceObserver(NetworkLibrary* cros) {
408  const NetworkDevice* cellular = cros->FindCellularDevice();
409  std::string new_cellular_device_path = cellular ?
410      cellular->device_path() : std::string();
411  if (cellular_device_path_ != new_cellular_device_path) {
412    if (!cellular_device_path_.empty()) {
413      cros->RemoveNetworkDeviceObserver(cellular_device_path_, this);
414    }
415    if (!new_cellular_device_path.empty()) {
416      was_sim_locked_ = cellular->is_sim_locked();
417      cros->AddNetworkDeviceObserver(new_cellular_device_path, this);
418    }
419    cellular_device_path_ = new_cellular_device_path;
420  }
421}
422
423void NetworkMenuButton::ShowOptionalMobileDataPromoNotification(
424    NetworkLibrary* cros) {
425  // Display one-time notification for non-Guest users on first use
426  // of Mobile Data connection or if there's a carrier deal defined
427  // show that even if user has already seen generic promo.
428  if (IsBrowserMode() && !UserManager::Get()->IsLoggedInAsGuest() &&
429      check_for_promo_ && BrowserList::GetLastActive() &&
430      cros->cellular_connected() && !cros->ethernet_connected() &&
431      !cros->wifi_connected()) {
432    const ServicesCustomizationDocument::CarrierDeal* deal =
433        GetCarrierDeal(cros);
434    std::string deal_text;
435    int carrier_deal_promo_pref = -1;
436    if (deal) {
437      carrier_deal_promo_pref = GetCarrierDealPromoShown();
438      const std::string locale = g_browser_process->GetApplicationLocale();
439      deal_text = deal->GetLocalizedString(locale, "notification_text");
440      deal_url_ = deal->top_up_url;
441    } else if (!ShouldShow3gPromoNotification()) {
442      check_for_promo_ = false;
443      return;
444    }
445
446    gfx::Rect button_bounds = GetScreenBounds();
447    // StatusArea button Y position is usually -1, fix it so that
448    // Contains() method for screen bounds works correctly.
449    button_bounds.set_y(button_bounds.y() + 1);
450    gfx::Rect screen_bounds(chromeos::CalculateScreenBounds(gfx::Size()));
451
452    // Chrome window is initialized in visible state off screen and then is
453    // moved into visible screen area. Make sure that we're on screen
454    // so that bubble is shown correctly.
455    if (!screen_bounds.Contains(button_bounds)) {
456      // If we're not on screen yet, delay notification display.
457      // It may be shown earlier, on next NetworkLibrary callback processing.
458      if (method_factory_.empty()) {
459        MessageLoop::current()->PostDelayedTask(FROM_HERE,
460            method_factory_.NewRunnableMethod(
461                &NetworkMenuButton::ShowOptionalMobileDataPromoNotification,
462                cros),
463            kPromoShowDelayMs);
464      }
465      return;
466    }
467
468    // Add deal text if it's defined.
469    std::wstring notification_text;
470    std::wstring default_text =
471        UTF16ToWide(l10n_util::GetStringUTF16(IDS_3G_NOTIFICATION_MESSAGE));
472    if (!deal_text.empty()) {
473      notification_text = StringPrintf(L"%ls\n\n%ls",
474                                       UTF8ToWide(deal_text).c_str(),
475                                       default_text.c_str());
476    } else {
477      notification_text = default_text;
478    }
479
480    // Use deal URL if it's defined or general "Network Settings" URL.
481    int link_message_id;
482    if (deal_url_.empty())
483      link_message_id = IDS_OFFLINE_NETWORK_SETTINGS;
484    else
485      link_message_id = IDS_STATUSBAR_NETWORK_VIEW_ACCOUNT;
486
487    mobile_data_bubble_ = MessageBubble::Show(
488        GetWidget(),
489        button_bounds,
490        BubbleBorder::TOP_RIGHT ,
491        ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_NOTIFICATION_3G),
492        notification_text,
493        UTF16ToWide(l10n_util::GetStringUTF16(link_message_id)),
494        this);
495
496    check_for_promo_ = false;
497    SetShow3gPromoNotification(false);
498    if (carrier_deal_promo_pref != kNotificationCountPrefDefault)
499      SetCarrierDealPromoShown(carrier_deal_promo_pref + 1);
500  }
501}
502
503}  // namespace chromeos
504