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 <list>
6
7#include "base/compiler_specific.h"
8#include "base/logging.h"
9#include "base/memory/ref_counted.h"
10#include "base/message_loop/message_loop.h"
11#include "base/run_loop.h"
12#include "remoting/host/desktop_resizer.h"
13#include "remoting/host/resizing_host_observer.h"
14#include "remoting/host/screen_resolution.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
17
18namespace remoting {
19
20std::ostream& operator<<(std::ostream& os, const ScreenResolution& resolution) {
21  return os << resolution.dimensions().width() << "x"
22            << resolution.dimensions().height() << " @ "
23            << resolution.dpi().x() << "x" << resolution.dpi().y();
24}
25
26bool operator==(const ScreenResolution& a, const ScreenResolution& b) {
27  return a.Equals(b);
28}
29
30const int kDefaultDPI = 96;
31
32ScreenResolution MakeResolution(int width, int height) {
33  return ScreenResolution(webrtc::DesktopSize(width, height),
34                          webrtc::DesktopVector(kDefaultDPI, kDefaultDPI));
35}
36
37class FakeDesktopResizer : public DesktopResizer {
38 public:
39  FakeDesktopResizer(const ScreenResolution& initial_resolution,
40                     bool exact_size_supported,
41                     const ScreenResolution* supported_resolutions,
42                     int num_supported_resolutions,
43                     int* restore_resolution_call_count)
44      : initial_resolution_(initial_resolution),
45        current_resolution_(initial_resolution),
46        exact_size_supported_(exact_size_supported),
47        set_resolution_call_count_(0),
48        restore_resolution_call_count_(restore_resolution_call_count) {
49    for (int i = 0; i < num_supported_resolutions; ++i) {
50      supported_resolutions_.push_back(supported_resolutions[i]);
51    }
52  }
53
54  virtual ~FakeDesktopResizer() {
55    EXPECT_EQ(initial_resolution_, GetCurrentResolution());
56  }
57
58  int set_resolution_call_count() { return set_resolution_call_count_; }
59
60  // remoting::DesktopResizer interface
61  virtual ScreenResolution GetCurrentResolution() OVERRIDE {
62    return current_resolution_;
63  }
64  virtual std::list<ScreenResolution> GetSupportedResolutions(
65      const ScreenResolution& preferred) OVERRIDE {
66    std::list<ScreenResolution> result = supported_resolutions_;
67    if (exact_size_supported_) {
68      result.push_back(preferred);
69    }
70    return result;
71  }
72  virtual void SetResolution(const ScreenResolution& resolution) OVERRIDE {
73    current_resolution_ = resolution;
74    ++set_resolution_call_count_;
75  }
76  virtual void RestoreResolution(const ScreenResolution& resolution) OVERRIDE {
77    current_resolution_ = resolution;
78    if (restore_resolution_call_count_)
79      ++(*restore_resolution_call_count_);
80  }
81
82 private:
83  ScreenResolution initial_resolution_;
84  ScreenResolution current_resolution_;
85  bool exact_size_supported_;
86  std::list<ScreenResolution> supported_resolutions_;
87
88  int set_resolution_call_count_;
89  int* restore_resolution_call_count_;
90};
91
92class ResizingHostObserverTest : public testing::Test {
93 public:
94  ResizingHostObserverTest()
95      : desktop_resizer_(NULL),
96        now_(base::Time::Now()) {
97  }
98
99  // This needs to be public because the derived test-case class needs to
100  // pass it to Bind, which fails if it's protected.
101  base::Time GetTime() {
102    return now_;
103  }
104
105 protected:
106  void SetDesktopResizer(scoped_ptr<FakeDesktopResizer> desktop_resizer) {
107    CHECK(!desktop_resizer_) << "Call SetDeskopResizer once per test";
108    desktop_resizer_ = desktop_resizer.get();
109
110    resizing_host_observer_.reset(
111        new ResizingHostObserver(desktop_resizer.PassAs<DesktopResizer>()));
112    resizing_host_observer_->SetNowFunctionForTesting(
113        base::Bind(&ResizingHostObserverTest::GetTimeAndIncrement,
114                   base::Unretained(this)));
115  }
116
117  ScreenResolution GetBestResolution(const ScreenResolution& client_size) {
118    resizing_host_observer_->SetScreenResolution(client_size);
119    return desktop_resizer_->GetCurrentResolution();
120  }
121
122  void VerifySizes(const ScreenResolution* client_sizes,
123                   const ScreenResolution* expected_sizes,
124                   int number_of_sizes) {
125    for (int i = 0; i < number_of_sizes; ++i) {
126      ScreenResolution best_size = GetBestResolution(client_sizes[i]);
127      EXPECT_EQ(expected_sizes[i], best_size)
128          << "Input resolution = " << client_sizes[i];
129    }
130  }
131
132  base::Time GetTimeAndIncrement() {
133    base::Time result = now_;
134    now_ += base::TimeDelta::FromSeconds(1);
135    return result;
136  }
137
138  scoped_ptr<ResizingHostObserver> resizing_host_observer_;
139  FakeDesktopResizer* desktop_resizer_;
140  base::Time now_;
141};
142
143// Check that the resolution isn't restored if it wasn't changed by this class.
144TEST_F(ResizingHostObserverTest, NoRestoreResolution) {
145  int restore_resolution_call_count = 0;
146  ScreenResolution initial = MakeResolution(640, 480);
147  scoped_ptr<FakeDesktopResizer> desktop_resizer(
148      new FakeDesktopResizer(initial, false, NULL, 0,
149                             &restore_resolution_call_count));
150  SetDesktopResizer(desktop_resizer.Pass());
151  VerifySizes(NULL, NULL, 0);
152  resizing_host_observer_.reset();
153  EXPECT_EQ(0, restore_resolution_call_count);
154}
155
156// Check that the host is not resized if GetSupportedSizes returns an empty
157// list (even if GetCurrentSize is supported).
158TEST_F(ResizingHostObserverTest, EmptyGetSupportedSizes) {
159  int restore_resolution_call_count = 0;
160  ScreenResolution initial = MakeResolution(640, 480);
161  scoped_ptr<FakeDesktopResizer> desktop_resizer(
162      new FakeDesktopResizer(initial, false, NULL, 0,
163                             &restore_resolution_call_count));
164  SetDesktopResizer(desktop_resizer.Pass());
165
166  ScreenResolution client_sizes[] = { MakeResolution(200, 100),
167                                      MakeResolution(100, 200) };
168  ScreenResolution expected_sizes[] = { initial, initial };
169  VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
170
171  resizing_host_observer_.reset();
172  EXPECT_EQ(0, restore_resolution_call_count);
173}
174
175// Check that if the implementation supports exact size matching, it is used.
176TEST_F(ResizingHostObserverTest, SelectExactSize) {
177  int restore_resolution_call_count = 0;
178  scoped_ptr<FakeDesktopResizer> desktop_resizer(
179        new FakeDesktopResizer(MakeResolution(640, 480), true, NULL, 0,
180                               &restore_resolution_call_count));
181  SetDesktopResizer(desktop_resizer.Pass());
182
183  ScreenResolution client_sizes[] = { MakeResolution(200, 100),
184                                      MakeResolution(100, 200),
185                                      MakeResolution(640, 480),
186                                      MakeResolution(480, 640),
187                                      MakeResolution(1280, 1024) };
188  VerifySizes(client_sizes, client_sizes, arraysize(client_sizes));
189  resizing_host_observer_.reset();
190  EXPECT_EQ(1, restore_resolution_call_count);
191}
192
193// Check that if the implementation supports a size that is no larger than
194// the requested size, then the largest such size is used.
195TEST_F(ResizingHostObserverTest, SelectBestSmallerSize) {
196  ScreenResolution supported_sizes[] = { MakeResolution(639, 479),
197                                         MakeResolution(640, 480) };
198  scoped_ptr<FakeDesktopResizer> desktop_resizer(
199      new FakeDesktopResizer(MakeResolution(640, 480), false,
200                             supported_sizes, arraysize(supported_sizes),
201                             NULL));
202  SetDesktopResizer(desktop_resizer.Pass());
203
204  ScreenResolution client_sizes[] = { MakeResolution(639, 479),
205                                      MakeResolution(640, 480),
206                                      MakeResolution(641, 481),
207                                      MakeResolution(999, 999) };
208  ScreenResolution expected_sizes[] = { supported_sizes[0], supported_sizes[1],
209                               supported_sizes[1], supported_sizes[1] };
210  VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
211}
212
213// Check that if the implementation supports only sizes that are larger than
214// the requested size, then the one that requires the least down-scaling.
215TEST_F(ResizingHostObserverTest, SelectBestScaleFactor) {
216  ScreenResolution supported_sizes[] = { MakeResolution(100, 100),
217                                         MakeResolution(200, 100) };
218  scoped_ptr<FakeDesktopResizer> desktop_resizer(
219      new FakeDesktopResizer(MakeResolution(200, 100), false,
220                             supported_sizes, arraysize(supported_sizes),
221                             NULL));
222  SetDesktopResizer(desktop_resizer.Pass());
223
224  ScreenResolution client_sizes[] = { MakeResolution(1, 1),
225                                      MakeResolution(99, 99),
226                                      MakeResolution(199, 99) };
227  ScreenResolution expected_sizes[] = { supported_sizes[0], supported_sizes[0],
228                               supported_sizes[1] };
229  VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
230}
231
232// Check that if the implementation supports two sizes that have the same
233// resultant scale factor, then the widest one is selected.
234TEST_F(ResizingHostObserverTest, SelectWidest) {
235  ScreenResolution supported_sizes[] = { MakeResolution(640, 480),
236                                         MakeResolution(480, 640) };
237  scoped_ptr<FakeDesktopResizer> desktop_resizer(
238      new FakeDesktopResizer(MakeResolution(480, 640), false,
239                             supported_sizes, arraysize(supported_sizes),
240                             NULL));
241  SetDesktopResizer(desktop_resizer.Pass());
242
243  ScreenResolution client_sizes[] = { MakeResolution(100, 100),
244                                      MakeResolution(480, 480),
245                                      MakeResolution(500, 500),
246                                      MakeResolution(640, 640),
247                                      MakeResolution(1000, 1000) };
248  ScreenResolution expected_sizes[] = { supported_sizes[0], supported_sizes[0],
249                               supported_sizes[0], supported_sizes[0],
250                               supported_sizes[0] };
251  VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
252}
253
254// Check that if the best match for the client size doesn't change, then we
255// don't call SetSize.
256TEST_F(ResizingHostObserverTest, NoSetSizeForSameSize) {
257  ScreenResolution supported_sizes[] = { MakeResolution(640, 480),
258                                         MakeResolution(480, 640) };
259  FakeDesktopResizer* desktop_resizer =
260      new FakeDesktopResizer(MakeResolution(480, 640), false,
261                             supported_sizes, arraysize(supported_sizes), NULL);
262  SetDesktopResizer(scoped_ptr<FakeDesktopResizer>(desktop_resizer));
263
264  ScreenResolution client_sizes[] = { MakeResolution(640, 640),
265                                      MakeResolution(1024, 768),
266                                      MakeResolution(640, 480) };
267  ScreenResolution expected_sizes[] = { MakeResolution(640, 480),
268                                        MakeResolution(640, 480),
269                                        MakeResolution(640, 480) };
270  VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
271  EXPECT_EQ(desktop_resizer->set_resolution_call_count(), 1);
272}
273
274// Check that desktop resizes are rate-limited, and that if multiple resize
275// requests are received in the time-out period, the most recent is respected.
276TEST_F(ResizingHostObserverTest, RateLimited) {
277  FakeDesktopResizer* desktop_resizer =
278      new FakeDesktopResizer(MakeResolution(640, 480), true, NULL, 0, NULL);
279  SetDesktopResizer(scoped_ptr<FakeDesktopResizer>(desktop_resizer));
280  resizing_host_observer_->SetNowFunctionForTesting(
281      base::Bind(&ResizingHostObserverTest::GetTime, base::Unretained(this)));
282
283  base::MessageLoop message_loop;
284  base::RunLoop run_loop;
285
286  EXPECT_EQ(GetBestResolution(MakeResolution(100, 100)),
287            MakeResolution(100, 100));
288  now_ += base::TimeDelta::FromMilliseconds(900);
289  EXPECT_EQ(GetBestResolution(MakeResolution(200, 200)),
290            MakeResolution(100, 100));
291  now_ += base::TimeDelta::FromMilliseconds(99);
292  EXPECT_EQ(GetBestResolution(MakeResolution(300, 300)),
293            MakeResolution(100, 100));
294  now_ += base::TimeDelta::FromMilliseconds(1);
295
296  // Due to the kMinimumResizeIntervalMs constant in resizing_host_observer.cc,
297  // We need to wait a total of 1000ms for the final resize to be processed.
298  // Since it was queued 900 + 99 ms after the first, we need to wait an
299  // additional 1ms. However, since RunLoop is not guaranteed to process tasks
300  // with the same due time in FIFO order, wait an additional 1ms for safety.
301  message_loop.PostDelayedTask(
302      FROM_HERE,
303      run_loop.QuitClosure(),
304      base::TimeDelta::FromMilliseconds(2));
305  run_loop.Run();
306
307  // If the QuitClosure fired before the final resize, it's a test failure.
308  EXPECT_EQ(desktop_resizer_->GetCurrentResolution(), MakeResolution(300, 300));
309}
310
311}  // namespace remoting
312