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: 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 56 double TabRecordingIndicatorAnimation::GetCurrentValue() const { 57 return current_part_index() % 2 ? 58 1.0 - MultiAnimation::GetCurrentValue() : 59 MultiAnimation::GetCurrentValue(); 60 } 61 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)); 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 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 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 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 122 bool IsPlayingAudio(content::WebContents* contents) { 123 return contents->WasRecentlyAudible(); 124 } 125 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 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 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 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 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 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 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 259 void SetTabAudioMuted(content::WebContents* contents, bool mute) { 260 if (!contents || !chrome::CanToggleAudioMute(contents)) 261 return; 262 contents->SetAudioMuted(mute); 263 } 264 265 bool IsTabAudioMuted(content::WebContents* contents) { 266 return contents && contents->IsAudioMuted(); 267 } 268 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