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/strings/string16.h" 8 #include "chrome/browser/media/audio_stream_indicator.h" 9 #include "chrome/browser/media/media_capture_devices_dispatcher.h" 10 #include "chrome/browser/media/media_stream_capture_indicator.h" 11 #include "grit/generated_resources.h" 12 #include "grit/theme_resources.h" 13 #include "ui/base/l10n/l10n_util.h" 14 #include "ui/base/resource/resource_bundle.h" 15 #include "ui/gfx/animation/multi_animation.h" 16 17 namespace chrome { 18 19 namespace { 20 21 // Interval between frame updates of the tab indicator animations. This is not 22 // the usual 60 FPS because a trade-off must be made between tab UI animation 23 // smoothness and media recording/playback performance on low-end hardware. 24 const int kIndicatorFrameIntervalMs = 50; // 20 FPS 25 26 // Fade-in/out duration for the tab indicator animations. Fade-in is quick to 27 // immediately notify the user. Fade-out is more gradual, so that the user has 28 // a chance of finding a tab that has quickly "blipped" on and off. 29 const int kIndicatorFadeInDurationMs = 200; 30 const int kIndicatorFadeOutDurationMs = 1000; 31 32 // Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the 33 // "in" state. 34 class TabRecordingIndicatorAnimation : public gfx::MultiAnimation { 35 public: 36 virtual ~TabRecordingIndicatorAnimation() {} 37 38 // Overridden to provide alternating "towards in" and "towards out" behavior. 39 virtual double GetCurrentValue() const OVERRIDE; 40 41 static scoped_ptr<TabRecordingIndicatorAnimation> Create(); 42 43 private: 44 TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts, 45 const base::TimeDelta interval) 46 : MultiAnimation(parts, interval) {} 47 48 // Number of times to "toggle throb" the recording and tab capture indicators 49 // when they first appear. 50 static const int kCaptureIndicatorThrobCycles = 5; 51 }; 52 53 double TabRecordingIndicatorAnimation::GetCurrentValue() const { 54 return current_part_index() % 2 ? 55 1.0 - MultiAnimation::GetCurrentValue() : 56 MultiAnimation::GetCurrentValue(); 57 } 58 59 scoped_ptr<TabRecordingIndicatorAnimation> 60 TabRecordingIndicatorAnimation::Create() { 61 MultiAnimation::Parts parts; 62 COMPILE_ASSERT(kCaptureIndicatorThrobCycles % 2 != 0, 63 must_be_odd_so_animation_finishes_in_showing_state); 64 for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) { 65 parts.push_back(MultiAnimation::Part( 66 i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs, 67 gfx::Tween::EASE_IN)); 68 } 69 const base::TimeDelta interval = 70 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs); 71 scoped_ptr<TabRecordingIndicatorAnimation> animation( 72 new TabRecordingIndicatorAnimation(parts, interval)); 73 animation->set_continuous(false); 74 return animation.Pass(); 75 } 76 77 } // namespace 78 79 bool ShouldTabShowFavicon(int capacity, 80 bool is_pinned_tab, 81 bool is_active_tab, 82 bool has_favicon, 83 TabMediaState media_state) { 84 if (!has_favicon) 85 return false; 86 int required_capacity = 1; 87 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab)) 88 ++required_capacity; 89 if (ShouldTabShowMediaIndicator( 90 capacity, is_pinned_tab, is_active_tab, has_favicon, media_state)) { 91 ++required_capacity; 92 } 93 return capacity >= required_capacity; 94 } 95 96 bool ShouldTabShowMediaIndicator(int capacity, 97 bool is_pinned_tab, 98 bool is_active_tab, 99 bool has_favicon, 100 TabMediaState media_state) { 101 if (media_state == TAB_MEDIA_STATE_NONE) 102 return false; 103 if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab)) 104 return capacity >= 2; 105 return capacity >= 1; 106 } 107 108 bool ShouldTabShowCloseButton(int capacity, 109 bool is_pinned_tab, 110 bool is_active_tab) { 111 if (is_pinned_tab) 112 return false; 113 else if (is_active_tab) 114 return true; 115 else 116 return capacity >= 3; 117 } 118 119 bool IsPlayingAudio(content::WebContents* contents) { 120 AudioStreamIndicator* audio_indicator = 121 MediaCaptureDevicesDispatcher::GetInstance()->GetAudioStreamIndicator() 122 .get(); 123 return audio_indicator && audio_indicator->IsPlayingAudio(contents); 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) { 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 (IsPlayingAudio(contents)) 141 return TAB_MEDIA_STATE_AUDIO_PLAYING; 142 143 return TAB_MEDIA_STATE_NONE; 144 } 145 146 const gfx::Image& GetTabMediaIndicatorImage(TabMediaState media_state) { 147 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 148 switch (media_state) { 149 case TAB_MEDIA_STATE_AUDIO_PLAYING: 150 return rb.GetNativeImageNamed(IDR_TAB_AUDIO_INDICATOR); 151 case TAB_MEDIA_STATE_RECORDING: 152 return rb.GetNativeImageNamed(IDR_TAB_RECORDING_INDICATOR); 153 case TAB_MEDIA_STATE_CAPTURING: 154 return rb.GetNativeImageNamed(IDR_TAB_CAPTURE_INDICATOR); 155 case TAB_MEDIA_STATE_NONE: 156 break; 157 } 158 NOTREACHED(); 159 return rb.GetNativeImageNamed(IDR_SAD_FAVICON); 160 } 161 162 scoped_ptr<gfx::Animation> CreateTabMediaIndicatorFadeAnimation( 163 TabMediaState media_state) { 164 if (media_state == TAB_MEDIA_STATE_RECORDING || 165 media_state == TAB_MEDIA_STATE_CAPTURING) { 166 return TabRecordingIndicatorAnimation::Create().PassAs<gfx::Animation>(); 167 } 168 169 // Note: While it seems silly to use a one-part MultiAnimation, it's the only 170 // gfx::Animation implementation that lets us control the frame interval. 171 gfx::MultiAnimation::Parts parts; 172 const bool is_for_fade_in = (media_state != TAB_MEDIA_STATE_NONE); 173 parts.push_back(gfx::MultiAnimation::Part( 174 is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs, 175 gfx::Tween::EASE_IN)); 176 const base::TimeDelta interval = 177 base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs); 178 scoped_ptr<gfx::MultiAnimation> animation( 179 new gfx::MultiAnimation(parts, interval)); 180 animation->set_continuous(false); 181 return animation.PassAs<gfx::Animation>(); 182 } 183 184 base::string16 AssembleTabTooltipText(const base::string16& title, 185 TabMediaState media_state) { 186 if (media_state == TAB_MEDIA_STATE_NONE) 187 return title; 188 189 base::string16 result = title; 190 if (!result.empty()) 191 result.append(1, '\n'); 192 switch (media_state) { 193 case TAB_MEDIA_STATE_AUDIO_PLAYING: 194 result.append( 195 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_AUDIO_PLAYING)); 196 break; 197 case TAB_MEDIA_STATE_RECORDING: 198 result.append( 199 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_RECORDING)); 200 break; 201 case TAB_MEDIA_STATE_CAPTURING: 202 result.append( 203 l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_MEDIA_STATE_CAPTURING)); 204 break; 205 case TAB_MEDIA_STATE_NONE: 206 NOTREACHED(); 207 break; 208 } 209 return result; 210 } 211 212 } // namespace chrome 213