Home | History | Annotate | Download | only in ui
      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 "extensions/common/extension.h"
     18 #include "grit/generated_resources.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 
     35 namespace ui {
     36 class LayerAnimationSequence;
     37 }
     38 
     39 namespace chromeos {
     40 namespace {
     41 
     42 // Color of the text of the warning message.
     43 const SkColor kTextColor = SK_ColorBLACK;
     44 
     45 // Color of the text of the warning message.
     46 const SkColor kErrorTextColor = SK_ColorRED;
     47 
     48 // Color of the window background.
     49 const SkColor kWindowBackgroundColor = SK_ColorWHITE;
     50 
     51 // Radius of the rounded corners of the window.
     52 const int kWindowCornerRadius = 4;
     53 
     54 // Creates and shows the message widget for |view| with |animation_time_ms|.
     55 void 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.
    102 class 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 
    220 IdleAppNameNotificationView::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 
    228 IdleAppNameNotificationView::~IdleAppNameNotificationView() {
    229   CloseMessage();
    230 }
    231 
    232 void IdleAppNameNotificationView::CloseMessage() {
    233   if (view_) {
    234     IdleAppNameNotificationDelegateView* view = view_;
    235     view_ = NULL;
    236     view->Close();
    237   }
    238 }
    239 
    240 bool IdleAppNameNotificationView::IsVisible() {
    241   return view_ != NULL;
    242 }
    243 
    244 base::string16 IdleAppNameNotificationView::GetShownTextForTest() {
    245   ui::AXViewState state;
    246   DCHECK(view_);
    247   view_->GetAccessibleState(&state);
    248   return state.name;
    249 }
    250 
    251 void 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