1// Copyright (c) 2012 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 "ui/chromeos/network/network_icon.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chromeos/network/device_state.h"
9#include "chromeos/network/network_connection_handler.h"
10#include "chromeos/network/network_state.h"
11#include "chromeos/network/network_state_handler.h"
12#include "chromeos/network/portal_detector/network_portal_detector.h"
13#include "grit/ui_chromeos_resources.h"
14#include "grit/ui_chromeos_strings.h"
15#include "third_party/cros_system_api/dbus/service_constants.h"
16#include "ui/base/l10n/l10n_util.h"
17#include "ui/base/resource/resource_bundle.h"
18#include "ui/base/webui/web_ui_util.h"
19#include "ui/chromeos/network/network_icon_animation.h"
20#include "ui/chromeos/network/network_icon_animation_observer.h"
21#include "ui/gfx/canvas.h"
22#include "ui/gfx/image/image_skia_operations.h"
23#include "ui/gfx/image/image_skia_source.h"
24#include "ui/gfx/rect.h"
25#include "ui/gfx/size_conversions.h"
26
27using chromeos::DeviceState;
28using chromeos::NetworkConnectionHandler;
29using chromeos::NetworkHandler;
30using chromeos::NetworkPortalDetector;
31using chromeos::NetworkState;
32using chromeos::NetworkStateHandler;
33using chromeos::NetworkTypePattern;
34
35namespace ui {
36namespace network_icon {
37
38namespace {
39
40//------------------------------------------------------------------------------
41// Struct to pass icon badges to NetworkIconImageSource.
42struct Badges {
43  Badges()
44      : top_left(NULL),
45        top_right(NULL),
46        bottom_left(NULL),
47        bottom_right(NULL) {
48  }
49  const gfx::ImageSkia* top_left;
50  const gfx::ImageSkia* top_right;
51  const gfx::ImageSkia* bottom_left;
52  const gfx::ImageSkia* bottom_right;
53};
54
55//------------------------------------------------------------------------------
56// class used for maintaining a map of network state and images.
57class NetworkIconImpl {
58 public:
59  NetworkIconImpl(const std::string& path, IconType icon_type);
60
61  // Determines whether or not the associated network might be dirty and if so
62  // updates and generates the icon. Does nothing if network no longer exists.
63  void Update(const chromeos::NetworkState* network);
64
65  // Returns the cached image url for |image_| based on |scale_factor|.
66  const std::string& GetImageUrl(float scale_factor);
67
68  const gfx::ImageSkia& image() const { return image_; }
69
70 private:
71  typedef std::map<float, std::string> ImageUrlMap;
72
73  // Updates |strength_index_| for wireless networks. Returns true if changed.
74  bool UpdateWirelessStrengthIndex(const chromeos::NetworkState* network);
75
76  // Updates the local state for cellular networks. Returns true if changed.
77  bool UpdateCellularState(const chromeos::NetworkState* network);
78
79  // Updates the portal state for wireless networks. Returns true if changed.
80  bool UpdatePortalState(const chromeos::NetworkState* network);
81
82  // Updates the VPN badge. Returns true if changed.
83  bool UpdateVPNBadge();
84
85  // Gets |badges| based on |network| and the current state.
86  void GetBadges(const NetworkState* network, Badges* badges);
87
88  // Gets the appropriate icon and badges and composites the image.
89  void GenerateImage(const chromeos::NetworkState* network);
90
91  // Network path, used for debugging.
92  std::string network_path_;
93
94  // Defines color theme and VPN badging
95  const IconType icon_type_;
96
97  // Cached state of the network when the icon was last generated.
98  std::string state_;
99
100  // Cached strength index of the network when the icon was last generated.
101  int strength_index_;
102
103  // Cached technology badge for the network when the icon was last generated.
104  const gfx::ImageSkia* technology_badge_;
105
106  // Cached vpn badge for the network when the icon was last generated.
107  const gfx::ImageSkia* vpn_badge_;
108
109  // Cached roaming state of the network when the icon was last generated.
110  std::string roaming_state_;
111
112  // Cached portal state of the network when the icon was last generated.
113  bool behind_captive_portal_;
114
115  // Generated icon image.
116  gfx::ImageSkia image_;
117
118  // Map of cached image urls by scale factor. Cleared whenever image_ is
119  // generated.
120  ImageUrlMap image_urls_;
121
122  DISALLOW_COPY_AND_ASSIGN(NetworkIconImpl);
123};
124
125//------------------------------------------------------------------------------
126// Maintain a static (global) icon map. Note: Icons are never destroyed;
127// it is assumed that a finite and reasonable number of network icons will be
128// created during a session.
129
130typedef std::map<std::string, NetworkIconImpl*> NetworkIconMap;
131
132NetworkIconMap* GetIconMapInstance(IconType icon_type, bool create) {
133  typedef std::map<IconType, NetworkIconMap*> IconTypeMap;
134  static IconTypeMap* s_icon_map = NULL;
135  if (s_icon_map == NULL) {
136    if (!create)
137      return NULL;
138    s_icon_map = new IconTypeMap;
139  }
140  if (s_icon_map->count(icon_type) == 0) {
141    if (!create)
142      return NULL;
143    (*s_icon_map)[icon_type] = new NetworkIconMap;
144  }
145  return (*s_icon_map)[icon_type];
146}
147
148NetworkIconMap* GetIconMap(IconType icon_type) {
149  return GetIconMapInstance(icon_type, true);
150}
151
152void PurgeIconMap(IconType icon_type,
153                  const std::set<std::string>& network_paths) {
154  NetworkIconMap* icon_map = GetIconMapInstance(icon_type, false);
155  if (!icon_map)
156    return;
157  for (NetworkIconMap::iterator loop_iter = icon_map->begin();
158       loop_iter != icon_map->end(); ) {
159    NetworkIconMap::iterator cur_iter = loop_iter++;
160    if (network_paths.count(cur_iter->first) == 0) {
161      delete cur_iter->second;
162      icon_map->erase(cur_iter);
163    }
164  }
165}
166
167//------------------------------------------------------------------------------
168// Utilities for generating icon images.
169
170// 'NONE' will default to ARCS behavior where appropriate (e.g. no network or
171// if a new type gets added).
172enum ImageType {
173  ARCS,
174  BARS,
175  NONE
176};
177
178// Amount to fade icons while connecting.
179const double kConnectingImageAlpha = 0.5;
180
181// Images for strength bars for wired networks.
182const int kNumBarsImages = 5;
183
184// Imagaes for strength arcs for wireless networks.
185const int kNumArcsImages = 5;
186
187// Number of discrete images to use for alpha fade animation
188const int kNumFadeImages = 10;
189
190//------------------------------------------------------------------------------
191// Classes for generating scaled images.
192
193const SkBitmap GetEmptyBitmap(const gfx::Size pixel_size) {
194  typedef std::pair<int, int> SizeKey;
195  typedef std::map<SizeKey, SkBitmap> SizeBitmapMap;
196  static SizeBitmapMap* s_empty_bitmaps = new SizeBitmapMap;
197
198  SizeKey key(pixel_size.width(), pixel_size.height());
199
200  SizeBitmapMap::iterator iter = s_empty_bitmaps->find(key);
201  if (iter != s_empty_bitmaps->end())
202    return iter->second;
203
204  SkBitmap empty;
205  empty.allocN32Pixels(key.first, key.second);
206  empty.eraseARGB(0, 0, 0, 0);
207  (*s_empty_bitmaps)[key] = empty;
208  return empty;
209}
210
211class EmptyImageSource: public gfx::ImageSkiaSource {
212 public:
213  explicit EmptyImageSource(const gfx::Size& size)
214      : size_(size) {
215  }
216
217  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
218    gfx::Size pixel_size = gfx::ToFlooredSize(gfx::ScaleSize(size_, scale));
219    SkBitmap empty_bitmap = GetEmptyBitmap(pixel_size);
220    return gfx::ImageSkiaRep(empty_bitmap, scale);
221  }
222
223 private:
224  const gfx::Size size_;
225
226  DISALLOW_COPY_AND_ASSIGN(EmptyImageSource);
227};
228
229// This defines how we assemble a network icon.
230class NetworkIconImageSource : public gfx::ImageSkiaSource {
231 public:
232  NetworkIconImageSource(const gfx::ImageSkia& icon, const Badges& badges)
233      : icon_(icon),
234        badges_(badges) {
235  }
236  virtual ~NetworkIconImageSource() {}
237
238  // TODO(pkotwicz): Figure out what to do when a new image resolution becomes
239  // available.
240  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
241    gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale);
242    if (icon_rep.is_null())
243      return gfx::ImageSkiaRep();
244    gfx::Canvas canvas(icon_rep, false);
245    if (badges_.top_left)
246      canvas.DrawImageInt(*badges_.top_left, 0, 0);
247    if (badges_.top_right)
248      canvas.DrawImageInt(*badges_.top_right,
249                          icon_.width() - badges_.top_right->width(), 0);
250    if (badges_.bottom_left) {
251      canvas.DrawImageInt(*badges_.bottom_left,
252                          0, icon_.height() - badges_.bottom_left->height());
253    }
254    if (badges_.bottom_right) {
255      canvas.DrawImageInt(*badges_.bottom_right,
256                          icon_.width() - badges_.bottom_right->width(),
257                          icon_.height() - badges_.bottom_right->height());
258    }
259    return canvas.ExtractImageRep();
260  }
261
262 private:
263  const gfx::ImageSkia icon_;
264  const Badges badges_;
265
266  DISALLOW_COPY_AND_ASSIGN(NetworkIconImageSource);
267};
268
269//------------------------------------------------------------------------------
270// Utilities for extracting icon images.
271
272// A struct used for caching image urls.
273struct ImageIdForNetworkType {
274  ImageIdForNetworkType(IconType icon_type,
275                        const std::string& network_type,
276                        float scale_factor) :
277      icon_type(icon_type),
278      network_type(network_type),
279      scale_factor(scale_factor) {}
280  bool operator<(const ImageIdForNetworkType& other) const {
281    if (icon_type != other.icon_type)
282      return icon_type < other.icon_type;
283    if (network_type != other.network_type)
284      return network_type < other.network_type;
285    return scale_factor < other.scale_factor;
286  }
287  IconType icon_type;
288  std::string network_type;
289  float scale_factor;
290};
291
292typedef std::map<ImageIdForNetworkType, std::string> ImageIdUrlMap;
293
294bool IconTypeIsDark(IconType icon_type) {
295  return (icon_type != ICON_TYPE_TRAY);
296}
297
298bool IconTypeHasVPNBadge(IconType icon_type) {
299  return (icon_type != ICON_TYPE_LIST);
300}
301
302int NumImagesForType(ImageType type) {
303  return (type == BARS) ? kNumBarsImages : kNumArcsImages;
304}
305
306gfx::ImageSkia* BaseImageForType(ImageType image_type, IconType icon_type) {
307  gfx::ImageSkia* image;
308  if (image_type == BARS) {
309    image =  ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
310        IconTypeIsDark(icon_type) ?
311        IDR_AURA_UBER_TRAY_NETWORK_BARS_DARK :
312        IDR_AURA_UBER_TRAY_NETWORK_BARS_LIGHT);
313  } else {
314    image =  ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
315        IconTypeIsDark(icon_type) ?
316        IDR_AURA_UBER_TRAY_NETWORK_ARCS_DARK :
317        IDR_AURA_UBER_TRAY_NETWORK_ARCS_LIGHT);
318  }
319  return image;
320}
321
322ImageType ImageTypeForNetworkType(const std::string& type) {
323  if (type == shill::kTypeWifi)
324    return ARCS;
325  else if (type == shill::kTypeCellular || type == shill::kTypeWimax)
326    return BARS;
327  return NONE;
328}
329
330gfx::ImageSkia GetImageForIndex(ImageType image_type,
331                                IconType icon_type,
332                                int index) {
333  int num_images = NumImagesForType(image_type);
334  if (index < 0 || index >= num_images)
335    return gfx::ImageSkia();
336  gfx::ImageSkia* images = BaseImageForType(image_type, icon_type);
337  int width = images->width();
338  int height = images->height() / num_images;
339  return gfx::ImageSkiaOperations::ExtractSubset(*images,
340      gfx::Rect(0, index * height, width, height));
341}
342
343const gfx::ImageSkia GetConnectedImage(IconType icon_type,
344                                       const std::string& network_type) {
345  if (network_type == shill::kTypeVPN) {
346    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
347        IDR_AURA_UBER_TRAY_NETWORK_VPN);
348  }
349  ImageType image_type = ImageTypeForNetworkType(network_type);
350  const int connected_index = NumImagesForType(image_type) - 1;
351  return GetImageForIndex(image_type, icon_type, connected_index);
352}
353
354const gfx::ImageSkia GetDisconnectedImage(IconType icon_type,
355                                          const std::string& network_type) {
356  if (network_type == shill::kTypeVPN) {
357    // Note: same as connected image, shouldn't normally be seen.
358    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
359        IDR_AURA_UBER_TRAY_NETWORK_VPN);
360  }
361  ImageType image_type = ImageTypeForNetworkType(network_type);
362  const int disconnected_index = 0;
363  return GetImageForIndex(image_type, icon_type, disconnected_index);
364}
365
366gfx::ImageSkia* ConnectingWirelessImage(ImageType image_type,
367                                        IconType icon_type,
368                                        double animation) {
369  static gfx::ImageSkia* s_bars_images_dark[kNumBarsImages - 1];
370  static gfx::ImageSkia* s_bars_images_light[kNumBarsImages - 1];
371  static gfx::ImageSkia* s_arcs_images_dark[kNumArcsImages - 1];
372  static gfx::ImageSkia* s_arcs_images_light[kNumArcsImages - 1];
373  int image_count = NumImagesForType(image_type) - 1;
374  int index = animation * nextafter(static_cast<float>(image_count), 0);
375  index = std::max(std::min(index, image_count - 1), 0);
376  gfx::ImageSkia** images;
377  bool dark = IconTypeIsDark(icon_type);
378  if (image_type == BARS)
379    images = dark ? s_bars_images_dark : s_bars_images_light;
380  else
381    images = dark ? s_arcs_images_dark : s_arcs_images_light;
382  if (!images[index]) {
383    // Lazily cache images.
384    gfx::ImageSkia source = GetImageForIndex(image_type, icon_type, index + 1);
385    images[index] = new gfx::ImageSkia(
386        gfx::ImageSkiaOperations::CreateBlendedImage(
387            gfx::ImageSkia(new EmptyImageSource(source.size()), source.size()),
388            source,
389            kConnectingImageAlpha));
390  }
391  return images[index];
392}
393
394gfx::ImageSkia* ConnectingVpnImage(double animation) {
395  int index = animation * nextafter(static_cast<float>(kNumFadeImages), 0);
396  static gfx::ImageSkia* s_vpn_images[kNumFadeImages];
397  if (!s_vpn_images[index]) {
398    // Lazily cache images.
399    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
400    gfx::ImageSkia* icon = rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN);
401    s_vpn_images[index] = new gfx::ImageSkia(
402        gfx::ImageSkiaOperations::CreateBlendedImage(
403            gfx::ImageSkia(new EmptyImageSource(icon->size()), icon->size()),
404            *icon,
405            animation));
406  }
407  return s_vpn_images[index];
408}
409
410gfx::ImageSkia* ConnectingVpnBadge(double animation) {
411  int index = animation * nextafter(static_cast<float>(kNumFadeImages), 0);
412  static gfx::ImageSkia* s_vpn_badges[kNumFadeImages];
413  if (!s_vpn_badges[index]) {
414    // Lazily cache images.
415    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
416    gfx::ImageSkia* icon =
417        rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED);  // For size
418    gfx::ImageSkia* badge =
419        rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN_BADGE);
420    s_vpn_badges[index] = new gfx::ImageSkia(
421        gfx::ImageSkiaOperations::CreateBlendedImage(
422            gfx::ImageSkia(new EmptyImageSource(icon->size()), icon->size()),
423            *badge,
424            animation));
425  }
426  return s_vpn_badges[index];
427}
428
429int StrengthIndex(int strength, int count) {
430  // Return an index in the range [1, count-1].
431  const float findex = (static_cast<float>(strength) / 100.0f) *
432      nextafter(static_cast<float>(count - 1), 0);
433  int index = 1 + static_cast<int>(findex);
434  index = std::max(std::min(index, count - 1), 1);
435  return index;
436}
437
438int GetStrengthIndex(const NetworkState* network) {
439  ImageType image_type = ImageTypeForNetworkType(network->type());
440  if (image_type == ARCS)
441    return StrengthIndex(network->signal_strength(), kNumArcsImages);
442  else if (image_type == BARS)
443    return StrengthIndex(network->signal_strength(), kNumBarsImages);
444  return 0;
445}
446
447const gfx::ImageSkia* BadgeForNetworkTechnology(const NetworkState* network,
448                                                IconType icon_type) {
449  const int kUnknownBadgeType = -1;
450  int id = kUnknownBadgeType;
451  const std::string& technology = network->network_technology();
452  if (technology == shill::kNetworkTechnologyEvdo) {
453    id = IconTypeIsDark(icon_type) ?
454        IDR_AURA_UBER_TRAY_NETWORK_EVDO_DARK :
455        IDR_AURA_UBER_TRAY_NETWORK_EVDO_LIGHT;
456  } else if (technology == shill::kNetworkTechnology1Xrtt) {
457    id = IDR_AURA_UBER_TRAY_NETWORK_1X;
458  } else if (technology == shill::kNetworkTechnologyGprs) {
459    id = IconTypeIsDark(icon_type) ?
460        IDR_AURA_UBER_TRAY_NETWORK_GPRS_DARK :
461        IDR_AURA_UBER_TRAY_NETWORK_GPRS_LIGHT;
462  } else if (technology == shill::kNetworkTechnologyEdge) {
463    id = IconTypeIsDark(icon_type) ?
464        IDR_AURA_UBER_TRAY_NETWORK_EDGE_DARK :
465        IDR_AURA_UBER_TRAY_NETWORK_EDGE_LIGHT;
466  } else if (technology == shill::kNetworkTechnologyUmts) {
467    id = IconTypeIsDark(icon_type) ?
468        IDR_AURA_UBER_TRAY_NETWORK_3G_DARK :
469        IDR_AURA_UBER_TRAY_NETWORK_3G_LIGHT;
470  } else if (technology == shill::kNetworkTechnologyHspa) {
471    id = IconTypeIsDark(icon_type) ?
472        IDR_AURA_UBER_TRAY_NETWORK_HSPA_DARK :
473        IDR_AURA_UBER_TRAY_NETWORK_HSPA_LIGHT;
474  } else if (technology == shill::kNetworkTechnologyHspaPlus) {
475    id = IconTypeIsDark(icon_type) ?
476        IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_DARK :
477        IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_LIGHT;
478  } else if (technology == shill::kNetworkTechnologyLte) {
479    id = IconTypeIsDark(icon_type) ?
480        IDR_AURA_UBER_TRAY_NETWORK_LTE_DARK :
481        IDR_AURA_UBER_TRAY_NETWORK_LTE_LIGHT;
482  } else if (technology == shill::kNetworkTechnologyLteAdvanced) {
483    id = IconTypeIsDark(icon_type) ?
484        IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_DARK :
485        IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_LIGHT;
486  } else if (technology == shill::kNetworkTechnologyGsm) {
487    id = IconTypeIsDark(icon_type) ?
488        IDR_AURA_UBER_TRAY_NETWORK_GPRS_DARK :
489        IDR_AURA_UBER_TRAY_NETWORK_GPRS_LIGHT;
490  }
491  if (id == kUnknownBadgeType)
492    return NULL;
493  else
494    return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(id);
495}
496
497const gfx::ImageSkia* BadgeForVPN(IconType icon_type) {
498  return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
499      IDR_AURA_UBER_TRAY_NETWORK_VPN_BADGE);
500}
501
502gfx::ImageSkia GetIcon(const NetworkState* network,
503                       IconType icon_type,
504                       int strength_index) {
505  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
506  if (network->Matches(NetworkTypePattern::Ethernet())) {
507    return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED);
508  } else if (network->Matches(NetworkTypePattern::Wireless())) {
509    DCHECK(strength_index > 0);
510    return GetImageForIndex(
511        ImageTypeForNetworkType(network->type()), icon_type, strength_index);
512  } else if (network->Matches(NetworkTypePattern::VPN())) {
513    return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN);
514  } else {
515    LOG(WARNING) << "Request for icon for unsupported type: "
516                 << network->type();
517    return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED);
518  }
519}
520
521//------------------------------------------------------------------------------
522// Get connecting images
523
524gfx::ImageSkia GetConnectingVpnImage(IconType icon_type) {
525  NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
526  const NetworkState* connected_network = NULL;
527  if (icon_type == ICON_TYPE_TRAY) {
528    connected_network =
529        handler->ConnectedNetworkByType(NetworkTypePattern::NonVirtual());
530  }
531  double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
532
533  if (connected_network) {
534    gfx::ImageSkia icon = GetImageForNetwork(connected_network, icon_type);
535    Badges badges;
536    badges.bottom_left = ConnectingVpnBadge(animation);
537    return gfx::ImageSkia(
538        new NetworkIconImageSource(icon, badges), icon.size());
539  } else {
540    gfx::ImageSkia* icon = ConnectingVpnImage(animation);
541    return gfx::ImageSkia(
542        new NetworkIconImageSource(*icon, Badges()), icon->size());
543  }
544}
545
546gfx::ImageSkia GetConnectingImage(IconType icon_type,
547                                  const std::string& network_type) {
548  if (network_type == shill::kTypeVPN)
549    return GetConnectingVpnImage(icon_type);
550
551  ImageType image_type = ImageTypeForNetworkType(network_type);
552  double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
553
554  gfx::ImageSkia* icon = ConnectingWirelessImage(
555      image_type, icon_type, animation);
556  return gfx::ImageSkia(
557      new NetworkIconImageSource(*icon, Badges()), icon->size());
558}
559
560std::string GetConnectingImageUrl(IconType icon_type,
561                                  const std::string& network_type,
562                                  float scale_factor) {
563  // Caching the connecting image is complicated and we will never draw more
564  // than a few per frame, so just generate the image url each time.
565  gfx::ImageSkia image = GetConnectingImage(icon_type, network_type);
566  gfx::ImageSkiaRep image_rep = image.GetRepresentation(scale_factor);
567  return webui::GetBitmapDataUrl(image_rep.sk_bitmap());
568}
569
570}  // namespace
571
572//------------------------------------------------------------------------------
573// NetworkIconImpl
574
575NetworkIconImpl::NetworkIconImpl(const std::string& path, IconType icon_type)
576    : network_path_(path),
577      icon_type_(icon_type),
578      strength_index_(-1),
579      technology_badge_(NULL),
580      vpn_badge_(NULL),
581      behind_captive_portal_(false) {
582  // Default image
583  image_ = GetDisconnectedImage(icon_type, shill::kTypeWifi);
584}
585
586void NetworkIconImpl::Update(const NetworkState* network) {
587  DCHECK(network);
588  // Determine whether or not we need to update the icon.
589  bool dirty = image_.isNull();
590
591  // If the network state has changed, the icon needs updating.
592  if (state_ != network->connection_state()) {
593    state_ = network->connection_state();
594    dirty = true;
595  }
596
597  dirty |= UpdatePortalState(network);
598
599  if (network->Matches(NetworkTypePattern::Wireless())) {
600    dirty |= UpdateWirelessStrengthIndex(network);
601  }
602
603  if (network->Matches(NetworkTypePattern::Cellular()))
604    dirty |= UpdateCellularState(network);
605
606  if (IconTypeHasVPNBadge(icon_type_) &&
607      network->Matches(NetworkTypePattern::NonVirtual())) {
608    dirty |= UpdateVPNBadge();
609  }
610
611  if (dirty) {
612    // Set the icon and badges based on the network and generate the image.
613    GenerateImage(network);
614  }
615}
616
617bool NetworkIconImpl::UpdateWirelessStrengthIndex(const NetworkState* network) {
618  int index = GetStrengthIndex(network);
619  if (index != strength_index_) {
620    strength_index_ = index;
621    return true;
622  }
623  return false;
624}
625
626bool NetworkIconImpl::UpdateCellularState(const NetworkState* network) {
627  bool dirty = false;
628  const gfx::ImageSkia* technology_badge =
629      BadgeForNetworkTechnology(network, icon_type_);
630  if (technology_badge != technology_badge_) {
631    technology_badge_ = technology_badge;
632    dirty = true;
633  }
634  std::string roaming_state = network->roaming();
635  if (roaming_state != roaming_state_) {
636    roaming_state_ = roaming_state;
637    dirty = true;
638  }
639  return dirty;
640}
641
642bool NetworkIconImpl::UpdatePortalState(const NetworkState* network) {
643  bool behind_captive_portal = false;
644  if (network && NetworkPortalDetector::IsInitialized()) {
645    NetworkPortalDetector::CaptivePortalState state =
646        NetworkPortalDetector::Get()->GetCaptivePortalState(network->guid());
647    behind_captive_portal =
648        state.status == NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL;
649  }
650
651  if (behind_captive_portal == behind_captive_portal_)
652    return false;
653  behind_captive_portal_ = behind_captive_portal;
654  return true;
655}
656
657bool NetworkIconImpl::UpdateVPNBadge() {
658  const NetworkState* vpn = NetworkHandler::Get()->network_state_handler()->
659      ConnectedNetworkByType(NetworkTypePattern::VPN());
660  if (vpn && vpn_badge_ == NULL) {
661    vpn_badge_ = BadgeForVPN(icon_type_);
662    return true;
663  } else if (!vpn && vpn_badge_ != NULL) {
664    vpn_badge_ = NULL;
665    return true;
666  }
667  return false;
668}
669
670void NetworkIconImpl::GetBadges(const NetworkState* network, Badges* badges) {
671  DCHECK(network);
672  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
673  NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
674
675  const std::string& type = network->type();
676  if (type == shill::kTypeWifi) {
677    if (network->security() != shill::kSecurityNone &&
678        IconTypeIsDark(icon_type_)) {
679      badges->bottom_right = rb.GetImageSkiaNamed(
680          IDR_AURA_UBER_TRAY_NETWORK_SECURE_DARK);
681    }
682  } else if (type == shill::kTypeWimax) {
683    technology_badge_ = rb.GetImageSkiaNamed(
684        IconTypeIsDark(icon_type_) ?
685        IDR_AURA_UBER_TRAY_NETWORK_4G_DARK :
686        IDR_AURA_UBER_TRAY_NETWORK_4G_LIGHT);
687  } else if (type == shill::kTypeCellular) {
688    if (network->roaming() == shill::kRoamingStateRoaming) {
689      // For networks that are always in roaming don't show roaming badge.
690      const DeviceState* device =
691          handler->GetDeviceState(network->device_path());
692      LOG_IF(WARNING, !device) << "Could not find device state for "
693                               << network->device_path();
694      if (!device || !device->provider_requires_roaming()) {
695        badges->bottom_right = rb.GetImageSkiaNamed(
696            IconTypeIsDark(icon_type_) ?
697            IDR_AURA_UBER_TRAY_NETWORK_ROAMING_DARK :
698            IDR_AURA_UBER_TRAY_NETWORK_ROAMING_LIGHT);
699      }
700    }
701  }
702  if (!network->IsConnectingState()) {
703    badges->top_left = technology_badge_;
704    badges->bottom_left = vpn_badge_;
705  }
706
707  if (behind_captive_portal_) {
708    gfx::ImageSkia* badge = rb.GetImageSkiaNamed(
709       IconTypeIsDark(icon_type_) ?
710       IDR_AURA_UBER_TRAY_NETWORK_PORTAL_DARK :
711       IDR_AURA_UBER_TRAY_NETWORK_PORTAL_LIGHT);
712    badges->bottom_right = badge;
713  }
714}
715
716void NetworkIconImpl::GenerateImage(const NetworkState* network) {
717  DCHECK(network);
718  gfx::ImageSkia icon = GetIcon(network, icon_type_, strength_index_);
719  Badges badges;
720  GetBadges(network, &badges);
721  image_ = gfx::ImageSkia(
722      new NetworkIconImageSource(icon, badges), icon.size());
723  image_urls_.clear();
724}
725
726const std::string& NetworkIconImpl::GetImageUrl(float scale_factor) {
727  ImageUrlMap::iterator iter = image_urls_.find(scale_factor);
728  if (iter != image_urls_.end())
729    return iter->second;
730
731  VLOG(2) << "Generating bitmap URL for: " << network_path_;
732  gfx::ImageSkiaRep image_rep = image_.GetRepresentation(scale_factor);
733  iter = image_urls_.insert(std::make_pair(
734      scale_factor, webui::GetBitmapDataUrl(image_rep.sk_bitmap()))).first;
735  return iter->second;
736}
737
738namespace {
739
740NetworkIconImpl* FindAndUpdateImageImpl(const NetworkState* network,
741                                        IconType icon_type) {
742  // Find or add the icon.
743  NetworkIconMap* icon_map = GetIconMap(icon_type);
744  NetworkIconImpl* icon;
745  NetworkIconMap::iterator iter = icon_map->find(network->path());
746  if (iter == icon_map->end()) {
747    icon = new NetworkIconImpl(network->path(), icon_type);
748    icon_map->insert(std::make_pair(network->path(), icon));
749  } else {
750    icon = iter->second;
751  }
752
753  // Update and return the icon's image.
754  icon->Update(network);
755  return icon;
756}
757
758}  // namespace
759
760//------------------------------------------------------------------------------
761// Public interface
762
763gfx::ImageSkia GetImageForNetwork(const NetworkState* network,
764                                  IconType icon_type) {
765  DCHECK(network);
766  if (!network->visible())
767    return GetDisconnectedImage(icon_type, network->type());
768
769  if (network->IsConnectingState())
770    return GetConnectingImage(icon_type, network->type());
771
772  NetworkIconImpl* icon = FindAndUpdateImageImpl(network, icon_type);
773  return icon->image();
774}
775
776std::string GetImageUrlForNetwork(const NetworkState* network,
777                                  IconType icon_type,
778                                  float scale_factor) {
779  DCHECK(network);
780  // Handle connecting icons.
781  if (network->IsConnectingState())
782    return GetConnectingImageUrl(icon_type, network->type(), scale_factor);
783
784  NetworkIconImpl* icon = FindAndUpdateImageImpl(network, icon_type);
785  return icon->GetImageUrl(scale_factor);
786}
787
788gfx::ImageSkia GetImageForConnectedNetwork(IconType icon_type,
789                                           const std::string& network_type) {
790  return GetConnectedImage(icon_type, network_type);
791}
792
793gfx::ImageSkia GetImageForConnectingNetwork(IconType icon_type,
794                                            const std::string& network_type) {
795  return GetConnectingImage(icon_type, network_type);
796}
797
798gfx::ImageSkia GetImageForDisconnectedNetwork(IconType icon_type,
799                                              const std::string& network_type) {
800  return GetDisconnectedImage(icon_type, network_type);
801}
802
803base::string16 GetLabelForNetwork(const chromeos::NetworkState* network,
804                                  IconType icon_type) {
805  DCHECK(network);
806  std::string activation_state = network->activation_state();
807  if (icon_type == ICON_TYPE_LIST) {
808    // Show "<network>: [Connecting|Activating]..."
809    if (network->IsConnectingState()) {
810      return l10n_util::GetStringFUTF16(
811          IDS_ASH_STATUS_TRAY_NETWORK_LIST_CONNECTING,
812          base::UTF8ToUTF16(network->name()));
813    }
814    if (activation_state == shill::kActivationStateActivating) {
815      return l10n_util::GetStringFUTF16(
816          IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATING,
817          base::UTF8ToUTF16(network->name()));
818    }
819    // Show "Activate <network>" in list view only.
820    if (activation_state == shill::kActivationStateNotActivated ||
821        activation_state == shill::kActivationStatePartiallyActivated) {
822      return l10n_util::GetStringFUTF16(
823          IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATE,
824          base::UTF8ToUTF16(network->name()));
825    }
826  } else {
827    // Show "[Connected to|Connecting to|Activating] <network>" (non-list view).
828    if (network->IsConnectedState()) {
829      return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED,
830                                        base::UTF8ToUTF16(network->name()));
831    }
832    if (network->IsConnectingState()) {
833      return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING,
834                                        base::UTF8ToUTF16(network->name()));
835    }
836    if (activation_state == shill::kActivationStateActivating) {
837      return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING,
838                                        base::UTF8ToUTF16(network->name()));
839    }
840  }
841
842  // Otherwise just show the network name or 'Ethernet'.
843  if (network->Matches(NetworkTypePattern::Ethernet())) {
844    return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET);
845  } else {
846    return base::UTF8ToUTF16(network->name());
847  }
848}
849
850int GetCellularUninitializedMsg() {
851  static base::Time s_uninitialized_state_time;
852  static int s_uninitialized_msg(0);
853
854  NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
855  if (handler->GetTechnologyState(NetworkTypePattern::Mobile())
856      == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) {
857    s_uninitialized_msg = IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
858    s_uninitialized_state_time = base::Time::Now();
859    return s_uninitialized_msg;
860  } else if (handler->GetScanningByType(NetworkTypePattern::Mobile())) {
861    s_uninitialized_msg = IDS_ASH_STATUS_TRAY_CELLULAR_SCANNING;
862    s_uninitialized_state_time = base::Time::Now();
863    return s_uninitialized_msg;
864  }
865  // There can be a delay between leaving the Initializing state and when
866  // a Cellular device shows up, so keep showing the initializing
867  // animation for a bit to avoid flashing the disconnect icon.
868  const int kInitializingDelaySeconds = 1;
869  base::TimeDelta dtime = base::Time::Now() - s_uninitialized_state_time;
870  if (dtime.InSeconds() < kInitializingDelaySeconds)
871    return s_uninitialized_msg;
872  return 0;
873}
874
875void GetDefaultNetworkImageAndLabel(IconType icon_type,
876                                    gfx::ImageSkia* image,
877                                    base::string16* label,
878                                    bool* animating) {
879  NetworkStateHandler* state_handler =
880      NetworkHandler::Get()->network_state_handler();
881  NetworkConnectionHandler* connect_handler =
882      NetworkHandler::Get()->network_connection_handler();
883  const NetworkState* connected_network =
884      state_handler->ConnectedNetworkByType(NetworkTypePattern::NonVirtual());
885  const NetworkState* connecting_network =
886      state_handler->ConnectingNetworkByType(NetworkTypePattern::Wireless());
887  if (!connecting_network && icon_type == ICON_TYPE_TRAY) {
888    connecting_network =
889        state_handler->ConnectingNetworkByType(NetworkTypePattern::VPN());
890  }
891
892  const NetworkState* network;
893  // If we are connecting to a network, and there is either no connected
894  // network, or the connection was user requested, use the connecting
895  // network.
896  if (connecting_network &&
897      (!connected_network ||
898       connect_handler->HasConnectingNetwork(connecting_network->path()))) {
899    network = connecting_network;
900  } else {
901    network = connected_network;
902  }
903
904  // Don't show ethernet in the tray
905  if (icon_type == ICON_TYPE_TRAY && network &&
906      network->Matches(NetworkTypePattern::Ethernet())) {
907    *image = gfx::ImageSkia();
908    *animating = false;
909    return;
910  }
911
912  if (!network) {
913    // If no connecting network, check if we are activating a network.
914    const NetworkState* mobile_network =
915        state_handler->FirstNetworkByType(NetworkTypePattern::Mobile());
916    if (mobile_network && (mobile_network->activation_state() ==
917                           shill::kActivationStateActivating)) {
918      network = mobile_network;
919    }
920  }
921  if (!network) {
922    // If no connecting network, check for cellular initializing.
923    int uninitialized_msg = GetCellularUninitializedMsg();
924    if (uninitialized_msg != 0) {
925      *image = GetImageForConnectingNetwork(icon_type, shill::kTypeCellular);
926      if (label)
927        *label = l10n_util::GetStringUTF16(uninitialized_msg);
928      *animating = true;
929    } else {
930      // Otherwise show the disconnected wifi icon.
931      *image = GetImageForDisconnectedNetwork(icon_type, shill::kTypeWifi);
932      if (label) {
933        *label = l10n_util::GetStringUTF16(
934            IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
935      }
936      *animating = false;
937    }
938    return;
939  }
940  *animating = network->IsConnectingState();
941  // Get icon and label for connected or connecting network.
942  *image = GetImageForNetwork(network, icon_type);
943  if (label)
944    *label = GetLabelForNetwork(network, icon_type);
945}
946
947void PurgeNetworkIconCache() {
948  NetworkStateHandler::NetworkStateList networks;
949  NetworkHandler::Get()->network_state_handler()->GetVisibleNetworkList(
950      &networks);
951  std::set<std::string> network_paths;
952  for (NetworkStateHandler::NetworkStateList::iterator iter = networks.begin();
953       iter != networks.end(); ++iter) {
954    network_paths.insert((*iter)->path());
955  }
956  PurgeIconMap(ICON_TYPE_TRAY, network_paths);
957  PurgeIconMap(ICON_TYPE_DEFAULT_VIEW, network_paths);
958  PurgeIconMap(ICON_TYPE_LIST, network_paths);
959}
960
961}  // namespace network_icon
962}  // namespace ui
963