1 // Copyright 2018 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 <set>
6 #include <sstream>
7 #include <utility>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/optional.h"
14 #include "base/run_loop.h"
15 #include "components/services/app_service/app_service_impl.h"
16 #include "components/services/app_service/public/cpp/intent_filter_util.h"
17 #include "components/services/app_service/public/cpp/intent_util.h"
18 #include "components/services/app_service/public/cpp/preferred_apps_list.h"
19 #include "components/services/app_service/public/cpp/publisher_base.h"
20 #include "components/services/app_service/public/mojom/types.mojom.h"
21 #include "content/public/test/browser_task_environment.h"
22 #include "mojo/public/cpp/bindings/pending_receiver.h"
23 #include "mojo/public/cpp/bindings/pending_remote.h"
24 #include "mojo/public/cpp/bindings/receiver_set.h"
25 #include "mojo/public/cpp/bindings/remote.h"
26 #include "mojo/public/cpp/bindings/remote_set.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28
29 namespace apps {
30
31 class FakePublisher : public apps::PublisherBase {
32 public:
FakePublisher(AppServiceImpl * impl,apps::mojom::AppType app_type,std::vector<std::string> initial_app_ids)33 FakePublisher(AppServiceImpl* impl,
34 apps::mojom::AppType app_type,
35 std::vector<std::string> initial_app_ids)
36 : app_type_(app_type), known_app_ids_(std::move(initial_app_ids)) {
37 mojo::PendingRemote<apps::mojom::Publisher> remote;
38 receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
39 impl->RegisterPublisher(std::move(remote), app_type_);
40 }
41
PublishMoreApps(std::vector<std::string> app_ids)42 void PublishMoreApps(std::vector<std::string> app_ids) {
43 for (auto& subscriber : subscribers_) {
44 CallOnApps(subscriber.get(), app_ids, /*uninstall=*/false);
45 }
46 for (const auto& app_id : app_ids) {
47 known_app_ids_.push_back(app_id);
48 }
49 }
50
UninstallApps(std::vector<std::string> app_ids,AppServiceImpl * impl)51 void UninstallApps(std::vector<std::string> app_ids, AppServiceImpl* impl) {
52 for (auto& subscriber : subscribers_) {
53 CallOnApps(subscriber.get(), app_ids, /*uninstall=*/true);
54 }
55 for (const auto& app_id : app_ids) {
56 known_app_ids_.push_back(app_id);
57 impl->RemovePreferredApp(app_type_, app_id);
58 }
59 }
60
61 std::string load_icon_app_id;
62
63 private:
Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,apps::mojom::ConnectOptionsPtr opts)64 void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
65 apps::mojom::ConnectOptionsPtr opts) override {
66 mojo::Remote<apps::mojom::Subscriber> subscriber(
67 std::move(subscriber_remote));
68 CallOnApps(subscriber.get(), known_app_ids_, /*uninstall=*/false);
69 subscribers_.Add(std::move(subscriber));
70 }
71
LoadIcon(const std::string & app_id,apps::mojom::IconKeyPtr icon_key,apps::mojom::IconType icon_type,int32_t size_hint_in_dip,bool allow_placeholder_icon,LoadIconCallback callback)72 void LoadIcon(const std::string& app_id,
73 apps::mojom::IconKeyPtr icon_key,
74 apps::mojom::IconType icon_type,
75 int32_t size_hint_in_dip,
76 bool allow_placeholder_icon,
77 LoadIconCallback callback) override {
78 load_icon_app_id = app_id;
79 std::move(callback).Run(apps::mojom::IconValue::New());
80 }
81
Launch(const std::string & app_id,int32_t event_flags,apps::mojom::LaunchSource launch_source,int64_t display_id)82 void Launch(const std::string& app_id,
83 int32_t event_flags,
84 apps::mojom::LaunchSource launch_source,
85 int64_t display_id) override {}
86
CallOnApps(apps::mojom::Subscriber * subscriber,std::vector<std::string> & app_ids,bool uninstall)87 void CallOnApps(apps::mojom::Subscriber* subscriber,
88 std::vector<std::string>& app_ids,
89 bool uninstall) {
90 std::vector<apps::mojom::AppPtr> apps;
91 for (const auto& app_id : app_ids) {
92 auto app = apps::mojom::App::New();
93 app->app_type = app_type_;
94 app->app_id = app_id;
95 if (uninstall) {
96 app->readiness = apps::mojom::Readiness::kUninstalledByUser;
97 }
98 apps.push_back(std::move(app));
99 }
100 subscriber->OnApps(std::move(apps));
101 }
102
103 apps::mojom::AppType app_type_;
104 std::vector<std::string> known_app_ids_;
105 mojo::ReceiverSet<apps::mojom::Publisher> receivers_;
106 mojo::RemoteSet<apps::mojom::Subscriber> subscribers_;
107 };
108
109 class FakeSubscriber : public apps::mojom::Subscriber {
110 public:
FakeSubscriber(AppServiceImpl * impl)111 explicit FakeSubscriber(AppServiceImpl* impl) {
112 mojo::PendingRemote<apps::mojom::Subscriber> remote;
113 receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
114 impl->RegisterSubscriber(std::move(remote), nullptr);
115 }
116
AppIdsSeen()117 std::string AppIdsSeen() {
118 std::stringstream ss;
119 for (const auto& app_id : app_ids_seen_) {
120 ss << app_id;
121 }
122 return ss.str();
123 }
124
PreferredApps()125 PreferredAppsList& PreferredApps() { return preferred_apps_; }
126
127 private:
OnApps(std::vector<apps::mojom::AppPtr> deltas)128 void OnApps(std::vector<apps::mojom::AppPtr> deltas) override {
129 for (const auto& delta : deltas) {
130 app_ids_seen_.insert(delta->app_id);
131 if (delta->readiness == apps::mojom::Readiness::kUninstalledByUser) {
132 preferred_apps_.DeleteAppId(delta->app_id);
133 }
134 }
135 }
136
Clone(mojo::PendingReceiver<apps::mojom::Subscriber> receiver)137 void Clone(mojo::PendingReceiver<apps::mojom::Subscriber> receiver) override {
138 receivers_.Add(this, std::move(receiver));
139 }
140
OnPreferredAppSet(const std::string & app_id,apps::mojom::IntentFilterPtr intent_filter)141 void OnPreferredAppSet(const std::string& app_id,
142 apps::mojom::IntentFilterPtr intent_filter) override {
143 preferred_apps_.AddPreferredApp(app_id, intent_filter);
144 }
145
OnPreferredAppRemoved(const std::string & app_id,apps::mojom::IntentFilterPtr intent_filter)146 void OnPreferredAppRemoved(
147 const std::string& app_id,
148 apps::mojom::IntentFilterPtr intent_filter) override {
149 preferred_apps_.DeletePreferredApp(app_id, intent_filter);
150 }
151
InitializePreferredApps(PreferredAppsList::PreferredApps preferred_apps)152 void InitializePreferredApps(
153 PreferredAppsList::PreferredApps preferred_apps) override {
154 preferred_apps_.Init(preferred_apps);
155 }
156
157 mojo::ReceiverSet<apps::mojom::Subscriber> receivers_;
158 std::set<std::string> app_ids_seen_;
159 apps::PreferredAppsList preferred_apps_;
160 };
161
162 class AppServiceImplTest : public testing::Test {
163 protected:
164 // base::test::TaskEnvironment task_environment_;
165 content::BrowserTaskEnvironment task_environment_;
166 base::ScopedTempDir temp_dir_;
167 };
168
TEST_F(AppServiceImplTest,PubSub)169 TEST_F(AppServiceImplTest, PubSub) {
170 const int size_hint_in_dip = 64;
171
172 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
173 AppServiceImpl impl(temp_dir_.GetPath(),
174 /*is_share_intents_supported=*/false);
175
176 // Start with one subscriber.
177 FakeSubscriber sub0(&impl);
178 impl.FlushMojoCallsForTesting();
179 EXPECT_EQ("", sub0.AppIdsSeen());
180
181 // Add one publisher.
182 FakePublisher pub0(&impl, apps::mojom::AppType::kArc,
183 std::vector<std::string>{"A", "B"});
184 impl.FlushMojoCallsForTesting();
185 EXPECT_EQ("AB", sub0.AppIdsSeen());
186
187 // Have that publisher publish more apps.
188 pub0.PublishMoreApps(std::vector<std::string>{"C", "D", "E"});
189 impl.FlushMojoCallsForTesting();
190 EXPECT_EQ("ABCDE", sub0.AppIdsSeen());
191
192 // Add a second publisher.
193 FakePublisher pub1(&impl, apps::mojom::AppType::kBuiltIn,
194 std::vector<std::string>{"m"});
195 impl.FlushMojoCallsForTesting();
196 EXPECT_EQ("ABCDEm", sub0.AppIdsSeen());
197
198 // Have both publishers publish more apps.
199 pub0.PublishMoreApps(std::vector<std::string>{"F"});
200 pub1.PublishMoreApps(std::vector<std::string>{"n"});
201 impl.FlushMojoCallsForTesting();
202 EXPECT_EQ("ABCDEFmn", sub0.AppIdsSeen());
203
204 // Add a second subscriber.
205 FakeSubscriber sub1(&impl);
206 impl.FlushMojoCallsForTesting();
207 EXPECT_EQ("ABCDEFmn", sub0.AppIdsSeen());
208 EXPECT_EQ("ABCDEFmn", sub1.AppIdsSeen());
209
210 // Publish more apps.
211 pub1.PublishMoreApps(std::vector<std::string>{"o", "p", "q"});
212 impl.FlushMojoCallsForTesting();
213 EXPECT_EQ("ABCDEFmnopq", sub0.AppIdsSeen());
214 EXPECT_EQ("ABCDEFmnopq", sub1.AppIdsSeen());
215
216 // Add a third publisher.
217 FakePublisher pub2(&impl, apps::mojom::AppType::kCrostini,
218 std::vector<std::string>{"$"});
219 impl.FlushMojoCallsForTesting();
220 EXPECT_EQ("$ABCDEFmnopq", sub0.AppIdsSeen());
221 EXPECT_EQ("$ABCDEFmnopq", sub1.AppIdsSeen());
222
223 // Publish more apps.
224 pub2.PublishMoreApps(std::vector<std::string>{"&"});
225 pub1.PublishMoreApps(std::vector<std::string>{"r"});
226 pub0.PublishMoreApps(std::vector<std::string>{"G"});
227 impl.FlushMojoCallsForTesting();
228 EXPECT_EQ("$&ABCDEFGmnopqr", sub0.AppIdsSeen());
229 EXPECT_EQ("$&ABCDEFGmnopqr", sub1.AppIdsSeen());
230
231 // Call LoadIcon on the impl twice.
232 //
233 // The first time (i == 0), it should be forwarded onto the AppType::kBuiltIn
234 // publisher (which is pub1) and no other publisher.
235 //
236 // The second time (i == 1), passing AppType::kUnknown, none of the
237 // publishers' LoadIcon's should fire, but the callback should still be run.
238 for (int i = 0; i < 2; i++) {
239 auto app_type = i == 0 ? apps::mojom::AppType::kBuiltIn
240 : apps::mojom::AppType::kUnknown;
241
242 bool callback_ran = false;
243 pub0.load_icon_app_id = "-";
244 pub1.load_icon_app_id = "-";
245 pub2.load_icon_app_id = "-";
246 auto icon_key = apps::mojom::IconKey::New(0, 0, 0);
247 constexpr bool allow_placeholder_icon = false;
248 impl.LoadIcon(
249 app_type, "o", std::move(icon_key),
250 apps::mojom::IconType::kUncompressed, size_hint_in_dip,
251 allow_placeholder_icon,
252 base::BindOnce(
253 [](bool* ran, apps::mojom::IconValuePtr iv) { *ran = true; },
254 &callback_ran));
255 impl.FlushMojoCallsForTesting();
256 EXPECT_TRUE(callback_ran);
257 EXPECT_EQ("-", pub0.load_icon_app_id);
258 EXPECT_EQ(i == 0 ? "o" : "-", pub1.load_icon_app_id);
259 EXPECT_EQ("-", pub2.load_icon_app_id);
260 }
261 }
262
263 // TODO(https://crbug.com/1074596) Test to see if the flakiness is fixed. If it
264 // is not fixed, please update to the same bug.
TEST_F(AppServiceImplTest,PreferredApps)265 TEST_F(AppServiceImplTest, PreferredApps) {
266 // Test Initialize.
267 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
268 AppServiceImpl impl(temp_dir_.GetPath(),
269 /*is_share_intents_supported=*/false);
270 impl.GetPreferredAppsForTesting().Init();
271
272 const char kAppId1[] = "abcdefg";
273 const char kAppId2[] = "aaaaaaa";
274 GURL filter_url = GURL("https://www.google.com/abc");
275 auto intent_filter = apps_util::CreateIntentFilterForUrlScope(filter_url);
276
277 impl.GetPreferredAppsForTesting().AddPreferredApp(kAppId1, intent_filter);
278
279 // Add one subscriber.
280 FakeSubscriber sub0(&impl);
281 task_environment_.RunUntilIdle();
282 EXPECT_EQ(sub0.PreferredApps().GetValue(),
283 impl.GetPreferredAppsForTesting().GetValue());
284
285 // Add another subscriber.
286 FakeSubscriber sub1(&impl);
287 task_environment_.RunUntilIdle();
288 EXPECT_EQ(sub1.PreferredApps().GetValue(),
289 impl.GetPreferredAppsForTesting().GetValue());
290
291 FakePublisher pub0(&impl, apps::mojom::AppType::kArc,
292 std::vector<std::string>{kAppId1, kAppId2});
293 task_environment_.RunUntilIdle();
294
295 // Test sync preferred app to all subscribers.
296 filter_url = GURL("https://www.abc.com/");
297 GURL another_filter_url = GURL("https://www.test.com/");
298 intent_filter = apps_util::CreateIntentFilterForUrlScope(filter_url);
299 auto another_intent_filter =
300 apps_util::CreateIntentFilterForUrlScope(another_filter_url);
301
302 task_environment_.RunUntilIdle();
303 EXPECT_EQ(base::nullopt,
304 sub0.PreferredApps().FindPreferredAppForUrl(filter_url));
305 EXPECT_EQ(base::nullopt,
306 sub1.PreferredApps().FindPreferredAppForUrl(filter_url));
307 EXPECT_EQ(base::nullopt,
308 sub0.PreferredApps().FindPreferredAppForUrl(another_filter_url));
309 EXPECT_EQ(base::nullopt,
310 sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
311
312 impl.AddPreferredApp(
313 apps::mojom::AppType::kUnknown, kAppId2, intent_filter->Clone(),
314 apps_util::CreateIntentFromUrl(filter_url), /*from_publisher=*/true);
315 impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId2,
316 another_intent_filter->Clone(),
317 apps_util::CreateIntentFromUrl(another_filter_url),
318 /*from_publisher=*/true);
319 task_environment_.RunUntilIdle();
320 EXPECT_EQ(kAppId2, sub0.PreferredApps().FindPreferredAppForUrl(filter_url));
321 EXPECT_EQ(kAppId2, sub1.PreferredApps().FindPreferredAppForUrl(filter_url));
322 EXPECT_EQ(kAppId2,
323 sub0.PreferredApps().FindPreferredAppForUrl(another_filter_url));
324 EXPECT_EQ(kAppId2,
325 sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
326
327 // Test that uninstall removes all the settings for the app.
328 pub0.UninstallApps(std::vector<std::string>{kAppId2}, &impl);
329 task_environment_.RunUntilIdle();
330 EXPECT_EQ(base::nullopt,
331 sub0.PreferredApps().FindPreferredAppForUrl(filter_url));
332 EXPECT_EQ(base::nullopt,
333 sub1.PreferredApps().FindPreferredAppForUrl(filter_url));
334 EXPECT_EQ(base::nullopt,
335 sub0.PreferredApps().FindPreferredAppForUrl(another_filter_url));
336 EXPECT_EQ(base::nullopt,
337 sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
338
339 impl.AddPreferredApp(
340 apps::mojom::AppType::kUnknown, kAppId2, intent_filter->Clone(),
341 apps_util::CreateIntentFromUrl(filter_url), /*from_publisher=*/true);
342 impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId2,
343 another_intent_filter->Clone(),
344 apps_util::CreateIntentFromUrl(another_filter_url),
345 /*from_publisher=*/true);
346 task_environment_.RunUntilIdle();
347
348 EXPECT_EQ(kAppId2, sub0.PreferredApps().FindPreferredAppForUrl(filter_url));
349 EXPECT_EQ(kAppId2, sub1.PreferredApps().FindPreferredAppForUrl(filter_url));
350 EXPECT_EQ(kAppId2,
351 sub0.PreferredApps().FindPreferredAppForUrl(another_filter_url));
352 EXPECT_EQ(kAppId2,
353 sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
354
355 // Test that remove setting for one filter.
356 impl.RemovePreferredAppForFilter(apps::mojom::AppType::kUnknown, kAppId2,
357 intent_filter->Clone());
358 task_environment_.RunUntilIdle();
359 EXPECT_EQ(base::nullopt,
360 sub0.PreferredApps().FindPreferredAppForUrl(filter_url));
361 EXPECT_EQ(base::nullopt,
362 sub1.PreferredApps().FindPreferredAppForUrl(filter_url));
363 EXPECT_EQ(kAppId2,
364 sub0.PreferredApps().FindPreferredAppForUrl(another_filter_url));
365 EXPECT_EQ(kAppId2,
366 sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
367 }
368
TEST_F(AppServiceImplTest,PreferredAppsPersistency)369 TEST_F(AppServiceImplTest, PreferredAppsPersistency) {
370 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
371
372 const char kAppId1[] = "abcdefg";
373 GURL filter_url = GURL("https://www.google.com/abc");
374 auto intent_filter = apps_util::CreateIntentFilterForUrlScope(filter_url);
375 {
376 base::RunLoop run_loop_read;
377 base::RunLoop run_loop_write;
378 AppServiceImpl impl(temp_dir_.GetPath(),
379 /*is_share_intents_supported=*/false,
380 run_loop_read.QuitClosure(),
381 run_loop_write.QuitClosure());
382 impl.FlushMojoCallsForTesting();
383 run_loop_read.Run();
384 impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId1,
385 intent_filter->Clone(),
386 apps_util::CreateIntentFromUrl(filter_url),
387 /*from_publisher=*/false);
388 run_loop_write.Run();
389 impl.FlushMojoCallsForTesting();
390 }
391 // Create a new impl to initialize preferred apps from the disk.
392 {
393 base::RunLoop run_loop_read;
394 AppServiceImpl impl(temp_dir_.GetPath(),
395 /*is_share_intents_supported=*/false,
396 run_loop_read.QuitClosure());
397 impl.FlushMojoCallsForTesting();
398 run_loop_read.Run();
399 EXPECT_EQ(kAppId1, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
400 filter_url));
401 }
402 }
403
TEST_F(AppServiceImplTest,PreferredAppsUpgrade)404 TEST_F(AppServiceImplTest, PreferredAppsUpgrade) {
405 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
406
407 const char kAppId1[] = "abcdefg";
408 const char kAppId2[] = "gfedcba";
409 GURL filter_url1 = GURL("https://www.google.com/abc");
410 GURL filter_url2 = GURL("https://www.abc.com");
411 auto intent_filter1 = apps_util::CreateIntentFilterForUrlScope(filter_url1);
412 auto intent_filter1_with_action = apps_util::CreateIntentFilterForUrlScope(
413 filter_url1, /*with_action_view=*/true);
414 auto intent_filter2_with_action = apps_util::CreateIntentFilterForUrlScope(
415 filter_url2, /*with_action_view=*/true);
416 {
417 base::RunLoop run_loop_read;
418 base::RunLoop run_loop_write;
419 AppServiceImpl impl(temp_dir_.GetPath(),
420 /*is_share_intents_supported=*/false,
421 run_loop_read.QuitClosure(),
422 run_loop_write.QuitClosure());
423 impl.FlushMojoCallsForTesting();
424 run_loop_read.Run();
425 impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId1,
426 intent_filter1->Clone(),
427 apps_util::CreateIntentFromUrl(filter_url1),
428 /*from_publisher=*/false);
429 impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId2,
430 intent_filter2_with_action->Clone(),
431 apps_util::CreateIntentFromUrl(filter_url2),
432 /*from_publisher=*/false);
433 run_loop_write.Run();
434 impl.FlushMojoCallsForTesting();
435
436 // If try to remove old intent filter with filter with action, it wouldn't
437 // work.
438 impl.RemovePreferredAppForFilter(apps::mojom::AppType::kUnknown, kAppId1,
439 intent_filter1_with_action->Clone());
440 task_environment_.RunUntilIdle();
441 EXPECT_EQ(kAppId1, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
442 filter_url1));
443 EXPECT_EQ(kAppId2, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
444 filter_url2));
445 }
446
447 // Create a new impl with sharing flag on to initialize preferred apps from
448 // the disk.
449 {
450 base::RunLoop run_loop_read;
451 base::RunLoop run_loop_write;
452 AppServiceImpl impl(temp_dir_.GetPath(),
453 /*is_share_intents_supported=*/true,
454 run_loop_read.QuitClosure(),
455 run_loop_write.QuitClosure());
456 impl.FlushMojoCallsForTesting();
457 run_loop_read.Run();
458 EXPECT_EQ(kAppId1, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
459 filter_url1));
460 EXPECT_EQ(kAppId2, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
461 filter_url2));
462 run_loop_write.Run();
463 impl.FlushMojoCallsForTesting();
464 }
465
466 // Create another new impl to read from disk and see if the filter is upgraded
467 // by trying to delete the entry using new filter.
468 {
469 base::RunLoop run_loop_read;
470 AppServiceImpl impl(temp_dir_.GetPath(),
471 /*is_share_intents_supported=*/false,
472 run_loop_read.QuitClosure());
473 impl.FlushMojoCallsForTesting();
474 run_loop_read.Run();
475 impl.RemovePreferredAppForFilter(apps::mojom::AppType::kUnknown, kAppId1,
476 intent_filter1_with_action->Clone());
477 impl.RemovePreferredAppForFilter(apps::mojom::AppType::kUnknown, kAppId2,
478 intent_filter2_with_action->Clone());
479 task_environment_.RunUntilIdle();
480 EXPECT_EQ(
481 base::nullopt,
482 impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(filter_url1));
483 EXPECT_EQ(
484 base::nullopt,
485 impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(filter_url2));
486 }
487 }
488
489 } // namespace apps
490