Commit 20fa2e0b authored by Nicholas Hollingum's avatar Nicholas Hollingum Committed by Chromium LUCI CQ

borealis: Add an observer interface to publish anonymous apps

When anonymous apps are detected by borealis' window manager, we inform
the BorealisApps publisher that it should publish an "app" for the given
anonymous ID.

Subsequently, anonymous apps can be handled like normal apps.

Bug: b/172979315
Change-Id: I00a610a551088353cffff305e06b9b52a0c1ee57
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2557769
Commit-Queue: Nic Hollingum <hollingum@google.com>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Reviewed-by: default avatarDaniel Ng <danielng@google.com>
Cr-Commit-Position: refs/heads/master@{#833170}
parent 96330378
...@@ -66,6 +66,9 @@ BorealisApps::BorealisApps( ...@@ -66,6 +66,9 @@ BorealisApps::BorealisApps(
: profile_(profile) { : profile_(profile) {
Registry()->AddObserver(this); Registry()->AddObserver(this);
anonymous_observation_.Observe(
&borealis::BorealisService::GetForProfile(profile_)->WindowManager());
PublisherBase::Initialize(app_service, apps::mojom::AppType::kBorealis); PublisherBase::Initialize(app_service, apps::mojom::AppType::kBorealis);
// TODO(b/170264723): When uninstalling borealis is completed, ensure that we // TODO(b/170264723): When uninstalling borealis is completed, ensure that we
...@@ -216,4 +219,28 @@ void BorealisApps::OnRegistryUpdated( ...@@ -216,4 +219,28 @@ void BorealisApps::OnRegistryUpdated(
} }
} }
void BorealisApps::NewAnonymousAppDetected(const std::string& shelf_app_id,
const std::string& shelf_app_name) {
apps::mojom::AppPtr app = apps::PublisherBase::MakeApp(
apps::mojom::AppType::kBorealis, shelf_app_id,
apps::mojom::Readiness::kReady, shelf_app_name,
apps::mojom::InstallSource::kUser);
app->icon_key = apps::mojom::IconKey::New(
apps::mojom::IconKey::kDoesNotChangeOverTime,
IDR_LOGO_BOREALIS_DEFAULT_192, apps::IconEffects::kNone);
app->recommendable = apps::mojom::OptionalBool::kFalse;
app->searchable = apps::mojom::OptionalBool::kFalse;
app->show_in_launcher = apps::mojom::OptionalBool::kFalse;
app->show_in_shelf = apps::mojom::OptionalBool::kTrue;
app->show_in_search = apps::mojom::OptionalBool::kFalse;
Publish(std::move(app), subscribers_);
}
void BorealisApps::WindowManagerWillBeDeleted(
borealis::BorealisWindowManager* window_manager) {
anonymous_observation_.Reset();
}
} // namespace apps } // namespace apps
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "base/scoped_observation.h"
#include "chrome/browser/apps/app_service/icon_key_util.h" #include "chrome/browser/apps/app_service/icon_key_util.h"
#include "chrome/browser/chromeos/borealis/borealis_window_manager.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h" #include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
#include "components/services/app_service/public/cpp/publisher_base.h" #include "components/services/app_service/public/cpp/publisher_base.h"
#include "components/services/app_service/public/mojom/app_service.mojom.h" #include "components/services/app_service/public/mojom/app_service.mojom.h"
...@@ -24,8 +26,10 @@ namespace apps { ...@@ -24,8 +26,10 @@ namespace apps {
// An app publisher (in the App Service sense) of Borealis apps. // An app publisher (in the App Service sense) of Borealis apps.
// See components/services/app_service/README.md. // See components/services/app_service/README.md.
class BorealisApps : public apps::PublisherBase, class BorealisApps
public guest_os::GuestOsRegistryService::Observer { : public apps::PublisherBase,
public guest_os::GuestOsRegistryService::Observer,
public borealis::BorealisWindowManager::AnonymousAppObserver {
public: public:
BorealisApps(const mojo::Remote<apps::mojom::AppService>& app_service, BorealisApps(const mojo::Remote<apps::mojom::AppService>& app_service,
Profile* profile); Profile* profile);
...@@ -70,11 +74,21 @@ class BorealisApps : public apps::PublisherBase, ...@@ -70,11 +74,21 @@ class BorealisApps : public apps::PublisherBase,
const std::vector<std::string>& removed_apps, const std::vector<std::string>& removed_apps,
const std::vector<std::string>& inserted_apps) override; const std::vector<std::string>& inserted_apps) override;
// borealis::BorealisWindowManager::AnonymousAppObserver overrides.
void NewAnonymousAppDetected(const std::string& shelf_app_id,
const std::string& shelf_app_name) override;
void WindowManagerWillBeDeleted(
borealis::BorealisWindowManager* window_manager) override;
mojo::RemoteSet<apps::mojom::Subscriber> subscribers_; mojo::RemoteSet<apps::mojom::Subscriber> subscribers_;
apps_util::IncrementingIconKeyFactory icon_key_factory_; apps_util::IncrementingIconKeyFactory icon_key_factory_;
Profile* const profile_; Profile* const profile_;
base::ScopedObservation<borealis::BorealisWindowManager,
borealis::BorealisWindowManager::AnonymousAppObserver>
anonymous_observation_{this};
}; };
} // namespace apps } // namespace apps
......
...@@ -3387,6 +3387,7 @@ source_set("unit_tests") { ...@@ -3387,6 +3387,7 @@ source_set("unit_tests") {
"borealis/borealis_installer_unittest.cc", "borealis/borealis_installer_unittest.cc",
"borealis/borealis_launch_watcher_unittest.cc", "borealis/borealis_launch_watcher_unittest.cc",
"borealis/borealis_task_unittest.cc", "borealis/borealis_task_unittest.cc",
"borealis/borealis_window_manager_unittest.cc",
"borealis/infra/described_unittest.cc", "borealis/infra/described_unittest.cc",
"borealis/infra/expected_unittest.cc", "borealis/infra/expected_unittest.cc",
"borealis/infra/state_manager_unittest.cc", "borealis/infra/state_manager_unittest.cc",
......
...@@ -17,6 +17,10 @@ namespace { ...@@ -17,6 +17,10 @@ namespace {
// Borealis windows are created with app/startup ids beginning with this. // Borealis windows are created with app/startup ids beginning with this.
const char kBorealisWindowPrefix[] = "org.chromium.borealis."; const char kBorealisWindowPrefix[] = "org.chromium.borealis.";
// Anonymous apps do not have a CrOS-standard app_id (i.e. one registered with
// the GuestOsRegistryService), so to identify them we prepend this.
const char kBorealisAnonymousPrefix[] = "borealis_anon:";
// Returns an ID for this window (which is the app_id or startup_id, depending // Returns an ID for this window (which is the app_id or startup_id, depending
// on which are set. The ID string is owned by the window. // on which are set. The ID string is owned by the window.
const std::string* GetWindowId(aura::Window* window) { const std::string* GetWindowId(aura::Window* window) {
...@@ -26,6 +30,12 @@ const std::string* GetWindowId(aura::Window* window) { ...@@ -26,6 +30,12 @@ const std::string* GetWindowId(aura::Window* window) {
return exo::GetShellStartupId(window); return exo::GetShellStartupId(window);
} }
// Returns a name for the app with the given |anon_id|.
std::string AnonymousIdentifierToName(const std::string& anon_id) {
return anon_id.substr(anon_id.find(kBorealisWindowPrefix) +
sizeof(kBorealisWindowPrefix) - 1);
}
} // namespace } // namespace
namespace borealis { namespace borealis {
...@@ -41,6 +51,21 @@ bool BorealisWindowManager::IsBorealisWindow(aura::Window* window) { ...@@ -41,6 +51,21 @@ bool BorealisWindowManager::IsBorealisWindow(aura::Window* window) {
BorealisWindowManager::BorealisWindowManager(Profile* profile) BorealisWindowManager::BorealisWindowManager(Profile* profile)
: profile_(profile) {} : profile_(profile) {}
BorealisWindowManager::~BorealisWindowManager() {
for (auto& observer : observers_) {
observer.WindowManagerWillBeDeleted(this);
}
DCHECK(!observers_.might_have_observers());
}
void BorealisWindowManager::AddObserver(AnonymousAppObserver* observer) {
observers_.AddObserver(observer);
}
void BorealisWindowManager::RemoveObserver(AnonymousAppObserver* observer) {
observers_.RemoveObserver(observer);
}
std::string BorealisWindowManager::GetShelfAppId(aura::Window* window) { std::string BorealisWindowManager::GetShelfAppId(aura::Window* window) {
if (!IsBorealisWindow(window)) if (!IsBorealisWindow(window))
return {}; return {};
...@@ -59,7 +84,18 @@ std::string BorealisWindowManager::GetShelfAppId(aura::Window* window) { ...@@ -59,7 +84,18 @@ std::string BorealisWindowManager::GetShelfAppId(aura::Window* window) {
return crostini_equivalent_id; return crostini_equivalent_id;
// The app has no registration, it is anonymous. // The app has no registration, it is anonymous.
return crostini_equivalent_id; std::string anon_id = kBorealisAnonymousPrefix + *GetWindowId(window);
HandleAnonymousApp(anon_id);
return anon_id;
}
void BorealisWindowManager::HandleAnonymousApp(const std::string& anon_id) {
if (known_anon_ids_.contains(anon_id))
return;
known_anon_ids_.emplace(anon_id);
std::string anon_name = AnonymousIdentifierToName(anon_id);
for (auto& observer : observers_)
observer.NewAnonymousAppDetected(anon_id, anon_name);
} }
} // namespace borealis } // namespace borealis
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
#include <string> #include <string>
#include "base/containers/flat_set.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
class Profile; class Profile;
namespace aura { namespace aura {
...@@ -21,12 +25,36 @@ class BorealisWindowManager { ...@@ -21,12 +25,36 @@ class BorealisWindowManager {
// and startup_id). // and startup_id).
static bool IsBorealisWindow(aura::Window* window); static bool IsBorealisWindow(aura::Window* window);
// An observer for tracking the creation and deletion of anonymous windows.
class AnonymousAppObserver : public base::CheckedObserver {
public:
// Called when a new App ID was detected that we do not know the app it
// belongs too. The |shelf_app_name| represents the system's best-guess for
// what the app should be called. This us usually not a localized string but
// something we read from the window's properties.
virtual void NewAnonymousAppDetected(const std::string& shelf_app_id,
const std::string& shelf_app_name) = 0;
// Called when the window manager is being deleted. Observers should
// unregister themselves from it.
virtual void WindowManagerWillBeDeleted(
BorealisWindowManager* window_manager) = 0;
};
explicit BorealisWindowManager(Profile* profile); explicit BorealisWindowManager(Profile* profile);
~BorealisWindowManager();
void AddObserver(AnonymousAppObserver* observer);
void RemoveObserver(AnonymousAppObserver* observer);
std::string GetShelfAppId(aura::Window* window); std::string GetShelfAppId(aura::Window* window);
private: private:
void HandleAnonymousApp(const std::string& anon_id);
Profile* const profile_; Profile* const profile_;
base::flat_set<std::string> known_anon_ids_;
base::ObserverList<AnonymousAppObserver> observers_;
}; };
} // namespace borealis } // namespace borealis
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/borealis/borealis_window_manager.h"
#include <memory>
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
#include "chrome/test/base/testing_profile.h"
#include "components/exo/shell_surface_util.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
namespace borealis {
namespace {
class MockAnonObserver
: public borealis::BorealisWindowManager::AnonymousAppObserver {
public:
MOCK_METHOD(void,
NewAnonymousAppDetected,
(const std::string&, const std::string&),
());
MOCK_METHOD(void, WindowManagerWillBeDeleted, (BorealisWindowManager*), ());
};
class BorealisWindowManagerTest : public testing::Test {
protected:
Profile* profile() { return &profile_; }
// Creates a widget for use in testing.
std::unique_ptr<aura::Window> MakeWindow(std::string name) {
auto win = std::make_unique<aura::Window>(nullptr);
win->Init(ui::LAYER_NOT_DRAWN);
exo::SetShellApplicationId(win.get(), name);
return win;
}
private:
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
};
TEST_F(BorealisWindowManagerTest, NonBorealisWindowHasNoId) {
BorealisWindowManager window_manager(profile());
std::unique_ptr<aura::Window> window = MakeWindow("not.a.borealis.window");
EXPECT_EQ(window_manager.GetShelfAppId(window.get()), "");
}
TEST_F(BorealisWindowManagerTest, BorealisWindowHasAnId) {
BorealisWindowManager window_manager(profile());
std::unique_ptr<aura::Window> window =
MakeWindow("org.chromium.borealis.foobarbaz");
EXPECT_NE(window_manager.GetShelfAppId(window.get()), "");
}
TEST_F(BorealisWindowManagerTest, ObserverNotifiedOnManagerShutdown) {
testing::StrictMock<MockAnonObserver> observer;
BorealisWindowManager window_manager(profile());
window_manager.AddObserver(&observer);
EXPECT_CALL(observer, WindowManagerWillBeDeleted(&window_manager))
.WillOnce(testing::Invoke([&observer](BorealisWindowManager* wm) {
wm->RemoveObserver(&observer);
}));
}
TEST_F(BorealisWindowManagerTest, ObserverCalledForAnonymousApp) {
testing::StrictMock<MockAnonObserver> observer;
EXPECT_CALL(observer,
NewAnonymousAppDetected(testing::ContainsRegex("anonymous_app"),
testing::_));
BorealisWindowManager window_manager(profile());
window_manager.AddObserver(&observer);
std::unique_ptr<aura::Window> window =
MakeWindow("org.chromium.borealis.anonymous_app");
window_manager.GetShelfAppId(window.get());
window_manager.RemoveObserver(&observer);
}
TEST_F(BorealisWindowManagerTest, ObserverNotCalledForKnownApp) {
// Generate a fake app.
vm_tools::apps::ApplicationList list;
list.set_vm_name("vm");
list.set_container_name("container");
list.set_vm_type(vm_tools::apps::ApplicationList_VmType_BOREALIS);
vm_tools::apps::App* app = list.add_apps();
app->set_desktop_file_id("foo.desktop");
app->mutable_name()->add_values()->set_value("foo");
app->set_no_display(false);
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile())
->UpdateApplicationList(list);
testing::StrictMock<MockAnonObserver> observer;
BorealisWindowManager window_manager(profile());
window_manager.AddObserver(&observer);
std::unique_ptr<aura::Window> window =
MakeWindow("org.chromium.borealis.wmclass.foo");
window_manager.GetShelfAppId(window.get());
window_manager.RemoveObserver(&observer);
}
} // namespace
} // namespace borealis
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "ash/public/cpp/window_properties.h" #include "ash/public/cpp/window_properties.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h" #include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
...@@ -387,11 +388,28 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBorealisBrowserTest, ...@@ -387,11 +388,28 @@ IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBorealisBrowserTest,
IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBorealisBrowserTest, IN_PROC_BROWSER_TEST_F(AppServiceAppWindowBorealisBrowserTest,
BorealisUnknownApp) { BorealisUnknownApp) {
views::Widget* widget = CreateExoWindow("org.chromium.borealis.wmclass.bar"); views::Widget* widget = CreateExoWindow("org.chromium.borealis.wmclass.bar");
std::string app_id = "crostini:org.chromium.termina.wmclass.bar"; std::string app_id = "borealis_anon:org.chromium.borealis.wmclass.bar";
EXPECT_EQ(1u, EXPECT_EQ(1u,
app_service_proxy_->InstanceRegistry().GetWindows(app_id).size()); app_service_proxy_->InstanceRegistry().GetWindows(app_id).size());
EXPECT_NE(-1, shelf_model()->ItemIndexByAppID(app_id)); ASSERT_NE(-1, shelf_model()->ItemIndexByAppID(app_id));
// Initially, anonymous apps haven't been published, as that is an
// asynchronous operation. This means their shelf item has no title.
EXPECT_TRUE(shelf_model()
->items()[shelf_model()->ItemIndexByAppID(app_id)]
.title.empty());
// Flushing calls here simulates the fraction-of-seconds delay between the
// window appearing and its app being published.
app_service_proxy_->FlushMojoCallsForTesting();
// Now that the app is published, it will have a name based on the app_id
EXPECT_EQ(
"wmclass.bar",
base::UTF16ToUTF8(shelf_model()
->items()[shelf_model()->ItemIndexByAppID(app_id)]
.title));
widget->CloseNow(); widget->CloseNow();
EXPECT_TRUE( EXPECT_TRUE(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment