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/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