Home | History | Annotate | Download | only in host
      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 "remoting/host/resizing_host_observer.h"
      6 
      7 #include <list>
      8 
      9 #include "base/bind.h"
     10 #include "base/logging.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "remoting/host/desktop_resizer.h"
     13 #include "remoting/host/screen_resolution.h"
     14 
     15 namespace {
     16 
     17 // Minimum amount of time to wait between desktop resizes. Note that this
     18 // constant is duplicated by the ResizingHostObserverTest.RateLimited
     19 // unit-test and must be kept in sync.
     20 const int kMinimumResizeIntervalMs = 1000;
     21 
     22 class CandidateSize {
     23  public:
     24   CandidateSize(const SkISize& candidate, const SkISize& preferred)
     25       : size_(candidate) {
     26     // Protect against division by zero.
     27     CHECK(!candidate.isEmpty());
     28     DCHECK(!preferred.isEmpty());
     29 
     30     // The client scale factor is the smaller of the candidate:preferred ratios
     31     // for width and height.
     32     if ((candidate.width() > preferred.width()) ||
     33         (candidate.height() > preferred.height())) {
     34       const float width_ratio =
     35           static_cast<float>(preferred.width()) / candidate.width();
     36       const float height_ratio =
     37           static_cast<float>(preferred.height()) / candidate.height();
     38       client_scale_factor_ = std::min(width_ratio, height_ratio);
     39     } else {
     40       // Since clients do not scale up, 1.0 is the maximum.
     41       client_scale_factor_ = 1.0;
     42     }
     43 
     44     // The aspect ratio "goodness" is defined as being the ratio of the smaller
     45     // of the two aspect ratios (candidate and preferred) to the larger. The
     46     // best aspect ratio is the one that most closely matches the preferred
     47     // aspect ratio (in other words, the ideal aspect ratio "goodness" is 1.0).
     48     // By keeping the values < 1.0, it allows ratios that differ in opposite
     49     // directions to be compared numerically.
     50     float candidate_aspect_ratio =
     51         static_cast<float>(candidate.width()) / candidate.height();
     52     float preferred_aspect_ratio =
     53         static_cast<float>(preferred.width()) / preferred.height();
     54     if (candidate_aspect_ratio > preferred_aspect_ratio) {
     55       aspect_ratio_goodness_ = preferred_aspect_ratio / candidate_aspect_ratio;
     56     } else {
     57       aspect_ratio_goodness_ = candidate_aspect_ratio / preferred_aspect_ratio;
     58     }
     59   }
     60 
     61   const SkISize& size() const { return size_; }
     62   float client_scale_factor() const { return client_scale_factor_; }
     63   float aspect_ratio_goodness() const { return aspect_ratio_goodness_; }
     64   int64 area() const {
     65     return static_cast<int64>(size_.width()) * size_.height();
     66   }
     67 
     68   bool IsBetterThan(const CandidateSize& other) const {
     69     // If either size would require down-scaling, prefer the one that down-
     70     // scales the least (since the client scale factor is at most 1.0, this
     71     // does not differentiate between sizes that don't require down-scaling).
     72     if (client_scale_factor() < other.client_scale_factor()) {
     73       return false;
     74     } else if (client_scale_factor() > other.client_scale_factor()) {
     75       return true;
     76     }
     77 
     78     // If the scale factors are the same, pick the size with the largest area.
     79     if (area() < other.area()) {
     80       return false;
     81     } else if (area() > other.area()) {
     82       return true;
     83     }
     84 
     85     // If the areas are equal, pick the size with the "best" aspect ratio.
     86     if (aspect_ratio_goodness() < other.aspect_ratio_goodness()) {
     87       return false;
     88     } else if (aspect_ratio_goodness() > other.aspect_ratio_goodness()) {
     89       return true;
     90     }
     91 
     92     // If the aspect ratios are equally good (for example, comparing 640x480
     93     // to 480x640 w.r.t. 640x640), just pick the widest, since desktop UIs
     94     // are typically designed for landscape aspect ratios.
     95     return size().width() > other.size().width();
     96   }
     97 
     98  private:
     99   float client_scale_factor_;
    100   float aspect_ratio_goodness_;
    101   SkISize size_;
    102 };
    103 
    104 }  // namespace
    105 
    106 namespace remoting {
    107 
    108 ResizingHostObserver::ResizingHostObserver(
    109     scoped_ptr<DesktopResizer> desktop_resizer)
    110     : desktop_resizer_(desktop_resizer.Pass()),
    111       original_size_(desktop_resizer_->GetCurrentSize()),
    112       now_function_(base::Bind(base::Time::Now)),
    113       weak_factory_(this) {
    114 }
    115 
    116 ResizingHostObserver::~ResizingHostObserver() {
    117   if (!original_size_.isZero())
    118     desktop_resizer_->RestoreSize(original_size_);
    119 }
    120 
    121 void ResizingHostObserver::SetScreenResolution(
    122     const ScreenResolution& resolution) {
    123   // Get the current time. This function is called exactly once for each call
    124   // to SetScreenResolution to simplify the implementation of unit-tests.
    125   base::Time now = now_function_.Run();
    126 
    127   if (resolution.IsEmpty())
    128     return;
    129 
    130   // Resizing the desktop too often is probably not a good idea, so apply a
    131   // simple rate-limiting scheme.
    132   base::TimeDelta minimum_resize_interval =
    133       base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs);
    134   base::Time next_allowed_resize =
    135       previous_resize_time_ + minimum_resize_interval;
    136 
    137   if (now < next_allowed_resize) {
    138     deferred_resize_timer_.Start(
    139         FROM_HERE,
    140         next_allowed_resize - now,
    141         base::Bind(&ResizingHostObserver::SetScreenResolution,
    142                    weak_factory_.GetWeakPtr(), resolution));
    143     return;
    144   }
    145 
    146   // If the implementation returns any sizes, pick the best one according to
    147   // the algorithm described in CandidateSize::IsBetterThen.
    148   SkISize dimensions = SkISize::Make(
    149       resolution.dimensions().width(), resolution.dimensions().height());
    150   std::list<SkISize> sizes = desktop_resizer_->GetSupportedSizes(dimensions);
    151   if (sizes.empty())
    152     return;
    153   CandidateSize best_size(sizes.front(), dimensions);
    154   for (std::list<SkISize>::const_iterator i = ++sizes.begin();
    155        i != sizes.end(); ++i) {
    156     CandidateSize candidate_size(*i, dimensions);
    157     if (candidate_size.IsBetterThan(best_size)) {
    158       best_size = candidate_size;
    159     }
    160   }
    161   SkISize current_size = desktop_resizer_->GetCurrentSize();
    162   if (best_size.size() != current_size)
    163     desktop_resizer_->SetSize(best_size.size());
    164 
    165   // Update the time of last resize to allow it to be rate-limited.
    166   previous_resize_time_ = now;
    167 }
    168 
    169 void ResizingHostObserver::SetNowFunctionForTesting(
    170     const base::Callback<base::Time(void)>& now_function) {
    171   now_function_ = now_function;
    172 }
    173 
    174 }  // namespace remoting
    175