root/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. AddResourceIcon
  2. PrepareForDisplay
  3. is_inline_install
  4. is_bundle_install
  5. is_external_install
  6. ShowExtensionInstallDialogImpl
  7. Layout
  8. unchecked_boxes_
  9. CreateLayout
  10. ContentsChanged
  11. ViewHierarchyChanged
  12. GetDialogButtons
  13. GetDialogButtonLabel
  14. GetDefaultDialogButton
  15. Cancel
  16. Accept
  17. GetModalType
  18. GetWindowTitle
  19. LinkClicked
  20. ToggleInlineExplanations
  21. Layout
  22. GetPreferredSize
  23. ButtonPressed
  24. UpdateInstallResultHistogram
  25. UpdateLinkActionHistogram
  26. GetDefaultShowDialogCallback
  27. lighter_color_
  28. AddDetail
  29. GetPreferredSize
  30. AnimateToState
  31. expanded_
  32. ButtonPressed
  33. LinkClicked
  34. AnimationProgressed
  35. AnimationEnded
  36. ChildPreferredSizeChanged
  37. ToggleDetailLevel
  38. ExpandWithoutAnimation

// Copyright (c) 2012 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 <vector>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/bundle_installer.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_install_prompt_experiment.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/constrained_window_views.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/installer/util/browser_distribution.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/google_chrome_strings.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/text_utils.h"
#include "ui/gfx/transform.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/link_listener.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"
#include "ui/views/window/dialog_delegate.h"

using content::OpenURLParams;
using content::Referrer;
using extensions::BundleInstaller;

namespace {

// Size of extension icon in top left of dialog.
const int kIconSize = 69;

// We offset the icon a little bit from the right edge of the dialog, to make it
// align with the button below it.
const int kIconOffset = 16;

// The dialog will resize based on its content, but this sets a maximum height
// before overflowing a scrollbar.
const int kDialogMaxHeight = 300;

// Width of the left column of the dialog when the extension requests
// permissions.
const int kPermissionsLeftColumnWidth = 250;

// Width of the left column of the dialog when the extension requests no
// permissions.
const int kNoPermissionsLeftColumnWidth = 200;

// Width of the left column for bundle install prompts. There's only one column
// in this case, so make it wider than normal.
const int kBundleLeftColumnWidth = 300;

// Width of the left column for external install prompts. The text is long in
// this case, so make it wider than normal.
const int kExternalInstallLeftColumnWidth = 350;

// Lighter color for labels.
const SkColor kLighterLabelColor = SkColorSetRGB(0x99, 0x99, 0x99);

// Represents an action on a clickable link created by the install prompt
// experiment. This is used to group the actions in UMA histograms named
// Extensions.InstallPromptExperiment.ShowDetails and
// Extensions.InstallPromptExperiment.ShowPermissions.
enum ExperimentLinkAction {
  LINK_SHOWN = 0,
  LINK_NOT_SHOWN,
  LINK_CLICKED,
  NUM_LINK_ACTIONS
};

typedef std::vector<base::string16> PermissionDetails;
class ExpandableContainerView;

void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) {
  views::View* parent = static_cast<views::View*>(data);
  views::ImageView* image_view = new views::ImageView();
  image_view->SetImage(*skia_image);
  parent->AddChildView(image_view);
}

// Creates a string for displaying |message| to the user. If it has to look
// like a entry in a bullet point list, one is added.
base::string16 PrepareForDisplay(const base::string16& message,
                                 bool bullet_point) {
  return bullet_point ? l10n_util::GetStringFUTF16(
      IDS_EXTENSION_PERMISSION_LINE,
      message) : message;
}

// A custom scrollable view implementation for the dialog.
class CustomScrollableView : public views::View {
 public:
  CustomScrollableView();
  virtual ~CustomScrollableView();

 private:
  virtual void Layout() OVERRIDE;

  DISALLOW_COPY_AND_ASSIGN(CustomScrollableView);
};

