OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 <memory> | |
6 | |
7 #include "base/macros.h" | |
8 #include "chrome/browser/extensions/extension_app_icon.h" | |
9 #include "chrome/browser/extensions/extension_app_icon_delegate.h" | |
10 #include "chrome/browser/extensions/extension_app_icon_loader.h" | |
11 #include "chrome/browser/extensions/extension_app_icon_service.h" | |
12 #include "chrome/browser/extensions/extension_service.h" | |
13 #include "chrome/browser/extensions/extension_service_test_base.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/browser/ui/app_icon_loader_delegate.h" | |
16 #include "chrome/common/chrome_constants.h" | |
17 #include "extensions/common/constants.h" | |
18 #include "ui/app_list/app_list_item.h" | |
19 | |
20 #if defined(OS_CHROMEOS) | |
21 #include "chrome/browser/chromeos/arc/arc_util.h" | |
22 #include "chrome/browser/ui/app_list/arc/arc_app_test.h" | |
23 #include "chrome/browser/ui/app_list/extension_app_model_builder.h" | |
24 #include "chrome/browser/ui/app_list/search/extension_app_result.h" | |
25 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h" | |
26 #include "components/arc/test/fake_app_instance.h" | |
27 #endif | |
28 | |
29 namespace extensions { | |
30 | |
31 namespace { | |
32 | |
33 constexpr char kTestAppId[] = "emfkafnhnpcmabnnkckkchdilgeoekbo"; | |
34 | |
35 // Receives icon image updates from ExtensionAppIcon. | |
36 class TestAppIcon : public ExtensionAppIconDelegate { | |
37 public: | |
38 TestAppIcon(content::BrowserContext* context, | |
39 const std::string& app_id, | |
40 int size) { | |
41 app_icon_ = | |
42 ExtensionAppIconService::Get(context)->CreateIcon(this, app_id, size); | |
43 DCHECK(app_icon_); | |
44 } | |
45 | |
46 ~TestAppIcon() override = default; | |
47 | |
48 int icon_update_count() const { return icon_update_count_; } | |
49 ExtensionAppIcon* app_icon() { return app_icon_.get(); } | |
50 const gfx::ImageSkia& image_skia() const { return app_icon_->image_skia(); } | |
51 | |
52 void WaitIconUpdated() { | |
53 base::RunLoop run_loop; | |
54 icon_updated_callback_ = run_loop.QuitClosure(); | |
55 run_loop.Run(); | |
56 icon_updated_callback_.Reset(); | |
xiyuan
2017/04/20 16:36:32
nit: We can avoid this call by making |icon_update
khmel
2017/04/20 17:46:18
I also found base::ResetAndReturn approach. Would
xiyuan
2017/04/20 17:52:46
That works.
| |
57 } | |
58 | |
59 private: | |
60 void OnIconUpdated(ExtensionAppIcon* icon) override { | |
61 ++icon_update_count_; | |
62 if (!icon_updated_callback_.is_null()) | |
63 icon_updated_callback_.Run(); | |
64 } | |
65 | |
66 std::unique_ptr<ExtensionAppIcon> app_icon_; | |
67 | |
68 int icon_update_count_ = 0; | |
69 | |
70 base::Closure icon_updated_callback_; | |
71 | |
72 DISALLOW_COPY_AND_ASSIGN(TestAppIcon); | |
73 }; | |
74 | |
75 // Receives icon image updates from ExtensionAppIconLoader. | |
76 class TestAppIconLoader : public AppIconLoaderDelegate { | |
77 public: | |
78 TestAppIconLoader() = default; | |
79 ~TestAppIconLoader() override = default; | |
80 | |
81 // AppIconLoaderDelegate: | |
82 void OnAppImageUpdated(const std::string& app_id, | |
83 const gfx::ImageSkia& image) override { | |
84 image_skia_ = image; | |
85 } | |
86 | |
87 const gfx::ImageSkia& image_skia() { return image_skia_; } | |
88 | |
89 private: | |
90 gfx::ImageSkia image_skia_; | |
91 | |
92 DISALLOW_COPY_AND_ASSIGN(TestAppIconLoader); | |
93 }; | |
94 | |
95 // Returns true if provided |image| consists from only empty pixels. | |
96 bool IsBlankImage(const gfx::ImageSkia& image) { | |
97 if (!image.width() || !image.height()) | |
98 return false; | |
99 | |
100 const SkBitmap* bitmap = image.bitmap(); | |
101 DCHECK(bitmap); | |
102 DCHECK_EQ(bitmap->width(), image.width()); | |
103 DCHECK_EQ(bitmap->height(), image.height()); | |
104 | |
105 for (int x = 0; x < bitmap->width(); ++x) { | |
106 for (int y = 0; y < bitmap->height(); ++y) { | |
107 if (*bitmap->getAddr32(x, y)) | |
108 return false; | |
109 } | |
110 } | |
111 return true; | |
112 } | |
113 | |
114 // Returns true if provided |image| is grayscale. | |
115 bool IsGrayscaleImage(const gfx::ImageSkia& image) { | |
116 const SkBitmap* bitmap = image.bitmap(); | |
117 DCHECK(bitmap); | |
118 for (int x = 0; x < bitmap->width(); ++x) { | |
119 for (int y = 0; y < bitmap->height(); ++y) { | |
120 const unsigned int pixel = *bitmap->getAddr32(x, y); | |
121 if ((pixel & 0xff) != ((pixel >> 8) & 0xff) || | |
122 (pixel & 0xff) != ((pixel >> 16) & 0xff)) { | |
123 return false; | |
124 } | |
125 } | |
126 } | |
127 return true; | |
128 } | |
129 | |
130 // Returns true if provided |image1| and |image2| are equal. | |
131 bool AreEqual(const gfx::ImageSkia& image1, const gfx::ImageSkia& image2) { | |
xiyuan
2017/04/20 16:36:32
You can use gfx::test::AreImagesEqual for this.
e
khmel
2017/04/20 17:46:18
Done.
| |
132 const SkBitmap* bitmap1 = image1.bitmap(); | |
133 DCHECK(bitmap1); | |
134 const SkBitmap* bitmap2 = image2.bitmap(); | |
135 DCHECK(bitmap2); | |
136 | |
137 if (bitmap1->width() != bitmap2->width() || | |
138 bitmap1->height() != bitmap2->height()) { | |
139 return false; | |
140 } | |
141 | |
142 for (int x = 0; x < bitmap1->width(); ++x) { | |
143 for (int y = 0; y < bitmap2->height(); ++y) { | |
144 if (*bitmap1->getAddr32(x, y) != *bitmap2->getAddr32(x, y)) | |
145 return false; | |
146 } | |
147 } | |
148 return true; | |
149 } | |
150 | |
151 } // namespace | |
152 | |
153 class ExtensionAppIconTest : public ExtensionServiceTestBase { | |
154 public: | |
155 ExtensionAppIconTest() = default; | |
156 ~ExtensionAppIconTest() override = default; | |
157 | |
158 // ExtensionServiceTestBase: | |
159 void SetUp() override { | |
160 ExtensionServiceTestBase::SetUp(); | |
161 | |
162 const base::FilePath source_install_dir = | |
163 data_dir().AppendASCII("app_list").AppendASCII("Extensions"); | |
164 const base::FilePath pref_path = | |
165 source_install_dir.DirName().Append(chrome::kPreferencesFilename); | |
166 InitializeInstalledExtensionService(pref_path, source_install_dir); | |
167 service_->Init(); | |
168 } | |
169 | |
170 private: | |
171 DISALLOW_COPY_AND_ASSIGN(ExtensionAppIconTest); | |
172 }; | |
173 | |
174 TEST_F(ExtensionAppIconTest, IconLifeCycle) { | |
175 TestAppIcon reference_icon(profile(), kTestAppId, | |
176 extension_misc::EXTENSION_ICON_MEDIUM); | |
177 EXPECT_EQ(1, reference_icon.icon_update_count()); | |
178 // By default no representation in image. | |
179 EXPECT_FALSE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
180 | |
181 // Accessing to bitmap requests default representations. | |
182 // Defualt blank image must be provided without an update. | |
183 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
xiyuan
2017/04/20 16:36:32
nit: avoid bitmap() since it is going way. Use Get
khmel
2017/04/20 17:46:18
Done.
| |
184 EXPECT_EQ(1, reference_icon.icon_update_count()); | |
185 EXPECT_TRUE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
186 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
187 reference_icon.image_skia().width()); | |
188 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
189 reference_icon.image_skia().height()); | |
190 EXPECT_TRUE(IsBlankImage(reference_icon.image_skia())); | |
191 | |
192 // Wait until real image is loaded. | |
193 reference_icon.WaitIconUpdated(); | |
194 EXPECT_EQ(2, reference_icon.icon_update_count()); | |
195 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
196 EXPECT_FALSE(IsGrayscaleImage(reference_icon.image_skia())); | |
197 | |
198 const gfx::ImageSkia image_before_disable = reference_icon.image_skia(); | |
199 // Disable extension. This should update icon and provide grayed image inline. | |
200 // Note update might be sent twice in CrOS. | |
201 service()->DisableExtension(kTestAppId, Extension::DISABLE_CORRUPTED); | |
202 const int update_count_after_disable = reference_icon.icon_update_count(); | |
203 EXPECT_NE(2, update_count_after_disable); | |
204 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
205 EXPECT_TRUE(IsGrayscaleImage(reference_icon.image_skia())); | |
206 | |
207 // Reenable extension. It should match previous enabled image | |
208 service()->EnableExtension(kTestAppId); | |
209 EXPECT_NE(update_count_after_disable, reference_icon.icon_update_count()); | |
210 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_disable)); | |
211 } | |
212 | |
213 #if defined(OS_CHROMEOS) | |
214 class ExtensionAppIconWithModelTest : public ExtensionAppIconTest { | |
215 public: | |
216 ExtensionAppIconWithModelTest() = default; | |
217 ~ExtensionAppIconWithModelTest() override = default; | |
218 | |
219 protected: | |
220 void CreateBuilder() { | |
221 model_.reset(new app_list::AppListModel); | |
222 controller_.reset(new test::TestAppListControllerDelegate); | |
223 builder_.reset(new ExtensionAppModelBuilder(controller_.get())); | |
224 builder_->InitializeWithProfile(profile(), model_.get()); | |
225 } | |
226 | |
227 void ResetBuilder() { | |
228 builder_.reset(); | |
229 controller_.reset(); | |
230 model_.reset(); | |
231 } | |
232 | |
233 app_list::AppListItem* FindAppListItem(const std::string& app_id) { | |
234 return model_->FindItem(app_id); | |
235 } | |
236 | |
237 test::TestAppListControllerDelegate* app_list_controller() { | |
238 return controller_.get(); | |
239 } | |
240 | |
241 private: | |
242 std::unique_ptr<app_list::AppListModel> model_; | |
243 std::unique_ptr<test::TestAppListControllerDelegate> controller_; | |
244 std::unique_ptr<ExtensionAppModelBuilder> builder_; | |
245 | |
246 DISALLOW_COPY_AND_ASSIGN(ExtensionAppIconWithModelTest); | |
247 }; | |
248 | |
249 // Validates that icons are the same for different consumers. | |
250 TEST_F(ExtensionAppIconWithModelTest, IconsTheSame) { | |
251 CreateBuilder(); | |
252 | |
253 TestAppIcon reference_icon(profile(), kTestAppId, | |
254 extension_misc::EXTENSION_ICON_MEDIUM); | |
255 | |
256 // Wait until reference data is loaded. | |
257 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
xiyuan
2017/04/20 16:36:31
nit: GetRepresentation(1.0f)
khmel
2017/04/20 17:46:18
Done.
| |
258 while (IsBlankImage(reference_icon.image_skia())) | |
xiyuan
2017/04/20 16:36:31
Why using a while loop instead of WaitIconUpdated
khmel
2017/04/20 17:46:18
Hmmm...., you are right.
| |
259 base::RunLoop().RunUntilIdle(); | |
260 | |
261 app_list::ExtensionAppResult search(profile(), kTestAppId, | |
262 app_list_controller(), true); | |
263 while (!AreEqual(reference_icon.image_skia(), search.icon())) | |
264 base::RunLoop().RunUntilIdle(); | |
265 | |
266 app_list::AppListItem* app_list_item = FindAppListItem(kTestAppId); | |
267 ASSERT_TRUE(app_list_item); | |
268 while (!AreEqual(reference_icon.image_skia(), app_list_item->icon())) | |
269 base::RunLoop().RunUntilIdle(); | |
270 | |
271 TestAppIconLoader loader_delegate; | |
272 ExtensionAppIconLoader loader( | |
273 profile(), extension_misc::EXTENSION_ICON_MEDIUM, &loader_delegate); | |
274 loader.FetchImage(kTestAppId); | |
275 while (!AreEqual(reference_icon.image_skia(), loader_delegate.image_skia())) | |
276 base::RunLoop().RunUntilIdle(); | |
277 | |
278 ResetBuilder(); | |
279 } | |
280 | |
281 TEST_F(ExtensionAppIconWithModelTest, ChromeBadging) { | |
282 ArcAppTest arc_test; | |
283 arc_test.SetUp(profile()); | |
284 | |
285 TestAppIcon reference_icon(profile(), kTestAppId, | |
286 extension_misc::EXTENSION_ICON_MEDIUM); | |
287 // Wait until reference data is loaded. | |
288 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
xiyuan
2017/04/20 16:36:31
nit: GetRepresentation(1.0f)
khmel
2017/04/20 17:46:18
Done.
| |
289 while (IsBlankImage(reference_icon.image_skia())) | |
290 base::RunLoop().RunUntilIdle(); | |
291 | |
292 const int icon_update_count_before_badging = | |
293 reference_icon.icon_update_count(); | |
294 const gfx::ImageSkia image_before_badging = reference_icon.image_skia(); | |
295 | |
296 // Badging should be applied once package is installed. | |
297 arc_test.app_instance()->RefreshAppList(); | |
298 std::vector<arc::mojom::AppInfo> fake_apps = arc_test.fake_apps(); | |
299 fake_apps[0].package_name = arc_test.fake_packages()[0].package_name; | |
300 arc_test.app_instance()->SendRefreshAppList(fake_apps); | |
301 arc_test.app_instance()->SendRefreshPackageList(arc_test.fake_packages()); | |
302 EXPECT_EQ(icon_update_count_before_badging + 1, | |
xiyuan
2017/04/20 16:36:32
nit: Other tests are using a GetCountAndReset that
khmel
2017/04/20 17:46:18
Done.
| |
303 reference_icon.icon_update_count()); | |
304 EXPECT_FALSE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
305 | |
306 // Opts out the Play Store. Badge should be gone and icon image is the same | |
307 // as it was before badging. | |
308 arc::SetArcPlayStoreEnabledForProfile(profile(), false); | |
309 EXPECT_EQ(icon_update_count_before_badging + 2, | |
310 reference_icon.icon_update_count()); | |
311 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
312 } | |
313 #endif | |
314 | |
315 } // namespace extensions | |
OLD | NEW |