Merge "[WebView] Clean up code for finding paths and sizes of native libs"
This commit is contained in:
committed by
Android (Google) Code Review
commit
4acbc89ff8
@@ -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;
|
||||
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);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
// 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 (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 */);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(path64)) {
|
||||
path64 += "/" + nativeLibFileName;
|
||||
File f = new File(path64);
|
||||
if (!f.exists()) {
|
||||
path64 = getLoadFromApkPath(ai.sourceDir,
|
||||
Build.SUPPORTED_64_BIT_ABIS,
|
||||
nativeLibFileName);
|
||||
}
|
||||
}
|
||||
WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
|
||||
is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
|
||||
|
||||
if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
|
||||
return new String[] { path32, path64 };
|
||||
if (DEBUG) {
|
||||
Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
|
||||
}
|
||||
return lib;
|
||||
}
|
||||
|
||||
private static String getLoadFromApkPath(String apkPath,
|
||||
String[] abiList,
|
||||
String 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
44
core/tests/webkit/Android.mk
Normal file
44
core/tests/webkit/Android.mk
Normal file
@@ -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_ADDITIONAL_DEPENDENCIES := \
|
||||
WebViewLoadingOnDiskTestApk \
|
||||
WebViewLoadingFromApkTestApk
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
30
core/tests/webkit/AndroidManifest.xml
Normal file
30
core/tests/webkit/AndroidManifest.xml
Normal file
@@ -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>
|
||||
29
core/tests/webkit/AndroidTest.xml
Normal file
29
core/tests/webkit/AndroidTest.xml
Normal file
@@ -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>
|
||||
66
core/tests/webkit/apk_with_native_libs/Android.mk
Normal file
66
core/tests/webkit/apk_with_native_libs/Android.mk
Normal file
@@ -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)
|
||||
@@ -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>
|
||||
32
core/tests/webkit/apk_with_native_libs/jni/Android.mk
Normal file
32
core/tests/webkit/apk_with_native_libs/jni/Android.mk
Normal file
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user