// Implements the extension installation dialog for TOOLKIT_VIEWS.
class ExtensionInstallDialogView : public views::DialogDelegateView,
                                   public views::LinkListener,
                                   public views::ButtonListener {
 public:
  ExtensionInstallDialogView(content::PageNavigator* navigator,
                             ExtensionInstallPrompt::Delegate* delegate,
                             const ExtensionInstallPrompt::Prompt& prompt);
  virtual ~ExtensionInstallDialogView();

  // Called when one of the child elements has expanded/collapsed.
  void ContentsChanged();

 private:
  // views::DialogDelegateView:
  virtual int GetDialogButtons() const OVERRIDE;
  virtual base::string16 GetDialogButtonLabel(
      ui::DialogButton button) const OVERRIDE;
  virtual int GetDefaultDialogButton() const OVERRIDE;
  virtual bool Cancel() OVERRIDE;
  virtual bool Accept() OVERRIDE;
  virtual ui::ModalType GetModalType() const OVERRIDE;
  virtual base::string16 GetWindowTitle() const OVERRIDE;
  virtual void Layout() OVERRIDE;
  virtual gfx::Size GetPreferredSize() OVERRIDE;
  virtual void ViewHierarchyChanged(
      const ViewHierarchyChangedDetails& details) OVERRIDE;

  // views::LinkListener:
  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;

  // views::ButtonListener:
  virtual void ButtonPressed(views::Button* sender,
                             const ui::Event& event) OVERRIDE;

  // Experimental: Toggles inline permission explanations with an animation.
  void ToggleInlineExplanations();

  // Creates a layout consisting of dialog header, extension name and icon.
  views::GridLayout* CreateLayout(
      views::View* parent,
      int left_column_width,
      int column_set_id,
      bool single_detail_row) const;

  bool is_inline_install() const {
    return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
  }

  bool is_bundle_install() const {
    return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
  }

  bool is_external_install() const {
    return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT;
  }

  // Updates the histogram that holds installation accepted/aborted data.
  void UpdateInstallResultHistogram(bool accepted) const;

  // Updates the histogram that holds data about whether "Show details" or
  // "Show permissions" links were shown and/or clicked.
  void UpdateLinkActionHistogram(int action_type) const;

  content::PageNavigator* navigator_;
  ExtensionInstallPrompt::Delegate* delegate_;
  const ExtensionInstallPrompt::Prompt& prompt_;

  // The scroll view containing all the details for the dialog (including all
  // collapsible/expandable sections).
  views::ScrollView* scroll_view_;

  // The container view for the scroll view.
  CustomScrollableView* scrollable_;

  // The container for the simpler view with only the dialog header and the
  // extension icon. Used for the experiment where the permissions are
  // initially hidden when the dialog shows.
  CustomScrollableView* scrollable_header_only_;

  // The preferred size of the dialog.
  gfx::Size dialog_size_;

  // Experimental: "Show details" link to expand inline explanations and reveal
  // permision dialog.
  views::Link* show_details_link_;

  // Experimental: Label for showing information about the checkboxes.
  views::Label* checkbox_info_label_;

  // Experimental: Contains pointers to inline explanation views.
  typedef std::vector<ExpandableContainerView*> InlineExplanations;
  InlineExplanations inline_explanations_;

  // Experimental: Number of unchecked checkboxes in the permission list.
  // If this becomes zero, the accept button is enabled, otherwise disabled.
  int unchecked_boxes_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView);
};

// A simple view that prepends a view with a bullet with the help of a grid
// layout.
class BulletedView : public views::View {
 public:
  explicit BulletedView(views::View* view);
 private:
  DISALLOW_COPY_AND_ASSIGN(BulletedView);
};

BulletedView::BulletedView(views::View* view) {
  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);
  views::ColumnSet* column_set = layout->AddColumnSet(0);
  column_set->AddColumn(views::GridLayout::LEADING,
                        views::GridLayout::LEADING,
                        0,
                        views::GridLayout::USE_PREF,
                        0, // no fixed width
                        0);
   column_set->AddColumn(views::GridLayout::LEADING,
                         views::GridLayout::LEADING,
                         0,
                         views::GridLayout::USE_PREF,
                         0,  // no fixed width
                         0);
  layout->StartRow(0, 0);
  layout->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
  layout->AddView(view);
}

// A simple view that prepends a view with a checkbox with the help of a grid
// layout. Used for the permission experiment.
// TODO(meacer): Remove once the experiment is completed.
class CheckboxedView : public views::View {
 public:
  CheckboxedView(views::View* view, views::ButtonListener* listener);
 private:
  DISALLOW_COPY_AND_ASSIGN(CheckboxedView);
};

CheckboxedView::CheckboxedView(views::View* view,
                               views::ButtonListener* listener) {
  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);
  views::ColumnSet* column_set = layout->AddColumnSet(0);
  column_set->AddColumn(views::GridLayout::LEADING,
                        views::GridLayout::LEADING,
                        0,
                        views::GridLayout::USE_PREF,
                        0, // no fixed width
                        0);
   column_set->AddColumn(views::GridLayout::LEADING,
                         views::GridLayout::LEADING,
                         0,
                         views::GridLayout::USE_PREF,
                         0,  // no fixed width
                         0);
  layout->StartRow(0, 0);
  views::Checkbox* checkbox = new views::Checkbox(base::string16());
  checkbox->set_listener(listener);
  // Alignment needs to be explicitly set again here, otherwise the views are
  // not vertically centered.
  layout->AddView(checkbox, 1, 1,
                  views::GridLayout::LEADING, views::GridLayout::CENTER);
  layout->AddView(view, 1, 1,
                  views::GridLayout::LEADING, views::GridLayout::CENTER);
}

// A view to display text with an expandable details section.
class ExpandableContainerView : public views::View,
                                public views::ButtonListener,
                                public views::LinkListener,
                                public gfx::AnimationDelegate {
 public:
  ExpandableContainerView(ExtensionInstallDialogView* owner,
                          const base::string16& description,
                          const PermissionDetails& details,
                          int horizontal_space,
                          bool parent_bulleted,
                          bool show_expand_link,
                          bool lighter_color_details);
  virtual ~ExpandableContainerView();

  // views::View:
  virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;

  // views::ButtonListener:
  virtual void ButtonPressed(views::Button* sender,
                             const ui::Event& event) OVERRIDE;

  // views::LinkListener:
  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;

  // gfx::AnimationDelegate:
  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;

  // Expand/Collapse the detail section for this ExpandableContainerView.
  void ToggleDetailLevel();

  // Expand the detail section without any animation.
  // TODO(meacer): Remove once the experiment is completed.
  void ExpandWithoutAnimation();

 private:
  // A view which displays all the details of an IssueAdviceInfoEntry.
  class DetailsView : public views::View {
   public:
    explicit DetailsView(int horizontal_space, bool parent_bulleted,
                         bool lighter_color);
    virtual ~DetailsView() {}

    // views::View:
    virtual gfx::Size GetPreferredSize() OVERRIDE;

    void AddDetail(const base::string16& detail);

    // Animates this to be a height proportional to |state|.
    void AnimateToState(double state);

   private:
    views::GridLayout* layout_;
    double state_;

    // Whether the detail text should be shown with a lighter color.
    bool lighter_color_;

    DISALLOW_COPY_AND_ASSIGN(DetailsView);
  };

  // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
  ExtensionInstallDialogView* owner_;

  // A view for showing |issue_advice.details|.
  DetailsView* details_view_;

  // The 'more details' link shown under the heading (changes to 'hide details'
  // when the details section is expanded).
  views::Link* more_details_;

  gfx::SlideAnimation slide_animation_;

  // The up/down arrow next to the 'more detail' link (points up/down depending
  // on whether the details section is expanded).
  views::ImageButton* arrow_toggle_;

  // Whether the details section is expanded.
  bool expanded_;

  DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView);
};

