Roll src/third_party/WebKit f36d5e0:68b67cd (svn 193299:193303)
[chromium-blink-merge.git] / remoting / host / resizing_host_observer.cc
blob60a9046ab3f7fb39eb656cc7a33672e90796b352
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.
5 #include "remoting/host/resizing_host_observer.h"
7 #include <list>
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"
15 namespace remoting {
16 namespace {
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;
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());
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;
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;
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();
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;
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;
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;
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();
109 private:
110 float client_scale_factor_;
111 float aspect_ratio_goodness_;
112 ScreenResolution resolution_;
115 } // namespace
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) {
124 ResizingHostObserver::~ResizingHostObserver() {
125 if (!original_resolution_.IsEmpty())
126 desktop_resizer_->RestoreResolution(original_resolution_);
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();
135 if (resolution.IsEmpty())
136 return;
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;
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;
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;
168 ScreenResolution current_resolution =
169 desktop_resizer_->GetCurrentResolution();
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());
177 // Update the time of last resize to allow it to be rate-limited.
178 previous_resize_time_ = now;
181 void ResizingHostObserver::SetNowFunctionForTesting(
182 const base::Callback<base::Time(void)>& now_function) {
183 now_function_ = now_function;
186 } // namespace remoting