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