15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/chromeos/network/tray_sms.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/shell.h"
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/system/tray/fixed_sized_scroll_view.h"
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/system_tray.h"
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/system/tray/system_tray_bubble.h"
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/system/tray/system_tray_notifier.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_constants.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_details_view.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_item_more.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_item_view.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_notification_view.h"
17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/strings/string_number_conversions.h"
18868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "chromeos/network/network_event_log.h"
203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "chromeos/network/network_handler.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ash_resources.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ash_strings.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/views/bubble/tray_bubble_view.h"
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/controls/image_view.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/controls/label.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/layout/box_layout.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/layout/fill_layout.h"
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/layout/grid_layout.h"
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/view.h"
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Min height of the list of messages in the popup.
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kMessageListMinHeight = 200;
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Top/bottom padding of the text items.
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kPaddingVertical = 10;
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)const char kSmsNumberKey[] = "number";
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)const char kSmsTextKey[] = "text";
423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetMessageFromDictionary(const base::DictionaryValue* message,
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              std::string* number,
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              std::string* text) {
463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!message->GetStringWithoutPathExpansion(kSmsNumberKey, number))
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!message->GetStringWithoutPathExpansion(kSmsTextKey, text))
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace ash {
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TraySms::SmsDefaultView : public TrayItemMore {
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  explicit SmsDefaultView(TraySms* owner)
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      : TrayItemMore(owner, true) {
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        IDR_AURA_UBER_TRAY_SMS));
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Update();
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~SmsDefaultView() {}
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void Update() {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    int message_count = static_cast<TraySms*>(owner())->messages().GetSize();
70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    base::string16 label = l10n_util::GetStringFUTF16(
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        IDS_ASH_STATUS_TRAY_SMS_MESSAGES, base::IntToString16(message_count));
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetLabel(label);
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetAccessibleName(label);
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// An entry (row) in SmsDetailedView or NotificationView.
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TraySms::SmsMessageView : public views::View,
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                public views::ButtonListener {
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  enum ViewType {
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    VIEW_DETAILED,
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    VIEW_NOTIFICATION
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  SmsMessageView(TraySms* owner,
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 ViewType view_type,
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 size_t index,
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const std::string& number,
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 const std::string& message)
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      : owner_(owner),
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        index_(index) {
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    number_label_ = new views::Label(
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_SMS_NUMBER,
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                   base::UTF8ToUTF16(number)),
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ui::ResourceBundle::GetSharedInstance().GetFontList(
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            ui::ResourceBundle::BoldFont));
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    message_label_ = new views::Label(base::UTF8ToUTF16(message));
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    message_label_->SetMultiLine(true);
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (view_type == VIEW_DETAILED)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      LayoutDetailedView();
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      LayoutNotificationView();
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~SmsMessageView() {
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from ButtonListener.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void ButtonPressed(views::Button* sender,
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                             const ui::Event& event) OVERRIDE {
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    owner_->RemoveMessage(index_);
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    owner_->Update(false);
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void LayoutDetailedView() {
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    views::ImageButton* close_button = new views::ImageButton(this);
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    close_button->SetImage(
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        views::CustomButton::STATE_NORMAL,
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            IDR_AURA_UBER_TRAY_SMS_DISMISS));
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const int msg_width = owner_->system_tray()->GetSystemBubble()->
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        bubble_view()->GetPreferredSize().width() -
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2);
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    message_label_->SizeToFit(msg_width);
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    views::GridLayout* layout = new views::GridLayout(this);
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetLayoutManager(layout);
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    views::ColumnSet* columns = layout->AddColumnSet(0);
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Message
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal);
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       0 /* resize percent */,
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       views::GridLayout::FIXED, msg_width, msg_width);
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Close button
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       0, /* resize percent */
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       views::GridLayout::FIXED,
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       kNotificationIconWidth, kNotificationIconWidth);
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->AddPaddingRow(0, kPaddingVertical);
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->StartRow(0, 0);
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->AddView(number_label_);
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->AddView(close_button, 1, 2);  // 2 rows for icon
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->StartRow(0, 0);
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->AddView(message_label_);
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    layout->AddPaddingRow(0, kPaddingVertical);
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void LayoutNotificationView() {
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetLayoutManager(
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AddChildView(number_label_);
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    message_label_->SizeToFit(kTrayNotificationContentsWidth);
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AddChildView(message_label_);
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  TraySms* owner_;
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  size_t index_;
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  views::Label* number_label_;
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  views::Label* message_label_;
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TraySms::SmsDetailedView : public TrayDetailsView,
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 public ViewClickListener {
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  explicit SmsDetailedView(TraySms* owner)
1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      : TrayDetailsView(owner) {
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Init();
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Update();
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~SmsDetailedView() {
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void Init() {
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CreateScrollableList();
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void Update() {
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    UpdateMessageList();
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Layout();
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SchedulePaint();
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from views::View.
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  virtual gfx::Size GetPreferredSize() const OVERRIDE {
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gfx::Size preferred_size = TrayDetailsView::GetPreferredSize();
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (preferred_size.height() < kMessageListMinHeight)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      preferred_size.set_height(kMessageListMinHeight);
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return preferred_size;
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void UpdateMessageList() {
2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const base::ListValue& messages =
2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        static_cast<TraySms*>(owner())->messages();
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    scroll_content()->RemoveAllChildViews(true);
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (size_t index = 0; index < messages.GetSize(); ++index) {
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      const base::DictionaryValue* message = NULL;
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (!messages.GetDictionary(index, &message)) {
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        LOG(ERROR) << "SMS message not a dictionary at: " << index;
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue;
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      std::string number, text;
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (!GetMessageFromDictionary(message, &number, &text)) {
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        LOG(ERROR) << "Error parsing SMS message";
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue;
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      SmsMessageView* msgview = new SmsMessageView(
2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          number, text);
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      scroll_content()->AddChildView(msgview);
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    scroller()->Layout();
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from ViewClickListener.
2352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  virtual void OnViewClicked(views::View* sender) OVERRIDE {
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (sender == footer()->content())
237a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      TransitionToDefaultView();
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class TraySms::SmsNotificationView : public TrayNotificationView {
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  SmsNotificationView(TraySms* owner,
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      size_t message_index,
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      const std::string& number,
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      const std::string& text)
2492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_SMS),
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        message_index_(message_index) {
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SmsMessageView* message_view = new SmsMessageView(
2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        owner, SmsMessageView::VIEW_NOTIFICATION, message_index_, number, text);
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    InitView(message_view);
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void Update(size_t message_index,
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              const std::string& number,
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              const std::string& text) {
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SmsMessageView* message_view = new SmsMessageView(
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        tray_sms(), SmsMessageView::VIEW_NOTIFICATION,
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        message_index_, number, text);
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    UpdateView(message_view);
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from TrayNotificationView:
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnClose() OVERRIDE {
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    tray_sms()->RemoveMessage(message_index_);
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnClickAction() OVERRIDE {
2712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    owner()->PopupDetailedView(0, true);
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TraySms* tray_sms() {
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return static_cast<TraySms*>(owner());
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  size_t message_index_;
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)TraySms::TraySms(SystemTray* system_tray)
2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    : SystemTrayItem(system_tray),
2862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      default_(NULL),
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      detailed_(NULL),
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      notification_(NULL) {
2893551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // TODO(armansito): SMS could be a special case for cellular that requires a
2903551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // user (perhaps the owner) to be logged in. If that is the case, then an
2913551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // additional check should be done before subscribing for SMS notifications.
2923551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (chromeos::NetworkHandler::IsInitialized())
2933551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this);
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TraySms::~TraySms() {
2973551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (chromeos::NetworkHandler::IsInitialized()) {
2983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(
2993551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        this);
3003551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  CHECK(default_ == NULL);
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  default_ = new SmsDefaultView(this);
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  default_->SetVisible(!messages_.empty());
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return default_;
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  CHECK(detailed_ == NULL);
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  HideNotificationView();
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (messages_.empty())
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  detailed_ = new SmsDetailedView(this);
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return detailed_;
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  CHECK(notification_ == NULL);
3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (detailed_)
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return NULL;
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  size_t index;
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string number, text;
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (GetLatestMessage(&index, &number, &text))
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    notification_ = new SmsNotificationView(this, index, number, text);
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return notification_;
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TraySms::DestroyDefaultView() {
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  default_ = NULL;
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TraySms::DestroyDetailedView() {
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  detailed_ = NULL;
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TraySms::DestroyNotificationView() {
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  notification_ = NULL;
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void TraySms::MessageReceived(const base::DictionaryValue& message) {
3433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
3443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  std::string message_text;
3453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!message.GetStringWithoutPathExpansion(
3463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
3473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    NET_LOG_ERROR("SMS message contains no content.", "");
3483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return;
3493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
3503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // TODO(armansito): A message might be due to a special "Message Waiting"
3513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // state that the message is in. Once SMS handling moves to shill, such
3523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  // messages should be filtered there so that this check becomes unnecessary.
3533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (message_text.empty()) {
3543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    NET_LOG_DEBUG("SMS has empty content text. Ignoring.", "");
3553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return;
3563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
3573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  std::string message_number;
3583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if (!message.GetStringWithoutPathExpansion(
3593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
3603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
3613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return;
3623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
3633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
3643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
3653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)                message_text, "");
3663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
3673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  base::DictionaryValue* dict = new base::DictionaryValue();
3683551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  dict->SetString(kSmsNumberKey, message_number);
3693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  dict->SetString(kSmsTextKey, message_text);
3703551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  messages_.Append(dict);
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Update(true);
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool TraySms::GetLatestMessage(size_t* index,
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               std::string* number,
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               std::string* text) {
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (messages_.empty())
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  base::DictionaryValue* message;
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  size_t message_index = messages_.GetSize() - 1;
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!messages_.GetDictionary(message_index, &message))
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!GetMessageFromDictionary(message, number, text))
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *index = message_index;
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TraySms::RemoveMessage(size_t index) {
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (index < messages_.GetSize())
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    messages_.Remove(index, NULL);
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TraySms::Update(bool notify) {
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (messages_.empty()) {
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (default_)
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default_->SetVisible(false);
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (detailed_)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      HideDetailedView();
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    HideNotificationView();
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (default_) {
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default_->SetVisible(true);
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default_->Update();
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (detailed_)
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      detailed_->Update();
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (notification_) {
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      size_t index;
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      std::string number, text;
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (GetLatestMessage(&index, &number, &text))
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        notification_->Update(index, number, text);
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (notify) {
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ShowNotificationView();
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace ash
420