text: // Copyright 2016 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "ash/display/touch_calibrator_view.h"
  
  #include <memory>
  
  #include "ash/public/cpp/shell_window_ids.h"
  #include "ash/resources/vector_icons/vector_icons.h"
  #include "ash/shell.h"
  #include "ui/aura/window.h"
  #include "ui/base/resource/resource_bundle.h"
  #include "ui/compositor/scoped_layer_animation_settings.h"
  #include "ui/gfx/animation/linear_animation.h"
  #include "ui/gfx/animation/throb_animation.h"
  #include "ui/gfx/canvas.h"
  #include "ui/gfx/paint_vector_icon.h"
  #include "ui/strings/grit/ui_strings.h"
  #include "ui/views/background.h"
  #include "ui/views/bubble/bubble_border.h"
  #include "ui/views/controls/label.h"
  #include "ui/views/widget/widget.h"
  
  namespace ash {
  
  namespace {
  
  constexpr char kWidgetName[] = "TouchCalibratorOverlay";
  
  constexpr int kAnimationFrameRate = 100;
  constexpr auto kFadeDuration = base::TimeDelta::FromMilliseconds(150);
  constexpr auto kPointMoveDuration = base::TimeDelta::FromMilliseconds(400);
  constexpr auto kPointMoveDurationLong = base::TimeDelta::FromMilliseconds(500);
  
  const SkColor kExitLabelColor = SkColorSetARGB(255, 138, 138, 138);
  constexpr int kExitLabelWidth = 300;
  constexpr int kExitLabelHeight = 20;
  
  const SkColor kTapHereLabelColor = SK_ColorWHITE;
  
  constexpr int kHintBoxWidth = 298;
  constexpr int kHintBoxHeight = 180;
  constexpr int kHintBoxLabelTextSize = 5;
  constexpr int kHintBoxSublabelTextSize = 3;
  
  constexpr int kThrobberCircleViewWidth = 64;
  constexpr float kThrobberCircleRadiusFactor = 3.f / 8.f;
  
  constexpr auto kFinalMessageTransitionDuration =
      base::TimeDelta::FromMilliseconds(200);
  constexpr int kCompleteMessageViewWidth = 427;
  constexpr int kCompleteMessageViewHeight = kThrobberCircleViewWidth;
  constexpr int kCompleteMessageTextSize = 16;
  
  constexpr int kTouchPointViewOffset = 100;
  
  constexpr int kTapLabelHeight = 48;
  constexpr int kTapLabelWidth = 80;
  
  const SkColor kHintLabelTextColor = SK_ColorBLACK;
  const SkColor kHintSublabelTextColor = SkColorSetARGB(255, 161, 161, 161);
  
  const SkColor kInnerCircleColor = SK_ColorWHITE;
  const SkColor kOuterCircleColor = SkColorSetA(kInnerCircleColor, 255 * 0.2);
  
  constexpr auto kCircleAnimationDuration =
      base::TimeDelta::FromMilliseconds(900);
  
  constexpr int kHintRectBorderRadius = 4;
  
  constexpr float kBackgroundFinalOpacity = 0.75f;
  
  constexpr int kTouchTargetWidth = 64;
  constexpr int kTouchTargetHeight = kTouchTargetWidth + kTouchTargetWidth / 2;
  
  constexpr float kTouchTargetVerticalOffsetFactor = 11.f / 24.f;
  
  const SkColor kTouchTargetInnerCircleColor = SkColorSetARGB(255, 66, 133, 244);
  const SkColor kTouchTargetOuterCircleColor =
      SkColorSetA(kTouchTargetInnerCircleColor, 255 * 0.2);
  const SkColor kHandIconColor = SkColorSetARGB(255, 201, 201, 201);
  constexpr float kHandIconHorizontalOffsetFactor = 7.f / 32.f;
  
  // Returns the initialization params for the widget that contains the touch
  // calibrator view.
  views::Widget::InitParams GetWidgetParams(aura::Window* root_window) {
    views::Widget::InitParams params;
    params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
    params.name = kWidgetName;
    params.z_order = ui::ZOrderLevel::kFloatingWindow;
    params.accept_events = true;
    params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
    params.parent =
        Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
    return params;
  }
  
  // Returns the size of bounding box required for |text| of given |font_list|.
  gfx::Size GetSizeForString(const base::string16& text,
                             const gfx::FontList& font_list) {
    int height = 0, width = 0;
    gfx::Canvas::SizeStringInt(text, font_list, &width, &height, 0, 0);
    return gfx::Size(width, height);
  }
  
  void AnimateLayerToPosition(views::View* view,
                              base::TimeDelta duration,
                              gfx::Point end_position,
                              float opacity = 1.f) {
    ui::ScopedLayerAnimationSettings slide_settings(view->layer()->GetAnimator());
    slide_settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    slide_settings.SetTransitionDuration(duration);
    view->SetBoundsRect(gfx::Rect(end_position, view->size()));
    view->layer()->SetOpacity(opacity);
  }
  
  }  // namespace
  
  // Creates a throbbing animated view with two concentric circles. The radius of
  // the inner circle is fixed while that of the outer circle oscillates between a
  // min and max radius. The animation takes |animation_duration| milliseconds
  // to complete. The center of these circles are at the center of the view
  // element.
  class CircularThrobberView : public views::View,
                               public views::AnimationDelegateViews {
   public:
    CircularThrobberView(int width,
                         const SkColor& inner_circle_color,
                         const SkColor& outer_circle_color,
                         base::TimeDelta animation_duration);
    ~CircularThrobberView() override;
  
    // views::View:
    void OnPaint(gfx::Canvas* canvas) override;
  
    // views::AnimationDelegateViews:
    void AnimationProgressed(const gfx::Animation* animation) override;
  
   private:
    // Radius of the inner circle.
    const int inner_radius_;
  
    // Current radius of the outer circle.
    int outer_radius_;
  
    // Minimum radius for outer animated circle.
    const int smallest_radius_animated_circle_;
  
    // Maximum radius for outer animated circle.
    const int largest_radius_animated_circle_;
  
    cc::PaintFlags inner_circle_flags_;
    cc::PaintFlags outer_circle_flags_;
  
    std::unique_ptr<gfx::ThrobAnimation> animation_;
  
    // Center of the concentric circles.
    const gfx::Point center_;
  
    DISALLOW_COPY_AND_ASSIGN(CircularThrobberView);
  };
  
  CircularThrobberView::CircularThrobberView(int width,
                                             const SkColor& inner_circle_color,
                                             const SkColor& outer_circle_color,
                                             base::TimeDelta animation_duration)
      : views::AnimationDelegateViews(this),
        inner_radius_(width / 4),
        outer_radius_(inner_radius_),
        smallest_radius_animated_circle_(width * kThrobberCircleRadiusFactor),
        largest_radius_animated_circle_(width / 2),
        center_(gfx::Point(width / 2, width / 2)) {
    SetSize(gfx::Size(width, width));
  
    inner_circle_flags_.setColor(inner_circle_color);
    inner_circle_flags_.setAntiAlias(true);
    inner_circle_flags_.setStyle(cc::PaintFlags::kFill_Style);
  
    outer_circle_flags_.setColor(outer_circle_color);
    outer_circle_flags_.setAntiAlias(true);
    outer_circle_flags_.setStyle(cc::PaintFlags::kFill_Style);
  
    animation_.reset(new gfx::ThrobAnimation(this));
    animation_->SetThrobDuration(animation_duration);
    animation_->StartThrobbing(-1);
  
    SchedulePaint();
  }
  
  CircularThrobberView::~CircularThrobberView() = default;
  
  void CircularThrobberView::OnPaint(gfx::Canvas* canvas) {
    canvas->DrawCircle(center_, outer_radius_, outer_circle_flags_);
    canvas->DrawCircle(center_, inner_radius_, inner_circle_flags_);
  }
  
  void CircularThrobberView::AnimationProgressed(
      const gfx::Animation* animation) {
    if (animation != animation_.get())
      return;
    outer_radius_ = animation->CurrentValueBetween(
        smallest_radius_animated_circle_, largest_radius_animated_circle_);
    SchedulePaint();
  }
  
  class TouchTargetThrobberView : public CircularThrobberView {
   public:
    TouchTargetThrobberView(const gfx::Rect& bounds,
                            const SkColor& inner_circle_color,
                            const SkColor& outer_circle_color,
                            const SkColor& hand_icon_color,
                            base::TimeDelta animation_duration);
    ~TouchTargetThrobberView() override;
  
    // views::View:
    void OnPaint(gfx::Canvas* canvas) override;
  
   private:
    const int horizontal_offset_;
  
    const int icon_width_;
  
    gfx::ImageSkia hand_icon_;
  
    DISALLOW_COPY_AND_ASSIGN(TouchTargetThrobberView);
  };
  
  TouchTargetThrobberView::TouchTargetThrobberView(
      const gfx::Rect& bounds,
      const SkColor& inner_circle_color,
      const SkColor& outer_circle_color,
      const SkColor& hand_icon_color,
      base::TimeDelta animation_duration)
      : CircularThrobberView(bounds.width(),
                             inner_circle_color,
                             outer_circle_color,
                             animation_duration),
        horizontal_offset_(bounds.width() * kHandIconHorizontalOffsetFactor),
        icon_width_(bounds.width() * 0.5f) {
    SetBoundsRect(bounds);
  
    hand_icon_ = gfx::CreateVectorIcon(kTouchCalibrationHandIcon, kHandIconColor);
  }
  
  TouchTargetThrobberView::~TouchTargetThrobberView() = default;
  
  void TouchTargetThrobberView::OnPaint(gfx::Canvas* canvas) {
    CircularThrobberView::OnPaint(canvas);
    canvas->DrawImageInt(hand_icon_, horizontal_offset_, icon_width_);
  }
  
  //   Circular      _________________________________
  //   Throbber     |                                 |
  //     View       |                                 |
  //  ___________   |                                 |
  // |           |  |                                 |
  // |           |  |                                 |
  // |     .     |  |            Hint Box             |
  // |           |  |                                 |
  // |___________|  |                                 |
  //                |                                 |
  //                |                                 |
  //                |_________________________________|
  //
  // This view is set next to the throbber circle view such that their centers
  // align. The hint box has a label text and a sublabel text to assist the
  // user by informing them about the next step in the calibration process.
  class HintBox : public views::View {
   public:
    HintBox(const gfx::Rect& bounds, int border_radius);
    ~HintBox() override;
  
    // views::View:
    void OnPaint(gfx::Canvas* canvas) override;
  
    void SetLabel(const base::string16& text, const SkColor& color);
    void SetSubLabel(const base::string16& text, const SkColor& color);
  
   private:
    void UpdateWidth(int updated_width);
  
    base::string16 label_text_;
    base::string16 sublabel_text_;
  
    SkColor label_color_;
    SkColor sublabel_color_;
  
    const int border_radius_;
  
    int base_border_;
  
    int arrow_width_;
  
    int horizontal_offset_;
  
    gfx::Rect rounded_rect_bounds_;
  
    gfx::FontList label_font_list_;
    gfx::FontList sublabel_font_list_;
  
    gfx::Rect label_text_bounds_;
    gfx::Rect sublabel_text_bounds_;
  
    cc::PaintFlags flags_;
  
    DISALLOW_COPY_AND_ASSIGN(HintBox);
  };
  
  HintBox::HintBox(const gfx::Rect& bounds, int border_radius)
      : border_radius_(border_radius) {
    SetBorder(std::make_unique<views::BubbleBorder>(
        base::i18n::IsRTL() ? views::BubbleBorder::RIGHT_CENTER
                            : views::BubbleBorder::LEFT_CENTER,
        views::BubbleBorder::NO_SHADOW_OPAQUE_BORDER, SK_ColorWHITE));
  
    arrow_width_ = (GetInsets().right() - GetInsets().left()) *
                   (base::i18n::IsRTL() ? 1 : -1);
  
    // Border on all sides are the same except on the side of the arrow, in which
    // case the width of the arrow is additional.
    base_border_ = base::i18n::IsRTL() ? GetInsets().left() : GetInsets().right();
  
    SetBounds(bounds.x(), bounds.y() - base_border_,
              bounds.width() + 2 * base_border_ + arrow_width_,
              bounds.height() + 2 * base_border_);
  
    rounded_rect_bounds_ = GetContentsBounds();
  
    flags_.setColor(SK_ColorWHITE);
    flags_.setStyle(cc::PaintFlags::kFill_Style);
    flags_.setAntiAlias(true);
  
    horizontal_offset_ =
        arrow_width_ + base_border_ + rounded_rect_bounds_.width() * 0.08f;
    int top_offset = horizontal_offset_;
    int line_gap = rounded_rect_bounds_.height() * 0.018f;
    int label_height = rounded_rect_bounds_.height() * 0.11f;
  
    label_text_bounds_.SetRect(horizontal_offset_, top_offset, 0, label_height);
  
    top_offset += label_text_bounds_.height() + line_gap;
  
    sublabel_text_bounds_.SetRect(horizontal_offset_, top_offset, 0,
                                  label_height);
  }
  
  HintBox::~HintBox() = default;
  
  void HintBox::UpdateWidth(int updated_width) {
    SetSize(gfx::Size(updated_width + 2 * base_border_ + arrow_width_, height()));
    rounded_rect_bounds_ = GetContentsBounds();
  }
  
  void HintBox::SetLabel(const base::string16& text, const SkColor& color) {
    label_text_ = text;
    label_color_ = color;
  
    label_font_list_ =
        ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
            kHintBoxLabelTextSize, gfx::Font::FontStyle::NORMAL,
            gfx::Font::Weight::NORMAL);
  
    // Adjust size of label bounds based on text and font.
    gfx::Size size = GetSizeForString(label_text_, label_font_list_);
    label_text_bounds_.set_size(
        gfx::Size(size.width(), label_text_bounds_.height()));
  
    // Check if the width of hint box needs to be updated.
    int minimum_expected_width =
        size.width() + 2 * horizontal_offset_ - arrow_width_;
    if (minimum_expected_width > rounded_rect_bounds_.width())
      UpdateWidth(minimum_expected_width);
  }
  
  void HintBox::SetSubLabel(const base::string16& text, const SkColor& color) {
    sublabel_text_ = text;
    sublabel_color_ = color;
  
    sublabel_font_list_ =
        ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
            kHintBoxSublabelTextSize, gfx::Font::FontStyle::NORMAL,
            gfx::Font::Weight::NORMAL);
  
    // Adjust size of sublabel label bounds based on text and font.
    gfx::Size size = GetSizeForString(sublabel_text_, sublabel_font_list_);
    sublabel_text_bounds_.set_size(
        gfx::Size(size.width(), sublabel_text_bounds_.height()));
  
    // Check if the width of hint box needs to be updated.
    int minimum_expected_width =
        size.width() + 2 * horizontal_offset_ - arrow_width_;
    if (minimum_expected_width > rounded_rect_bounds_.width())
      UpdateWidth(minimum_expected_width);
  }
  
  void HintBox::OnPaint(gfx::Canvas* canvas) {
    views::View::OnPaint(canvas);
    canvas->DrawRoundRect(rounded_rect_bounds_, border_radius_, flags_);
    canvas->DrawStringRectWithFlags(label_text_, label_font_list_, label_color_,
                                    label_text_bounds_, gfx::Canvas::NO_ELLIPSIS);
    canvas->DrawStringRectWithFlags(sublabel_text_, sublabel_font_list_,
                                    sublabel_color_, sublabel_text_bounds_,
                                    gfx::Canvas::NO_ELLIPSIS);
  }
  
  class CompletionMessageView : public views::View {
   public:
    CompletionMessageView(const gfx::Rect& bounds, const base::string16& message);
    ~CompletionMessageView() override;
  
    // views::View:
    void OnPaint(gfx::Canvas* canvas) override;
  
   private:
    const base::string16 message_;
    gfx::FontList font_list_;
  
    gfx::Rect text_bounds_;
  
    gfx::ImageSkia check_icon_;
  
    cc::PaintFlags flags_;
  
    DISALLOW_COPY_AND_ASSIGN(CompletionMessageView);
  };
  
  CompletionMessageView::CompletionMessageView(const gfx::Rect& bounds,
                                               const base::string16& message)
      : message_(message) {
    SetBoundsRect(bounds);
  
    int x_offset = height() * 5.f / 4.f;
    text_bounds_.SetRect(x_offset, 0, width() - x_offset, height());
  
    font_list_ = ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
        kCompleteMessageTextSize, gfx::Font::FontStyle::NORMAL,
        gfx::Font::Weight::NORMAL);
  
    // crbug/676513 moves this file to src/ash which will require an ash icon
    // file.
    check_icon_ =
        gfx::CreateVectorIcon(kTouchCalibrationCompleteCheckIcon, SK_ColorWHITE);
  
    flags_.setColor(SK_ColorWHITE);
    flags_.setStyle(cc::PaintFlags::kFill_Style);
    flags_.setAntiAlias(true);
  }
  
  CompletionMessageView::~CompletionMessageView() = default;
  
  void CompletionMessageView::OnPaint(gfx::Canvas* canvas) {
    canvas->DrawImageInt(check_icon_, 0, 0);
  
    // TODO(malaykeshav): Work with elizabethchiu@ to get better UX for RTL.
    canvas->DrawStringRectWithFlags(
        message_, font_list_, flags_.getColor(), text_bounds_,
        gfx::Canvas::TEXT_ALIGN_LEFT | gfx::Canvas::NO_SUBPIXEL_RENDERING);
  }
  
  TouchCalibratorView::TouchCalibratorView(const display::Display& target_display,
                                           bool is_primary_view)
      : views::AnimationDelegateViews(this),
        display_(target_display),
        is_primary_view_(is_primary_view),
        exit_label_(nullptr),
        tap_label_(nullptr),
        throbber_circle_(nullptr),
        hint_box_view_(nullptr),
        touch_point_view_(nullptr) {
    aura::Window* root = Shell::GetRootWindowForDisplayId(display_.id());
    widget_.reset(new views::Widget);
    widget_->Init(GetWidgetParams(root));
    widget_->SetContentsView(this);
    widget_->SetBounds(display_.bounds());
    widget_->Show();
    set_owned_by_client();
  
    animator_.reset(
        new gfx::LinearAnimation(kFadeDuration, kAnimationFrameRate, this));
  
    InitViewContents();
    AdvanceToNextState();
  }
  
  TouchCalibratorView::~TouchCalibratorView() {
    state_ = UNKNOWN;
    widget_->Hide();
    animator_->End();
  }
  
  void TouchCalibratorView::InitViewContents() {
    // Initialize the background rect.
    background_rect_ =
        gfx::RectF(0, 0, display_.bounds().width(), display_.bounds().height());
  
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    // Initialize exit label that informs the user how to exit the touch
    // calibration setup.
    exit_label_ = new views::Label(
        rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_EXIT_LABEL),
        {rb.GetFontListWithDelta(8, gfx::Font::FontStyle::NORMAL,
                                 gfx::Font::Weight::NORMAL)});
    exit_label_->SetBounds((display_.bounds().width() - kExitLabelWidth) / 2,
                           display_.bounds().height() * 3.f / 4, kExitLabelWidth,
                           kExitLabelHeight);
    exit_label_->SetAutoColorReadabilityEnabled(false);
    exit_label_->SetEnabledColor(kExitLabelColor);
    exit_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    exit_label_->SetSubpixelRenderingEnabled(false);
    exit_label_->SetVisible(false);
  
    AddChildView(exit_label_);
  
    // If this is not the screen that is being calibrated, then this is all we
    // need to display.
    if (!is_primary_view_)
      return;
  
    // Initialize the touch point view that contains the animated circle that the
    // user needs to tap.
    const int kTouchPointViewHeight = kThrobberCircleViewWidth + kTapLabelHeight;
    const int kThrobberCircleViewHorizontalOffset =
        (kTapLabelWidth - kThrobberCircleViewWidth) / 2;
  
    throbber_circle_ =
        new CircularThrobberView(kThrobberCircleViewWidth, kInnerCircleColor,
                                 kOuterCircleColor, kCircleAnimationDuration);
    throbber_circle_->SetPosition(
        gfx::Point(kThrobberCircleViewHorizontalOffset, 0));
  
    // Initialize the tap label.
    tap_label_ = new views::Label(
        rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_TAP_HERE_LABEL),
        {rb.GetFontListWithDelta(6, gfx::Font::FontStyle::NORMAL,
                                 gfx::Font::Weight::NORMAL)});
    tap_label_->SetBounds(0, kThrobberCircleViewWidth, kTapLabelWidth,
                          kTapLabelHeight);
    tap_label_->SetEnabledColor(kTapHereLabelColor);
    tap_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    tap_label_->SetAutoColorReadabilityEnabled(false);
    tap_label_->SetSubpixelRenderingEnabled(false);
    tap_label_->SetVisible(false);
  
    touch_point_view_ = new views::View;
    touch_point_view_->SetBounds(kTouchPointViewOffset, kTouchPointViewOffset,
                                 kTapLabelWidth, kTouchPointViewHeight);
    touch_point_view_->SetVisible(false);
    touch_point_view_->SetPaintToLayer();
    touch_point_view_->layer()->SetFillsBoundsOpaquely(false);
    touch_point_view_->layer()->GetAnimator()->AddObserver(this);
    touch_point_view_->SetBackground(
        views::CreateSolidBackground(SK_ColorTRANSPARENT));
  
    touch_point_view_->AddChildView(throbber_circle_);
    touch_point_view_->AddChildView(tap_label_);
  
    AddChildView(touch_point_view_);
  
    // Initialize the Hint Box view.
    base::string16 hint_label_text =
        rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_HINT_LABEL_TEXT);
    base::string16 hint_sublabel_text =
        rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_HINT_SUBLABEL_TEXT);
  
    int tpv_width = touch_point_view_->width();
  
    gfx::Size size(kHintBoxWidth, kHintBoxHeight);
  
    gfx::Point position(touch_point_view_->x() + tpv_width * 1.2f,
                        touch_point_view_->y() +
                            (kThrobberCircleViewWidth / 2.f) -
                            (size.height() / 2.f));
  
    HintBox* hint_box =
        new HintBox(gfx::Rect(position, size), kHintRectBorderRadius);
    hint_box->SetVisible(false);
    hint_box->SetLabel(hint_label_text, kHintLabelTextColor);
    hint_box->SetSubLabel(hint_sublabel_text, kHintSublabelTextColor);
    hint_box_view_ = hint_box;
  
    AddChildView(hint_box_view_);
  
    // Initialize the animated hint box throbber view.
    TouchTargetThrobberView* target_view = new TouchTargetThrobberView(
        gfx::Rect((hint_box->width() - kTouchTargetWidth) / 2,
                  hint_box->height() * kTouchTargetVerticalOffsetFactor,
                  kTouchTargetWidth, kTouchTargetHeight),
        kTouchTargetInnerCircleColor, kTouchTargetOuterCircleColor,
        kHandIconColor, kCircleAnimationDuration);
    target_view->SetVisible(true);
  
    hint_box_view_->AddChildView(target_view);
  
    // Initialize the view that contains the calibration complete message which
    // will be displayed at the end.
    base::string16 finish_msg_text =
        rb.GetLocalizedString(IDS_DISPLAY_TOUCH_CALIBRATION_FINISH_LABEL);
  
    gfx::Rect msg_view_bounds(
        (display_.bounds().width() - kCompleteMessageViewWidth) / 2,
        display_.bounds().height() / 3, kCompleteMessageViewWidth,
        kCompleteMessageViewHeight);
    completion_message_view_ =
        new CompletionMessageView(msg_view_bounds, finish_msg_text);
    completion_message_view_->SetVisible(false);
    completion_message_view_->SetPaintToLayer();
    completion_message_view_->layer()->SetFillsBoundsOpaquely(false);
    completion_message_view_->layer()->GetAnimator()->AddObserver(this);
    completion_message_view_->SetBackground(
        views::CreateSolidBackground(SK_ColorTRANSPARENT));
  
    AddChildView(completion_message_view_);
  }
  
  void TouchCalibratorView::OnPaint(gfx::Canvas* canvas) {
    OnPaintBackground(canvas);
  }
  
  void TouchCalibratorView::OnPaintBackground(gfx::Canvas* canvas) {
    float opacity;
  
    // If current state is a fade in or fade out state then update opacity
    // based on how far the animation has progressed.
    if (animator_ && (state_ == TouchCalibratorView::BACKGROUND_FADING_OUT ||
                      state_ == TouchCalibratorView::BACKGROUND_FADING_IN)) {
      opacity = static_cast<float>(animator_->CurrentValueBetween(
          start_opacity_value_, end_opacity_value_));
    } else {
      opacity = state_ == BACKGROUND_FADING_OUT ? 0.f : kBackgroundFinalOpacity;
    }
  
    flags_.setColor(SkColorSetA(SK_ColorBLACK,
                                std::numeric_limits<uint8_t>::max() * opacity));
    canvas->DrawRect(background_rect_, flags_);
  }
  
  void TouchCalibratorView::AnimationProgressed(const gfx::Animation* animation) {
    if (!is_primary_view_) {
      SchedulePaint();
      return;
    }
  }
  
  void TouchCalibratorView::AnimationCanceled(const gfx::Animation* animation) {
    AnimationEnded(animation);
  }
  
  void TouchCalibratorView::AnimationEnded(const gfx::Animation* animation) {
    switch (state_) {
      case BACKGROUND_FADING_IN:
        exit_label_->SetVisible(true);
        state_ = is_primary_view_ ? DISPLAY_POINT_1 : CALIBRATION_COMPLETE;
        if (is_primary_view_) {
          touch_point_view_->SetVisible(true);
          hint_box_view_->SetVisible(true);
        }
        break;
      case BACKGROUND_FADING_OUT:
        exit_label_->SetVisible(false);
        if (is_primary_view_)
          completion_message_view_->SetVisible(false);
        widget_->Hide();
        break;
      default:
        break;
    }
  }
  
  void TouchCalibratorView::OnLayerAnimationStarted(
      ui::LayerAnimationSequence* sequence) {}
  
  void TouchCalibratorView::OnLayerAnimationEnded(
      ui::LayerAnimationSequence* sequence) {
    switch (state_) {
      case ANIMATING_1_TO_2:
        state_ = DISPLAY_POINT_2;
        tap_label_->SetVisible(true);
        break;
      case ANIMATING_2_TO_3:
        state_ = DISPLAY_POINT_3;
        break;
      case ANIMATING_3_TO_4:
        state_ = DISPLAY_POINT_4;
        break;
      case ANIMATING_FINAL_MESSAGE:
        state_ = CALIBRATION_COMPLETE;
        break;
      default:
        break;
    }
  }
  
  void TouchCalibratorView::OnLayerAnimationAborted(
      ui::LayerAnimationSequence* sequence) {
    OnLayerAnimationEnded(sequence);
  }
  
  void TouchCalibratorView::OnLayerAnimationScheduled(
      ui::LayerAnimationSequence* sequence) {}
  
  void TouchCalibratorView::AdvanceToNextState() {
    // Stop any previous animations and skip them to the end.
    SkipCurrentAnimation();
  
    switch (state_) {
      case UNKNOWN:
      case BACKGROUND_FADING_IN:
        state_ = BACKGROUND_FADING_IN;
        start_opacity_value_ = 0.f;
        end_opacity_value_ = kBackgroundFinalOpacity;
  
        flags_.setStyle(cc::PaintFlags::kFill_Style);
        animator_->SetDuration(kFadeDuration);
        animator_->Start();
        return;
      case DISPLAY_POINT_1:
        state_ = ANIMATING_1_TO_2;
  
        // The touch point has to be animated from the top left corner of the
        // screen to the top right corner.
        AnimateLayerToPosition(
            touch_point_view_, kPointMoveDuration,
            gfx::Point(display_.bounds().width() - kTouchPointViewOffset -
                           touch_point_view_->width(),
                       touch_point_view_->y()));
        hint_box_view_->SetVisible(false);
        return;
      case DISPLAY_POINT_2:
        state_ = ANIMATING_2_TO_3;
  
        // The touch point has to be animated from the top right corner of the
        // screen to the bottom left corner.
        AnimateLayerToPosition(
            touch_point_view_, kPointMoveDurationLong,
            gfx::Point(kTouchPointViewOffset, display_.bounds().height() -
                                                  kTouchPointViewOffset -
                                                  touch_point_view_->height()));
        return;
      case DISPLAY_POINT_3:
        state_ = ANIMATING_3_TO_4;
  
        // The touch point has to be animated from the bottom left corner of the
        // screen to the bottom right corner.
        AnimateLayerToPosition(
            touch_point_view_, kPointMoveDuration,
            gfx::Point(display_.bounds().width() - kTouchPointViewOffset -
                           touch_point_view_->width(),
                       touch_point_view_->y()));
        return;
      case DISPLAY_POINT_4:
        state_ = ANIMATING_FINAL_MESSAGE;
        completion_message_view_->layer()->SetOpacity(0.f);
        completion_message_view_->SetVisible(true);
  
        touch_point_view_->SetVisible(false);
  
        AnimateLayerToPosition(completion_message_view_,
                               kFinalMessageTransitionDuration,
                               gfx::Point(completion_message_view_->x(),
                                          display_.bounds().height() / 2));
        return;
      case CALIBRATION_COMPLETE:
        state_ = BACKGROUND_FADING_OUT;
        if (is_primary_view_) {
          // In case of primary view, we also need to fade out the calibration
          // complete message view.
          AnimateLayerToPosition(
              completion_message_view_, kFadeDuration,
              gfx::Point(completion_message_view_->x(),
                         completion_message_view_->y() +
                             2 * completion_message_view_->height()),
              0.f);
        }
  
        start_opacity_value_ = kBackgroundFinalOpacity;
        end_opacity_value_ = 0.f;
  
        flags_.setStyle(cc::PaintFlags::kFill_Style);
        animator_->SetDuration(kFadeDuration);
        animator_->Start();
        return;
      default:
        return;
    }
  }
  
  bool TouchCalibratorView::GetDisplayPointLocation(gfx::Point* location) {
    DCHECK(location);
    if (!is_primary_view_)
      return false;
  
    if (state_ != DISPLAY_POINT_1 && state_ != DISPLAY_POINT_2 &&
        state_ != DISPLAY_POINT_3 && state_ != DISPLAY_POINT_4) {
      return false;
    }
  
    if (!touch_point_view_ || !throbber_circle_)
      return false;
    // TODO(malaykeshav): Can use views::ConvertPointToScreen()
    location->SetPoint(touch_point_view_->x() + touch_point_view_->width() / 2.f,
                       touch_point_view_->y() + touch_point_view_->width() / 2.f);
    return true;
  }
  
  void TouchCalibratorView::SkipToFinalState() {
    state_ = CALIBRATION_COMPLETE;
  
    exit_label_->SetVisible(false);
  
    if (is_primary_view_) {
      touch_point_view_->SetVisible(false);
      hint_box_view_->SetVisible(false);
    }
  
    AdvanceToNextState();
  }
  
  void TouchCalibratorView::SkipCurrentAnimation() {
    if (animator_->is_animating())
      animator_->End();
    if (touch_point_view_ &&
        touch_point_view_->layer()->GetAnimator()->is_animating()) {
      touch_point_view_->layer()->GetAnimator()->StopAnimating();
    }
  }
  
  }  // namespace ash
  