diff options
11 files changed, 756 insertions, 105 deletions
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java index 175f35f41ebf..341c69fd2eba 100644 --- a/core/java/android/webkit/WebViewLibraryLoader.java +++ b/core/java/android/webkit/WebViewLibraryLoader.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -26,6 +28,7 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import dalvik.system.VMRuntime; @@ -36,7 +39,11 @@ import java.util.Arrays; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -class WebViewLibraryLoader { +/** + * @hide + */ +@VisibleForTesting +public class WebViewLibraryLoader { private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName(); private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 = @@ -94,7 +101,7 @@ class WebViewLibraryLoader { /** * Create a single relro file by invoking an isolated process that to do the actual work. */ - static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) { + static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) { final String abi = is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]; @@ -112,12 +119,12 @@ class WebViewLibraryLoader { }; try { - if (nativeLibraryPath == null) { + if (nativeLib == null || nativeLib.path == null) { throw new IllegalArgumentException( "Native library paths to the WebView RelRo process must not be null!"); } int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess( - RelroFileCreator.class.getName(), new String[] { nativeLibraryPath }, + RelroFileCreator.class.getName(), new String[] { nativeLib.path }, "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler); if (pid <= 0) throw new Exception("Failed to start the relro file creator process"); } catch (Throwable t) { @@ -127,25 +134,48 @@ class WebViewLibraryLoader { } } + /** + * Perform preparations needed to allow loading WebView from an application. This method should + * be called whenever we change WebView provider. + * @return the number of relro processes started. + */ static int prepareNativeLibraries(PackageInfo webviewPackageInfo) throws WebViewFactory.MissingWebViewPackageException { - String[] nativeLibs = updateWebViewZygoteVmSize(webviewPackageInfo); + WebViewNativeLibrary nativeLib32bit = + getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */); + WebViewNativeLibrary nativeLib64bit = + getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */); + updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit); + + return createRelros(nativeLib32bit, nativeLib64bit); + } + + /** + * @return the number of relro processes started. + */ + private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) { if (DEBUG) Log.v(LOGTAG, "creating relro files"); int numRelros = 0; - // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any - // unexpected values will be handled there to ensure that we trigger notifying any process - // waiting on relro creation. if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); - createRelroFile(false /* is64Bit */, nativeLibs[0]); - numRelros++; + if (nativeLib32bit == null) { + Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); + createRelroFile(false /* is64Bit */, nativeLib32bit); + numRelros++; + } } if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); - createRelroFile(true /* is64Bit */, nativeLibs[1]); - numRelros++; + if (nativeLib64bit == null) { + Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); + createRelroFile(true /* is64Bit */, nativeLib64bit); + numRelros++; + } } return numRelros; } @@ -154,53 +184,28 @@ class WebViewLibraryLoader { * * @return the native WebView libraries in the new WebView APK. */ - private static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo) + private static void updateWebViewZygoteVmSize( + @Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) throws WebViewFactory.MissingWebViewPackageException { // Find the native libraries of the new WebView package, to change the size of the // memory region in the Zygote reserved for the library. - String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo); - if (nativeLibs != null) { - long newVmSize = 0L; - - for (String path : nativeLibs) { - if (path == null || TextUtils.isEmpty(path)) continue; - if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path); - File f = new File(path); - if (f.exists()) { - newVmSize = Math.max(newVmSize, f.length()); - continue; - } - if (path.contains("!/")) { - String[] split = TextUtils.split(path, "!/"); - if (split.length == 2) { - try (ZipFile z = new ZipFile(split[0])) { - ZipEntry e = z.getEntry(split[1]); - if (e != null && e.getMethod() == ZipEntry.STORED) { - newVmSize = Math.max(newVmSize, e.getSize()); - continue; - } - } - catch (IOException e) { - Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e); - } - } - } - Log.e(LOGTAG, "error sizing load for " + path); - } + long newVmSize = 0L; - if (DEBUG) { - Log.v(LOGTAG, "Based on library size, need " + newVmSize - + " bytes of address space."); - } - // The required memory can be larger than the file on disk (due to .bss), and an - // upgraded version of the library will likely be larger, so always attempt to - // reserve twice as much as we think to allow for the library to grow during this - // boot cycle. - newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); - Log.d(LOGTAG, "Setting new address space to " + newVmSize); - setWebViewZygoteVmSize(newVmSize); + if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size); + if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size); + + if (DEBUG) { + Log.v(LOGTAG, "Based on library size, need " + newVmSize + + " bytes of address space."); } - return nativeLibs; + // The required memory can be larger than the file on disk (due to .bss), and an + // upgraded version of the library will likely be larger, so always attempt to + // reserve twice as much as we think to allow for the library to grow during this + // boot cycle. + newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); + Log.d(LOGTAG, "Setting new address space to " + newVmSize); + setWebViewZygoteVmSize(newVmSize); } /** @@ -250,64 +255,78 @@ class WebViewLibraryLoader { /** * Fetch WebView's native library paths from {@param packageInfo}. + * @hide */ - static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo) - throws WebViewFactory.MissingWebViewPackageException { + @Nullable + @VisibleForTesting + public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo, + boolean is64bit) throws WebViewFactory.MissingWebViewPackageException { ApplicationInfo ai = packageInfo.applicationInfo; final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai); - String path32; - String path64; - boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi); - if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { - // Multi-arch case. - if (primaryArchIs64bit) { - // Primary arch: 64-bit, secondary: 32-bit. - path64 = ai.nativeLibraryDir; - path32 = ai.secondaryNativeLibraryDir; - } else { - // Primary arch: 32-bit, secondary: 64-bit. - path64 = ai.secondaryNativeLibraryDir; - path32 = ai.nativeLibraryDir; - } - } else if (primaryArchIs64bit) { - // Single-arch 64-bit. - path64 = ai.nativeLibraryDir; - path32 = ""; - } else { - // Single-arch 32-bit. - path32 = ai.nativeLibraryDir; - path64 = ""; + String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */); + + WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName, + is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir); + + if (DEBUG) { + Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path)); } + return lib; + } - // Form the full paths to the extracted native libraries. - // If libraries were not extracted, try load from APK paths instead. - if (!TextUtils.isEmpty(path32)) { - path32 += "/" + nativeLibFileName; - File f = new File(path32); - if (!f.exists()) { - path32 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_32_BIT_ABIS, - nativeLibFileName); - } + /** + * @return the directory of the native WebView library with bitness {@param is64bit}. + * @hide + */ + @VisibleForTesting + public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) { + // Primary arch has the same bitness as the library we are looking for. + if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir; + + // Secondary arch has the same bitness as the library we are looking for. + if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { + return ai.secondaryNativeLibraryDir; } - if (!TextUtils.isEmpty(path64)) { - path64 += "/" + nativeLibFileName; - File f = new File(path64); - if (!f.exists()) { - path64 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_64_BIT_ABIS, - nativeLibFileName); - } + + return ""; + } + + /** + * @return an object describing a native WebView library given the directory path of that + * library, or null if the library couldn't be found. + */ + @Nullable + private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai, + String nativeLibFileName, String[] abiList, String libDirectory) + throws WebViewFactory.MissingWebViewPackageException { + if (TextUtils.isEmpty(libDirectory)) return null; + String libPath = libDirectory + "/" + nativeLibFileName; + File f = new File(libPath); + if (f.exists()) { + return new WebViewNativeLibrary(libPath, f.length()); + } else { + return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName); } + } - if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64); - return new String[] { path32, path64 }; + /** + * @hide + */ + @VisibleForTesting + public static class WebViewNativeLibrary { + public final String path; + public final long size; + + WebViewNativeLibrary(String path, long size) { + this.path = path; + this.size = size; + } } - private static String getLoadFromApkPath(String apkPath, - String[] abiList, - String nativeLibFileName) + private static WebViewNativeLibrary getLoadFromApkPath(String apkPath, + String[] abiList, + String nativeLibFileName) throws WebViewFactory.MissingWebViewPackageException { // Search the APK for a native library conforming to a listed ABI. try (ZipFile z = new ZipFile(apkPath)) { @@ -316,13 +335,13 @@ class WebViewLibraryLoader { ZipEntry e = z.getEntry(entry); if (e != null && e.getMethod() == ZipEntry.STORED) { // Return a path formatted for dlopen() load from APK. - return apkPath + "!/" + entry; + return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize()); } } } catch (IOException e) { throw new WebViewFactory.MissingWebViewPackageException(e); } - return ""; + return null; } /** diff --git a/core/tests/webkit/Android.mk b/core/tests/webkit/Android.mk new file mode 100644 index 000000000000..cd0c3d25a68b --- /dev/null +++ b/core/tests/webkit/Android.mk @@ -0,0 +1,44 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, unit_tests_src) + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test + +LOCAL_PACKAGE_NAME := WebViewLoadingTests +LOCAL_CERTIFICATE := platform + +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_REQUIRED_MODULES := \ + WebViewLoadingOnDiskTestApk \ + WebViewLoadingFromApkTestApk + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/webkit/AndroidManifest.xml b/core/tests/webkit/AndroidManifest.xml new file mode 100644 index 000000000000..42accdf66891 --- /dev/null +++ b/core/tests/webkit/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webkit.tests" + android:sharedUserId="android.uid.system"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.webkit.tests" + android:label="Frameworks WebView Loader Tests" /> + +</manifest> diff --git a/core/tests/webkit/AndroidTest.xml b/core/tests/webkit/AndroidTest.xml new file mode 100644 index 000000000000..78cfa462beeb --- /dev/null +++ b/core/tests/webkit/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks WebView Loading Tests."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="WebViewLoadingTests.apk" /> + <option name="test-file-name" value="WebViewLoadingOnDiskTestApk.apk" /> + <option name="test-file-name" value="WebViewLoadingFromApkTestApk.apk" /> + <option name="cleanup-apks" value="true" /> + <option name="alt-dir" value="out" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.webkit.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/core/tests/webkit/apk_with_native_libs/Android.mk b/core/tests/webkit/apk_with_native_libs/Android.mk new file mode 100644 index 000000000000..7c6c36e0a6a2 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/Android.mk @@ -0,0 +1,66 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +MY_PATH := $(LOCAL_PATH) + +# Set shared variables +MY_MODULE_TAGS := optional +MY_JNI_SHARED_LIBRARIES := libwebviewtest_jni +MY_MODULE_PATH := $(TARGET_OUT_DATA_APPS) +MY_SRC_FILES := $(call all-java-files-under, src) +MY_SDK_VERSION := system_current +MY_PROGUARD_ENABLED := disabled +MY_MULTILIB := both + +# Recurse down the file tree. +include $(call all-subdir-makefiles) + + + +# Builds an apk containing native libraries that will be unzipped on the device. +include $(CLEAR_VARS) + +LOCAL_PATH := $(MY_PATH) +LOCAL_PACKAGE_NAME := WebViewLoadingOnDiskTestApk +LOCAL_MANIFEST_FILE := ondisk/AndroidManifest.xml + +LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS) +LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES) +LOCAL_MODULE_PATH := $(MY_MODULE_PATH) +LOCAL_SRC_FILES := $(MY_SRC_FILES) +LOCAL_SDK_VERSION := $(MY_SDK_VERSION) +LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED) +LOCAL_MULTILIB := $(MY_MULTILIB) + +include $(BUILD_PACKAGE) + + +# Builds an apk containing uncompressed native libraries that have to be +# accessed through the APK itself on the device. +include $(CLEAR_VARS) + +LOCAL_PATH := $(MY_PATH) +LOCAL_PACKAGE_NAME := WebViewLoadingFromApkTestApk +LOCAL_MANIFEST_FILE := inapk/AndroidManifest.xml + +LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS) +LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES) +LOCAL_MODULE_PATH := $(MY_MODULE_PATH) +LOCAL_SRC_FILES := $(MY_SRC_FILES) +LOCAL_SDK_VERSION := $(MY_SDK_VERSION) +LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED) +LOCAL_MULTILIB := $(MY_MULTILIB) + +include $(BUILD_PACKAGE) diff --git a/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml new file mode 100644 index 000000000000..868b2388d135 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webviewloading_test_from_apk" + android:versionCode="1" + android:versionName="0.0.0.1"> + + <application android:label="WebView Loading Test APK" + android:multiArch="true" + android:extractNativeLibs="false"> + <meta-data android:name="com.android.webview.WebViewLibrary" + android:value="libwebviewtest_jni.so" /> + </application> +</manifest> diff --git a/core/tests/webkit/apk_with_native_libs/jni/Android.mk b/core/tests/webkit/apk_with_native_libs/jni/Android.mk new file mode 100644 index 000000000000..fd5b5eb50c5f --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/jni/Android.mk @@ -0,0 +1,32 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := libwebviewtest_jni + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := WebViewTestJniOnLoad.cpp + +LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) + +LOCAL_SDK_VERSION := current + +LOCAL_MULTILIB := both + +include $(BUILD_SHARED_LIBRARY) diff --git a/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp new file mode 100644 index 000000000000..9b0502fc286d --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <jni.h> + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + return JNI_VERSION_1_4; +} diff --git a/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml new file mode 100644 index 000000000000..ffffeb8e1630 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webviewloading_test_on_disk" + android:versionCode="1" + android:versionName="0.0.0.1"> + + <application android:label="WebView Loading Test APK" + android:multiArch="true"> + <meta-data android:name="com.android.webview.WebViewLibrary" + android:value="libwebviewtest_jni.so" /> + </application> +</manifest> diff --git a/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java new file mode 100644 index 000000000000..0efa4b4ac694 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.webview.chromium; + +/** + * An empty class for testing purposes. + */ +public class WebViewLoadingTestClass { +} diff --git a/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java new file mode 100644 index 000000000000..e2f2d37a4d68 --- /dev/null +++ b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import android.support.test.filters.MediumTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.InstrumentationRegistry; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link WebViewLibraryLoader}. + * Use the following command to run these tests: + * make WebViewLoadingTests \ + * && adb install -r -d \ + * ${ANDROID_PRODUCT_OUT}/data/app/WebViewLoadingTests/WebViewLoadingTests.apk \ + * && adb shell am instrument -e class 'android.webkit.WebViewLibraryLoaderTest' -w \ + * 'com.android.webkit.tests/android.support.test.runner.AndroidJUnitRunner' + */ +@RunWith(AndroidJUnit4.class) +public final class WebViewLibraryLoaderTest { + private static final String WEBVIEW_LIBS_ON_DISK_TEST_APK = + "com.android.webviewloading_test_on_disk"; + private static final String WEBVIEW_LIBS_IN_APK_TEST_APK = + "com.android.webviewloading_test_from_apk"; + private static final String WEBVIEW_LOADING_TEST_NATIVE_LIB = "libwebviewtest_jni.so"; + + private PackageInfo webviewOnDiskPackageInfo; + private PackageInfo webviewFromApkPackageInfo; + + @Before public void setUp() throws PackageManager.NameNotFoundException { + PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); + webviewOnDiskPackageInfo = + pm.getPackageInfo(WEBVIEW_LIBS_ON_DISK_TEST_APK, PackageManager.GET_META_DATA); + webviewFromApkPackageInfo = + pm.getPackageInfo(WEBVIEW_LIBS_IN_APK_TEST_APK, PackageManager.GET_META_DATA); + } + + private static boolean is64BitDevice() { + return Build.SUPPORTED_64_BIT_ABIS.length > 0; + } + + // We test the getWebViewNativeLibraryDirectory method here because it handled several different + // cases/combinations and it seems unnecessary to create one test-apk for each such combination + // and arch. + + /** + * Ensure we fetch the correct native library directories in the multi-arch case where + * the primary ABI is 64-bit. + */ + @SmallTest + @Test public void testGetWebViewLibDirMultiArchPrimary64bit() { + final String nativeLib = "nativeLib"; + final String secondaryNativeLib = "secondaryNativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("arm64-v8a"). + setNativeLibraryDir(nativeLib). + setSecondaryCpuAbi("armeabi"). + setSecondaryNativeLibraryDir(secondaryNativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual32Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */); + String actual64Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */); + assertEquals(nativeLib, actual64Lib); + assertEquals(secondaryNativeLib, actual32Lib); + } + + /** + * Ensure we fetch the correct native library directory in the 64-bit single-arch case. + */ + @SmallTest + @Test public void testGetWebViewLibDirSingleArch64bit() { + final String nativeLib = "nativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("arm64-v8a"). + setNativeLibraryDir(nativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual64Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */); + assertEquals(nativeLib, actual64Lib); + } + + /** + * Ensure we fetch the correct native library directory in the 32-bit single-arch case. + */ + @SmallTest + @Test public void testGetWebViewLibDirSingleArch32bit() { + final String nativeLib = "nativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("armeabi-v7a"). + setNativeLibraryDir(nativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual32Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */); + assertEquals(nativeLib, actual32Lib); + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebViewLibraryPathOnDisk32Bit() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, false /* is64bit */); + String expectedLibaryDirectory = is64BitDevice() ? + webviewOnDiskPackageInfo.applicationInfo.secondaryNativeLibraryDir : + webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir; + String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + assertEquals("Fetched incorrect 32-bit path from WebView library.", + lib32Path, actualNativeLib.path); + } + + /** + * Ensure we fetch the correct 64-bit library path from an APK with 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebViewLibraryPathOnDisk64Bit() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, true /* is64bit */); + String lib64Path = webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir + + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + assertEquals("Fetched incorrect 64-bit path from WebView library.", + lib64Path, actualNativeLib.path); + } + + /** + * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebView32BitLibrarySizeOnDiskIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, false /* is64bit */); + assertTrue(actual32BitNativeLib.size > 0); + } + + /** + * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebView64BitLibrarySizeOnDiskIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, true /* is64bit */); + assertTrue(actual64BitNativeLib.size > 0); + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView32BitLibraryPathFromApk() + throws WebViewFactory.MissingWebViewPackageException, IOException { + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, false /* is64bit */); + // The device might have ignored the app's request to not extract native libs, so first + // check whether the library paths match those of extracted libraries. + String expectedLibaryDirectory = is64BitDevice() ? + webviewFromApkPackageInfo.applicationInfo.secondaryNativeLibraryDir : + webviewFromApkPackageInfo.applicationInfo.nativeLibraryDir; + String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + if (lib32Path.equals(actualNativeLib.path)) { + // If the libraries were extracted to disk, ensure that they're actually there. + assertTrue("The given WebView library doesn't exist.", + new File(actualNativeLib.path).exists()); + } else { // The libraries were not extracted to disk. + assertIsValidZipEntryPath(actualNativeLib.path, + webviewFromApkPackageInfo.applicationInfo.sourceDir); + } + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView64BitLibraryPathFromApk() + throws WebViewFactory.MissingWebViewPackageException, IOException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, true /* is64bit */); + assertIsValidZipEntryPath(actualNativeLib.path, + webviewFromApkPackageInfo.applicationInfo.sourceDir); + } + + private static void assertIsValidZipEntryPath(String path, String zipFilePath) + throws IOException { + assertTrue("The path to a zip entry must start with the path to the zip file itself." + + "Expected zip path: " + zipFilePath + ", actual zip entry: " + path, + path.startsWith(zipFilePath + "!/")); + String[] pathSplit = path.split("!/"); + assertEquals("A zip file path should have two parts, the zip path, and the zip entry path.", + 2, pathSplit.length); + ZipFile zipFile = new ZipFile(pathSplit[0]); + assertNotNull("Path doesn't point to a valid zip entry: " + path, + zipFile.getEntry(pathSplit[1])); + } + + + /** + * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView32BitLibrarySizeFromApkIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, false /* is64bit */); + assertTrue(actual32BitNativeLib.size > 0); + } + + /** + * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView64BitLibrarySizeFromApkIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, true /* is64bit */); + assertTrue(actual64BitNativeLib.size > 0); + } + + private static class ApplicationInfoBuilder { + ApplicationInfo ai; + + public ApplicationInfoBuilder setPrimaryCpuAbi(String primaryCpuAbi) { + ai.primaryCpuAbi = primaryCpuAbi; + return this; + } + + public ApplicationInfoBuilder setSecondaryCpuAbi(String secondaryCpuAbi) { + ai.secondaryCpuAbi = secondaryCpuAbi; + return this; + } + + public ApplicationInfoBuilder setNativeLibraryDir(String nativeLibraryDir) { + ai.nativeLibraryDir = nativeLibraryDir; + return this; + } + + public ApplicationInfoBuilder setSecondaryNativeLibraryDir( + String secondaryNativeLibraryDir) { + ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir; + return this; + } + + public ApplicationInfoBuilder setMetaData(Bundle metaData) { + ai.metaData = metaData; + return this; + } + + public ApplicationInfoBuilder() { + ai = new android.content.pm.ApplicationInfo(); + } + + public ApplicationInfo create() { + return ai; + } + } +} |