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;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import dalvik.system.VMRuntime;
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 =
/**
* 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];
};
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) {
}
}
+ /**
+ * 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;
}
*
* @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);
}
/**
/**
* 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)) {
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;
}
/**
--- /dev/null
+# 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_ADDITIONAL_DEPENDENCIES := \
+ WebViewLoadingOnDiskTestApk \
+ WebViewLoadingFromApkTestApk
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+# 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)
--- /dev/null
+<?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>
--- /dev/null
+#
+# 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)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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 {
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}