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