Commit 568a975f authored by Samuel Huang's avatar Samuel Huang Committed by Commit Bot

[DevUI DFM] Add API and backend to install and load the DFM.

This CL adds DevUiModuleProvider classes (Java and C++) as backend to
install and load the DevUI DFM. The C++ API must be called from the UI
thread. It consists of query helpers:
* DevUiModuleProvider::GetInstance().ModuleInstalled(),
* DevUiModuleProvider::GetInstance().ModuleLoaded(),
and asynchronous (i.e., takes callbacks) actions:
* DevUiModuleProvider::GetInstance().InstallModule(),
* DevUiModuleProvider::GetInstance().LoadModule().

The Java backend handles DFM installation, and implements standard
on-demand DFM installation flow. For simplicity, no Java UI elements
(e.g., using Toast or InfoBar) as used. Instead, an interstitial page
that calls the C++ API will be used.

Some sources of complexity, also encountered by VrModuleProvider (upon
which the code is based) are:
* Interaction between C++ and Java DevUiModuleProvider via JNI calls
  and interaction between the singletons.
* Queue management for InstallModule() and LoadModule(), to handle the
  corner case of multiple concurrent install / load requests.

Bug: 927131
Change-Id: I4a6f3e4209857542a63e49ff79871a50bc5da4ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1769170
Commit-Queue: Samuel Huang <huangs@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#692087}
parent ca0c93fe
...@@ -10,6 +10,7 @@ import("//build/util/process_version.gni") ...@@ -10,6 +10,7 @@ import("//build/util/process_version.gni")
import("//build/util/version.gni") import("//build/util/version.gni")
import("//chrome/android/chrome_common_shared_library.gni") import("//chrome/android/chrome_common_shared_library.gni")
import("//chrome/android/chrome_public_apk_tmpl.gni") import("//chrome/android/chrome_public_apk_tmpl.gni")
import("//chrome/android/features/dev_ui/dev_ui_module.gni")
import("//chrome/android/features/tab_ui/buildflags.gni") import("//chrome/android/features/tab_ui/buildflags.gni")
import("//chrome/android/features/tab_ui/tab_management_java_sources.gni") import("//chrome/android/features/tab_ui/tab_management_java_sources.gni")
import("//chrome/android/features/vr/public_vr_java_sources.gni") import("//chrome/android/features/vr/public_vr_java_sources.gni")
...@@ -530,6 +531,10 @@ java_group("chrome_all_java") { ...@@ -530,6 +531,10 @@ java_group("chrome_all_java") {
if (disable_tab_ui_dfm) { if (disable_tab_ui_dfm) {
deps += [ "//chrome/android/features/tab_ui:java" ] deps += [ "//chrome/android/features/tab_ui:java" ]
} }
if (dfmify_dev_ui) {
deps += [ "//chrome/android/modules/dev_ui/provider:java" ]
}
} }
# This is a list of all base module jni headers. New features should add their # This is a list of all base module jni headers. New features should add their
...@@ -1110,20 +1115,20 @@ android_resources("chrome_public_apk_resources") { ...@@ -1110,20 +1115,20 @@ android_resources("chrome_public_apk_resources") {
} }
version_resource_dir = "$target_gen_dir/templates/chrome_version_xml/res" version_resource_dir = "$target_gen_dir/templates/chrome_version_xml/res"
verson_resource_file = "$version_resource_dir/values/strings.xml" version_resource_file = "$version_resource_dir/values/strings.xml"
process_version("version_xml") { process_version("version_xml") {
process_only = true process_only = true
template_file = "java/version_strings.xml.template" template_file = "java/version_strings.xml.template"
sources = [ sources = [
"//chrome/VERSION", "//chrome/VERSION",
] ]
output = verson_resource_file output = version_resource_file
} }
android_resources("product_version_resources") { android_resources("product_version_resources") {
resource_dirs = [] resource_dirs = []
generated_resource_dirs = [ version_resource_dir ] generated_resource_dirs = [ version_resource_dir ]
generated_resource_files = [ verson_resource_file ] generated_resource_files = [ version_resource_file ]
custom_package = "org.chromium.base" custom_package = "org.chromium.base"
deps = [ deps = [
":version_xml", ":version_xml",
......
...@@ -14,12 +14,11 @@ buildflag_header("buildflags") { ...@@ -14,12 +14,11 @@ buildflag_header("buildflags") {
} }
android_library("java") { android_library("java") {
java_files =
[ "java/src/org/chromium/chrome/browser/dev_ui/DevUiModuleProvider.java" ]
deps = [ deps = [
"//base:base_java", "//base:base_java",
"//chrome/android/features/dev_ui/public:java",
] ]
java_files = [ "java/src/org/chromium/chrome/features/dev_ui/DevUiImpl.java" ]
} }
android_assets("pak_assets") { android_assets("pak_assets") {
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
# found in the LICENSE file. # found in the LICENSE file.
declare_args() { declare_args() {
# Whether Developer UI (chrome:// pages) should be split into a separate DFM. # Whether Developer UI (chrome:// pages) should be split into a separate
# Dynamic Feature Module (DFM: //docs/android_dynamic_feature_modules.md).
dfmify_dev_ui = false dfmify_dev_ui = false
} }
......
...@@ -13,5 +13,5 @@ ...@@ -13,5 +13,5 @@
<dist:fusing dist:include="false" /> <dist:fusing dist:include="false" />
</dist:module> </dist:module>
<application></application> <application />
</manifest> </manifest>
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.dev_ui; package org.chromium.chrome.features.dev_ui;
import org.chromium.base.annotations.UsedByReflection; import org.chromium.base.annotations.UsedByReflection;
/** /** Implementation for the DevUI DFM. */
* Provider for the Developer UI DFM. Currently this is a placeholder that serves as a kludge, so @UsedByReflection("DevUiModule")
* Java code exists somewhere. public class DevUiImpl implements DevUi {}
*/
@UsedByReflection("DevUiModuleProvider.java")
public interface DevUiModuleProvider {}
# Copyright 2019 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.
import("//build/config/android/rules.gni")
android_library("java") {
deps = [
"//base:base_java",
"//components/module_installer/android:module_installer_java",
"//components/module_installer/android:module_interface_java",
]
java_files = [ "java/src/org/chromium/chrome/features/dev_ui/DevUi.java" ]
# Need this to generate DevUiModule.java.
annotation_processor_deps =
[ "//components/module_installer/android:module_interface_processor" ]
}
include_rules = [
"+components/module_installer",
]
// Copyright 2019 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.
package org.chromium.chrome.features.dev_ui;
import org.chromium.components.module_installer.ModuleInterface;
/** Interface to call into DevUI feature. */
@ModuleInterface(module = "dev_ui", impl = "org.chromium.chrome.features.dev_ui.DevUiImpl")
public interface DevUi {}
# Copyright 2019 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.
import("//build/config/android/rules.gni")
android_library("java") {
deps = [
"//base:base_java",
"//chrome/android/features/dev_ui/public:java",
"//components/module_installer/android:module_installer_java",
]
java_files = [
"java/src/org/chromium/chrome/features/dev_ui/DevUiModuleProvider.java",
]
}
generate_jni("jni_headers") {
sources = [
"java/src/org/chromium/chrome/features/dev_ui/DevUiModuleProvider.java",
]
}
// Copyright 2019 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.
package org.chromium.chrome.features.dev_ui;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.UsedByReflection;
/** Helpers for DevUI DFM installation. */
@JNINamespace("dev_ui")
@UsedByReflection("DevUiModuleProvider")
public class DevUiModuleProvider {
private static final String TAG = "DEV_UI";
private long mNativeDevUiModuleProvider;
@CalledByNative
private DevUiModuleProvider(long nativeDevUiModuleProvider) {
mNativeDevUiModuleProvider = nativeDevUiModuleProvider;
}
@CalledByNative
private static boolean isModuleInstalled() {
return DevUiModule.isInstalled();
}
@CalledByNative
private void installModule() {
DevUiModule.install((success) -> {
Log.i(TAG, "Install status: %s", success);
if (mNativeDevUiModuleProvider != 0) {
nativeOnInstallResult(mNativeDevUiModuleProvider, success);
}
});
}
@CalledByNative
private void onNativeDestroy() {
mNativeDevUiModuleProvider = 0;
}
private native void nativeOnInstallResult(long nativeDevUiModuleProvider, boolean success);
}
...@@ -2941,6 +2941,14 @@ jumbo_split_static_library("browser") { ...@@ -2941,6 +2941,14 @@ jumbo_split_static_library("browser") {
] ]
deps += [ "//chrome/browser/supervised_user/supervised_user_error_page" ] deps += [ "//chrome/browser/supervised_user/supervised_user_error_page" ]
} }
if (dfmify_dev_ui) {
sources += [
"android/dev_ui/dev_ui_module_provider.cc",
"android/dev_ui/dev_ui_module_provider.h",
]
deps += [ "//chrome/android/modules/dev_ui/provider:jni_headers" ]
}
} else { # !is_android } else { # !is_android
sources += [ sources += [
"accessibility/invert_bubble_prefs.cc", "accessibility/invert_bubble_prefs.cc",
......
...@@ -132,6 +132,7 @@ include_rules = [ ...@@ -132,6 +132,7 @@ include_rules = [
"+components/mirroring/browser", "+components/mirroring/browser",
"+components/mirroring/mojom", "+components/mirroring/mojom",
"+components/mirroring/service", "+components/mirroring/service",
"+components/module_installer/android",
"+components/nacl/broker", "+components/nacl/broker",
"+components/nacl/browser", "+components/nacl/browser",
"+components/nacl/common", "+components/nacl/common",
......
include_rules = [
"+chrome/android/modules/dev_ui/provider/jni_headers",
]
// Copyright 2019 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/android/dev_ui/dev_ui_module_provider.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/task/post_task.h"
#include "chrome/android/modules/dev_ui/provider/jni_headers/DevUiModuleProvider_jni.h"
#include "chrome/browser/android/dfm_resource_bundle_helper.h"
namespace dev_ui {
namespace {
constexpr base::TaskTraits kWorkerTaskTraits = {
base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
} // namespace
// static
DevUiModuleProvider& DevUiModuleProvider::GetInstance() {
static base::NoDestructor<DevUiModuleProvider> instance;
return *instance;
}
bool DevUiModuleProvider::ModuleInstalled() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return Java_DevUiModuleProvider_isModuleInstalled(
base::android::AttachCurrentThread());
}
bool DevUiModuleProvider::ModuleLoaded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return DevUiModuleProvider::GetInstance().is_loaded_;
}
void DevUiModuleProvider::InstallModule(
base::OnceCallback<void(bool)> on_finished) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Also use the call's side effect of ensuring that split compat is loaded.
// TODO(tiborg): Once ModuleInstalled() is made more light-weight, add
// explicit split compat loading code here.
if (ModuleInstalled()) {
std::move(on_finished).Run(true);
return;
}
on_attempted_install_callbacks_.push(std::move(on_finished));
// Don't request DevUI module multiple times in parallel.
if (on_attempted_install_callbacks_.size() > 1)
return;
// This should always return, since there is no InfoBar UI to retry (thus
// avoiding crbug.com/996925 and crbug.com/996959).
Java_DevUiModuleProvider_installModule(base::android::AttachCurrentThread(),
j_dev_ui_module_provider_);
}
// Called by Java.
void DevUiModuleProvider::OnInstallResult(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(on_attempted_install_callbacks_.size(), 0UL);
while (!on_attempted_install_callbacks_.empty()) {
std::move(on_attempted_install_callbacks_.front()).Run(success);
on_attempted_install_callbacks_.pop();
}
}
void DevUiModuleProvider::LoadModule(base::OnceCallback<void()> on_loaded) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ModuleLoaded()) {
std::move(on_loaded).Run();
return;
}
on_loaded_callbacks_.push(std::move(on_loaded));
// Don't load multiple times in parallel.
if (on_loaded_callbacks_.size() > 1)
return;
// android::LoadDevUiResources() is assumed to always succeed.
base::PostTaskAndReply(FROM_HERE, kWorkerTaskTraits,
base::BindOnce(&android::LoadDevUiResources),
base::BindOnce(&DevUiModuleProvider::OnLoadedModule,
base::Unretained(this)));
}
DevUiModuleProvider::DevUiModuleProvider()
: j_dev_ui_module_provider_(Java_DevUiModuleProvider_Constructor(
base::android::AttachCurrentThread(),
reinterpret_cast<jlong>(this))) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
DevUiModuleProvider::~DevUiModuleProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Java_DevUiModuleProvider_onNativeDestroy(base::android::AttachCurrentThread(),
j_dev_ui_module_provider_);
}
void DevUiModuleProvider::OnLoadedModule() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_loaded_ = true;
DCHECK_GT(on_loaded_callbacks_.size(), 0UL);
while (!on_loaded_callbacks_.empty()) {
std::move(on_loaded_callbacks_.front()).Run();
on_loaded_callbacks_.pop();
}
}
} // namespace dev_ui
// Copyright 2019 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_ANDROID_DEV_UI_DEV_UI_MODULE_PROVIDER_H_
#define CHROME_BROWSER_ANDROID_DEV_UI_DEV_UI_MODULE_PROVIDER_H_
#include <jni.h>
#include <queue>
#include "base/android/jni_android.h"
#include "base/callback.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
namespace dev_ui {
class DevUiModuleProvider {
public:
static DevUiModuleProvider& GetInstance();
// Returns true if the DevUI module is installed.
bool ModuleInstalled();
// Returns true if the DevUI module is installed and loaded.
bool ModuleLoaded();
// Asynchronously requests to install the DevUI module. |on_finished| is
// called after the module install is completed, and takes a bool to indicate
// whether module install is successful.
void InstallModule(base::OnceCallback<void(bool)> on_finished);
// Called by Java.
void OnInstallResult(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool success);
// Assuming that the DevUI module is installed, asynchronously loads DevUI
// resources.
void LoadModule(base::OnceCallback<void()> on_loaded);
private:
friend base::NoDestructor<DevUiModuleProvider>;
DevUiModuleProvider();
~DevUiModuleProvider();
DevUiModuleProvider(const DevUiModuleProvider&) = delete;
void operator=(const DevUiModuleProvider&) = delete;
// Callback for LoadModule().
void OnLoadedModule();
base::android::ScopedJavaGlobalRef<jobject> j_dev_ui_module_provider_;
// Queue of InstallModule() callbacks.
std::queue<base::OnceCallback<void(bool)>> on_attempted_install_callbacks_;
// Queue of LoadModule() callbacks.
std::queue<base::OnceCallback<void()>> on_loaded_callbacks_;
// Source of truth for ModuleLoaded(), updated when LoadModule() succeeds.
bool is_loaded_ = false;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace dev_ui
#endif // CHROME_BROWSER_ANDROID_DEV_UI_DEV_UI_MODULE_PROVIDER_H_
...@@ -209,7 +209,7 @@ template("chrome_extra_paks") { ...@@ -209,7 +209,7 @@ template("chrome_extra_paks") {
# * resources.pak # * resources.pak
# * locale .pak files # * locale .pak files
# #
# Paramters: # Parameters:
# output_dir [required]: Directory to output .pak files. Locale .pak files # output_dir [required]: Directory to output .pak files. Locale .pak files
# will always be place in $output_dir/locales # will always be place in $output_dir/locales
# additional_extra_paks: List of extra .pak sources for resources.pak. # additional_extra_paks: List of extra .pak sources for resources.pak.
......
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