Home | History | Annotate | Download | only in tabs
      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