• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/ui/tabs/tab_utils.h"
6 
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"
19 
20 namespace chrome {
21 
22 namespace {
23 
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
28 
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;
34 
35 // Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
36 // "in" state.
37 class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
38  public:
~TabRecordingIndicatorAnimation()39   virtual ~TabRecordingIndicatorAnimation() {}
40 
41   // Overridden to provide alternating "towards in" and "towards out" behavior.
42   virtual double GetCurrentValue() const OVERRIDE;
43 
44   static scoped_ptr<TabRecordingIndicatorAnimation> Create();
45 
46  private:
TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts & parts,const base::TimeDelta interval)47   TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
48                                  const base::TimeDelta interval)
49       : MultiAnimation(parts, interval) {}
50 
51   // Number of times to "toggle throb" the recording and tab capture indicators
52   // when they first appear.
53   static const int kCaptureIndicatorThrobCycles = 5;
54 };
55 
GetCurrentValue() const56 double TabRecordingIndicatorAnimation::GetCurrentValue() const {
57   return current_part_index() % 2 ?
58       1.0 - MultiAnimation::GetCurrentValue() :
59       MultiAnimation::GetCurrentValue();
60 }
61 
62 scoped_ptr<TabRecordingIndicatorAnimation>
Create()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));
71   }
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();
78 }
79 
80 }  // namespace
81 
ShouldTabShowFavicon(int capacity,bool is_pinned_tab,bool is_active_tab,bool has_favicon,TabMediaState media_state)82 bool ShouldTabShowFavicon(int capacity,
83                           bool is_pinned_tab,
84                           bool is_active_tab,
85                           bool has_favicon,
86                           TabMediaState media_state) {
87   if (!has_favicon)
88     return false;
89   int required_capacity = 1;
90   if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
91     ++required_capacity;
92   if (ShouldTabShowMediaIndicator(
93           capacity, is_pinned_tab, is_active_tab, has_favicon, media_state)) {
94     ++required_capacity;
95   }
96   return capacity >= required_capacity;
97 }
98 
ShouldTabShowMediaIndicator(int capacity,bool is_pinned_tab,bool is_active_tab,bool has_favicon,TabMediaState media_state)99 bool ShouldTabShowMediaIndicator(int capacity,
100                                  bool is_pinned_tab,
101                                  bool is_active_tab,
102                                  bool has_favicon,
103                                  TabMediaState media_state) {
104   if (media_state == TAB_MEDIA_STATE_NONE)
105     return false;
106   if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
107     return capacity >= 2;
108   return capacity >= 1;
109 }
110 
ShouldTabShowCloseButton(int capacity,bool is_pinned_tab,bool is_active_tab)111 bool ShouldTabShowCloseButton(int capacity,
112                               bool is_pinned_tab,
113                               bool is_active_tab) {
114   if (is_pinned_tab)
115     return false;
116   else if (is_active_tab)
117     return true;
118   else
119     return capacity >= 3;
120 }
121 
IsPlayingAudio(content::WebContents * contents)122 bool IsPlayingAudio(content::WebContents* contents) {
123   return contents->WasRecentlyAudible();
124 }
125 
GetTabMediaStateForContents(content::WebContents * contents)126 TabMediaState GetTabMediaStateForContents(content::WebContents* contents) {
127   if (!contents)
128     return TAB_MEDIA_STATE_NONE;
129 
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;
138   }
139 
140   if (IsTabAudioMutingFeatureEnabled() && contents->IsAudioMuted())
141     return TAB_MEDIA_STATE_AUDIO_MUTING;
142   if (IsPlayingAudio(contents))
143     return TAB_MEDIA_STATE_AUDIO_PLAYING;
144 
145   return TAB_MEDIA_STATE_NONE;
146 }
147 
GetTabMediaIndicatorImage(TabMediaState media_state)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:
160       break;
161   }
162   NOTREACHED();
163   return rb.GetNativeImageNamed(IDR_SAD_FAVICON);
164 }
165 
GetTabMediaIndicatorAffordanceImage(TabMediaState media_state)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);
177   }
178   NOTREACHED();
179   return GetTabMediaIndicatorImage(media_state);
180 }
181 
CreateTabMediaIndicatorFadeAnimation(TabMediaState 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>();
187   }
188 
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>();
202 }
203 
AssembleTabTooltipText(const base::string16 & title,TabMediaState media_state)204 base::string16 AssembleTabTooltipText(const base::string16& title,
205                                       TabMediaState media_state) {
206   if (media_state == TAB_MEDIA_STATE_NONE)
207     return title;
208 
209   base::string16 result = title;
210   if (!result.empty())
211     result.append(1, '\n');
212   switch (media_state) {
213     case TAB_MEDIA_STATE_AUDIO_PLAYING:
214       result.append(
215           l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_PLAYING));
216       break;
217     case TAB_MEDIA_STATE_AUDIO_MUTING:
218       result.append(
219           l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_MUTING));
220       break;
221     case TAB_MEDIA_STATE_RECORDING:
222       result.append(
223           l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_RECORDING));
224       break;
225     case TAB_MEDIA_STATE_CAPTURING:
226       result.append(
227           l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_CAPTURING));
228       break;
229     case TAB_MEDIA_STATE_NONE:
230       NOTREACHED();
231       break;
232   }
233   return result;
234 }
235 
IsTabAudioMutingFeatureEnabled()236 bool IsTabAudioMutingFeatureEnabled() {
237 #if defined(USE_AURA)
238   return base::CommandLine::ForCurrentProcess()->HasSwitch(
239       switches::kEnableTabAudioMuting);
240 #else
241   return false;
242 #endif
243 }
244 
CanToggleAudioMute(content::WebContents * contents)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:
253       return false;
254   }
255   NOTREACHED();
256   return false;
257 }
258 
SetTabAudioMuted(content::WebContents * contents,bool mute)259 void SetTabAudioMuted(content::WebContents* contents, bool mute) {
260   if (!contents || !chrome::CanToggleAudioMute(contents))
261     return;
262   contents->SetAudioMuted(mute);
263 }
264 
IsTabAudioMuted(content::WebContents * contents)265 bool IsTabAudioMuted(content::WebContents* contents) {
266   return contents && contents->IsAudioMuted();
267 }
268 
AreAllTabsMuted(const TabStripModel & tab_strip,const std::vector<int> & indices)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();
272        ++i) {
273     if (!IsTabAudioMuted(tab_strip.GetWebContentsAt(*i)))
274       return false;
275   }
276   return true;
277 }
278 
279 }  // namespace chrome
280