Commit 9b660407 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Use crazy linker for M- and "/." trick for N+

Instead of copying libraries on M-, this changes to using the crazy
linker, which loads the library without any knowledge of what libraries
have been loaded by the system linker. This requires manual JNI
registration. To make sure we only register WebLayer JNI when needed,
a minimal set of JNI methods is registered, and the full set is
registered later if we require WebView compatibility.

In N+, we switch to prepending "/." to library paths instead of using
symlinks, which allows us to avoid writing any files to the apps data
directory. This simplifies the interface quite a bit, allowing
initializeWebViewCompatibilityMode() to be sync instead of async. I will
follow up with the Android team to make sure we have a better solution
for future versions of Android.

The API has not been used in the client app yet, so it should be safe
to remove M- support for the copy library approach in the
implementation. The old version of initializeWebViewCompatibilityMode()
was kept so the WebLayer API version compat tests would still pass.

TBR=agrieve@chromium.org

Bug: 1051358
Change-Id: I2cd5ae0e025feaeb5c41cf9ddfafc91cd09fbad7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2090024
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749973}
parent 43548e91
...@@ -63,6 +63,6 @@ source_set("webview_entry_point") { ...@@ -63,6 +63,6 @@ source_set("webview_entry_point") {
if (webview_includes_weblayer) { if (webview_includes_weblayer) {
defines = [ "WEBVIEW_INCLUDES_WEBLAYER" ] defines = [ "WEBVIEW_INCLUDES_WEBLAYER" ]
deps += [ "//weblayer:weblayer_lib" ] deps += [ "//weblayer:weblayer_lib_webview" ]
} }
} }
...@@ -32,6 +32,10 @@ bool NativeInit(base::android::LibraryProcessType library_process_type) { ...@@ -32,6 +32,10 @@ bool NativeInit(base::android::LibraryProcessType library_process_type) {
// Most of the initialization is done in LibraryLoadedOnMainThread(), not here. // Most of the initialization is done in LibraryLoadedOnMainThread(), not here.
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm); base::android::InitVM(vm);
#if defined(WEBVIEW_INCLUDES_WEBLAYER)
if (!weblayer::RegisterMinimalNatives())
return -1;
#endif
base::android::SetNativeInitializationHook(&NativeInit); base::android::SetNativeInitializationHook(&NativeInit);
return JNI_VERSION_1_4; return JNI_VERSION_1_4;
} }
...@@ -132,6 +132,11 @@ template("system_webview_apk_or_module_tmpl") { ...@@ -132,6 +132,11 @@ template("system_webview_apk_or_module_tmpl") {
"//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline", "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline",
] ]
loadable_modules = [ "$root_out_dir/libcrashpad_handler_trampoline.so" ] loadable_modules = [ "$root_out_dir/libcrashpad_handler_trampoline.so" ]
if (webview_includes_weblayer) {
deps += [ "//base/android/linker:chromium_android_linker" ]
loadable_modules +=
[ "$root_out_dir/libchromium_android_linker$shlib_extension" ]
}
} }
if (_include_secondary_support) { if (_include_secondary_support) {
_trampoline = "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline($android_secondary_abi_toolchain)" _trampoline = "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline($android_secondary_abi_toolchain)"
...@@ -142,6 +147,11 @@ template("system_webview_apk_or_module_tmpl") { ...@@ -142,6 +147,11 @@ template("system_webview_apk_or_module_tmpl") {
_secondary_out_dir = get_label_info(_trampoline, "root_out_dir") _secondary_out_dir = get_label_info(_trampoline, "root_out_dir")
secondary_abi_loadable_modules = secondary_abi_loadable_modules =
[ "$_secondary_out_dir/libcrashpad_handler_trampoline.so" ] [ "$_secondary_out_dir/libcrashpad_handler_trampoline.so" ]
if (webview_includes_weblayer) {
deps += [ "//base/android/linker:chromium_android_linker($android_secondary_abi_toolchain)" ]
secondary_abi_loadable_modules +=
[ "$_secondary_out_dir/libchromium_android_linker$shlib_extension" ]
}
} }
if (!_use_trichrome_library || android_64bit_target_cpu) { if (!_use_trichrome_library || android_64bit_target_cpu) {
......
...@@ -124,6 +124,9 @@ public class LibraryLoader { ...@@ -124,6 +124,9 @@ public class LibraryLoader {
// Whether the configuration has been set. // Whether the configuration has been set.
private boolean mConfigurationSet; private boolean mConfigurationSet;
// Runs after all native libraries have been loaded.
private Runnable mPostLoadHook;
@IntDef({LoadState.NOT_LOADED, LoadState.MAIN_DEX_LOADED, LoadState.LOADED}) @IntDef({LoadState.NOT_LOADED, LoadState.MAIN_DEX_LOADED, LoadState.LOADED})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
private @interface LoadState { private @interface LoadState {
...@@ -147,10 +150,6 @@ public class LibraryLoader { ...@@ -147,10 +150,6 @@ public class LibraryLoader {
// will be reported via UMA. Set once when the libraries are done loading. // will be reported via UMA. Set once when the libraries are done loading.
private long mLibraryLoadTimeMs; private long mLibraryLoadTimeMs;
// The suffix to add when loading the library. This is useful if a library with the same name is
// loaded in the same process, as can be the case with WebView/WebLayer.
private String mLibrarySuffix = "";
/** /**
* Call this method to determine if the chromium project must load the library * Call this method to determine if the chromium project must load the library
* directly from a zip file. * directly from a zip file.
...@@ -186,13 +185,10 @@ public class LibraryLoader { ...@@ -186,13 +185,10 @@ public class LibraryLoader {
mLibraryProcessType = type; mLibraryProcessType = type;
} }
/** /** Sets a function to run after all native libraries have been loaded. */
* Sets a suffix to use when loading the library. This will be appended to the library names public void setPostLoadHook(Runnable postLoadHook) {
* for calls to System.loadLibrary().
*/
public void setLibrarySuffix(String suffix) {
assert mLoadState == LoadState.NOT_LOADED; assert mLoadState == LoadState.NOT_LOADED;
mLibrarySuffix = suffix; mPostLoadHook = postLoadHook;
} }
/** /**
...@@ -492,7 +488,7 @@ public class LibraryLoader { ...@@ -492,7 +488,7 @@ public class LibraryLoader {
// Load libraries using the system linker. // Load libraries using the system linker.
for (String library : NativeLibraries.LIBRARIES) { for (String library : NativeLibraries.LIBRARIES) {
if (!isInZipFile()) { if (!isInZipFile()) {
System.loadLibrary(library + mLibrarySuffix); System.loadLibrary(library);
} else { } else {
// Load directly from the APK. // Load directly from the APK.
boolean is64Bit = ApiHelperForM.isProcess64Bit(); boolean is64Bit = ApiHelperForM.isProcess64Bit();
...@@ -540,6 +536,10 @@ public class LibraryLoader { ...@@ -540,6 +536,10 @@ public class LibraryLoader {
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_LOAD_FAILED, e); throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_LOAD_FAILED, e);
} }
if (mPostLoadHook != null) {
mPostLoadHook.run();
}
} }
@VisibleForTesting @VisibleForTesting
......
...@@ -61,6 +61,7 @@ void SetNativeInitializationHook( ...@@ -61,6 +61,7 @@ void SetNativeInitializationHook(
void SetNonMainDexJniRegistrationHook( void SetNonMainDexJniRegistrationHook(
NonMainDexJniRegistrationHook jni_registration_hook) { NonMainDexJniRegistrationHook jni_registration_hook) {
DCHECK(!g_jni_registration_hook);
g_jni_registration_hook = jni_registration_hook; g_jni_registration_hook = jni_registration_hook;
} }
......
...@@ -40,6 +40,7 @@ CHROME_SPECIFIC = BuildFileMatchRegex( ...@@ -40,6 +40,7 @@ CHROME_SPECIFIC = BuildFileMatchRegex(
# WebView specific files which are not in Monochrome.apk # WebView specific files which are not in Monochrome.apk
WEBVIEW_SPECIFIC = BuildFileMatchRegex( WEBVIEW_SPECIFIC = BuildFileMatchRegex(
r'lib/.*/libwebviewchromium\.so', r'lib/.*/libwebviewchromium\.so',
r'lib/.*/libchromium_android_linker\.so',
r'assets/webview_licenses.notice', r'assets/webview_licenses.notice',
r'res/.*/icon_webview(.webp)?', r'res/.*/icon_webview(.webp)?',
r'META-INF/.*', r'META-INF/.*',
......
...@@ -86,7 +86,7 @@ if (is_android) { ...@@ -86,7 +86,7 @@ if (is_android) {
} }
} }
static_library("weblayer_lib") { source_set("weblayer_lib_base") {
sources = [ sources = [
"app/content_main_delegate_impl.cc", "app/content_main_delegate_impl.cc",
"app/content_main_delegate_impl.h", "app/content_main_delegate_impl.h",
...@@ -303,6 +303,7 @@ static_library("weblayer_lib") { ...@@ -303,6 +303,7 @@ static_library("weblayer_lib") {
if (is_android) { if (is_android) {
sources += [ sources += [
"$target_gen_dir/browser/java/weblayer_minimal_jni_registration.h",
"app/jni_onload.cc", "app/jni_onload.cc",
"app/jni_onload.h", "app/jni_onload.h",
"browser/android/exception_filter.cc", "browser/android/exception_filter.cc",
...@@ -344,13 +345,16 @@ static_library("weblayer_lib") { ...@@ -344,13 +345,16 @@ static_library("weblayer_lib") {
"//android_webview:generate_aw_strings", "//android_webview:generate_aw_strings",
"//components/android_system_error_page", "//components/android_system_error_page",
"//components/autofill/android:provider", "//components/autofill/android:provider",
"//components/crash/android:crash_android",
"//components/crash/android:crashpad_main", "//components/crash/android:crashpad_main",
"//components/embedder_support/android:context_menu", "//components/embedder_support/android:context_menu",
"//components/embedder_support/android:util",
"//components/embedder_support/android:web_contents_delegate", "//components/embedder_support/android:web_contents_delegate",
"//components/embedder_support/android/metrics", "//components/embedder_support/android/metrics",
"//components/javascript_dialogs", "//components/javascript_dialogs",
"//components/metrics", "//components/metrics",
"//components/minidump_uploader", "//components/minidump_uploader",
"//components/permissions/android:native",
"//components/safe_browsing/content/common:interfaces", "//components/safe_browsing/content/common:interfaces",
"//components/safe_browsing/content/renderer:throttles", "//components/safe_browsing/content/renderer:throttles",
"//components/safe_browsing/core/common", "//components/safe_browsing/core/common",
...@@ -358,6 +362,7 @@ static_library("weblayer_lib") { ...@@ -358,6 +362,7 @@ static_library("weblayer_lib") {
"//services/resource_coordinator/public/cpp/memory_instrumentation:browser", "//services/resource_coordinator/public/cpp/memory_instrumentation:browser",
"//ui/android", "//ui/android",
"//weblayer/browser/java:jni", "//weblayer/browser/java:jni",
"//weblayer/browser/java:weblayer_minimal_jni_registration",
"//weblayer/browser/safe_browsing:safe_browsing", "//weblayer/browser/safe_browsing:safe_browsing",
] ]
} else { } else {
...@@ -399,6 +404,45 @@ static_library("weblayer_lib") { ...@@ -399,6 +404,45 @@ static_library("weblayer_lib") {
} }
} }
if (is_android) {
# Lib used in standalone WebView which allows manual JNI registration.
static_library("weblayer_lib_webview") {
public_deps = [ ":weblayer_lib_base" ]
deps = [
"//base",
"//weblayer/browser/java:jni",
"//weblayer/browser/java:weblayer_jni_registration",
]
sources = [
"$target_gen_dir/browser/java/weblayer_jni_registration.h",
"browser/web_view_compatibility_helper_impl.cc",
]
defines = [ "WEBLAYER_MANUAL_JNI_REGISTRATION" ]
# Explicit dependency required for JNI registration to be able to
# find the native side functions.
if (is_component_build) {
deps += [
"//device/gamepad",
"//media/midi",
"//ui/events/devices",
]
}
}
# Lib used in Monochrome which does not support manual JNI registration.
# Separate from the standalone WebView version to reduce APK size.
static_library("weblayer_lib") {
public_deps = [ ":weblayer_lib_base" ]
deps = [ "//weblayer/browser/java:jni" ]
sources = [ "browser/web_view_compatibility_helper_impl.cc" ]
}
} else {
static_library("weblayer_lib") {
public_deps = [ ":weblayer_lib_base" ]
}
}
grit("resources") { grit("resources") {
source = "weblayer_resources.grd" source = "weblayer_resources.grd"
...@@ -420,7 +464,7 @@ if (is_android) { ...@@ -420,7 +464,7 @@ if (is_android) {
shared_library("libweblayer") { shared_library("libweblayer") {
sources = [ "app/entry_point.cc" ] sources = [ "app/entry_point.cc" ]
deps = [ deps = [
":weblayer_lib", ":weblayer_lib_webview",
"//base", "//base",
"//content/public/app:both", "//content/public/app:both",
] ]
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "content/public/app/content_jni_onload.h" #include "content/public/app/content_jni_onload.h"
#include "content/public/app/content_main.h" #include "content/public/app/content_main.h"
#include "weblayer/app/content_main_delegate_impl.h" #include "weblayer/app/content_main_delegate_impl.h"
#include "weblayer/browser/java/weblayer_minimal_jni_registration.h"
namespace weblayer { namespace weblayer {
...@@ -34,4 +35,9 @@ bool OnJNIOnLoadInit() { ...@@ -34,4 +35,9 @@ bool OnJNIOnLoadInit() {
return true; return true;
} }
bool RegisterMinimalNatives() {
return weblayer_minimal::RegisterMainDexNatives(
base::android::AttachCurrentThread());
}
} // namespace weblayer } // namespace weblayer
...@@ -11,6 +11,10 @@ namespace weblayer { ...@@ -11,6 +11,10 @@ namespace weblayer {
bool OnJNIOnLoadInit(); bool OnJNIOnLoadInit();
// Registers the minimal set of natives needed to later register the full set of
// natives if WebView compatibility is needed.
bool RegisterMinimalNatives();
} // namespace weblayer } // namespace weblayer
#endif // WEBLAYER_APP_JNI_ONLOAD_H_ #endif // WEBLAYER_APP_JNI_ONLOAD_H_
...@@ -32,8 +32,7 @@ public class WebViewCompatibilityTest { ...@@ -32,8 +32,7 @@ public class WebViewCompatibilityTest {
public void testBothLoadPage() throws Exception { public void testBothLoadPage() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
WebLayer.initializeWebViewCompatibilityMode( WebLayer.initializeWebViewCompatibilityMode(
InstrumentationRegistry.getTargetContext().getApplicationContext(), InstrumentationRegistry.getTargetContext().getApplicationContext());
InstrumentationRegistry.getTargetContext().getCacheDir(), null);
}); });
mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html")); mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("simple_page.html"));
WebView webView = TestThreadUtils.runOnUiThreadBlocking( WebView webView = TestThreadUtils.runOnUiThreadBlocking(
......
...@@ -32,6 +32,18 @@ java_cpp_enum("generated_enums") { ...@@ -32,6 +32,18 @@ java_cpp_enum("generated_enums") {
] ]
} }
_webview_compat_sources =
[ "org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java" ]
android_library("webview_compat_java") {
sources = _webview_compat_sources
deps = [
"//base:base_java",
"//base:jni_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
android_library("java") { android_library("java") {
sources = [ sources = [
"org/chromium/weblayer_private/ActionModeCallback.java", "org/chromium/weblayer_private/ActionModeCallback.java",
...@@ -75,6 +87,7 @@ android_library("java") { ...@@ -75,6 +87,7 @@ android_library("java") {
":gms_bridge_java", ":gms_bridge_java",
":interfaces_java", ":interfaces_java",
":weblayer_resources", ":weblayer_resources",
":webview_compat_java",
"//base:base_java", "//base:base_java",
"//base:jni_java", "//base:jni_java",
"//components/autofill/android:provider_java", "//components/autofill/android:provider_java",
...@@ -117,6 +130,20 @@ android_library("java") { ...@@ -117,6 +130,20 @@ android_library("java") {
"//third_party/android_sdk:public_framework_system_java" "//third_party/android_sdk:public_framework_system_java"
} }
generate_jni_registration("weblayer_jni_registration") {
targets = [ ":java" ]
header_output = "$target_gen_dir/$target_name.h"
sources_blacklist = _webview_compat_sources
namespace = "weblayer"
}
generate_jni_registration("weblayer_minimal_jni_registration") {
targets = [ ":webview_compat_java" ]
header_output = "$target_gen_dir/$target_name.h"
namespace = "weblayer_minimal"
no_transitive_deps = true
}
android_resources("weblayer_test_resources") { android_resources("weblayer_test_resources") {
resource_dirs = [ "res_test" ] resource_dirs = [ "res_test" ]
custom_package = "org.chromium.weblayer_private.test" custom_package = "org.chromium.weblayer_private.test"
...@@ -153,6 +180,7 @@ generate_jni("jni") { ...@@ -153,6 +180,7 @@ generate_jni("jni") {
"org/chromium/weblayer_private/UrlBarControllerImpl.java", "org/chromium/weblayer_private/UrlBarControllerImpl.java",
"org/chromium/weblayer_private/WebLayerExceptionFilter.java", "org/chromium/weblayer_private/WebLayerExceptionFilter.java",
"org/chromium/weblayer_private/WebLayerImpl.java", "org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/WebViewCompatibilityHelperImpl.java",
"org/chromium/weblayer_private/metrics/MetricsServiceClient.java", "org/chromium/weblayer_private/metrics/MetricsServiceClient.java",
"org/chromium/weblayer_private/metrics/UmaUtils.java", "org/chromium/weblayer_private/metrics/UmaUtils.java",
"org/chromium/weblayer_private/resources/ResourceMapper.java", "org/chromium/weblayer_private/resources/ResourceMapper.java",
......
...@@ -190,9 +190,10 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -190,9 +190,10 @@ public final class WebLayerImpl extends IWebLayer.Stub {
mIsWebViewCompatMode = remoteContext != null mIsWebViewCompatMode = remoteContext != null
&& !remoteContext.getClassLoader().equals(WebLayerImpl.class.getClassLoader()); && !remoteContext.getClassLoader().equals(WebLayerImpl.class.getClassLoader());
if (mIsWebViewCompatMode && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { if (mIsWebViewCompatMode && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// We need to change the library name for Android M and below, otherwise the system will // Load the library with the crazy linker.
// load the version loaded for WebView. LibraryLoader.getInstance().setLinkerImplementation(true, false);
LibraryLoader.getInstance().setLibrarySuffix("-weblayer"); LibraryLoader.getInstance().setPostLoadHook(
WebViewCompatibilityHelperImpl::registerJni);
} }
Context appContext = minimalInitForContext(appContextWrapper, remoteContextWrapper); Context appContext = minimalInitForContext(appContextWrapper, remoteContextWrapper);
......
// 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.
package org.chromium.weblayer_private;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.NativeMethods;
/**
* Minimal native interface to allow registering all natives if WebView compatibility is needed.
*/
@MainDex
@JNINamespace("weblayer")
public final class WebViewCompatibilityHelperImpl {
public static void registerJni() {
WebViewCompatibilityHelperImplJni.get().registerJni();
}
@NativeMethods
interface Natives {
void registerJni();
}
}
// 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 "weblayer/browser/java/jni/WebViewCompatibilityHelperImpl_jni.h"
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
#include "base/android/library_loader/library_loader_hooks.h" // nogncheck
#include "weblayer/browser/java/weblayer_jni_registration.h" // nogncheck
#endif
namespace weblayer {
namespace {
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
void RegisterNonMainDexNativesHook() {
RegisterNonMainDexNatives(base::android::AttachCurrentThread());
}
#endif
} // namespace
void JNI_WebViewCompatibilityHelperImpl_RegisterJni(JNIEnv* env) {
#if defined(WEBLAYER_MANUAL_JNI_REGISTRATION)
RegisterMainDexNatives(base::android::AttachCurrentThread());
base::android::SetNonMainDexJniRegistrationHook(
RegisterNonMainDexNativesHook);
#endif
}
} // namespace weblayer
...@@ -7,9 +7,12 @@ ...@@ -7,9 +7,12 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/version_info/android/channel_getter.h"
#include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/shared_url_loader_factory.h"
#if defined(OS_ANDROID)
#include "components/version_info/android/channel_getter.h"
#endif
using version_info::Channel; using version_info::Channel;
namespace weblayer { namespace weblayer {
...@@ -47,7 +50,11 @@ WebLayerVariationsServiceClient::GetNetworkTimeTracker() { ...@@ -47,7 +50,11 @@ WebLayerVariationsServiceClient::GetNetworkTimeTracker() {
} }
Channel WebLayerVariationsServiceClient::GetChannel() { Channel WebLayerVariationsServiceClient::GetChannel() {
#if defined(OS_ANDROID)
return version_info::android::GetChannel(); return version_info::android::GetChannel();
#else
return version_info::Channel::UNKNOWN;
#endif
} }
bool WebLayerVariationsServiceClient::OverridesRestrictParameter( bool WebLayerVariationsServiceClient::OverridesRestrictParameter(
......
...@@ -14,6 +14,7 @@ import android.os.RemoteException; ...@@ -14,6 +14,7 @@ import android.os.RemoteException;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.AndroidRuntimeException; import android.util.AndroidRuntimeException;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
...@@ -33,7 +34,6 @@ import java.lang.reflect.Field; ...@@ -33,7 +34,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
/** /**
* WebLayer is responsible for initializing state necessary to use any of the classes in web layer. * WebLayer is responsible for initializing state necessary to use any of the classes in web layer.
...@@ -59,18 +59,12 @@ public class WebLayer { ...@@ -59,18 +59,12 @@ public class WebLayer {
@NonNull @NonNull
private final IWebLayer mImpl; private final IWebLayer mImpl;
private static WebViewCompatibilityHelper sWebViewCompatHelper; private static ClassLoader sWebViewCompatClassLoader;
/** The result of calling {@link #initializeWebViewCompatibilityMode}. */ /** The result of calling {@link #initializeWebViewCompatibilityMode}. */
public enum WebViewCompatibilityResult { public enum WebViewCompatibilityResult {
/** Native libs were copied to data directory. */ /** Compatibility mode has been successfully set up. */
SUCCESS_COPIED, SUCCESS,
/** Correct libs have already been copied, or symlinks were used. */
SUCCESS_CACHED,
/** IOException was thrown, could mean there is not enough disk space. */
FAILURE_IO_ERROR,
/** This version of the WebLayer implementation does not support WebView compatibility. */ /** This version of the WebLayer implementation does not support WebView compatibility. */
FAILURE_UNSUPPORTED_VERSION, FAILURE_UNSUPPORTED_VERSION,
...@@ -100,18 +94,24 @@ public class WebLayer { ...@@ -100,18 +94,24 @@ public class WebLayer {
} }
} }
/** Deprecated. Use initializeWebViewCompatibilityMode(Context) instead. */
public static void initializeWebViewCompatibilityMode(@NonNull Context appContext,
@NonNull File baseDir, @NonNull Callback<WebViewCompatibilityResult> callback) {
WebViewCompatibilityResult result = initializeWebViewCompatibilityMode(appContext);
if (callback != null) {
callback.onResult(result);
}
}
/** /**
* Performs initialization needed to run WebView and WebLayer in the same process. This should * Performs initialization needed to run WebView and WebLayer in the same process.
* be called as early as possible if this functionality is needed.
* *
* @param appContext The hosting application's Context. * @param appContext The hosting application's Context.
* @param baseDir The directory to copy any necessary files into.
* @param callback Callback called on success or failure.
*/ */
public static void initializeWebViewCompatibilityMode(@NonNull Context appContext, public static WebViewCompatibilityResult initializeWebViewCompatibilityMode(
@NonNull File baseDir, @NonNull Callback<WebViewCompatibilityResult> callback) { @NonNull Context appContext) {
ThreadCheck.ensureOnUiThread(); ThreadCheck.ensureOnUiThread();
if (sWebViewCompatHelper != null) { if (sWebViewCompatClassLoader != null) {
throw new AndroidRuntimeException( throw new AndroidRuntimeException(
"initializeWebViewCompatibilityMode() has already been called."); "initializeWebViewCompatibilityMode() has already been called.");
} }
...@@ -121,13 +121,14 @@ public class WebLayer { ...@@ -121,13 +121,14 @@ public class WebLayer {
+ "loaded."); + "loaded.");
} }
try { try {
sWebViewCompatHelper = WebViewCompatibilityHelper.initialize( Pair<ClassLoader, WebLayer.WebViewCompatibilityResult> result =
appContext, getOrCreateRemoteContext(appContext), baseDir, callback); WebViewCompatibilityHelper.initialize(
appContext, getOrCreateRemoteContext(appContext));
sWebViewCompatClassLoader = result.first;
return result.second;
} catch (Exception e) { } catch (Exception e) {
if (callback != null) {
callback.onResult(WebViewCompatibilityResult.FAILURE_OTHER);
}
Log.e(TAG, "Unable to initialize WebView compatibility", e); Log.e(TAG, "Unable to initialize WebView compatibility", e);
return WebViewCompatibilityResult.FAILURE_OTHER;
} }
} }
...@@ -265,8 +266,8 @@ public class WebLayer { ...@@ -265,8 +266,8 @@ public class WebLayer {
int majorVersion = -1; int majorVersion = -1;
String version = "<unavailable>"; String version = "<unavailable>";
try { try {
if (sWebViewCompatHelper != null) { if (sWebViewCompatClassLoader != null) {
remoteClassLoader = sWebViewCompatHelper.getWebLayerClassLoader(); remoteClassLoader = sWebViewCompatClassLoader;
} }
if (remoteClassLoader == null) { if (remoteClassLoader == null) {
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader(); remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
...@@ -285,7 +286,7 @@ public class WebLayer { ...@@ -285,7 +286,7 @@ public class WebLayer {
majorVersion = mFactory.getImplementationMajorVersion(); majorVersion = mFactory.getImplementationMajorVersion();
version = mFactory.getImplementationVersion(); version = mFactory.getImplementationVersion();
} catch (PackageManager.NameNotFoundException | ReflectiveOperationException } catch (PackageManager.NameNotFoundException | ReflectiveOperationException
| RemoteException | ExecutionException | InterruptedException e) { | RemoteException e) {
Log.e(TAG, "Unable to create WebLayerFactory", e); Log.e(TAG, "Unable to create WebLayerFactory", e);
} }
mAvailable = available; mAvailable = available;
......
...@@ -9,51 +9,41 @@ import android.content.pm.ApplicationInfo; ...@@ -9,51 +9,41 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import dalvik.system.BaseDexClassLoader; import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
/** Helper class which performs initialization needed for WebView compatibility. */ /** Helper class which performs initialization needed for WebView compatibility. */
@SuppressWarnings("NoAndroidAsyncTaskCheck")
final class WebViewCompatibilityHelper { final class WebViewCompatibilityHelper {
private final PackageInfo mPackageInfo; /** Creates a the ClassLoader to use for WebView compatibility. */
private final GetClassLoaderPathsTask mClassLoaderPathsTask; static Pair<ClassLoader, WebLayer.WebViewCompatibilityResult> initialize(
Context appContext, Context remoteContext)
WebViewCompatibilityHelper(String[] libraryPaths, PackageInfo packageInfo, File baseDir,
Callback<WebLayer.WebViewCompatibilityResult> callback) {
mPackageInfo = packageInfo;
mClassLoaderPathsTask =
new GetClassLoaderPathsTask(libraryPaths, mPackageInfo, baseDir, callback);
mClassLoaderPathsTask.executeOnExecutor(android.os.AsyncTask.THREAD_POOL_EXECUTOR);
}
/** Creates a WebViewCompatibilityHelper and begins initialization. */
static WebViewCompatibilityHelper initialize(Context appContext, Context remoteContext,
File baseDir, Callback<WebLayer.WebViewCompatibilityResult> callback)
throws PackageManager.NameNotFoundException, ReflectiveOperationException { throws PackageManager.NameNotFoundException, ReflectiveOperationException {
PackageInfo info = PackageInfo info =
appContext.getPackageManager().getPackageInfo(remoteContext.getPackageName(), appContext.getPackageManager().getPackageInfo(remoteContext.getPackageName(),
PackageManager.GET_SHARED_LIBRARY_FILES PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_UNINSTALLED_PACKAGES); | PackageManager.MATCH_UNINSTALLED_PACKAGES);
if (!isSupportedVersion(info.versionName)) { if (!isSupportedVersion(info.versionName)) {
if (callback != null) { return Pair.create(
callback.onResult(WebLayer.WebViewCompatibilityResult.FAILURE_UNSUPPORTED_VERSION); null, WebLayer.WebViewCompatibilityResult.FAILURE_UNSUPPORTED_VERSION);
}
return null;
} }
return new WebViewCompatibilityHelper( // Prepend "/." to all library paths. This changes the library path while still pointing to
getLibraryPaths(remoteContext.getClassLoader()), info, baseDir, callback); // the same directory, allowing us to get around a check in the JVM.
String[] libraryPaths = getLibraryPaths(remoteContext.getClassLoader());
for (int i = 0; i < libraryPaths.length; i++) {
assert libraryPaths[i].startsWith("/");
libraryPaths[i] = "/." + libraryPaths[i];
}
ClassLoader classLoader = new PathClassLoader(getAllApkPaths(info.applicationInfo),
TextUtils.join(File.pathSeparator, libraryPaths),
ClassLoader.getSystemClassLoader());
return Pair.create(classLoader, WebLayer.WebViewCompatibilityResult.SUCCESS);
} }
/** /**
...@@ -75,22 +65,11 @@ final class WebViewCompatibilityHelper { ...@@ -75,22 +65,11 @@ final class WebViewCompatibilityHelper {
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return false; return false;
} }
return majorVersion >= 81; // M- only supports WebView compat via copying the libraries on 81, so only support 82+.
} if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
return majorVersion >= 82;
/**
* Returns the class loader to be used for WebView compatibility. This class loader will not
* interfere with any WebViews that are created.
*/
ClassLoader getWebLayerClassLoader() throws ExecutionException, InterruptedException {
ClassLoaderPaths paths = mClassLoaderPathsTask.get();
if (paths.mResult != WebLayer.WebViewCompatibilityResult.SUCCESS_CACHED
&& paths.mResult != WebLayer.WebViewCompatibilityResult.SUCCESS_COPIED) {
return null;
} }
return new DexClassLoader(getAllApkPaths(mPackageInfo.applicationInfo), return majorVersion >= 81;
paths.mOptimizedDirectory, paths.mLibrarySearchPath,
ClassLoader.getSystemClassLoader());
} }
/** Returns the library paths the given class loader will search. */ /** Returns the library paths the given class loader will search. */
...@@ -103,7 +82,7 @@ final class WebViewCompatibilityHelper { ...@@ -103,7 +82,7 @@ final class WebViewCompatibilityHelper {
} }
/** This is mostly taken from ApplicationInfo.getAllApkPaths(). */ /** This is mostly taken from ApplicationInfo.getAllApkPaths(). */
private String getAllApkPaths(ApplicationInfo info) { private static String getAllApkPaths(ApplicationInfo info) {
// The OS version of this method also includes resourceDirs, but this is not available in // The OS version of this method also includes resourceDirs, but this is not available in
// the SDK. // the SDK.
final String[][] inputLists = {info.sharedLibraryFiles, info.splitSourceDirs}; final String[][] inputLists = {info.sharedLibraryFiles, info.splitSourceDirs};
...@@ -121,158 +100,5 @@ final class WebViewCompatibilityHelper { ...@@ -121,158 +100,5 @@ final class WebViewCompatibilityHelper {
return TextUtils.join(File.pathSeparator, output); return TextUtils.join(File.pathSeparator, output);
} }
private class ClassLoaderPaths { private WebViewCompatibilityHelper() {}
String mLibrarySearchPath;
String mOptimizedDirectory;
WebLayer.WebViewCompatibilityResult mResult =
WebLayer.WebViewCompatibilityResult.SUCCESS_CACHED;
}
/**
* Task to retrieve file paths which should be used when creating a class loader compatible
* with WebView.
*/
private class GetClassLoaderPathsTask
extends android.os.AsyncTask<Void, Void, ClassLoaderPaths> {
private static final String PRIVATE_DIR = "weblayer_private";
private final String[] mOriginalLibraryPaths;
private final PackageInfo mPackageInfo;
private final File mBaseDir;
private final Callback<WebLayer.WebViewCompatibilityResult> mCallback;
public GetClassLoaderPathsTask(String[] libraryPaths, PackageInfo packageInfo, File baseDir,
Callback<WebLayer.WebViewCompatibilityResult> callback) {
mOriginalLibraryPaths = libraryPaths;
mPackageInfo = packageInfo;
mBaseDir = baseDir;
mCallback = callback;
}
@Override
protected ClassLoaderPaths doInBackground(Void... params) {
File webLayerDir = getWebLayerDir();
ClassLoaderPaths paths = new ClassLoaderPaths();
try {
addLibrarySearchPath(webLayerDir, paths);
} catch (ErrnoException | IOException e) {
// Make sure to clean up anything left over after an IOException.
recursivelyDeleteFile(new File(mBaseDir, PRIVATE_DIR));
paths.mResult = WebLayer.WebViewCompatibilityResult.FAILURE_IO_ERROR;
return paths;
}
File codeCacheDir = new File(webLayerDir, "codecache");
codeCacheDir.mkdirs();
paths.mOptimizedDirectory = codeCacheDir.toString();
return paths;
}
@Override
protected void onPostExecute(ClassLoaderPaths paths) {
if (mCallback != null) {
mCallback.onResult(paths.mResult);
}
}
/**
* Creates the directory to copy/symlink WebView libraries. This will be a directory
* structure like files/weblayer_private/374100010/.
*/
private File getWebLayerDir() {
File webLayerDir = new File(mBaseDir, PRIVATE_DIR);
webLayerDir.mkdirs();
// Clean up previous versions before we copy any files.
String[] names = webLayerDir.list();
String versionCodeString = String.valueOf(mPackageInfo.versionCode);
if (names != null) {
for (String name : names) {
if (!name.equals(versionCodeString)) {
recursivelyDeleteFile(new File(webLayerDir, name));
}
}
}
File finalDir = new File(webLayerDir, versionCodeString);
finalDir.mkdirs();
return finalDir;
}
private void addLibrarySearchPath(File webLayerDir, ClassLoaderPaths paths)
throws ErrnoException, IOException {
ArrayList<String> finalPaths = new ArrayList<>();
for (int i = 0; i < mOriginalLibraryPaths.length; i++) {
File path = new File(mOriginalLibraryPaths[i]);
// Skip libs from other directories like /vendor.
if (!path.toString().startsWith("/data")) {
continue;
}
File newPath = new File(webLayerDir, "lib" + i);
String suffix = "";
if (path.toString().contains("!")) {
String[] splitPath = path.toString().split("!");
path = new File(splitPath[0]);
suffix = "!" + splitPath[1];
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// If this path isn't a directory, it's probably pointing to the APK. We don't
// want to try to copy the APK, and libraries are only stored in the APK in
// Monochrome/Trichrome which is only used for N+.
if (!path.isDirectory()) {
continue;
}
for (String libName : path.list()) {
// Make sure we only copy the necessary lib.
if (!libName.equals("libwebviewchromium.so")) {
continue;
}
File oldFile = new File(path, libName);
File newFile = new File(newPath, libName.replace(".so", "-weblayer.so"));
if (!newFile.exists()) {
newPath.mkdirs();
copyFile(oldFile, newFile);
paths.mResult = WebLayer.WebViewCompatibilityResult.SUCCESS_COPIED;
}
}
} else {
newPath.mkdirs();
// Make sure to re-create the symlink in case the WebView APK moved.
newPath.delete();
Os.symlink(path.toString(), newPath.toString());
}
finalPaths.add(newPath.toString() + suffix);
}
paths.mLibrarySearchPath = TextUtils.join(File.pathSeparator, finalPaths);
}
private void copyFile(File src, File dst) throws IOException {
try (FileInputStream inputStream = new FileInputStream(src);
FileOutputStream outputStream = new FileOutputStream(dst)) {
byte[] bytes = new byte[8192];
int length;
while ((length = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, length);
}
}
}
private void recursivelyDeleteFile(File currentFile) {
if (!currentFile.exists()) {
// This file could be a broken symlink, so try to delete. If we don't delete a
// broken symlink, the directory containing it cannot be deleted.
currentFile.delete();
return;
}
if (currentFile.isDirectory()) {
File[] files = currentFile.listFiles();
if (files != null) {
for (File file : files) {
recursivelyDeleteFile(file);
}
}
}
currentFile.delete();
}
}
} }
...@@ -4,265 +4,49 @@ ...@@ -4,265 +4,49 @@
package org.chromium.weblayer; package org.chromium.weblayer;
import android.content.pm.ApplicationInfo; import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build; import android.os.Build;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.util.Pair;
import dalvik.system.DexClassLoader;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.base.FileUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.TestFileUtil;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/** /**
* Tests for (@link WebViewCompatibilityHelper}. * Tests for (@link WebViewCompatibilityHelper}.
*/ */
@RunWith(BaseJUnit4ClassRunner.class) @RunWith(BaseJUnit4ClassRunner.class)
public class WebViewCompatibilityHelperTest { public class WebViewCompatibilityHelperTest {
private File mTmpDir;
private File mOriginalLibDir;
private File mNewLibDir;
private PackageInfo mPackageInfo;
private static class ResultHelper implements Callback<WebLayer.WebViewCompatibilityResult> {
private WebLayer.WebViewCompatibilityResult mResult;
private CallbackHelper mCallbackHelper = new CallbackHelper();
@Override
public void onResult(WebLayer.WebViewCompatibilityResult result) {
mResult = result;
mCallbackHelper.notifyCalled();
}
public WebLayer.WebViewCompatibilityResult getResult() throws TimeoutException {
mCallbackHelper.waitForFirst();
return mResult;
}
}
@Before
public void setUp() {
mTmpDir = InstrumentationRegistry.getTargetContext().getCacheDir();
mOriginalLibDir = new File(mTmpDir, "original");
mOriginalLibDir.mkdirs();
mNewLibDir = new File(mTmpDir, "new");
mPackageInfo = new PackageInfo();
mPackageInfo.versionCode = 1;
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.publicSourceDir = new File(mTmpDir, "fake.apk").toString();
mPackageInfo.applicationInfo = applicationInfo;
}
@Test @Test
@SmallTest @SmallTest
public void testLibsCopied() throws Exception { public void testLibraryPaths() throws Exception {
writeFile(new File(mOriginalLibDir, "libwebviewchromium.so"), "foo"); Context appContext = InstrumentationRegistry.getTargetContext();
writeFile(new File(mOriginalLibDir, "libbar.so"), "bar"); Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
String[] libraryPaths = new String[] {mOriginalLibDir.toString()}; Pair<ClassLoader, WebLayer.WebViewCompatibilityResult> result =
ResultHelper helper = new ResultHelper(); WebViewCompatibilityHelper.initialize(appContext, remoteContext);
DexClassLoader classLoader = (DexClassLoader) new WebViewCompatibilityHelper( Assert.assertEquals(result.second, WebLayer.WebViewCompatibilityResult.SUCCESS);
libraryPaths, mPackageInfo, mNewLibDir, helper) String[] libraryPaths = WebViewCompatibilityHelper.getLibraryPaths(result.first);
.getWebLayerClassLoader(); for (String path : libraryPaths) {
Assert.assertEquals(helper.getResult(), getExpectedCopyResult()); Assert.assertTrue(path.startsWith("/./"));
File libDir = new File(mNewLibDir, "weblayer_private/1/lib0");
File foo = new File(libDir, getExpectedLibFileName());
Assert.assertEquals(readFile(foo), "foo");
Assert.assertEquals(classLoader.findLibrary(getExpectedLibName()), foo.toString());
// M- will only copy libwebviewchromium.so.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
File bar = new File(libDir, "libbar.so");
Assert.assertEquals(readFile(bar), "bar");
Assert.assertEquals(classLoader.findLibrary("bar"), bar.toString());
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Assert.assertFalse(isSymlink(libDir));
} else {
Assert.assertTrue(isSymlink(libDir));
} }
} }
@Test @Test
@SmallTest @SmallTest
public void testIoError() throws Exception { public void testSupportedVersion() throws Exception {
writeFile(new File(mOriginalLibDir, "libwebviewchromium.so"), "foo");
String[] libraryPaths = new String[] {mOriginalLibDir.toString()};
ResultHelper helper = new ResultHelper();
DexClassLoader classLoader = (DexClassLoader) new WebViewCompatibilityHelper(
libraryPaths, mPackageInfo, new File("/bad_dir"), helper)
.getWebLayerClassLoader();
Assert.assertNull(classLoader);
Assert.assertEquals(
helper.getResult(), WebLayer.WebViewCompatibilityResult.FAILURE_IO_ERROR);
}
@Test
@SmallTest
public void testMultipleLibDirs() throws Exception {
writeFile(new File(mOriginalLibDir, "foo/foo"), "foo");
writeFile(new File(mOriginalLibDir, "bar/libwebviewchromium.so"), "bar");
String[] libraryPaths = new String[] {new File(mOriginalLibDir, "foo").toString(),
new File(mOriginalLibDir, "bar").toString()};
ResultHelper helper = new ResultHelper();
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), getExpectedCopyResult());
// M- will only copy libwebviewchromium.so.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Assert.assertFalse(new File(mNewLibDir, "weblayer_private/1/lib0/").exists()); Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("81.0.2.5"));
Assert.assertTrue(
new File(mNewLibDir, "weblayer_private/1/lib1/" + getExpectedLibFileName())
.exists());
} else { } else {
Assert.assertTrue(new File(mNewLibDir, "weblayer_private/1/lib0/foo").exists());
Assert.assertTrue(
new File(mNewLibDir, "weblayer_private/1/lib1/" + getExpectedLibFileName())
.exists());
}
}
@Test
@SmallTest
public void testLibsNotCopiedAgain() throws Exception {
writeFile(new File(mOriginalLibDir, "libwebviewchromium.so"), "foo");
String[] libraryPaths = new String[] {mOriginalLibDir.toString()};
ResultHelper helper = new ResultHelper();
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), getExpectedCopyResult());
File foo = new File(mNewLibDir, "weblayer_private/1/lib0/" + getExpectedLibFileName());
Assert.assertTrue(foo.exists());
long lastModified = foo.lastModified();
// Sleep for 1 sec to guarantee last modified would have changed.
Thread.sleep(1000);
helper = new ResultHelper();
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), WebLayer.WebViewCompatibilityResult.SUCCESS_CACHED);
Assert.assertTrue(foo.exists());
Assert.assertEquals(foo.lastModified(), lastModified);
}
@Test
@SmallTest
public void testOldLibsDeleted() throws Exception {
writeFile(new File(mOriginalLibDir, "libwebviewchromium.so"), "foo");
String[] libraryPaths = new String[] {mOriginalLibDir.toString()};
ResultHelper helper = new ResultHelper();
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), getExpectedCopyResult());
File oldFile = new File(mNewLibDir, "weblayer_private/1/lib0/" + getExpectedLibFileName());
Assert.assertTrue(oldFile.exists());
FileUtils.recursivelyDeleteFile(mOriginalLibDir, FileUtils.DELETE_ALL);
File originalLibDir2 = new File(mTmpDir, "original2");
originalLibDir2.mkdirs();
writeFile(new File(originalLibDir2, "libwebviewchromium.so"), "foo2");
mPackageInfo.versionCode = 2;
libraryPaths = new String[] {originalLibDir2.toString()};
helper = new ResultHelper();
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), getExpectedCopyResult());
Assert.assertFalse(oldFile.exists());
Assert.assertFalse(new File(mNewLibDir, "weblayer_private/1").exists());
Assert.assertEquals(readFile(new File(mNewLibDir,
"weblayer_private/2/lib0/" + getExpectedLibFileName())),
"foo2");
}
@Test
@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testZipPath() throws Exception {
File originalZip = new File(mOriginalLibDir, "foo.apk");
writeFile(originalZip, "zip stuff");
String[] libraryPaths = new String[] {originalZip.toString() + "!/libs"};
ResultHelper helper = new ResultHelper();
ClassLoader classLoader =
new WebViewCompatibilityHelper(libraryPaths, mPackageInfo, mNewLibDir, helper)
.getWebLayerClassLoader();
Assert.assertEquals(helper.getResult(), getExpectedCopyResult());
File newZip = new File(mNewLibDir, "weblayer_private/1/lib0");
Assert.assertArrayEquals(WebViewCompatibilityHelper.getLibraryPaths(classLoader),
new String[] {newZip.toString() + "!/libs"});
Assert.assertTrue(newZip.exists());
Assert.assertTrue(isSymlink(newZip));
}
@Test
@SmallTest
public void testSupportedVersion() throws Exception {
Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion("81.0.2.5")); Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion("81.0.2.5"));
}
Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion("82.0.2.5")); Assert.assertTrue(WebViewCompatibilityHelper.isSupportedVersion("82.0.2.5"));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("80.0.2.5")); Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("80.0.2.5"));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("")); Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(""));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("82.0")); Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion("82.0"));
Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(null)); Assert.assertFalse(WebViewCompatibilityHelper.isSupportedVersion(null));
} }
private void writeFile(File file, String contents) throws Exception {
file.getParentFile().mkdirs();
FileUtils.copyStreamToFile(new ByteArrayInputStream(contents.getBytes()), file);
}
private String readFile(File file) throws Exception {
// Assert.assertTrue(file.exists());
return new String(TestFileUtil.readUtf8File(file.toString(), 1024 /* sizeLimit */));
}
private boolean isSymlink(File file) throws IOException {
File canonical;
if (file.getParent() == null) {
canonical = file;
} else {
File canonicalDir = file.getParentFile().getCanonicalFile();
canonical = new File(canonicalDir, file.getName());
}
return !canonical.getCanonicalFile().equals(canonical.getAbsoluteFile());
}
private static String getExpectedLibName() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
return "webviewchromium-weblayer";
}
return "webviewchromium";
}
private static String getExpectedLibFileName() {
return "lib" + getExpectedLibName() + ".so";
}
private static WebLayer.WebViewCompatibilityResult getExpectedCopyResult() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
return WebLayer.WebViewCompatibilityResult.SUCCESS_COPIED;
}
return WebLayer.WebViewCompatibilityResult.SUCCESS_CACHED;
}
} }
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