void ShowExtensionInstallDialogImpl(
    const ExtensionInstallPrompt::ShowParams& show_params,
    ExtensionInstallPrompt::Delegate* delegate,
    const ExtensionInstallPrompt::Prompt& prompt) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CreateBrowserModalDialogViews(
      new ExtensionInstallDialogView(show_params.navigator, delegate, prompt),
      show_params.parent_window)->Show();
}

}  // namespace

CustomScrollableView::CustomScrollableView() {}
CustomScrollableView::~CustomScrollableView() {}

void CustomScrollableView::Layout() {
  SetBounds(x(), y(), width(), GetHeightForWidth(width()));
  views::View::Layout();
}

ExtensionInstallDialogView::ExtensionInstallDialogView(
    content::PageNavigator* navigator,
    ExtensionInstallPrompt::Delegate* delegate,
    const ExtensionInstallPrompt::Prompt& prompt)
    : navigator_(navigator),
      delegate_(delegate),
      prompt_(prompt),
      scroll_view_(NULL),
      scrollable_(NULL),
      scrollable_header_only_(NULL),
      show_details_link_(NULL),
      checkbox_info_label_(NULL),
      unchecked_boxes_(0) {
  // Possible grid layouts without ExtensionPermissionDialog experiment:
  // Inline install
  //      w/ permissions                 no permissions
  // +--------------------+------+  +--------------+------+
  // | heading            | icon |  | heading      | icon |
  // +--------------------|      |  +--------------|      |
  // | rating             |      |  | rating       |      |
  // +--------------------|      |  +--------------+      |
  // | user_count         |      |  | user_count   |      |
  // +--------------------|      |  +--------------|      |
  // | store_link         |      |  | store_link   |      |
  // +--------------------+------+  +--------------+------+
  // |      separator            |
  // +--------------------+------+
  // | permissions_header |      |
  // +--------------------+------+
  // | permission1        |      |
  // +--------------------+------+
  // | permission2        |      |
  // +--------------------+------+
  //
  // Regular install
  // w/ permissions XOR oauth issues    no permissions
  // +--------------------+------+  +--------------+------+
  // | heading            | icon |  | heading      | icon |
  // +--------------------|      |  +--------------+------+
  // | permissions_header |      |
  // +--------------------|      |
  // | permission1        |      |
  // +--------------------|      |
  // | permission2        |      |
  // +--------------------+------+
  //
  // w/ permissions AND oauth issues
  // +--------------------+------+
  // | heading            | icon |
  // +--------------------|      |
  // | permissions_header |      |
  // +--------------------|      |
  // | permission1        |      |
  // +--------------------|      |
  // | permission2        |      |
  // +--------------------+------+
  // | oauth header              |
  // +---------------------------+
  // | oauth issue 1             |
  // +---------------------------+
  // | oauth issue 2             |
  // +---------------------------+
  //
  // If the ExtensionPermissionDialog is on, the layout is modified depending
  // on the experiment group. For text only experiment, a footer is added at the
  // bottom of the layouts. For others, inline details are added below some of
  // the permissions.
  //
  // Regular install w/ permissions and footer (experiment):
  // +--------------------+------+
  // | heading            | icon |
  // +--------------------|      |
  // | permissions_header |      |
  // +--------------------|      |
  // | permission1        |      |
  // +--------------------|      |
  // | permission2        |      |
  // +--------------------+------+
  // | footer text        |      |
  // +--------------------+------+
  //
  // Regular install w/ permissions and inline explanations (experiment):
  // +--------------------+------+
  // | heading            | icon |
  // +--------------------|      |
  // | permissions_header |      |
  // +--------------------|      |
  // | permission1        |      |
  // +--------------------|      |
  // | explanation1       |      |
  // +--------------------|      |
  // | permission2        |      |
  // +--------------------|      |
  // | explanation2       |      |
  // +--------------------+------+
  //
  // Regular install w/ permissions and inline explanations (experiment):
  // +--------------------+------+
  // | heading            | icon |
  // +--------------------|      |
  // | permissions_header |      |
  // +--------------------|      |
  // |checkbox|permission1|      |
  // +--------------------|      |
  // |checkbox|permission2|      |
  // +--------------------+------+
  //
  // Additionally, links or informational text is added to non-client areas of
  // the dialog depending on the experiment group.

  int left_column_width =
      (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() +
       prompt.GetRetainedFileCount()) > 0 ?
          kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth;
  if (is_bundle_install())
    left_column_width = kBundleLeftColumnWidth;
  if (is_external_install())
    left_column_width = kExternalInstallLeftColumnWidth;

  scroll_view_ = new views::ScrollView();
  scroll_view_->set_hide_horizontal_scrollbar(true);
  AddChildView(scroll_view_);

  int column_set_id = 0;
  // Create the full scrollable view which will contain all the information
  // including the permissions.
  scrollable_ = new CustomScrollableView();
  views::GridLayout* layout = CreateLayout(
      scrollable_, left_column_width, column_set_id, false);
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

  if (prompt.ShouldShowPermissions() &&
      prompt.experiment()->should_show_expandable_permission_list()) {
    // If the experiment should hide the permission list initially, create a
    // simple layout that contains only the header, extension name and icon.
    scrollable_header_only_ = new CustomScrollableView();
    CreateLayout(scrollable_header_only_, left_column_width,
                 column_set_id, true);
    scroll_view_->SetContents(scrollable_header_only_);
  } else {
    scroll_view_->SetContents(scrollable_);
  }

  int dialog_width = left_column_width + 2 * views::kPanelHorizMargin;
  if (!is_bundle_install())
    dialog_width += views::kPanelHorizMargin + kIconSize + kIconOffset;

  // Widen the dialog for experiment with checkboxes so that the information
  // label fits the area to the left of the buttons.
  if (prompt.experiment()->show_checkboxes())
    dialog_width += 4 * views::kPanelHorizMargin;

  if (prompt.has_webstore_data()) {
    layout->StartRow(0, column_set_id);
    views::View* rating = new views::View();
    rating->SetLayoutManager(new views::BoxLayout(
        views::BoxLayout::kHorizontal, 0, 0, 0));
    layout->AddView(rating);
    prompt.AppendRatingStars(AddResourceIcon, rating);

    const gfx::FontList& small_font_list =
        rb.GetFontList(ui::ResourceBundle::SmallFont);
    views::Label* rating_count =
        new views::Label(prompt.GetRatingCount(), small_font_list);
    // Add some space between the stars and the rating count.
    rating_count->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
    rating->AddChildView(rating_count);

    layout->StartRow(0, column_set_id);
    views::Label* user_count =
        new views::Label(prompt.GetUserCount(), small_font_list);
    user_count->SetAutoColorReadabilityEnabled(false);
    user_count->SetEnabledColor(SK_ColorGRAY);
    layout->AddView(user_count);

    layout->StartRow(0, column_set_id);
    views::Link* store_link = new views::Link(
        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
    store_link->SetFontList(small_font_list);
    store_link->set_listener(this);
    layout->AddView(store_link);
  }

  if (is_bundle_install()) {
    BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
        BundleInstaller::Item::STATE_PENDING);
    for (size_t i = 0; i < items.size(); ++i) {
      base::string16 extension_name =
          base::UTF8ToUTF16(items[i].localized_name);
      base::i18n::AdjustStringForLocaleDirection(&extension_name);
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      layout->StartRow(0, column_set_id);
      views::Label* extension_label = new views::Label(
          PrepareForDisplay(extension_name, true));
      extension_label->SetMultiLine(true);
      extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      extension_label->SizeToFit(left_column_width);
      layout->AddView(extension_label);
    }
  }

  if (prompt.ShouldShowPermissions()) {
    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

    if (prompt.GetPermissionCount() > 0) {
      if (is_inline_install()) {
        layout->StartRow(0, column_set_id);
        layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
                        3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      }

      layout->StartRow(0, column_set_id);
      views::Label* permissions_header = NULL;
      if (is_bundle_install()) {
        // We need to pass the FontList in the constructor, rather than calling
        // SetFontList later, because otherwise SizeToFit mis-judges the width
        // of the line.
        permissions_header = new views::Label(
            prompt.GetPermissionsHeading(),
            rb.GetFontList(ui::ResourceBundle::MediumFont));
      } else {
        permissions_header = new views::Label(prompt.GetPermissionsHeading());
      }
      permissions_header->SetMultiLine(true);
      permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      permissions_header->SizeToFit(left_column_width);
      layout->AddView(permissions_header);

      for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
        layout->StartRow(0, column_set_id);
        views::Label* permission_label =
            new views::Label(prompt.GetPermission(i));

        const SkColor kTextHighlight = SK_ColorRED;
        const SkColor kBackgroundHighlight = SkColorSetRGB(0xFB, 0xF7, 0xA3);
        if (prompt.experiment()->ShouldHighlightText(
            prompt.GetPermission(i))) {
          permission_label->SetAutoColorReadabilityEnabled(false);
          permission_label->SetEnabledColor(kTextHighlight);
        } else if (prompt.experiment()->ShouldHighlightBackground(
            prompt.GetPermission(i))) {
          permission_label->SetLineHeight(18);
          permission_label->set_background(
              views::Background::CreateSolidBackground(kBackgroundHighlight));
        }

        permission_label->SetMultiLine(true);
        permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
        permission_label->SizeToFit(left_column_width);

        if (prompt.experiment()->show_checkboxes()) {
          layout->AddView(new CheckboxedView(permission_label, this));
          ++unchecked_boxes_;
        } else {
          layout->AddView(new BulletedView(permission_label));
        }
        // If we have more details to provide, show them in collapsed form.
        if (!prompt.GetPermissionsDetails(i).empty()) {
          layout->StartRow(0, column_set_id);
          PermissionDetails details;
          details.push_back(
              PrepareForDisplay(prompt.GetPermissionsDetails(i), false));
          ExpandableContainerView* details_container =
              new ExpandableContainerView(
                  this, base::string16(), details, left_column_width,
                  true, true, false);
          layout->AddView(details_container);
        }

        if (prompt.experiment()->should_show_inline_explanations()) {
          base::string16 explanation =
              prompt.experiment()->GetInlineExplanation(
                  prompt.GetPermission(i));
          if (!explanation.empty()) {
            PermissionDetails details;
            details.push_back(explanation);
            ExpandableContainerView* container =
                new ExpandableContainerView(this, base::string16(), details,
                                            left_column_width,
                                            false, false, true);
            // Inline explanations are expanded by default if there is
            // no "Show details" link.
            if (!prompt.experiment()->show_details_link())
              container->ExpandWithoutAnimation();
            layout->StartRow(0, column_set_id);
            layout->AddView(container);
            inline_explanations_.push_back(container);
          }
        }
      }
    } else {
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      layout->StartRow(0, column_set_id);
      views::Label* permission_label = new views::Label(
          l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
      permission_label->SetMultiLine(true);
      permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      permission_label->SizeToFit(left_column_width);
      layout->AddView(permission_label);
    }
  }

  if (prompt.GetOAuthIssueCount()) {
    // Slide in under the permissions, if there are any. If there are
    // permissions, the OAuth prompt stretches all the way to the right of the
    // dialog. If there are no permissions, the OAuth prompt just takes up the
    // left column.
    int space_for_oauth = left_column_width;
    if (prompt.GetPermissionCount()) {
      space_for_oauth += kIconSize;
      views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
      column_set->AddColumn(views::GridLayout::FILL,
                            views::GridLayout::FILL,
                            1,
                            views::GridLayout::USE_PREF,
                            0,  // no fixed width
                            space_for_oauth);
    }

    layout->StartRowWithPadding(0, column_set_id,
                                0, views::kRelatedControlVerticalSpacing);
    views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading());
    oauth_header->SetMultiLine(true);
    oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    oauth_header->SizeToFit(left_column_width);
    layout->AddView(oauth_header);

    for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
      layout->StartRowWithPadding(
          0, column_set_id,
          0, views::kRelatedControlVerticalSpacing);

      PermissionDetails details;
      const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i);
      for (size_t x = 0; x < entry.details.size(); ++x)
        details.push_back(entry.details[x]);
      ExpandableContainerView* issue_advice_view =
          new ExpandableContainerView(
              this, entry.description, details, space_for_oauth,
              true, true, false);
      layout->AddView(issue_advice_view);
    }
  }
  if (prompt.GetRetainedFileCount()) {
    // Slide in under the permissions or OAuth, if there are any. If there are
    // either, the retained files prompt stretches all the way to the right of
    // the dialog. If there are no permissions or OAuth, the retained files
    // prompt just takes up the left column.
    int space_for_files = left_column_width;
    if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) {
      space_for_files += kIconSize;
      views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
      column_set->AddColumn(views::GridLayout::FILL,
                            views::GridLayout::FILL,
                            1,
                            views::GridLayout::USE_PREF,
                            0,  // no fixed width
                            space_for_files);
    }

    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

    layout->StartRow(0, column_set_id);
    views::Label* retained_files_header = NULL;
    retained_files_header =
        new views::Label(prompt.GetRetainedFilesHeading());
    retained_files_header->SetMultiLine(true);
    retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    retained_files_header->SizeToFit(space_for_files);
    layout->AddView(retained_files_header);

    layout->StartRow(0, column_set_id);
    PermissionDetails details;
    for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i)
      details.push_back(prompt.GetRetainedFile(i));
    ExpandableContainerView* issue_advice_view =
        new ExpandableContainerView(
            this, base::string16(), details, space_for_files,
            false, true, false);
    layout->AddView(issue_advice_view);
  }

  DCHECK(prompt.type() >= 0);
  UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
                            prompt.type(),
                            ExtensionInstallPrompt::NUM_PROMPT_TYPES);

  if (prompt.ShouldShowPermissions()) {
    if (prompt.ShouldShowExplanationText()) {
      views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
      column_set->AddColumn(views::GridLayout::LEADING,
                            views::GridLayout::FILL,
                            1,
                            views::GridLayout::USE_PREF,
                            0,
                            0);
      // Add two rows of space so that the text stands out.
      layout->AddPaddingRow(0, 2 * views::kRelatedControlVerticalSpacing);

      layout->StartRow(0, column_set_id);
      views::Label* explanation = new views::Label(
          prompt.experiment()->GetExplanationText());
      explanation->SetMultiLine(true);
      explanation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      explanation->SizeToFit(left_column_width + kIconSize);
      layout->AddView(explanation);
    }

    if (prompt.experiment()->should_show_expandable_permission_list() ||
        (prompt.experiment()->show_details_link() &&
            prompt.experiment()->should_show_inline_explanations() &&
            !inline_explanations_.empty())) {
      // Don't show the "Show details" link if there are OAuth issues or
      // retained files. These have their own "Show details" links and having
      // multiple levels of links is confusing.
      if (prompt.GetOAuthIssueCount() + prompt.GetRetainedFileCount() == 0) {
        int text_id =
            prompt.experiment()->should_show_expandable_permission_list() ?
            IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_PERMISSIONS :
            IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_DETAILS;
        show_details_link_ = new views::Link(
            l10n_util::GetStringUTF16(text_id));
        show_details_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
        show_details_link_->set_listener(this);
        UpdateLinkActionHistogram(LINK_SHOWN);
      } else {
        UpdateLinkActionHistogram(LINK_NOT_SHOWN);
      }
    }

    if (prompt.experiment()->show_checkboxes()) {
      checkbox_info_label_ = new views::Label(
          l10n_util::GetStringUTF16(
              IDS_EXTENSION_PROMPT_EXPERIMENT_CHECKBOX_INFO));
      checkbox_info_label_->SetMultiLine(true);
      checkbox_info_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      checkbox_info_label_->SetAutoColorReadabilityEnabled(false);
      checkbox_info_label_->SetEnabledColor(kLighterLabelColor);
    }
  }

  gfx::Size scrollable_size = scrollable_->GetPreferredSize();
  scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
  dialog_size_ = gfx::Size(
      dialog_width,
      std::min(scrollable_size.height(), kDialogMaxHeight));

  if (scrollable_header_only_) {
    gfx::Size header_only_size = scrollable_header_only_->GetPreferredSize();
    scrollable_header_only_->SetBoundsRect(gfx::Rect(header_only_size));
    dialog_size_ = gfx::Size(
        dialog_width, std::min(header_only_size.height(), kDialogMaxHeight));
  }
}

