blob: 941223c4053808b71b811e5bf623cbe17bf6b924 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/android/extensions/extension_actions_bridge.h"
#include <memory>
#include <variant>
#include "base/android/jni_string.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/android/extensions/extension_actions_bridge_factory.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_registry.h"
#include "ui/events/android/key_event_android.h"
#include "ui/events/event.h"
#include "ui/events/platform_event.h"
#include "ui/gfx/android/java_bitmap.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/ui/android/extensions/jni_headers/ExtensionAction_jni.h"
#include "chrome/browser/ui/android/extensions/jni_headers/ExtensionActionsBridge_jni.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
using content::WebContents;
namespace extensions {
ExtensionActionsBridge::IconObserver::IconObserver(
ExtensionActionsBridge* bridge,
const Extension& extension,
ExtensionAction& action)
: bridge_(bridge),
action_id_(extension.id()),
icon_factory_(&extension, &action, this) {}
ExtensionActionsBridge::IconObserver::~IconObserver() = default;
gfx::Image ExtensionActionsBridge::IconObserver::GetIcon(int tab_id) {
return icon_factory_.GetIcon(tab_id);
}
void ExtensionActionsBridge::IconObserver::OnIconUpdated() {
bridge_->OnToolbarIconUpdated(action_id_);
}
ExtensionActionsBridge::ExtensionActionsBridge(Profile* profile)
: profile_(profile),
model_(ToolbarActionsModel::Get(profile)),
keybinding_registry_(
std::make_unique<ExtensionKeybindingRegistryAndroid>(profile)) {
java_object_ = Java_ExtensionActionsBridge_Constructor(
AttachCurrentThread(), reinterpret_cast<jlong>(this));
model_observation_.Observe(model_);
}
ExtensionActionsBridge::~ExtensionActionsBridge() {
Java_ExtensionActionsBridge_destroy(AttachCurrentThread(), java_object_);
}
// static
ExtensionActionsBridge* ExtensionActionsBridge::Get(Profile* profile) {
return ExtensionActionsBridgeFactory::GetForProfile(profile);
}
ScopedJavaLocalRef<jobject> ExtensionActionsBridge::GetJavaObject() {
return java_object_.AsLocalRef(AttachCurrentThread());
}
bool ExtensionActionsBridge::AreActionsInitialized(JNIEnv* env) {
return model_->actions_initialized();
}
std::vector<ToolbarActionsModel::ActionId> ExtensionActionsBridge::GetActionIds(
JNIEnv* env) {
const auto& ids = model_->action_ids();
return std::vector(ids.begin(), ids.end());
}
ScopedJavaLocalRef<jobject> ExtensionActionsBridge::GetAction(
JNIEnv* env,
const ToolbarActionsModel::ActionId& action_id,
int tab_id) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
DCHECK(registry);
ExtensionActionManager* manager = ExtensionActionManager::Get(profile_);
DCHECK(manager);
const Extension* extension =
registry->enabled_extensions().GetByID(action_id);
if (extension == nullptr) {
return nullptr;
}
ExtensionAction* action = manager->GetExtensionAction(*extension);
if (action == nullptr) {
return nullptr;
}
return Java_ExtensionAction_Constructor(env, action_id,
action->GetTitle(tab_id));
}
ScopedJavaLocalRef<jobject> ExtensionActionsBridge::GetActionIcon(
JNIEnv* env,
const ToolbarActionsModel::ActionId& action_id,
int tab_id) {
IconObserver* icon_observer = EnsureIconObserver(action_id);
if (!icon_observer) {
return nullptr;
}
gfx::Image image = icon_observer->GetIcon(tab_id);
return gfx::ConvertToJavaBitmap(*image.ToSkBitmap());
}
ExtensionAction::ShowAction ExtensionActionsBridge::RunAction(
JNIEnv* env,
const ToolbarActionsModel::ActionId& action_id,
int tab_id,
content::WebContents* web_contents) {
// `tab_id` is unused, but it makes Java unit testing easier. See comments in
// ExtensionActionsBridge.java for details.
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
DCHECK(registry);
ExtensionActionManager* manager = ExtensionActionManager::Get(profile_);
DCHECK(manager);
const Extension* extension =
registry->enabled_extensions().GetByID(action_id);
if (extension == nullptr) {
return ExtensionAction::ShowAction::kNone;
}
ExtensionActionRunner* runner =
ExtensionActionRunner::GetForWebContents(web_contents);
if (runner == nullptr) {
return ExtensionAction::ShowAction::kNone;
}
return runner->RunAction(extension, /*grant_tab_permissions=*/true);
}
bool ExtensionActionsBridge::ExtensionsEnabled(JNIEnv* env) {
ExtensionManagement* extension_management =
ExtensionManagementFactory::GetForBrowserContext(profile_);
return extension_management->ExtensionsEnabledForDesktopAndroid();
}
jni_zero::ScopedJavaLocalRef<jobject>
ExtensionActionsBridge::HandleKeyDownEvent(
JNIEnv* env,
const ui::KeyEventAndroid& key_event) {
std::variant<bool, ToolbarActionsModel::ActionId> result =
keybinding_registry_->HandleKeyDownEvent(key_event);
if (result.index() == 0) {
return Java_HandleKeyEventResult_Constructor(env, std::get<bool>(result),
"");
}
return Java_HandleKeyEventResult_Constructor(
env, false, std::get<ToolbarActionsModel::ActionId>(result));
}
void ExtensionActionsBridge::OnToolbarActionAdded(
const ToolbarActionsModel::ActionId& id) {
Java_ExtensionActionsBridge_onActionAdded(AttachCurrentThread(), java_object_,
id);
}
void ExtensionActionsBridge::OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& id) {
RemoveIconObserver(id);
Java_ExtensionActionsBridge_onActionRemoved(AttachCurrentThread(),
java_object_, id);
}
void ExtensionActionsBridge::OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& id) {
Java_ExtensionActionsBridge_onActionUpdated(AttachCurrentThread(),
java_object_, id);
}
void ExtensionActionsBridge::OnToolbarModelInitialized() {
Java_ExtensionActionsBridge_onActionModelInitialized(AttachCurrentThread(),
java_object_);
}
void ExtensionActionsBridge::OnToolbarPinnedActionsChanged() {
Java_ExtensionActionsBridge_onPinnedActionsChanged(AttachCurrentThread(),
java_object_);
}
ExtensionActionsBridge::IconObserver*
ExtensionActionsBridge::EnsureIconObserver(
const ToolbarActionsModel::ActionId& action_id) {
if (!icon_observers_.contains(action_id)) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
DCHECK(registry);
ExtensionActionManager* manager = ExtensionActionManager::Get(profile_);
DCHECK(manager);
const Extension* extension =
registry->enabled_extensions().GetByID(action_id);
if (extension == nullptr) {
return nullptr;
}
ExtensionAction* action = manager->GetExtensionAction(*extension);
if (action == nullptr) {
return nullptr;
}
icon_observers_.emplace(
action_id, std::make_unique<IconObserver>(this, *extension, *action));
}
auto it = icon_observers_.find(action_id);
if (it == icon_observers_.end()) {
return nullptr;
}
return it->second.get();
}
void ExtensionActionsBridge::RemoveIconObserver(
const ToolbarActionsModel::ActionId& action_id) {
icon_observers_.erase(action_id);
}
void ExtensionActionsBridge::OnToolbarIconUpdated(
const ToolbarActionsModel::ActionId& action_id) {
Java_ExtensionActionsBridge_onActionIconUpdated(AttachCurrentThread(),
java_object_, action_id);
}
static ScopedJavaLocalRef<jobject> JNI_ExtensionActionsBridge_Get(
JNIEnv* env,
Profile* profile) {
ExtensionActionsBridge* bridge = ExtensionActionsBridge::Get(profile);
DCHECK(bridge);
return bridge->GetJavaObject();
}
} // namespace extensions