1// Copyright 2014 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 "chrome/browser/android/shortcut_helper.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/test/base/chrome_render_view_host_test_harness.h"
9#include "content/public/browser/web_contents.h"
10#include "ui/gfx/screen.h"
11#include "ui/gfx/screen_type_delegate.h"
12
13// A dummy implementation of gfx::Screen, since ShortcutHelper needs access to
14// a gfx::Display's device scale factor.
15// This is inspired by web_contents_video_capture_device_unittest.cc
16// A bug has been opened to merge all those mocks: http://crbug.com/417227
17class FakeScreen : public gfx::Screen {
18 public:
19  FakeScreen() : display_(0x1337, gfx::Rect(0, 0, 2560, 1440)) {
20  }
21  virtual ~FakeScreen() {}
22
23  void SetDisplayDeviceScaleFactor(float device_scale_factor) {
24    display_.set_device_scale_factor(device_scale_factor);
25  }
26
27  // gfx::Screen implementation (only what's needed for testing).
28  virtual bool IsDIPEnabled() OVERRIDE { return true; }
29  virtual gfx::Point GetCursorScreenPoint() OVERRIDE { return gfx::Point(); }
30  virtual gfx::NativeWindow GetWindowUnderCursor() OVERRIDE { return NULL; }
31  virtual gfx::NativeWindow GetWindowAtScreenPoint(
32      const gfx::Point& point) OVERRIDE { return NULL; }
33  virtual int GetNumDisplays() const OVERRIDE { return 1; }
34  virtual std::vector<gfx::Display> GetAllDisplays() const OVERRIDE {
35    return std::vector<gfx::Display>(1, display_);
36  }
37  virtual gfx::Display GetDisplayNearestWindow(
38      gfx::NativeView view) const OVERRIDE {
39    return display_;
40  }
41  virtual gfx::Display GetDisplayNearestPoint(
42      const gfx::Point& point) const OVERRIDE {
43    return display_;
44  }
45  virtual gfx::Display GetDisplayMatching(
46      const gfx::Rect& match_rect) const OVERRIDE {
47    return display_;
48  }
49  virtual gfx::Display GetPrimaryDisplay() const OVERRIDE {
50    return display_;
51  }
52  virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE {}
53  virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE {}
54
55 private:
56  gfx::Display display_;
57
58  DISALLOW_COPY_AND_ASSIGN(FakeScreen);
59};
60
61class ShortcutHelperTest : public ChromeRenderViewHostTestHarness  {
62 protected:
63  ShortcutHelperTest() : shortcut_helper_(NULL) {}
64  virtual ~ShortcutHelperTest() {}
65
66  static jobject CreateShortcutHelperJava(JNIEnv* env) {
67    jclass clazz = env->FindClass("org/chromium/chrome/browser/ShortcutHelper");
68    jmethodID constructor =
69        env->GetMethodID(clazz, "<init>",
70                         "(Landroid/content/Context;"
71                             "Lorg/chromium/chrome/browser/Tab;)V");
72    return env->NewObject(clazz, constructor, jobject(), jobject());
73  }
74
75  void ResetShorcutHelper() {
76    if (shortcut_helper_)
77      delete shortcut_helper_;
78
79    JNIEnv* env = base::android::AttachCurrentThread();
80    shortcut_helper_ =
81        new ShortcutHelper(env, CreateShortcutHelperJava(env), web_contents());
82  }
83
84  virtual void SetUp() OVERRIDE {
85    gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, &fake_screen_);
86    ASSERT_EQ(&fake_screen_, gfx::Screen::GetNativeScreen());
87
88    ChromeRenderViewHostTestHarness::SetUp();
89
90    ResetShorcutHelper();
91  }
92
93  virtual void TearDown() OVERRIDE {
94    delete shortcut_helper_;
95    shortcut_helper_ = NULL;
96
97    ChromeRenderViewHostTestHarness::TearDown();
98
99    gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL);
100  }
101
102  GURL FindBestMatchingIcon(const std::vector<content::Manifest::Icon>& icons) {
103    return shortcut_helper_->FindBestMatchingIcon(icons);
104  }
105
106  void SetDisplayDeviceScaleFactor(float device_scale_factor) {
107    fake_screen_.SetDisplayDeviceScaleFactor(device_scale_factor);
108
109    ResetShorcutHelper();
110  }
111
112  static int GetPreferredIconSizeInDp() {
113    return ShortcutHelper::kPreferredIconSizeInDp;
114  }
115
116  static content::Manifest::Icon CreateIcon(
117      const std::string& url,
118      const std::string& type,
119      double density,
120      const std::vector<gfx::Size> sizes) {
121    content::Manifest::Icon icon;
122    icon.src = GURL(url);
123    if (!type.empty())
124      icon.type = base::NullableString16(base::UTF8ToUTF16(type), false);
125    icon.density = density;
126    icon.sizes = sizes;
127
128    return icon;
129  }
130
131 private:
132  ShortcutHelper* shortcut_helper_;
133  FakeScreen fake_screen_;
134
135  DISALLOW_COPY_AND_ASSIGN(ShortcutHelperTest);
136};
137
138TEST_F(ShortcutHelperTest, NoIcons) {
139  // No icons should return the empty URL.
140  std::vector<content::Manifest::Icon> icons;
141  GURL url = FindBestMatchingIcon(icons);
142  EXPECT_TRUE(url.is_empty());
143}
144
145TEST_F(ShortcutHelperTest, NoSizes) {
146  // Icon with no sizes are ignored.
147  std::vector<content::Manifest::Icon> icons;
148  icons.push_back(
149      CreateIcon("http://foo.com/icon.png", "", 1.0, std::vector<gfx::Size>()));
150
151  GURL url = FindBestMatchingIcon(icons);
152  EXPECT_TRUE(url.is_empty());
153}
154
155TEST_F(ShortcutHelperTest, MIMETypeFiltering) {
156  // Icons with type specified to a MIME type that isn't a valid image MIME type
157  // are ignored.
158  std::vector<gfx::Size> sizes;
159  sizes.push_back(gfx::Size(10, 10));
160
161  std::vector<content::Manifest::Icon> icons;
162  icons.push_back(
163      CreateIcon("http://foo.com/icon.png", "image/foo_bar", 1.0, sizes));
164  icons.push_back(CreateIcon("http://foo.com/icon.png", "image/", 1.0, sizes));
165  icons.push_back(CreateIcon("http://foo.com/icon.png", "image/", 1.0, sizes));
166  icons.push_back(
167      CreateIcon("http://foo.com/icon.png", "video/mp4", 1.0, sizes));
168
169  GURL url = FindBestMatchingIcon(icons);
170  EXPECT_TRUE(url.is_empty());
171
172  icons.clear();
173  icons.push_back(
174      CreateIcon("http://foo.com/icon.png", "image/png", 1.0, sizes));
175  url = FindBestMatchingIcon(icons);
176  EXPECT_EQ("http://foo.com/icon.png", url.spec());
177
178  icons.clear();
179  icons.push_back(
180      CreateIcon("http://foo.com/icon.png", "image/gif", 1.0, sizes));
181  url = FindBestMatchingIcon(icons);
182  EXPECT_EQ("http://foo.com/icon.png", url.spec());
183
184  icons.clear();
185  icons.push_back(
186      CreateIcon("http://foo.com/icon.png", "image/jpeg", 1.0, sizes));
187  url = FindBestMatchingIcon(icons);
188  EXPECT_EQ("http://foo.com/icon.png", url.spec());
189}
190
191TEST_F(ShortcutHelperTest, PreferredSizeOfCurrentDensityIsUsedFirst) {
192  // This test has three icons each are marked with sizes set to the preferred
193  // icon size for the associated density.
194  std::vector<gfx::Size> sizes_1;
195  sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp(),
196                              GetPreferredIconSizeInDp()));
197
198  std::vector<gfx::Size> sizes_2;
199  sizes_2.push_back(gfx::Size(GetPreferredIconSizeInDp() * 2,
200                              GetPreferredIconSizeInDp() * 2));
201
202  std::vector<gfx::Size> sizes_3;
203  sizes_3.push_back(gfx::Size(GetPreferredIconSizeInDp() * 3,
204                              GetPreferredIconSizeInDp() * 3));
205
206  std::vector<content::Manifest::Icon> icons;
207  icons.push_back(CreateIcon("http://foo.com/icon_x1.png", "", 1.0, sizes_1));
208  icons.push_back(CreateIcon("http://foo.com/icon_x2.png", "", 2.0, sizes_2));
209  icons.push_back(CreateIcon("http://foo.com/icon_x3.png", "", 3.0, sizes_3));
210
211  SetDisplayDeviceScaleFactor(1.0f);
212  GURL url = FindBestMatchingIcon(icons);
213  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
214
215  SetDisplayDeviceScaleFactor(2.0f);
216  url = FindBestMatchingIcon(icons);
217  EXPECT_EQ("http://foo.com/icon_x2.png", url.spec());
218
219  SetDisplayDeviceScaleFactor(3.0f);
220  url = FindBestMatchingIcon(icons);
221  EXPECT_EQ("http://foo.com/icon_x3.png", url.spec());
222}
223
224TEST_F(ShortcutHelperTest, PreferredSizeOfDefaultDensityIsUsedSecond) {
225  // This test has three icons. The first one is of density zero and is marked
226  // with three sizes which are the preferred icon size for density 1, 2 and 3.
227  // The icon for density 2 and 3 have a size set to 2x2 and 3x3.
228  // Regardless of the device scale factor, the icon of density 1 is going to be
229  // used because it matches the preferred size.
230  std::vector<gfx::Size> sizes_1;
231  sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp(),
232                              GetPreferredIconSizeInDp()));
233  sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp() * 2,
234                              GetPreferredIconSizeInDp() * 2));
235  sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp() * 3,
236                              GetPreferredIconSizeInDp() * 3));
237
238  std::vector<gfx::Size> sizes_2;
239  sizes_2.push_back(gfx::Size(2, 2));
240
241  std::vector<gfx::Size> sizes_3;
242  sizes_3.push_back(gfx::Size(3, 3));
243
244  std::vector<content::Manifest::Icon> icons;
245  icons.push_back(CreateIcon("http://foo.com/icon_x1.png", "", 1.0, sizes_1));
246  icons.push_back(CreateIcon("http://foo.com/icon_x2.png", "", 2.0, sizes_2));
247  icons.push_back(CreateIcon("http://foo.com/icon_x3.png", "", 3.0, sizes_3));
248
249  SetDisplayDeviceScaleFactor(1.0f);
250  GURL url = FindBestMatchingIcon(icons);
251  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
252
253  SetDisplayDeviceScaleFactor(2.0f);
254  url = FindBestMatchingIcon(icons);
255  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
256
257  SetDisplayDeviceScaleFactor(3.0f);
258  url = FindBestMatchingIcon(icons);
259  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
260}
261
262TEST_F(ShortcutHelperTest, DeviceDensityFirst) {
263  // If there is no perfect icon but an icon of the current device density is
264  // present, it will be picked.
265  // This test has three icons each are marked with sizes set to the preferred
266  // icon size for the associated density.
267  std::vector<gfx::Size> sizes;
268  sizes.push_back(gfx::Size(2, 2));
269
270  std::vector<content::Manifest::Icon> icons;
271  icons.push_back(CreateIcon("http://foo.com/icon_x1.png", "", 1.0, sizes));
272  icons.push_back(CreateIcon("http://foo.com/icon_x2.png", "", 2.0, sizes));
273  icons.push_back(CreateIcon("http://foo.com/icon_x3.png", "", 3.0, sizes));
274
275  SetDisplayDeviceScaleFactor(1.0f);
276  GURL url = FindBestMatchingIcon(icons);
277  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
278
279  SetDisplayDeviceScaleFactor(2.0f);
280  url = FindBestMatchingIcon(icons);
281  EXPECT_EQ("http://foo.com/icon_x2.png", url.spec());
282
283  SetDisplayDeviceScaleFactor(3.0f);
284  url = FindBestMatchingIcon(icons);
285  EXPECT_EQ("http://foo.com/icon_x3.png", url.spec());
286}
287
288TEST_F(ShortcutHelperTest, DeviceDensityFallback) {
289  // If there is no perfect icon but and no icon of the current display density,
290  // an icon of density 1.0 will be used.
291  std::vector<gfx::Size> sizes;
292  sizes.push_back(gfx::Size(2, 2));
293
294  std::vector<content::Manifest::Icon> icons;
295  icons.push_back(CreateIcon("http://foo.com/icon_x1.png", "", 1.0, sizes));
296  icons.push_back(CreateIcon("http://foo.com/icon_x2.png", "", 2.0, sizes));
297
298  SetDisplayDeviceScaleFactor(3.0f);
299  GURL url = FindBestMatchingIcon(icons);
300  EXPECT_EQ("http://foo.com/icon_x1.png", url.spec());
301}
302
303TEST_F(ShortcutHelperTest, DoNotUseOtherDensities) {
304  // If there are only icons of densities that are not the current display
305  // density or the default density, they are ignored.
306  std::vector<gfx::Size> sizes;
307  sizes.push_back(gfx::Size(2, 2));
308
309  std::vector<content::Manifest::Icon> icons;
310  icons.push_back(CreateIcon("http://foo.com/icon_x2.png", "", 2.0, sizes));
311
312  SetDisplayDeviceScaleFactor(3.0f);
313  GURL url = FindBestMatchingIcon(icons);
314  EXPECT_TRUE(url.is_empty());
315}
316
317TEST_F(ShortcutHelperTest, NotSquareIconsAreIgnored) {
318  std::vector<gfx::Size> sizes;
319  sizes.push_back(gfx::Size(20, 2));
320
321  std::vector<content::Manifest::Icon> icons;
322  icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes));
323
324  GURL url = FindBestMatchingIcon(icons);
325  EXPECT_TRUE(url.is_empty());
326}
327
328TEST_F(ShortcutHelperTest, ClosestIconToPreferred) {
329  // This test verifies ShortcutHelper::FindBestMatchingIcon by passing
330  // different icon sizes and checking which one is picked.
331  // The Device Scale Factor is 1.0 and the preferred icon size is returned by
332  // GetPreferredIconSizeInDp().
333  int very_small = GetPreferredIconSizeInDp() / 4;
334  int small = GetPreferredIconSizeInDp() / 2;
335  int bit_small = GetPreferredIconSizeInDp() - 1;
336  int bit_big = GetPreferredIconSizeInDp() + 1;
337  int big = GetPreferredIconSizeInDp() * 2;
338  int very_big = GetPreferredIconSizeInDp() * 4;
339
340  // (very_small, bit_small) => bit_small
341  {
342    std::vector<gfx::Size> sizes_1;
343    sizes_1.push_back(gfx::Size(very_small, very_small));
344
345    std::vector<gfx::Size> sizes_2;
346    sizes_2.push_back(gfx::Size(bit_small, bit_small));
347
348    std::vector<content::Manifest::Icon> icons;
349    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
350    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
351
352    GURL url = FindBestMatchingIcon(icons);
353    EXPECT_EQ("http://foo.com/icon.png", url.spec());
354  }
355
356  // (very_small, bit_small, smaller) => bit_small
357  {
358    std::vector<gfx::Size> sizes_1;
359    sizes_1.push_back(gfx::Size(very_small, very_small));
360
361    std::vector<gfx::Size> sizes_2;
362    sizes_2.push_back(gfx::Size(bit_small, bit_small));
363
364    std::vector<gfx::Size> sizes_3;
365    sizes_3.push_back(gfx::Size(small, small));
366
367    std::vector<content::Manifest::Icon> icons;
368    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
369    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
370    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_3));
371
372    GURL url = FindBestMatchingIcon(icons);
373    EXPECT_EQ("http://foo.com/icon.png", url.spec());
374  }
375
376  // (very_big, big) => big
377  {
378    std::vector<gfx::Size> sizes_1;
379    sizes_1.push_back(gfx::Size(very_big, very_big));
380
381    std::vector<gfx::Size> sizes_2;
382    sizes_2.push_back(gfx::Size(big, big));
383
384    std::vector<content::Manifest::Icon> icons;
385    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
386    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
387
388    GURL url = FindBestMatchingIcon(icons);
389    EXPECT_EQ("http://foo.com/icon.png", url.spec());
390  }
391
392  // (very_big, big, bit_big) => bit_big
393  {
394    std::vector<gfx::Size> sizes_1;
395    sizes_1.push_back(gfx::Size(very_big, very_big));
396
397    std::vector<gfx::Size> sizes_2;
398    sizes_2.push_back(gfx::Size(big, big));
399
400    std::vector<gfx::Size> sizes_3;
401    sizes_3.push_back(gfx::Size(bit_big, bit_big));
402
403    std::vector<content::Manifest::Icon> icons;
404    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
405    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_2));
406    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_3));
407
408    GURL url = FindBestMatchingIcon(icons);
409    EXPECT_EQ("http://foo.com/icon.png", url.spec());
410  }
411
412  // (bit_small, very_big) => very_big
413  {
414    std::vector<gfx::Size> sizes_1;
415    sizes_1.push_back(gfx::Size(bit_small, bit_small));
416
417    std::vector<gfx::Size> sizes_2;
418    sizes_2.push_back(gfx::Size(very_big, very_big));
419
420    std::vector<content::Manifest::Icon> icons;
421    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
422    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
423
424    GURL url = FindBestMatchingIcon(icons);
425    EXPECT_EQ("http://foo.com/icon.png", url.spec());
426  }
427
428  // (bit_small, bit_big) => bit_big
429  {
430    std::vector<gfx::Size> sizes_1;
431    sizes_1.push_back(gfx::Size(bit_small, bit_small));
432
433    std::vector<gfx::Size> sizes_2;
434    sizes_2.push_back(gfx::Size(bit_big, bit_big));
435
436    std::vector<content::Manifest::Icon> icons;
437    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
438    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
439
440    GURL url = FindBestMatchingIcon(icons);
441    EXPECT_EQ("http://foo.com/icon.png", url.spec());
442  }
443}
444
445TEST_F(ShortcutHelperTest, UseAnyIfNoPreferredSize) {
446  // 'any' (ie. gfx::Size(0,0)) should be used if there is no icon of a
447  // preferred size. An icon with the current device scale factor is preferred
448  // over one with the default density.
449
450  // 'any' with preferred size => preferred size
451  {
452    std::vector<gfx::Size> sizes_1;
453    sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp(),
454                                GetPreferredIconSizeInDp()));
455    std::vector<gfx::Size> sizes_2;
456    sizes_2.push_back(gfx::Size(0,0));
457
458    std::vector<content::Manifest::Icon> icons;
459    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_1));
460    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_2));
461
462    GURL url = FindBestMatchingIcon(icons);
463    EXPECT_EQ("http://foo.com/icon.png", url.spec());
464  }
465
466  // 'any' with nearly preferred size => any
467  {
468    std::vector<gfx::Size> sizes_1;
469    sizes_1.push_back(gfx::Size(GetPreferredIconSizeInDp() + 1,
470                                GetPreferredIconSizeInDp() + 1));
471    std::vector<gfx::Size> sizes_2;
472    sizes_2.push_back(gfx::Size(0,0));
473
474    std::vector<content::Manifest::Icon> icons;
475    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes_1));
476    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 1.0, sizes_2));
477
478    GURL url = FindBestMatchingIcon(icons);
479    EXPECT_EQ("http://foo.com/icon.png", url.spec());
480  }
481
482  // 'any' on default density and current density => current density.
483  {
484    std::vector<gfx::Size> sizes;
485    sizes.push_back(gfx::Size(0,0));
486
487    std::vector<content::Manifest::Icon> icons;
488    icons.push_back(CreateIcon("http://foo.com/icon_no.png", "", 1.0, sizes));
489    icons.push_back(CreateIcon("http://foo.com/icon.png", "", 3.0, sizes));
490
491    SetDisplayDeviceScaleFactor(3.0f);
492    GURL url = FindBestMatchingIcon(icons);
493    EXPECT_EQ("http://foo.com/icon.png", url.spec());
494  }
495}
496