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"
20 struct LastMuteMetadata
21 : public content::WebContentsUserData
<LastMuteMetadata
> {
22 std::string cause
; // Extension ID or constant from header file
25 explicit LastMuteMetadata(content::WebContents
* contents
) {}
26 friend class content::WebContentsUserData
<LastMuteMetadata
>;
29 DEFINE_WEB_CONTENTS_USER_DATA_KEY(LastMuteMetadata
);
33 const char kMutedToggleCauseUser
[] = "user";
34 const char kMutedToggleCauseCapture
[] = "auto-forced for capture";
38 // Interval between frame updates of the tab indicator animations. This is not
39 // the usual 60 FPS because a trade-off must be made between tab UI animation
40 // smoothness and media recording/playback performance on low-end hardware.
41 const int kIndicatorFrameIntervalMs
= 50; // 20 FPS
43 // Fade-in/out duration for the tab indicator animations. Fade-in is quick to
44 // immediately notify the user. Fade-out is more gradual, so that the user has
45 // a chance of finding a tab that has quickly "blipped" on and off.
46 const int kIndicatorFadeInDurationMs
= 200;
47 const int kIndicatorFadeOutDurationMs
= 1000;
49 // Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
51 class TabRecordingIndicatorAnimation
: public gfx::MultiAnimation
{
53 ~TabRecordingIndicatorAnimation() override
{}
55 // Overridden to provide alternating "towards in" and "towards out" behavior.
56 double GetCurrentValue() const override
;
58 static scoped_ptr
<TabRecordingIndicatorAnimation
> Create();
61 TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts
& parts
,
62 const base::TimeDelta interval
)
63 : MultiAnimation(parts
, interval
) {}
65 // Number of times to "toggle throb" the recording and tab capture indicators
66 // when they first appear.
67 static const int kCaptureIndicatorThrobCycles
= 5;
70 double TabRecordingIndicatorAnimation::GetCurrentValue() const {
71 return current_part_index() % 2 ?
72 1.0 - MultiAnimation::GetCurrentValue() :
73 MultiAnimation::GetCurrentValue();
76 scoped_ptr
<TabRecordingIndicatorAnimation
>
77 TabRecordingIndicatorAnimation::Create() {
78 MultiAnimation::Parts parts
;
79 static_assert(kCaptureIndicatorThrobCycles
% 2 != 0,
80 "odd number of cycles required so animation finishes in showing state");
81 for (int i
= 0; i
< kCaptureIndicatorThrobCycles
; ++i
) {
82 parts
.push_back(MultiAnimation::Part(
83 i
% 2 ? kIndicatorFadeOutDurationMs
: kIndicatorFadeInDurationMs
,
84 gfx::Tween::EASE_IN
));
86 const base::TimeDelta interval
=
87 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs
);
88 scoped_ptr
<TabRecordingIndicatorAnimation
> animation(
89 new TabRecordingIndicatorAnimation(parts
, interval
));
90 animation
->set_continuous(false);
91 return animation
.Pass();
96 bool ShouldTabShowFavicon(int capacity
,
100 TabMediaState media_state
) {
103 int required_capacity
= 1;
104 if (ShouldTabShowCloseButton(capacity
, is_pinned_tab
, is_active_tab
))
106 if (ShouldTabShowMediaIndicator(
107 capacity
, is_pinned_tab
, is_active_tab
, has_favicon
, media_state
)) {
110 return capacity
>= required_capacity
;
113 bool ShouldTabShowMediaIndicator(int capacity
,
117 TabMediaState media_state
) {
118 if (media_state
== TAB_MEDIA_STATE_NONE
)
120 if (ShouldTabShowCloseButton(capacity
, is_pinned_tab
, is_active_tab
))
121 return capacity
>= 2;
122 return capacity
>= 1;
125 bool ShouldTabShowCloseButton(int capacity
,
127 bool is_active_tab
) {
130 else if (is_active_tab
)
133 return capacity
>= 3;
136 bool IsPlayingAudio(content::WebContents
* contents
) {
137 return contents
->WasRecentlyAudible();
140 TabMediaState
GetTabMediaStateForContents(content::WebContents
* contents
) {
142 return TAB_MEDIA_STATE_NONE
;
144 scoped_refptr
<MediaStreamCaptureIndicator
> indicator
=
145 MediaCaptureDevicesDispatcher::GetInstance()->
146 GetMediaStreamCaptureIndicator();
147 if (indicator
.get()) {
148 if (indicator
->IsBeingMirrored(contents
))
149 return TAB_MEDIA_STATE_CAPTURING
;
150 if (indicator
->IsCapturingUserMedia(contents
))
151 return TAB_MEDIA_STATE_RECORDING
;
154 if (IsTabAudioMutingFeatureEnabled() && contents
->IsAudioMuted())
155 return TAB_MEDIA_STATE_AUDIO_MUTING
;
156 if (IsPlayingAudio(contents
))
157 return TAB_MEDIA_STATE_AUDIO_PLAYING
;
159 return TAB_MEDIA_STATE_NONE
;
162 const gfx::Image
& GetTabMediaIndicatorImage(TabMediaState media_state
) {
163 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
164 switch (media_state
) {
165 case TAB_MEDIA_STATE_AUDIO_PLAYING
:
166 return rb
.GetNativeImageNamed(IDR_TAB_AUDIO_INDICATOR
);
167 case TAB_MEDIA_STATE_AUDIO_MUTING
:
168 return rb
.GetNativeImageNamed(IDR_TAB_AUDIO_MUTING_INDICATOR
);
169 case TAB_MEDIA_STATE_RECORDING
:
170 return rb
.GetNativeImageNamed(IDR_TAB_RECORDING_INDICATOR
);
171 case TAB_MEDIA_STATE_CAPTURING
:
172 return rb
.GetNativeImageNamed(IDR_TAB_CAPTURE_INDICATOR
);
173 case TAB_MEDIA_STATE_NONE
:
177 return rb
.GetNativeImageNamed(IDR_SAD_FAVICON
);
180 const gfx::Image
& GetTabMediaIndicatorAffordanceImage(
181 TabMediaState media_state
) {
182 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
183 switch (media_state
) {
184 case TAB_MEDIA_STATE_AUDIO_PLAYING
:
185 case TAB_MEDIA_STATE_AUDIO_MUTING
:
186 return rb
.GetNativeImageNamed(IDR_TAB_AUDIO_MUTING_AFFORDANCE
);
187 case TAB_MEDIA_STATE_NONE
:
188 case TAB_MEDIA_STATE_RECORDING
:
189 case TAB_MEDIA_STATE_CAPTURING
:
190 return GetTabMediaIndicatorImage(media_state
);
193 return GetTabMediaIndicatorImage(media_state
);
196 scoped_ptr
<gfx::Animation
> CreateTabMediaIndicatorFadeAnimation(
197 TabMediaState media_state
) {
198 if (media_state
== TAB_MEDIA_STATE_RECORDING
||
199 media_state
== TAB_MEDIA_STATE_CAPTURING
) {
200 return TabRecordingIndicatorAnimation::Create();
203 // Note: While it seems silly to use a one-part MultiAnimation, it's the only
204 // gfx::Animation implementation that lets us control the frame interval.
205 gfx::MultiAnimation::Parts parts
;
206 const bool is_for_fade_in
= (media_state
!= TAB_MEDIA_STATE_NONE
);
207 parts
.push_back(gfx::MultiAnimation::Part(
208 is_for_fade_in
? kIndicatorFadeInDurationMs
: kIndicatorFadeOutDurationMs
,
209 gfx::Tween::EASE_IN
));
210 const base::TimeDelta interval
=
211 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs
);
212 scoped_ptr
<gfx::MultiAnimation
> animation(
213 new gfx::MultiAnimation(parts
, interval
));
214 animation
->set_continuous(false);
215 return animation
.Pass();
218 base::string16
AssembleTabTooltipText(const base::string16
& title
,
219 TabMediaState media_state
) {
220 if (media_state
== TAB_MEDIA_STATE_NONE
)
223 base::string16 result
= title
;
225 result
.append(1, '\n');
226 switch (media_state
) {
227 case TAB_MEDIA_STATE_AUDIO_PLAYING
:
229 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_PLAYING
));
231 case TAB_MEDIA_STATE_AUDIO_MUTING
:
233 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_MUTING
));
235 case TAB_MEDIA_STATE_RECORDING
:
237 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_RECORDING
));
239 case TAB_MEDIA_STATE_CAPTURING
:
241 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_CAPTURING
));
243 case TAB_MEDIA_STATE_NONE
:
250 bool IsTabAudioMutingFeatureEnabled() {
251 return base::CommandLine::ForCurrentProcess()->HasSwitch(
252 switches::kEnableTabAudioMuting
);
255 bool CanToggleAudioMute(content::WebContents
* contents
) {
256 switch (GetTabMediaStateForContents(contents
)) {
257 case TAB_MEDIA_STATE_NONE
:
258 case TAB_MEDIA_STATE_AUDIO_PLAYING
:
259 case TAB_MEDIA_STATE_AUDIO_MUTING
:
260 return IsTabAudioMutingFeatureEnabled();
261 case TAB_MEDIA_STATE_RECORDING
:
262 case TAB_MEDIA_STATE_CAPTURING
:
269 const std::string
& GetTabAudioMutedCause(content::WebContents
* contents
) {
270 LastMuteMetadata::CreateForWebContents(contents
); // Create if not exists.
271 if (GetTabMediaStateForContents(contents
) == TAB_MEDIA_STATE_CAPTURING
) {
272 // For tab capture, libcontent forces muting off.
273 LastMuteMetadata::FromWebContents(contents
)->cause
=
274 kMutedToggleCauseCapture
;
276 return LastMuteMetadata::FromWebContents(contents
)->cause
;
279 void SetTabAudioMuted(content::WebContents
* contents
,
281 const std::string
& cause
) {
282 if (!contents
|| !chrome::CanToggleAudioMute(contents
))
285 LastMuteMetadata::CreateForWebContents(contents
); // Create if not exists.
286 LastMuteMetadata::FromWebContents(contents
)->cause
= cause
;
288 contents
->SetAudioMuted(mute
);
291 bool IsTabAudioMuted(content::WebContents
* contents
) {
292 return contents
&& contents
->IsAudioMuted();
295 bool AreAllTabsMuted(const TabStripModel
& tab_strip
,
296 const std::vector
<int>& indices
) {
297 for (std::vector
<int>::const_iterator i
= indices
.begin(); i
!= indices
.end();
299 if (!IsTabAudioMuted(tab_strip
.GetWebContentsAt(*i
)))
305 } // namespace chrome