1 // Copyright (c) 2012 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/tabs/tab_utils.h"
7 #include "base/command_line.h"
8 #include "base/strings/string16.h"
9 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
10 #include "chrome/browser/media/media_stream_capture_indicator.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/common/chrome_switches.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "content/public/browser/web_contents.h"
15 #include "grit/theme_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/gfx/animation/multi_animation.h"
24 // Interval between frame updates of the tab indicator animations. This is not
25 // the usual 60 FPS because a trade-off must be made between tab UI animation
26 // smoothness and media recording/playback performance on low-end hardware.
27 const int kIndicatorFrameIntervalMs = 50; // 20 FPS
29 // Fade-in/out duration for the tab indicator animations. Fade-in is quick to
30 // immediately notify the user. Fade-out is more gradual, so that the user has
31 // a chance of finding a tab that has quickly "blipped" on and off.
32 const int kIndicatorFadeInDurationMs = 200;
33 const int kIndicatorFadeOutDurationMs = 1000;
35 // Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
37 class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
39 virtual ~TabRecordingIndicatorAnimation() {}
41 // Overridden to provide alternating "towards in" and "towards out" behavior.
42 virtual double GetCurrentValue() const OVERRIDE;
44 static scoped_ptr<TabRecordingIndicatorAnimation> Create();
47 TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
48 const base::TimeDelta interval)
49 : MultiAnimation(parts, interval) {}
51 // Number of times to "toggle throb" the recording and tab capture indicators
52 // when they first appear.
53 static const int kCaptureIndicatorThrobCycles = 5;
56 double TabRecordingIndicatorAnimation::GetCurrentValue() const {
57 return current_part_index() % 2 ?
58 1.0 - MultiAnimation::GetCurrentValue() :
59 MultiAnimation::GetCurrentValue();
62 scoped_ptr<TabRecordingIndicatorAnimation>
63 TabRecordingIndicatorAnimation::Create() {
64 MultiAnimation::Parts parts;
65 COMPILE_ASSERT(kCaptureIndicatorThrobCycles % 2 != 0,
66 must_be_odd_so_animation_finishes_in_showing_state);
67 for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) {
68 parts.push_back(MultiAnimation::Part(
69 i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs,
70 gfx::Tween::EASE_IN));
72 const base::TimeDelta interval =
73 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
74 scoped_ptr<TabRecordingIndicatorAnimation> animation(
75 new TabRecordingIndicatorAnimation(parts, interval));
76 animation->set_continuous(false);
77 return animation.Pass();
82 bool ShouldTabShowFavicon(int capacity,
86 TabMediaState media_state) {
89 int required_capacity = 1;
90 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
92 if (ShouldTabShowMediaIndicator(
93 capacity, is_pinned_tab, is_active_tab, has_favicon, media_state)) {
96 return capacity >= required_capacity;
99 bool ShouldTabShowMediaIndicator(int capacity,
103 TabMediaState media_state) {
104 if (media_state == TAB_MEDIA_STATE_NONE)
106 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
107 return capacity >= 2;
108 return capacity >= 1;
111 bool ShouldTabShowCloseButton(int capacity,
113 bool is_active_tab) {
116 else if (is_active_tab)
119 return capacity >= 3;
122 bool IsPlayingAudio(content::WebContents* contents) {
123 return contents->WasRecentlyAudible();
126 TabMediaState GetTabMediaStateForContents(content::WebContents* contents) {
128 return TAB_MEDIA_STATE_NONE;
130 scoped_refptr<MediaStreamCaptureIndicator> indicator =
131 MediaCaptureDevicesDispatcher::GetInstance()->
132 GetMediaStreamCaptureIndicator();
133 if (indicator.get()) {
134 if (indicator->IsBeingMirrored(contents))
135 return TAB_MEDIA_STATE_CAPTURING;
136 if (indicator->IsCapturingUserMedia(contents))
137 return TAB_MEDIA_STATE_RECORDING;
140 if (IsTabAudioMutingFeatureEnabled() && contents->IsAudioMuted())
141 return TAB_MEDIA_STATE_AUDIO_MUTING;
142 if (IsPlayingAudio(contents))
143 return TAB_MEDIA_STATE_AUDIO_PLAYING;
145 return TAB_MEDIA_STATE_NONE;
148 const gfx::Image& GetTabMediaIndicatorImage(TabMediaState media_state) {
149 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
150 switch (media_state) {
151 case TAB_MEDIA_STATE_AUDIO_PLAYING:
152 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_INDICATOR);
153 case TAB_MEDIA_STATE_AUDIO_MUTING:
154 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_MUTING_INDICATOR);
155 case TAB_MEDIA_STATE_RECORDING:
156 return rb.GetNativeImageNamed(IDR_TAB_RECORDING_INDICATOR);
157 case TAB_MEDIA_STATE_CAPTURING:
158 return rb.GetNativeImageNamed(IDR_TAB_CAPTURE_INDICATOR);
159 case TAB_MEDIA_STATE_NONE:
163 return rb.GetNativeImageNamed(IDR_SAD_FAVICON);
166 const gfx::Image& GetTabMediaIndicatorAffordanceImage(
167 TabMediaState media_state) {
168 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
169 switch (media_state) {
170 case TAB_MEDIA_STATE_AUDIO_PLAYING:
171 case TAB_MEDIA_STATE_AUDIO_MUTING:
172 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_MUTING_AFFORDANCE);
173 case TAB_MEDIA_STATE_NONE:
174 case TAB_MEDIA_STATE_RECORDING:
175 case TAB_MEDIA_STATE_CAPTURING:
176 return GetTabMediaIndicatorImage(media_state);
179 return GetTabMediaIndicatorImage(media_state);
182 scoped_ptr<gfx::Animation> CreateTabMediaIndicatorFadeAnimation(
183 TabMediaState media_state) {
184 if (media_state == TAB_MEDIA_STATE_RECORDING ||
185 media_state == TAB_MEDIA_STATE_CAPTURING) {
186 return TabRecordingIndicatorAnimation::Create().PassAs<gfx::Animation>();
189 // Note: While it seems silly to use a one-part MultiAnimation, it's the only
190 // gfx::Animation implementation that lets us control the frame interval.
191 gfx::MultiAnimation::Parts parts;
192 const bool is_for_fade_in = (media_state != TAB_MEDIA_STATE_NONE);
193 parts.push_back(gfx::MultiAnimation::Part(
194 is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs,
195 gfx::Tween::EASE_IN));
196 const base::TimeDelta interval =
197 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
198 scoped_ptr<gfx::MultiAnimation> animation(
199 new gfx::MultiAnimation(parts, interval));
200 animation->set_continuous(false);
201 return animation.PassAs<gfx::Animation>();
204 base::string16 AssembleTabTooltipText(const base::string16& title,
205 TabMediaState media_state) {
206 if (media_state == TAB_MEDIA_STATE_NONE)
209 base::string16 result = title;
211 result.append(1, '\n');
212 switch (media_state) {
213 case TAB_MEDIA_STATE_AUDIO_PLAYING:
215 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_PLAYING));
217 case TAB_MEDIA_STATE_AUDIO_MUTING:
219 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_MUTING));
221 case TAB_MEDIA_STATE_RECORDING:
223 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_RECORDING));
225 case TAB_MEDIA_STATE_CAPTURING:
227 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_CAPTURING));
229 case TAB_MEDIA_STATE_NONE:
236 bool IsTabAudioMutingFeatureEnabled() {
237 #if defined(USE_AURA)
238 return base::CommandLine::ForCurrentProcess()->HasSwitch(
239 switches::kEnableTabAudioMuting);
245 bool CanToggleAudioMute(content::WebContents* contents) {
246 switch (GetTabMediaStateForContents(contents)) {
247 case TAB_MEDIA_STATE_NONE:
248 case TAB_MEDIA_STATE_AUDIO_PLAYING:
249 case TAB_MEDIA_STATE_AUDIO_MUTING:
250 return IsTabAudioMutingFeatureEnabled();
251 case TAB_MEDIA_STATE_RECORDING:
252 case TAB_MEDIA_STATE_CAPTURING:
259 void SetTabAudioMuted(content::WebContents* contents, bool mute) {
260 if (!contents || !chrome::CanToggleAudioMute(contents))
262 contents->SetAudioMuted(mute);
265 bool IsTabAudioMuted(content::WebContents* contents) {
266 return contents && contents->IsAudioMuted();
269 bool AreAllTabsMuted(const TabStripModel& tab_strip,
270 const std::vector<int>& indices) {
271 for (std::vector<int>::const_iterator i = indices.begin(); i != indices.end();
273 if (!IsTabAudioMuted(tab_strip.GetWebContentsAt(*i)))
279 } // namespace chrome