Commit 3582e2d0 authored by Nigel Tao's avatar Nigel Tao Committed by Commit Bot

First draft of an App Service extension publisher

Lots of TODOs, but there's enough implemented so that running "chrome
--enable-features=AppService" will populate the launcher with extension
apps' names and icons, and clicking on the icon will launch the app.

BUG=826982

Change-Id: Id424abac859dde69ffdac3c28a473fce61fbe8df
Reviewed-on: https://chromium-review.googlesource.com/c/1351975
Commit-Queue: Nigel Tao <nigeltao@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#613334}
parent b8d90223
......@@ -3125,6 +3125,8 @@ jumbo_split_static_library("browser") {
sources += [
"apps/app_service/built_in_chromeos_apps.cc",
"apps/app_service/built_in_chromeos_apps.h",
"apps/app_service/extension_apps.cc",
"apps/app_service/extension_apps.h",
"ash_service_registry.cc",
"ash_service_registry.h",
"component_updater/cros_component_installer_chromeos.cc",
......
......@@ -98,6 +98,21 @@ void LoadIconFromExtension(apps::mojom::IconCompression icon_compression,
extensions::IconsInfo::GetIconResource(extension, size_hint_in_px,
ExtensionIconSet::MATCH_BIGGER);
// TODO(crbug.com/826982): at some point near here, we should call into
// chrome/browser/extensions/chrome_app_icon.h code to get e.g. graying out
// disabled icons, or rounding off corners. It's not entirely trivial. The
// ChromeAppIcon object expects to be long-lived, calling back into the
// ChromeAppIconDelegate e.g. when the extension's underlying icon changes,
// or when an enabled/disabled state change means graying or un-graying an
// extension's icon.
//
// For example, in chrome/browser/ui/app_list, ExtensionAppItem implements
// ChromeAppIconDelegate, as the ExtensionAppItem is a long-lived object.
// It is a model, in the model-view-controller sense.
//
// In contrast, this API here (a Mojo IPC API) is a request-response IPC,
// with nothing long-lived enough to be the ChromeAppIconDelegate.
switch (icon_compression) {
case apps::mojom::IconCompression::kUnknown:
break;
......
......@@ -31,10 +31,11 @@ AppServiceProxy::AppServiceProxy(Profile* profile) {
app_service_->RegisterSubscriber(std::move(subscriber), nullptr);
#if defined(OS_CHROMEOS)
// The AppServiceProxy is also a publisher, of built-in apps. That
// The AppServiceProxy is also a publisher, of a variety of app types. That
// responsibility isn't intrinsically part of the AppServiceProxy, but doing
// that here is as good a place as any.
// that here, for each such app type, is as good a place as any.
built_in_chrome_os_apps_.Initialize(app_service_, profile);
extension_apps_.Initialize(app_service_, profile);
#endif // OS_CHROMEOS
}
......
......@@ -14,6 +14,7 @@
#if defined(OS_CHROMEOS)
#include "chrome/browser/apps/app_service/built_in_chromeos_apps.h"
#include "chrome/browser/apps/app_service/extension_apps.h"
#endif // OS_CHROMEOS
class Profile;
......@@ -54,6 +55,7 @@ class AppServiceProxy : public KeyedService, public apps::mojom::Subscriber {
#if defined(OS_CHROMEOS)
BuiltInChromeOsApps built_in_chrome_os_apps_;
ExtensionApps extension_apps_;
#endif // OS_CHROMEOS
DISALLOW_COPY_AND_ASSIGN(AppServiceProxy);
......
......@@ -25,13 +25,16 @@ apps::mojom::AppPtr Convert(const app_list::InternalApp& internal_app) {
return apps::mojom::AppPtr();
}
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kBuiltIn;
app->app_id = internal_app.app_id;
app->readiness = apps::mojom::Readiness::kReady;
app->name = l10n_util::GetStringUTF8(internal_app.name_string_resource_id);
app->icon_key = apps::mojom::IconKey::New();
app->icon_key->icon_type = apps::mojom::IconType::kResource;
app->icon_key->u_key = static_cast<uint64_t>(internal_app.icon_resource_id);
app->show_in_launcher = internal_app.show_in_launcher
? apps::mojom::OptionalBool::kTrue
: apps::mojom::OptionalBool::kFalse;
......
// Copyright 2018 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/apps/app_service/extension_apps.h"
#include <utility>
#include <vector>
#include "ash/public/cpp/shelf_types.h"
#include "chrome/browser/apps/app_service/app_icon_factory.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/extension_app_utils.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/services/app_service/public/mojom/types.mojom.h"
#include "extensions/browser/extension_registry.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "ui/display/types/display_constants.h"
// TODO(crbug.com/826982): life cycle events. Extensions can be installed and
// uninstalled. ExtensionApps should implement extensions::InstallObserver and
// be able to show download progress in the UI, a la ExtensionAppModelBuilder.
// This might involve using an extensions::InstallTracker. It might also need
// the equivalent of a LauncherExtensionAppUpdater.
// TODO(crbug.com/826982): ExtensionAppItem's can be 'badged', which means that
// it's an extension app that has its Android analog installed. We should cater
// for that here.
// TODO(crbug.com/826982): do we also need to watch prefs, the same as
// ExtensionAppModelBuilder?
// TODO(crbug.com/826982): support the is_platform_app bit. We might not need
// to plumb this all the way through the Mojo methods, as AFAICT it's only used
// for populating the context menu, which is done on the app publisher side
// (i.e. in this C++ file) and not at all on the app subscriber side.
namespace apps {
ExtensionApps::ExtensionApps()
: binding_(this), profile_(nullptr), next_u_key_(1), observer_(this) {}
ExtensionApps::~ExtensionApps() = default;
void ExtensionApps::Initialize(const apps::mojom::AppServicePtr& app_service,
Profile* profile) {
apps::mojom::PublisherPtr publisher;
binding_.Bind(mojo::MakeRequest(&publisher));
app_service->RegisterPublisher(std::move(publisher),
apps::mojom::AppType::kExtension);
profile_ = profile;
if (profile_) {
observer_.Add(extensions::ExtensionRegistry::Get(profile_));
}
}
void ExtensionApps::Connect(apps::mojom::SubscriberPtr subscriber,
apps::mojom::ConnectOptionsPtr opts) {
std::vector<apps::mojom::AppPtr> apps;
if (profile_) {
// TODO(crbug.com/826982): consider disabled and terminated extensions, not
// just enabled ones, as per AppListControllerDelegate::GetApps in
// https://cs.chromium.org/chromium/src/chrome/browser/ui/app_list/app_list_controller_delegate.cc?g=0&l=193
for (const auto& extension :
extensions::ExtensionRegistry::Get(profile_)->enabled_extensions()) {
if (extension->is_app()) {
apps.push_back(Convert(extension.get()));
}
}
}
subscriber->OnApps(std::move(apps));
subscribers_.AddPtr(std::move(subscriber));
}
void ExtensionApps::LoadIcon(const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconCompression icon_compression,
int32_t size_hint_in_dip,
LoadIconCallback callback) {
if (!icon_key.is_null() &&
(icon_key->icon_type == apps::mojom::IconType::kExtension) &&
!icon_key->s_key.empty()) {
LoadIconFromExtension(icon_compression, size_hint_in_dip,
std::move(callback), profile_, icon_key->s_key);
return;
}
// On failure, we still run the callback, with the zero IconValue.
std::move(callback).Run(apps::mojom::IconValue::New());
}
void ExtensionApps::Launch(const std::string& app_id, int32_t event_flags) {
if (!profile_) {
return;
}
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
app_id);
if (!extension || !extensions::util::IsAppLaunchable(app_id, profile_) ||
RunExtensionEnableFlow(app_id)) {
return;
}
// TODO(crbug.com/826982): add an arg to the Publisher.Launch Mojo method so
// that we can discriminate, here, between launching via the app list main
// view, launching via the app list search box, or launching via some other
// mechanism, such as the shelf.
//
// See also the UMA histogram mechanism in app_service_app_item.cc.
ash::ShelfLaunchSource launch_source = ash::LAUNCH_FROM_APP_LIST;
// TODO(crbug.com/826982): similarly, thread a display_id arg through the
// Publisher.Launch Mojo method.
int64_t display_id = display::kInvalidDisplayId;
if (launch_source == ash::LAUNCH_FROM_APP_LIST) {
extensions::RecordAppListMainLaunch(extension);
} else {
// TODO, as per above.
}
ChromeLauncherController::instance()->LaunchApp(
ash::ShelfID(app_id), launch_source, event_flags, display_id);
}
apps::mojom::AppPtr ExtensionApps::Convert(
const extensions::Extension* extension) {
apps::mojom::AppPtr app = apps::mojom::App::New();
app->app_type = apps::mojom::AppType::kExtension;
app->app_id = extension->id();
app->readiness = apps::mojom::Readiness::kReady;
app->name = extension->name();
app->icon_key = apps::mojom::IconKey::New();
app->icon_key->icon_type = apps::mojom::IconType::kExtension;
app->icon_key->s_key = extension->id();
app->icon_key->u_key = next_u_key_++;
app->show_in_launcher = app_list::ShouldShowInLauncher(extension, profile_)
? apps::mojom::OptionalBool::kTrue
: apps::mojom::OptionalBool::kFalse;
return app;
}
bool ExtensionApps::RunExtensionEnableFlow(const std::string& app_id) {
if (extensions::util::IsAppLaunchableWithoutEnabling(app_id, profile_)) {
return false;
}
// TODO(crbug.com/826982): run the extension enable flow, doing what
// chrome/browser/ui/app_list/extension_app_item.h does, even if we don't do
// it in exactly the same way.
//
// Re-using the ExtensionEnableFlow code is not entirely trivial. An
// ExtensionEnableFlow is created for one particular app_id, the same way
// that an ExtensionAppItem maps 1:1 to an app_id. In contrast, this class
// (the ExtensionApps publisher) handles all app_id's, not just one.
return true;
}
} // namespace apps
// Copyright 2018 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.
#ifndef CHROME_BROWSER_APPS_APP_SERVICE_EXTENSION_APPS_H_
#define CHROME_BROWSER_APPS_APP_SERVICE_EXTENSION_APPS_H_
#include "base/macros.h"
#include "base/scoped_observer.h"
#include "chrome/services/app_service/public/mojom/app_service.mojom.h"
#include "extensions/browser/extension_registry_observer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"
class Profile;
namespace apps {
// An app publisher (in the App Service sense) of extension-backed apps,
// including Chrome Apps (platform apps and legacy packaged apps) and hosted
// apps (including desktop PWAs).
//
// In the future, desktop PWAs will be migrated to a new system.
//
// See chrome/services/app_service/README.md.
class ExtensionApps : public apps::mojom::Publisher,
public extensions::ExtensionRegistryObserver {
public:
ExtensionApps();
~ExtensionApps() override;
void Initialize(const apps::mojom::AppServicePtr& app_service,
Profile* profile);
private:
// apps::mojom::Publisher overrides.
void Connect(apps::mojom::SubscriberPtr subscriber,
apps::mojom::ConnectOptionsPtr opts) override;
void LoadIcon(const std::string& app_id,
apps::mojom::IconKeyPtr icon_key,
apps::mojom::IconCompression icon_compression,
int32_t size_hint_in_dip,
LoadIconCallback callback) override;
void Launch(const std::string& app_id, int32_t event_flags) override;
// extensions::ExtensionRegistryObserver overrides.
// TODO(crbug.com/826982): implement.
// Checks if extension is disabled and if enable flow should be started.
// Returns true if extension enable flow is started or there is already one
// running.
bool RunExtensionEnableFlow(const std::string& app_id);
apps::mojom::AppPtr Convert(const extensions::Extension* extension);
mojo::Binding<apps::mojom::Publisher> binding_;
Profile* profile_;
mojo::InterfacePtrSet<apps::mojom::Subscriber> subscribers_;
// |next_u_key_| is incremented every time Convert returns a valid AppPtr, so
// that when an extension's icon has changed, this apps::mojom::Publisher
// sends a different IconKey even though the IconKey's s_key hasn't changed.
uint64_t next_u_key_;
ScopedObserver<extensions::ExtensionRegistry,
extensions::ExtensionRegistryObserver>
observer_;
DISALLOW_COPY_AND_ASSIGN(ExtensionApps);
};
} // namespace apps
#endif // CHROME_BROWSER_APPS_APP_SERVICE_EXTENSION_APPS_H_
......@@ -227,6 +227,7 @@ void AppsNavigationThrottle::OnIntentPickerClosed(
break;
case apps::mojom::AppType::kBuiltIn:
case apps::mojom::AppType::kCrostini:
case apps::mojom::AppType::kExtension:
NOTREACHED();
}
RecordUma(launch_name, app_type, close_reason, should_persist);
......@@ -363,6 +364,7 @@ AppsNavigationThrottle::PickerAction AppsNavigationThrottle::GetPickerAction(
return PickerAction::PWA_APP_PRESSED;
case apps::mojom::AppType::kBuiltIn:
case apps::mojom::AppType::kCrostini:
case apps::mojom::AppType::kExtension:
NOTREACHED();
}
}
......
......@@ -25,10 +25,11 @@ struct App {
// The types of apps available in the registry.
enum AppType {
kUnknown = 0,
kArc, // Android app.
kBuiltIn, // Built-in app.
kCrostini, // Linux app.
kWeb, // Web app.
kArc, // Android app.
kBuiltIn, // Built-in app.
kCrostini, // Linux app.
kExtension, // Extension-backed app.
kWeb, // Web app.
};
// Whether an app is ready to launch, i.e. installed.
......
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