ExtensionInstallDialogView::~ExtensionInstallDialogView() {}

views::GridLayout* ExtensionInstallDialogView::CreateLayout(
    views::View* parent,
    int left_column_width,
    int column_set_id,
    bool single_detail_row) const {
  views::GridLayout* layout = views::GridLayout::CreatePanel(parent);
  parent->SetLayoutManager(layout);

  views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
  column_set->AddColumn(views::GridLayout::LEADING,
                        views::GridLayout::FILL,
                        0,  // no resizing
                        views::GridLayout::USE_PREF,
                        0,  // no fixed width
                        left_column_width);
  if (!is_bundle_install()) {
    column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
    column_set->AddColumn(views::GridLayout::TRAILING,
                          views::GridLayout::LEADING,
                          0,  // no resizing
                          views::GridLayout::USE_PREF,
                          0,  // no fixed width
                          kIconSize);
  }

  layout->StartRow(0, column_set_id);

  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

  views::Label* heading = new views::Label(
      prompt_.GetHeading(), rb.GetFontList(ui::ResourceBundle::MediumFont));
  heading->SetMultiLine(true);
  heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  heading->SizeToFit(left_column_width);
  layout->AddView(heading);

  if (!is_bundle_install()) {
    // Scale down to icon size, but allow smaller icons (don't scale up).
    const gfx::ImageSkia* image = prompt_.icon().ToImageSkia();
    gfx::Size size(image->width(), image->height());
    if (size.width() > kIconSize || size.height() > kIconSize)
      size = gfx::Size(kIconSize, kIconSize);
    views::ImageView* icon = new views::ImageView();
    icon->SetImageSize(size);
    icon->SetImage(*image);
    icon->SetHorizontalAlignment(views::ImageView::CENTER);
    icon->SetVerticalAlignment(views::ImageView::CENTER);
    if (single_detail_row) {
      layout->AddView(icon);
    } else {
      int icon_row_span = 1;
      if (is_inline_install()) {
        // Also span the rating, user_count and store_link rows.
        icon_row_span = 4;
      } else if (prompt_.ShouldShowPermissions()) {
        size_t permission_count = prompt_.GetPermissionCount();
        // Also span the permission header and each of the permission rows (all
        // have a padding row above it). This also works for the 'no special
        // permissions' case.
        icon_row_span = 3 + permission_count * 2;
      } else if (prompt_.GetOAuthIssueCount()) {
        // Also span the permission header and each of the permission rows (all
        // have a padding row above it).
        icon_row_span = 3 + prompt_.GetOAuthIssueCount() * 2;
      } else if (prompt_.GetRetainedFileCount()) {
        // Also span the permission header and the retained files container.
        icon_row_span = 4;
      }
      layout->AddView(icon, 1, icon_row_span);
    }
  }
  return layout;
}

