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/tray_update.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/root_window_controller.h"
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/shelf/shelf_layout_manager.h"
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/shelf/shelf_widget.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/shell.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/status_area_widget.h"
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/system/tray/fixed_sized_image_view.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/system_tray.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/system_tray_delegate.h"
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/system/tray/system_tray_notifier.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/system/tray/tray_constants.h"
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/time/time.h"
18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/timer/timer.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ash_resources.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ash_strings.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/aura/window.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/compositor/layer.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/compositor/layer_animation_observer.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/compositor/layer_animation_sequence.h"
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/image/image.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/controls/image_view.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/controls/label.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/layout/box_layout.h"
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/widget/widget.h"
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// How many seconds should we wait before showing the nag reminder?
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kUpdateNaggingTimeSeconds = 24 * 60 * 60;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// How long should the nag reminder be displayed?
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kShowUpdateNaggerForSeconds = 15;
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int DecideResource(ash::UpdateObserver::UpdateSeverity severity, bool dark) {
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (severity) {
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case ash::UpdateObserver::UPDATE_NORMAL:
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK:
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    IDR_AURA_UBER_TRAY_UPDATE;
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case ash::UpdateObserver::UPDATE_LOW_GREEN:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_GREEN :
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    IDR_AURA_UBER_TRAY_UPDATE_GREEN;
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case ash::UpdateObserver::UPDATE_HIGH_ORANGE:
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_ORANGE :
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    IDR_AURA_UBER_TRAY_UPDATE_ORANGE;
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    case ash::UpdateObserver::UPDATE_SEVERE_RED:
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_RED :
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    IDR_AURA_UBER_TRAY_UPDATE_RED;
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NOTREACHED() << "Unknown update severity level.";
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0;
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
63c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochclass UpdateView : public ash::ActionableView {
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  explicit UpdateView(ash::UpdateObserver::UpdateSeverity severity) {
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetLayoutManager(new
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        views::BoxLayout(views::BoxLayout::kHorizontal,
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ash::kTrayPopupPaddingHorizontal, 0,
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ash::kTrayPopupPaddingBetweenItems));
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    views::ImageView* image =
73c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        new ash::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    image->SetImage(bundle.GetImageNamed(DecideResource(severity, true)).
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ToImageSkia());
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AddChildView(image);
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AddChildView(new views::Label(
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE)));
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetAccessibleName(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE));
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~UpdateView() {}
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from ActionableView.
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual bool PerformAction(const ui::Event& event) OVERRIDE {
88a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    ash::Shell::GetInstance()->
89a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        system_tray_delegate()->RequestRestartForUpdate();
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(UpdateView);
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace ash {
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace tray {
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class UpdateNagger : public ui::LayerAnimationObserver {
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  explicit UpdateNagger(SystemTrayItem* owner)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      : owner_(owner) {
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    RestartTimer();
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    owner_->system_tray()->GetWidget()->GetNativeView()->layer()->
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        GetAnimator()->AddObserver(this);
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~UpdateNagger() {
111c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    StatusAreaWidget* status_area =
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (status_area) {
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      status_area->system_tray()->GetWidget()->GetNativeView()->layer()->
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          GetAnimator()->RemoveObserver(this);
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void RestartTimer() {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    timer_.Stop();
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    timer_.Start(FROM_HERE,
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 base::TimeDelta::FromSeconds(kUpdateNaggingTimeSeconds),
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 this,
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 &UpdateNagger::Nag);
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  void Nag() {
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    owner_->PopupDetailedView(kShowUpdateNaggerForSeconds, false);
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Overridden from ui::LayerAnimationObserver.
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnLayerAnimationEnded(
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ui::LayerAnimationSequence* sequence) OVERRIDE {
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // TODO(oshima): Find out if the updator will be shown on non
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // primary display.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (Shell::GetPrimaryRootWindowController()->shelf()->IsVisible())
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      timer_.Stop();
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else if (!timer_.IsRunning())
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      RestartTimer();
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnLayerAnimationAborted(
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ui::LayerAnimationSequence* sequence) OVERRIDE {}
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual void OnLayerAnimationScheduled(
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ui::LayerAnimationSequence* sequence) OVERRIDE {}
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SystemTrayItem* owner_;
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::OneShotTimer<UpdateNagger> timer_;
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(UpdateNagger);
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace tray
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)TrayUpdate::TrayUpdate(SystemTray* system_tray)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_UPDATE),
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      severity_(UpdateObserver::UPDATE_NORMAL) {
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  Shell::GetInstance()->system_tray_notifier()->AddUpdateObserver(this);
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)TrayUpdate::~TrayUpdate() {
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  Shell::GetInstance()->system_tray_notifier()->RemoveUpdateObserver(this);
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool TrayUpdate::GetInitialVisibility() {
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade();
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* TrayUpdate::CreateDefaultView(user::LoginStatus status) {
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (!Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade())
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return new UpdateView(severity_);
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* TrayUpdate::CreateDetailedView(user::LoginStatus status) {
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return CreateDefaultView(status);
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TrayUpdate::DestroyDetailedView() {
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (nagger_) {
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // The nagger was being displayed. Now that the detailed view is being
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // closed, that means either the user clicks on it to restart, or the user
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // didn't click on it to restart. In either case, start the timer to show
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // the nag reminder again after the specified time.
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nagger_->RestartTimer();
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void TrayUpdate::OnUpdateRecommended(UpdateObserver::UpdateSeverity severity) {
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  severity_ = severity;
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetImageFromResourceId(DecideResource(severity_, false));
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  tray_view()->SetVisible(true);
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!Shell::GetPrimaryRootWindowController()->shelf()->IsVisible() &&
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !nagger_.get()) {
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // The shelf is not visible, and there is no nagger scheduled.
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    nagger_.reset(new tray::UpdateNagger(this));
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace ash
203