1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/system/cast/tray_cast.h"
6 
7 #include <map>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "ash/metrics/user_metrics_recorder.h"
13 #include "ash/public/cpp/ash_view_ids.h"
14 #include "ash/resources/vector_icons/vector_icons.h"
15 #include "ash/shell.h"
16 #include "ash/strings/grit/ash_strings.h"
17 #include "ash/system/tray/hover_highlight_view.h"
18 #include "ash/system/tray/tray_constants.h"
19 #include "ash/system/tray/tray_detailed_view.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "build/branding_buildflags.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/gfx/paint_vector_icon.h"
26 #include "ui/gfx/vector_icon_types.h"
27 #include "ui/views/controls/button/button.h"
28 #include "ui/views/controls/scroll_view.h"
29 
30 namespace ash {
31 
32 namespace {
33 
34 // Returns the correct vector icon for |icon_type|. Some types may be different
35 // for branded builds.
SinkIconTypeToIcon(SinkIconType icon_type)36 const gfx::VectorIcon& SinkIconTypeToIcon(SinkIconType icon_type) {
37   switch (icon_type) {
38 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
39     case SinkIconType::kCast:
40       return kSystemMenuCastDeviceIcon;
41     case SinkIconType::kEducation:
42       return kSystemMenuCastEducationIcon;
43     case SinkIconType::kHangout:
44       return kSystemMenuCastHangoutIcon;
45     case SinkIconType::kMeeting:
46       return kSystemMenuCastMeetingIcon;
47 #else
48     case SinkIconType::kCast:
49     case SinkIconType::kEducation:
50       return kSystemMenuCastGenericIcon;
51     case SinkIconType::kHangout:
52     case SinkIconType::kMeeting:
53       return kSystemMenuCastMessageIcon;
54 #endif
55     case SinkIconType::kGeneric:
56       return kSystemMenuCastGenericIcon;
57     case SinkIconType::kCastAudioGroup:
58       return kSystemMenuCastAudioGroupIcon;
59     case SinkIconType::kCastAudio:
60       return kSystemMenuCastAudioIcon;
61     case SinkIconType::kWiredDisplay:
62       return kSystemMenuCastGenericIcon;
63   }
64 
65   NOTREACHED();
66   return kSystemMenuCastGenericIcon;
67 }
68 
69 }  // namespace
70 
71 namespace tray {
72 
CastDetailedView(DetailedViewDelegate * delegate)73 CastDetailedView::CastDetailedView(DetailedViewDelegate* delegate)
74     : TrayDetailedView(delegate) {
75   CreateItems();
76   OnDevicesUpdated(CastConfigController::Get()->GetSinksAndRoutes());
77   CastConfigController::Get()->AddObserver(this);
78 }
79 
~CastDetailedView()80 CastDetailedView::~CastDetailedView() {
81   CastConfigController::Get()->RemoveObserver(this);
82 }
83 
CreateItems()84 void CastDetailedView::CreateItems() {
85   CreateScrollableList();
86   CreateTitleRow(IDS_ASH_STATUS_TRAY_CAST);
87 }
88 
OnDevicesUpdated(const std::vector<SinkAndRoute> & sinks_routes)89 void CastDetailedView::OnDevicesUpdated(
90     const std::vector<SinkAndRoute>& sinks_routes) {
91   // Add/update existing.
92   for (const auto& device : sinks_routes)
93     sinks_and_routes_.insert(std::make_pair(device.sink.id, device));
94 
95   // Remove non-existent sinks. Removing an element invalidates all existing
96   // iterators.
97   auto iter = sinks_and_routes_.begin();
98   while (iter != sinks_and_routes_.end()) {
99     bool has_receiver = false;
100     for (auto& receiver : sinks_routes) {
101       if (iter->first == receiver.sink.id)
102         has_receiver = true;
103     }
104 
105     if (has_receiver)
106       ++iter;
107     else
108       iter = sinks_and_routes_.erase(iter);
109   }
110 
111   // Update UI.
112   UpdateReceiverListFromCachedData();
113   Layout();
114 }
115 
GetClassName() const116 const char* CastDetailedView::GetClassName() const {
117   return "CastDetailedView";
118 }
119 
UpdateReceiverListFromCachedData()120 void CastDetailedView::UpdateReceiverListFromCachedData() {
121   // Remove all of the existing views.
122   view_to_sink_map_.clear();
123   scroll_content()->RemoveAllChildViews(true);
124 
125   // Add a view for each receiver.
126   for (auto& it : sinks_and_routes_) {
127     const CastSink& sink = it.second.sink;
128     views::View* container = AddScrollListItem(
129         SinkIconTypeToIcon(sink.sink_icon_type), base::UTF8ToUTF16(sink.name));
130     view_to_sink_map_[container] = sink.id;
131   }
132 
133   scroll_content()->SizeToPreferredSize();
134   scroller()->Layout();
135 }
136 
HandleViewClicked(views::View * view)137 void CastDetailedView::HandleViewClicked(views::View* view) {
138   // Find the receiver we are going to cast to.
139   auto it = view_to_sink_map_.find(view);
140   if (it != view_to_sink_map_.end()) {
141     CastConfigController::Get()->CastToSink(it->second);
142     Shell::Get()->metrics()->RecordUserMetricsAction(
143         UMA_STATUS_AREA_DETAILED_CAST_VIEW_LAUNCH_CAST);
144   }
145 }
146 
147 }  // namespace tray
148 }  // namespace ash
149