void ExtensionInstallDialogView::ContentsChanged() {
  Layout();
}

void ExtensionInstallDialogView::ViewHierarchyChanged(
    const ViewHierarchyChangedDetails& details) {
  // Since we want the links to show up in the same visual row as the accept
  // and cancel buttons, which is provided by the framework, we must add the
  // buttons to the non-client view, which is the parent of this view.
  // Similarly, when we're removed from the view hierarchy, we must take care
  // to clean up those items as well.
  if (details.child == this) {
    if (details.is_add) {
      if (show_details_link_)
        details.parent->AddChildView(show_details_link_);
      if (checkbox_info_label_)
        details.parent->AddChildView(checkbox_info_label_);
    } else {
      if (show_details_link_)
        details.parent->RemoveChildView(show_details_link_);
      if (checkbox_info_label_)
        details.parent->RemoveChildView(checkbox_info_label_);
    }
  }
}

int ExtensionInstallDialogView::GetDialogButtons() const {
  int buttons = prompt_.GetDialogButtons();
  // Simply having just an OK button is *not* supported. See comment on function
  // GetDialogButtons in dialog_delegate.h for reasons.
  DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
  return buttons;
}

base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
    ui::DialogButton button) const {
  switch (button) {
    case ui::DIALOG_BUTTON_OK:
      return prompt_.GetAcceptButtonLabel();
    case ui::DIALOG_BUTTON_CANCEL:
      return prompt_.HasAbortButtonLabel() ?
          prompt_.GetAbortButtonLabel() :
          l10n_util::GetStringUTF16(IDS_CANCEL);
    default:
      NOTREACHED();
      return base::string16();
  }
}

