- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / host / resizing_host_observer.cc
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       original_resolution_(desktop_resizer_->GetCurrentResolution()),
121       now_function_(base::Bind(base::Time::Now)),
122       weak_factory_(this) {
123 }
124
125 ResizingHostObserver::~ResizingHostObserver() {
126   if (!original_resolution_.IsEmpty())
127     desktop_resizer_->RestoreResolution(original_resolution_);
128 }
129
130 void ResizingHostObserver::SetScreenResolution(
131     const ScreenResolution& resolution) {
132   // Get the current time. This function is called exactly once for each call
133   // to SetScreenResolution to simplify the implementation of unit-tests.
134   base::Time now = now_function_.Run();
135
136   if (resolution.IsEmpty())
137     return;
138
139   // Resizing the desktop too often is probably not a good idea, so apply a
140   // simple rate-limiting scheme.
141   base::TimeDelta minimum_resize_interval =
142       base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs);
143   base::Time next_allowed_resize =
144       previous_resize_time_ + minimum_resize_interval;
145
146   if (now < next_allowed_resize) {
147     deferred_resize_timer_.Start(
148         FROM_HERE,
149         next_allowed_resize - now,
150         base::Bind(&ResizingHostObserver::SetScreenResolution,
151                    weak_factory_.GetWeakPtr(), resolution));
152     return;
153   }
154
155   // If the implementation returns any resolutions, pick the best one according
156   // to the algorithm described in CandidateResolution::IsBetterThen.
157   std::list<ScreenResolution> resolutions =
158       desktop_resizer_->GetSupportedResolutions(resolution);
159   if (resolutions.empty())
160     return;
161   CandidateResolution best_candidate(resolutions.front(), resolution);
162   for (std::list<ScreenResolution>::const_iterator i = ++resolutions.begin();
163        i != resolutions.end(); ++i) {
164     CandidateResolution candidate(*i, resolution);
165     if (candidate.IsBetterThan(best_candidate)) {
166       best_candidate = candidate;
167     }
168   }
169   ScreenResolution current_resolution =
170       desktop_resizer_->GetCurrentResolution();
171   if (!best_candidate.resolution().Equals(current_resolution))
172     desktop_resizer_->SetResolution(best_candidate.resolution());
173
174   // Update the time of last resize to allow it to be rate-limited.
175   previous_resize_time_ = now;
176 }
177
178 void ResizingHostObserver::SetNowFunctionForTesting(
179     const base::Callback<base::Time(void)>& now_function) {
180   now_function_ = now_function;
181 }
182
183 }  // namespace remoting