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
20namespace chrome {
21
22namespace {
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.
27const 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.
32const int kIndicatorFadeInDurationMs = 200;
33const int kIndicatorFadeOutDurationMs = 1000;
34
35// Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
36// "in" state.
37class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
38 public:
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:
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
56double TabRecordingIndicatorAnimation::GetCurrentValue() const {
57  return current_part_index() % 2 ?
58      1.0 - MultiAnimation::GetCurrentValue() :
59      MultiAnimation::GetCurrentValue();
60}
61
62scoped_ptr<TabRecordingIndicatorAnimation>
63TabRecordingIndicatorAnimation::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
82bool 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
99bool 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
111bool 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
122bool IsPlayingAudio(content::WebContents* contents) {
123  return contents->WasRecentlyAudible();
124}
125
126TabMediaState 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
148const 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
166const 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
182scoped_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
204base::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
236bool IsTabAudioMutingFeatureEnabled() {
237#if defined(USE_AURA)
238  return base::CommandLine::ForCurrentProcess()->HasSwitch(
239      switches::kEnableTabAudioMuting);
240#else
241  return false;
242#endif
243}
244
245bool 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
259void SetTabAudioMuted(content::WebContents* contents, bool mute) {
260  if (!contents || !chrome::CanToggleAudioMute(contents))
261    return;
262  contents->SetAudioMuted(mute);
263}
264
265bool IsTabAudioMuted(content::WebContents* contents) {
266  return contents && contents->IsAudioMuted();
267}
268
269bool 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