int ExtensionInstallDialogView::GetDefaultDialogButton() const {
  return ui::DIALOG_BUTTON_CANCEL;
}

bool ExtensionInstallDialogView::Cancel() {
  UpdateInstallResultHistogram(false);
  delegate_->InstallUIAbort(true);
  return true;
}

bool ExtensionInstallDialogView::Accept() {
  UpdateInstallResultHistogram(true);
  delegate_->InstallUIProceed();
  return true;
}

ui::ModalType ExtensionInstallDialogView::GetModalType() const {
  return ui::MODAL_TYPE_WINDOW;
}

base::string16 ExtensionInstallDialogView::GetWindowTitle() const {
  return prompt_.GetDialogTitle();
}

void ExtensionInstallDialogView::LinkClicked(views::Link* source,
                                             int event_flags) {
  if (source == show_details_link_) {
    UpdateLinkActionHistogram(LINK_CLICKED);
    // Show details link is used to either reveal whole permission list or to
    // reveal inline explanations.
    if (prompt_.experiment()->should_show_expandable_permission_list()) {
      gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
      int spacing = bounds.height() -
          scrollable_header_only_->GetPreferredSize().height();
      int content_height = std::min(scrollable_->GetPreferredSize().height(),
                                    kDialogMaxHeight);
      bounds.set_height(spacing + content_height);
      scroll_view_->SetContents(scrollable_);
      GetWidget()->SetBoundsConstrained(bounds);
      ContentsChanged();
    } else {
      ToggleInlineExplanations();
    }
    show_details_link_->SetVisible(false);
  } else {
    GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
                   prompt_.extension()->id());
    OpenURLParams params(
        store_url, Referrer(), NEW_FOREGROUND_TAB,
        content::PAGE_TRANSITION_LINK,
        false);
    navigator_->OpenURL(params);
    GetWidget()->Close();
  }
}

