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.
5 #include "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h"
7 #include "apps/saved_files_service.h"
8 #include "base/files/file_path.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "extensions/common/extension.h"
12 #include "extensions/common/permissions/api_permission.h"
13 #include "extensions/common/permissions/permission_message_provider.h"
14 #include "extensions/common/permissions/permissions_data.h"
15 #include "grit/generated_resources.h"
16 #include "grit/theme_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/animation/animation.h"
20 #include "ui/gfx/animation/animation_delegate.h"
21 #include "ui/gfx/animation/slide_animation.h"
22 #include "ui/gfx/text_constants.h"
23 #include "ui/views/controls/button/button.h"
24 #include "ui/views/controls/button/image_button.h"
25 #include "ui/views/controls/label.h"
26 #include "ui/views/controls/scroll_view.h"
27 #include "ui/views/layout/box_layout.h"
28 #include "ui/views/layout/fill_layout.h"
29 #include "ui/views/layout/grid_layout.h"
30 #include "ui/views/layout/layout_constants.h"
34 // A view to display a title with an expandable permissions list section.
35 class ExpandableContainerView : public views::View,
36 public views::ButtonListener,
37 public gfx::AnimationDelegate {
39 ExpandableContainerView(
41 const base::string16& title,
42 const std::vector<base::string16>& permission_messages);
43 virtual ~ExpandableContainerView();
46 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
48 // views::ButtonListener:
49 virtual void ButtonPressed(views::Button* sender,
50 const ui::Event& event) OVERRIDE;
52 // gfx::AnimationDelegate:
53 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
54 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
56 // Expand/Collapse the detail section for this ExpandableContainerView.
57 void ToggleDetailLevel();
60 // A view which displays the permission messages as a bulleted list.
61 class DetailsView : public views::View {
63 explicit DetailsView(std::vector<base::string16> messages);
64 virtual ~DetailsView() {}
67 virtual gfx::Size GetPreferredSize() OVERRIDE;
69 // Animates this to be a height proportional to |ratio|.
70 void AnimateToRatio(double ratio);
73 // The current state of the animation, as a decimal from 0 to 1 (0 is fully
74 // collapsed, 1 is fully expanded).
75 double visible_ratio_;
77 DISALLOW_COPY_AND_ASSIGN(DetailsView);
80 // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
83 // A view for showing |permission_messages|.
84 DetailsView* details_view_;
86 gfx::SlideAnimation slide_animation_;
88 // The up/down arrow next to the heading (points up/down depending on whether
89 // the details section is expanded).
90 views::ImageButton* arrow_toggle_;
92 // Whether the details section is expanded.
95 DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView);
98 ExpandableContainerView::DetailsView::DetailsView(
99 std::vector<base::string16> messages)
100 : visible_ratio_(0) {
101 views::GridLayout* layout = views::GridLayout::CreatePanel(this);
102 SetLayoutManager(layout);
104 // Create 2 columns: one for the bullet, one for the bullet text.
105 static const int kColumnSet = 1;
106 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSet);
107 column_set->AddPaddingColumn(0, 10);
108 column_set->AddColumn(views::GridLayout::LEADING,
109 views::GridLayout::LEADING,
111 views::GridLayout::USE_PREF,
114 column_set->AddPaddingColumn(0, 5);
115 column_set->AddColumn(views::GridLayout::LEADING,
116 views::GridLayout::LEADING,
118 views::GridLayout::USE_PREF,
122 // Add permissions to scrollable view.
123 for (std::vector<base::string16>::const_iterator it = messages.begin();
124 it != messages.end();
126 views::Label* permission_label = new views::Label(*it);
128 permission_label->SetMultiLine(true);
129 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
131 layout->StartRow(0, kColumnSet);
132 // Extract only the bullet from the IDS_EXTENSION_PERMISSION_LINE text.
133 layout->AddView(new views::Label(l10n_util::GetStringFUTF16(
134 IDS_EXTENSION_PERMISSION_LINE, base::string16())));
135 // Place the text second, so multi-lined permissions line up below the
137 layout->AddView(permission_label);
139 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
143 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() {
144 gfx::Size size = views::View::GetPreferredSize();
145 return gfx::Size(size.width(), size.height() * visible_ratio_);
148 void ExpandableContainerView::DetailsView::AnimateToRatio(double ratio) {
149 visible_ratio_ = ratio;
150 PreferredSizeChanged();
154 ExpandableContainerView::ExpandableContainerView(
156 const base::string16& title,
157 const std::vector<base::string16>& permission_messages)
160 slide_animation_(this),
163 views::GridLayout* layout = new views::GridLayout(this);
164 SetLayoutManager(layout);
165 const int kMainColumnSetId = 0;
166 views::ColumnSet* column_set = layout->AddColumnSet(kMainColumnSetId);
167 column_set->AddColumn(views::GridLayout::LEADING,
168 views::GridLayout::LEADING,
170 views::GridLayout::USE_PREF,
174 // A column set that is split in half, to allow for the expand/collapse button
175 // image to be aligned to the right of the view.
176 const int kSplitColumnSetId = 1;
177 views::ColumnSet* split_column_set = layout->AddColumnSet(kSplitColumnSetId);
178 split_column_set->AddColumn(views::GridLayout::LEADING,
179 views::GridLayout::LEADING,
181 views::GridLayout::USE_PREF,
184 split_column_set->AddPaddingColumn(0,
185 views::kRelatedControlHorizontalSpacing);
186 split_column_set->AddColumn(views::GridLayout::TRAILING,
187 views::GridLayout::LEADING,
189 views::GridLayout::USE_PREF,
193 // To display the heading and count next to each other, create a sub-view
194 // with a box layout that stacks them horizontally.
195 views::View* title_view = new views::View();
196 title_view->SetLayoutManager(
197 new views::BoxLayout(views::BoxLayout::kHorizontal,
200 views::kRelatedControlSmallHorizontalSpacing));
202 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
204 // Format the title as 'Title - number'. Unfortunately, this needs to be in
205 // separate views because labels only support a single font per view.
206 title_view->AddChildView(
207 new views::Label(title, rb.GetFontList(ui::ResourceBundle::BoldFont)));
208 title_view->AddChildView(
209 new views::Label(base::UTF8ToUTF16("\xe2\x80\x93"))); // En-dash.
210 title_view->AddChildView(
211 new views::Label(base::IntToString16(permission_messages.size())));
213 arrow_toggle_ = new views::ImageButton(this);
214 arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
215 rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
217 layout->StartRow(0, kSplitColumnSetId);
218 layout->AddView(title_view);
219 layout->AddView(arrow_toggle_);
221 details_view_ = new DetailsView(permission_messages);
222 layout->StartRow(0, kMainColumnSetId);
223 layout->AddView(details_view_);
226 ExpandableContainerView::~ExpandableContainerView() {
229 void ExpandableContainerView::ButtonPressed(views::Button* sender,
230 const ui::Event& event) {
234 void ExpandableContainerView::AnimationProgressed(
235 const gfx::Animation* animation) {
236 DCHECK_EQ(&slide_animation_, animation);
238 details_view_->AnimateToRatio(animation->GetCurrentValue());
242 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
244 if (animation->GetCurrentValue() != 0.0) {
245 arrow_toggle_->SetImage(
246 views::Button::STATE_NORMAL,
247 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
250 arrow_toggle_->SetImage(
251 views::Button::STATE_NORMAL,
252 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
258 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
262 void ExpandableContainerView::ToggleDetailLevel() {
263 expanded_ = !expanded_;
265 if (slide_animation_.IsShowing())
266 slide_animation_.Hide();
268 slide_animation_.Show();
273 AppInfoPermissionsTab::AppInfoPermissionsTab(
274 gfx::NativeWindow parent_window,
276 const extensions::Extension* app,
277 const base::Closure& close_callback)
278 : AppInfoTab(parent_window, profile, app, close_callback) {
279 this->SetLayoutManager(new views::FillLayout);
281 // Create a scrollview and add it to the tab.
282 views::View* scrollable_content = new views::View();
283 scroll_view_ = new views::ScrollView();
284 scroll_view_->SetContents(scrollable_content);
285 AddChildView(scroll_view_);
287 // Give the inner scrollview (the 'scrollable' part) a layout.
288 views::GridLayout* layout =
289 views::GridLayout::CreatePanel(scrollable_content);
290 scrollable_content->SetLayoutManager(layout);
292 // Main column that stretches to the width of the view.
293 static const int kMainColumnSetId = 0;
294 views::ColumnSet* main_column_set = layout->AddColumnSet(kMainColumnSetId);
295 main_column_set->AddColumn(
296 views::GridLayout::FILL,
297 views::GridLayout::FILL,
298 1, // This column resizes to the width of the dialog.
299 views::GridLayout::USE_PREF,
303 const std::vector<base::string16> required_permission_messages =
304 GetRequiredPermissionMessages();
305 const std::vector<base::string16> optional_permission_messages =
306 GetOptionalPermissionMessages();
307 const std::vector<base::string16> retained_file_permission_messages =
308 GetRetainedFilePermissionMessages();
310 if (required_permission_messages.empty() &&
311 optional_permission_messages.empty() &&
312 retained_file_permission_messages.empty()) {
313 // If there are no permissions at all, display an appropriate message.
314 views::Label* no_permissions_text = new views::Label(
315 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_NO_PERMISSIONS_TEXT));
316 no_permissions_text->SetHorizontalAlignment(gfx::ALIGN_LEFT);
318 layout->StartRow(0, kMainColumnSetId);
319 layout->AddView(no_permissions_text);
321 if (!required_permission_messages.empty()) {
322 ExpandableContainerView* details_container = new ExpandableContainerView(
324 l10n_util::GetStringUTF16(
325 IDS_APPLICATION_INFO_REQUIRED_PERMISSIONS_TEXT),
326 required_permission_messages);
327 // Required permissions are visible by default.
328 details_container->ToggleDetailLevel();
330 layout->StartRow(0, kMainColumnSetId);
331 layout->AddView(details_container);
332 layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing);
335 if (!optional_permission_messages.empty()) {
336 ExpandableContainerView* details_container = new ExpandableContainerView(
338 l10n_util::GetStringUTF16(
339 IDS_APPLICATION_INFO_OPTIONAL_PERMISSIONS_TEXT),
340 optional_permission_messages);
342 layout->StartRow(0, kMainColumnSetId);
343 layout->AddView(details_container);
344 layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing);
347 if (!retained_file_permission_messages.empty()) {
348 ExpandableContainerView* details_container = new ExpandableContainerView(
350 l10n_util::GetStringUTF16(
351 IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT),
352 retained_file_permission_messages);
354 layout->StartRow(0, kMainColumnSetId);
355 layout->AddView(details_container);
356 layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing);
361 AppInfoPermissionsTab::~AppInfoPermissionsTab() {
364 void AppInfoPermissionsTab::Layout() {
365 // To avoid 'jumping' issues when the scrollbar becomes visible, size the
366 // scrollable area as though it always has a visible scrollbar.
367 views::View* contents_view = scroll_view_->contents();
368 int content_width = width() - scroll_view_->GetScrollBarWidth();
369 int content_height = contents_view->GetHeightForWidth(content_width);
370 contents_view->SetBounds(0, 0, content_width, content_height);
371 scroll_view_->SetBounds(0, 0, width(), height());
374 const extensions::PermissionSet* AppInfoPermissionsTab::GetRequiredPermissions()
376 return extensions::PermissionsData::GetRequiredPermissions(app_);
379 const std::vector<base::string16>
380 AppInfoPermissionsTab::GetRequiredPermissionMessages() const {
381 return extensions::PermissionMessageProvider::Get()->GetWarningMessages(
382 GetRequiredPermissions(), app_->GetType());
385 const extensions::PermissionSet* AppInfoPermissionsTab::GetOptionalPermissions()
387 return extensions::PermissionsData::GetOptionalPermissions(app_);
390 const std::vector<base::string16>
391 AppInfoPermissionsTab::GetOptionalPermissionMessages() const {
392 return extensions::PermissionMessageProvider::Get()->GetWarningMessages(
393 GetOptionalPermissions(), app_->GetType());
396 const std::vector<base::FilePath>
397 AppInfoPermissionsTab::GetRetainedFilePermissions() const {
398 std::vector<base::FilePath> retained_file_paths;
399 if (app_->HasAPIPermission(extensions::APIPermission::kFileSystem)) {
400 std::vector<apps::SavedFileEntry> retained_file_entries =
401 apps::SavedFilesService::Get(profile_)->GetAllFileEntries(app_->id());
402 for (std::vector<apps::SavedFileEntry>::const_iterator it =
403 retained_file_entries.begin();
404 it != retained_file_entries.end();
406 retained_file_paths.push_back(it->path);
409 return retained_file_paths;
412 const std::vector<base::string16>
413 AppInfoPermissionsTab::GetRetainedFilePermissionMessages() const {
414 const std::vector<base::FilePath> permissions = GetRetainedFilePermissions();
415 std::vector<base::string16> file_permission_messages;
416 for (std::vector<base::FilePath>::const_iterator it = permissions.begin();
417 it != permissions.end();
419 file_permission_messages.push_back(it->LossyDisplayName());
421 return file_permission_messages;