1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/ui/idle_app_name_notification_view.h" 6 7#include <string> 8 9#include "ash/shell.h" 10#include "ash/shell_delegate.h" 11#include "ash/shell_window_ids.h" 12#include "ash/wm/window_animations.h" 13#include "base/strings/string_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "base/time/time.h" 16#include "base/timer/timer.h" 17#include "chrome/grit/generated_resources.h" 18#include "extensions/common/extension.h" 19#include "ui/accessibility/ax_view_state.h" 20#include "ui/aura/window.h" 21#include "ui/base/l10n/l10n_util.h" 22#include "ui/base/resource/resource_bundle.h" 23#include "ui/compositor/layer_animation_observer.h" 24#include "ui/compositor/scoped_layer_animation_settings.h" 25#include "ui/gfx/canvas.h" 26#include "ui/gfx/font_list.h" 27#include "ui/gfx/text_utils.h" 28#include "ui/views/controls/label.h" 29#include "ui/views/layout/box_layout.h" 30#include "ui/views/layout/fill_layout.h" 31#include "ui/views/view.h" 32#include "ui/views/widget/widget.h" 33#include "ui/views/widget/widget_delegate.h" 34 35namespace ui { 36class LayerAnimationSequence; 37} 38 39namespace chromeos { 40namespace { 41 42// Color of the text of the warning message. 43const SkColor kTextColor = SK_ColorBLACK; 44 45// Color of the text of the warning message. 46const SkColor kErrorTextColor = SK_ColorRED; 47 48// Color of the window background. 49const SkColor kWindowBackgroundColor = SK_ColorWHITE; 50 51// Radius of the rounded corners of the window. 52const int kWindowCornerRadius = 4; 53 54// Creates and shows the message widget for |view| with |animation_time_ms|. 55void CreateAndShowWidgetWithContent(views::WidgetDelegate* delegate, 56 views::View* view, 57 int animation_time_ms) { 58 aura::Window* root_window = ash::Shell::GetTargetRootWindow(); 59 gfx::Size rs = root_window->bounds().size(); 60 gfx::Size ps = view->GetPreferredSize(); 61 gfx::Rect bounds((rs.width() - ps.width()) / 2, 62 -ps.height(), 63 ps.width(), 64 ps.height()); 65 views::Widget::InitParams params; 66 params.type = views::Widget::InitParams::TYPE_POPUP; 67 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 68 params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET; 69 params.accept_events = false; 70 params.keep_on_top = true; 71 params.remove_standard_frame = true; 72 params.delegate = delegate; 73 params.bounds = bounds; 74 params.parent = ash::Shell::GetContainer( 75 root_window, ash::kShellWindowId_SettingBubbleContainer); 76 views::Widget* widget = new views::Widget; 77 widget->Init(params); 78 widget->SetContentsView(view); 79 gfx::NativeView native_view = widget->GetNativeView(); 80 native_view->SetName("KioskIdleAppNameNotification"); 81 82 // Note: We cannot use the Window show/hide animations since they are disabled 83 // for kiosk by command line. 84 ui::LayerAnimator* animator = new ui::LayerAnimator( 85 base::TimeDelta::FromMilliseconds(animation_time_ms)); 86 native_view->layer()->SetAnimator(animator); 87 widget->Show(); 88 89 // We don't care about the show animation since it is off screen, so stop the 90 // started animation and move the message into view. 91 animator->StopAnimating(); 92 bounds.set_y((rs.height() - ps.height()) / 20); 93 widget->SetBounds(bounds); 94 95 // Allow to use the message for spoken feedback. 96 view->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); 97} 98 99} // namespace 100 101// The class which implements the content view for the message. 102class IdleAppNameNotificationDelegateView 103 : public views::WidgetDelegateView, 104 public ui::ImplicitAnimationObserver { 105 public: 106 // An idle message which will get shown from the caller and hides itself after 107 // a time, calling |owner->CloseMessage| to inform the owner that it got 108 // destroyed. The |app_name| is a string which gets used as message and 109 // |error| is true if something is not correct. 110 // |message_visibility_time_in_ms| ms's after creation the message will start 111 // to remove itself from the screen. 112 IdleAppNameNotificationDelegateView(IdleAppNameNotificationView *owner, 113 const base::string16& app_name, 114 bool error, 115 int message_visibility_time_in_ms) 116 : owner_(owner), 117 widget_closed_(false) { 118 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 119 // Add the application name label to the message. 120 AddLabel(app_name, 121 rb.GetFontList(ui::ResourceBundle::BoldFont), 122 error ? kErrorTextColor : kTextColor); 123 spoken_text_ = app_name; 124 SetLayoutManager(new views::FillLayout); 125 126 // Set a timer which will trigger to remove the message after the given 127 // time. 128 hide_timer_.Start( 129 FROM_HERE, 130 base::TimeDelta::FromMilliseconds(message_visibility_time_in_ms), 131 this, 132 &IdleAppNameNotificationDelegateView::RemoveMessage); 133 } 134 135 virtual ~IdleAppNameNotificationDelegateView() { 136 // The widget is already closing, but the other cleanup items need to be 137 // performed. 138 widget_closed_ = true; 139 Close(); 140 } 141 142 // Close the widget immediately. This can be called from the owner or from 143 // this class. 144 void Close() { 145 // Stop the timer (if it was running). 146 hide_timer_.Stop(); 147 // Inform our owner that we are going away. 148 if (owner_) { 149 IdleAppNameNotificationView* owner = owner_; 150 owner_ = NULL; 151 owner->CloseMessage(); 152 } 153 // Close the owning widget - if required. 154 if (!widget_closed_) { 155 widget_closed_ = true; 156 GetWidget()->Close(); 157 } 158 } 159 160 // Animate the window away (and close once done). 161 void RemoveMessage() { 162 aura::Window* widget_view = GetWidget()->GetNativeView(); 163 ui::Layer* layer = widget_view->layer(); 164 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); 165 settings.AddObserver(this); 166 gfx::Rect rect = widget_view->bounds(); 167 rect.set_y(-GetPreferredSize().height()); 168 layer->SetBounds(rect); 169 } 170 171 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 172 SkPaint paint; 173 paint.setStyle(SkPaint::kFill_Style); 174 paint.setColor(kWindowBackgroundColor); 175 canvas->DrawRoundRect(GetLocalBounds(), kWindowCornerRadius, paint); 176 views::WidgetDelegateView::OnPaint(canvas); 177 } 178 179 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE { 180 state->name = spoken_text_; 181 state->role = ui::AX_ROLE_ALERT; 182 } 183 184 // ImplicitAnimationObserver overrides 185 virtual void OnImplicitAnimationsCompleted() OVERRIDE { 186 Close(); 187 } 188 189 private: 190 // Adds the label to the view, using |text| with a |font| and a |text_color|. 191 void AddLabel(const base::string16& text, 192 const gfx::FontList& font, 193 SkColor text_color) { 194 views::Label* label = new views::Label; 195 label->SetText(text); 196 label->SetHorizontalAlignment(gfx::ALIGN_CENTER); 197 label->SetFontList(font); 198 label->SetEnabledColor(text_color); 199 label->SetDisabledColor(text_color); 200 label->SetAutoColorReadabilityEnabled(false); 201 AddChildView(label); 202 } 203 204 // A timer which calls us to remove the message from the screen. 205 base::OneShotTimer<IdleAppNameNotificationDelegateView> hide_timer_; 206 207 // The owner of this message which needs to get notified when the message 208 // closes. 209 IdleAppNameNotificationView* owner_; 210 211 // The spoken text. 212 base::string16 spoken_text_; 213 214 // True if the widget got already closed. 215 bool widget_closed_; 216 217 DISALLOW_COPY_AND_ASSIGN(IdleAppNameNotificationDelegateView); 218}; 219 220IdleAppNameNotificationView::IdleAppNameNotificationView( 221 int message_visibility_time_in_ms, 222 int animation_time_ms, 223 const extensions::Extension* extension) 224 : view_(NULL) { 225 ShowMessage(message_visibility_time_in_ms, animation_time_ms, extension); 226} 227 228IdleAppNameNotificationView::~IdleAppNameNotificationView() { 229 CloseMessage(); 230} 231 232void IdleAppNameNotificationView::CloseMessage() { 233 if (view_) { 234 IdleAppNameNotificationDelegateView* view = view_; 235 view_ = NULL; 236 view->Close(); 237 } 238} 239 240bool IdleAppNameNotificationView::IsVisible() { 241 return view_ != NULL; 242} 243 244base::string16 IdleAppNameNotificationView::GetShownTextForTest() { 245 ui::AXViewState state; 246 DCHECK(view_); 247 view_->GetAccessibleState(&state); 248 return state.name; 249} 250 251void IdleAppNameNotificationView::ShowMessage( 252 int message_visibility_time_in_ms, 253 int animation_time_ms, 254 const extensions::Extension* extension) { 255 DCHECK(!view_); 256 257 base::string16 app_name; 258 bool error = false; 259 if (extension && 260 !base::ContainsOnlyChars(extension->name(), base::kWhitespaceASCII)) { 261 app_name = base::UTF8ToUTF16(extension->name()); 262 } else { 263 error = true; 264 app_name = l10n_util::GetStringUTF16( 265 IDS_IDLE_APP_NAME_UNKNOWN_APPLICATION_NOTIFICATION); 266 } 267 268 view_ = new IdleAppNameNotificationDelegateView( 269 this, 270 app_name, 271 error, 272 message_visibility_time_in_ms + animation_time_ms); 273 CreateAndShowWidgetWithContent(view_, view_, animation_time_ms); 274} 275 276} // namespace chromeos 277