void ExtensionInstallDialogView::ToggleInlineExplanations() {
  for (InlineExplanations::iterator it = inline_explanations_.begin();
      it != inline_explanations_.end(); ++it)
    (*it)->ToggleDetailLevel();
}

void ExtensionInstallDialogView::Layout() {
  scroll_view_->SetBounds(0, 0, width(), height());

  if (show_details_link_ || checkbox_info_label_) {
    views::LabelButton* cancel_button = GetDialogClientView()->cancel_button();
    gfx::Rect parent_bounds = parent()->GetContentsBounds();
    // By default, layouts have an inset of kButtonHEdgeMarginNew. In order to
    // align the link horizontally with the left side of the contents of the
    // layout, put a horizontal margin with this amount.
    const int horizontal_margin = views::kButtonHEdgeMarginNew;
    const int vertical_margin = views::kButtonVEdgeMarginNew;
    int y_buttons = parent_bounds.bottom() -
        cancel_button->GetPreferredSize().height() - vertical_margin;
    int max_width = dialog_size_.width() - cancel_button->width() * 2 -
        horizontal_margin * 2 - views::kRelatedButtonHSpacing;
    if (show_details_link_) {
      gfx::Size link_size = show_details_link_->GetPreferredSize();
      show_details_link_->SetBounds(
          horizontal_margin,
          y_buttons + (cancel_button->height() - link_size.height()) / 2,
          link_size.width(), link_size.height());
    }
    if (checkbox_info_label_) {
      gfx::Size label_size = checkbox_info_label_->GetPreferredSize();
      checkbox_info_label_->SetBounds(
          horizontal_margin,
          y_buttons + (cancel_button->height() - label_size.height()) / 2,
          label_size.width(), label_size.height());
      checkbox_info_label_->SizeToFit(max_width);
    }
  }
  // Disable accept button if there are unchecked boxes and
  // the experiment is on.
  if (prompt_.experiment()->show_checkboxes())
    GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);

  DialogDelegateView::Layout();
}

gfx::Size ExtensionInstallDialogView::GetPreferredSize() {
  return dialog_size_;
}

void ExtensionInstallDialogView::ButtonPressed(views::Button* sender,
                                               const ui::Event& event) {
  if (std::string(views::Checkbox::kViewClassName) == sender->GetClassName()) {
    views::Checkbox* checkbox = static_cast<views::Checkbox*>(sender);
    if (checkbox->checked())
      --unchecked_boxes_;
    else
      ++unchecked_boxes_;

    GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
    checkbox_info_label_->SetVisible(unchecked_boxes_ > 0);
  }
}

void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
    const {
  if (prompt_.type() == ExtensionInstallPrompt::INSTALL_PROMPT)
    UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted);
}

void ExtensionInstallDialogView::UpdateLinkActionHistogram(int action_type)
    const {
  if (prompt_.experiment()->should_show_expandable_permission_list()) {
    // The clickable link in the UI is "Show Permissions".
    UMA_HISTOGRAM_ENUMERATION(
        "Extensions.InstallPromptExperiment.ShowPermissions",
        action_type,
        NUM_LINK_ACTIONS);
  } else {
    // The clickable link in the UI is "Show Details".
    UMA_HISTOGRAM_ENUMERATION(
        "Extensions.InstallPromptExperiment.ShowDetails",
        action_type,
        NUM_LINK_ACTIONS);
  }
}

// static
ExtensionInstallPrompt::ShowDialogCallback
ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
  return base::Bind(&ShowExtensionInstallDialogImpl);
}

// ExpandableContainerView::DetailsView ----------------------------------------

ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
                                                  bool parent_bulleted,
                                                  bool lighter_color)
    : layout_(new views::GridLayout(this)),
      state_(0),
      lighter_color_(lighter_color) {
  SetLayoutManager(layout_);
  views::ColumnSet* column_set = layout_->AddColumnSet(0);
  // If the parent is using bullets for its items, then a padding of one unit
  // will make the child item (which has no bullet) look like a sibling of its
  // parent. Therefore increase the indentation by one more unit to show that it
  // is in fact a child item (with no missing bullet) and not a sibling.
  int padding =
      views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1);
  column_set->AddPaddingColumn(0, padding);
  column_set->AddColumn(views::GridLayout::LEADING,
                        views::GridLayout::LEADING,
                        0,
                        views::GridLayout::FIXED,
                        horizontal_space - padding,
                        0);
}

