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