void ExpandableContainerView::DetailsView::AddDetail(
    const base::string16& detail) {
  layout_->StartRowWithPadding(0, 0,
                               0, views::kRelatedControlSmallVerticalSpacing);
  views::Label* detail_label =
      new views::Label(PrepareForDisplay(detail, false));
  detail_label->SetMultiLine(true);
  detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  if (lighter_color_) {
    detail_label->SetEnabledColor(kLighterLabelColor);
    detail_label->SetAutoColorReadabilityEnabled(false);
  }
  layout_->AddView(detail_label);
}

gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() {
  gfx::Size size = views::View::GetPreferredSize();
  return gfx::Size(size.width(), size.height() * state_);
}

void ExpandableContainerView::DetailsView::AnimateToState(double state) {
  state_ = state;
  PreferredSizeChanged();
  SchedulePaint();
}

// ExpandableContainerView -----------------------------------------------------

ExpandableContainerView::ExpandableContainerView(
    ExtensionInstallDialogView* owner,
    const base::string16& description,
    const PermissionDetails& details,
    int horizontal_space,
    bool parent_bulleted,
    bool show_expand_link,
    bool lighter_color_details)
    : owner_(owner),
      details_view_(NULL),
      more_details_(NULL),
      slide_animation_(this),
      arrow_toggle_(NULL),
      expanded_(false) {
  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);
  int column_set_id = 0;
  views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
  column_set->AddColumn(views::GridLayout::LEADING,
                        views::GridLayout::LEADING,
                        0,
                        views::GridLayout::USE_PREF,
                        0,
                        0);
  if (!description.empty()) {
    layout->StartRow(0, column_set_id);

    views::Label* description_label = new views::Label(description);
    description_label->SetMultiLine(true);
    description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    description_label->SizeToFit(horizontal_space);
    layout->AddView(new BulletedView(description_label));
  }

  if (details.empty())
    return;

  details_view_ = new DetailsView(horizontal_space, parent_bulleted,
                                  lighter_color_details);

  layout->StartRow(0, column_set_id);
  layout->AddView(details_view_);

  for (size_t i = 0; i < details.size(); ++i)
    details_view_->AddDetail(details[i]);

  // TODO(meacer): Remove show_expand_link when the experiment is completed.
  if (show_expand_link) {
    views::Link* link = new views::Link(
        l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));

    // Make sure the link width column is as wide as needed for both Show and
    // Hide details, so that the arrow doesn't shift horizontally when we
    // toggle.
    int link_col_width =
        views::kRelatedControlHorizontalSpacing +
        std::max(gfx::GetStringWidth(
                     l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS),
                     link->font_list()),
                 gfx::GetStringWidth(
                     l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS),
                     link->font_list()));

    column_set = layout->AddColumnSet(++column_set_id);
    // Padding to the left of the More Details column. If the parent is using
    // bullets for its items, then a padding of one unit will make the child
    // item (which has no bullet) look like a sibling of its parent. Therefore
    // increase the indentation by one more unit to show that it is in fact a
    // child item (with no missing bullet) and not a sibling.
    column_set->AddPaddingColumn(
        0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1));
    // The More Details column.
    column_set->AddColumn(views::GridLayout::LEADING,
                          views::GridLayout::LEADING,
                          0,
                          views::GridLayout::FIXED,
                          link_col_width,
                          link_col_width);
    // The Up/Down arrow column.
    column_set->AddColumn(views::GridLayout::LEADING,
                          views::GridLayout::LEADING,
                          0,
                          views::GridLayout::USE_PREF,
                          0,
                          0);

    // Add the More Details link.
    layout->StartRow(0, column_set_id);
    more_details_ = link;
    more_details_->set_listener(this);
    more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    layout->AddView(more_details_);

    // Add the arrow after the More Details link.
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    arrow_toggle_ = new views::ImageButton(this);
    arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
                            rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
    layout->AddView(arrow_toggle_);
  }
}

ExpandableContainerView::~ExpandableContainerView() {
}

void ExpandableContainerView::ButtonPressed(
    views::Button* sender, const ui::Event& event) {
  ToggleDetailLevel();
}

void ExpandableContainerView::LinkClicked(
    views::Link* source, int event_flags) {
  ToggleDetailLevel();
}

void ExpandableContainerView::AnimationProgressed(
    const gfx::Animation* animation) {
  DCHECK_EQ(&slide_animation_, animation);
  if (details_view_)
    details_view_->AnimateToState(animation->GetCurrentValue());
}

void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
  if (arrow_toggle_) {
    if (animation->GetCurrentValue() != 0.0) {
      arrow_toggle_->SetImage(
          views::Button::STATE_NORMAL,
          ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
              IDR_UP_ARROW));
    } else {
      arrow_toggle_->SetImage(
          views::Button::STATE_NORMAL,
          ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
              IDR_DOWN_ARROW));
    }
  }
  if (more_details_) {
    more_details_->SetText(expanded_ ?
        l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
        l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
  }
}

void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
  owner_->ContentsChanged();
}

void ExpandableContainerView::ToggleDetailLevel() {
  expanded_ = !expanded_;

  if (slide_animation_.IsShowing())
    slide_animation_.Hide();
  else
    slide_animation_.Show();
}

void ExpandableContainerView::ExpandWithoutAnimation() {
  expanded_ = true;
  details_view_->AnimateToState(1.0);
}

/* [<][>][^][v][top][bottom][index][help] */