package android.webkit;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Method;
@SystemApi
public final class WebViewFactory {
private static final String CHROMIUM_WEBVIEW_FACTORY =
"com.android.webview.chromium.WebViewChromiumFactoryProviderForP";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
"persist.sys.webview.vmsize";
private static final String LOGTAG = "WebViewFactory";
private static final boolean DEBUG = false;
private static WebViewFactoryProvider sProviderInstance;
private static final Object sProviderLock = new Object();
private static PackageInfo sPackageInfo;
private static Boolean sWebViewSupported;
private static boolean sWebViewDisabled;
private static String sDataDirectorySuffix;
public static final int LIBLOAD_SUCCESS = 0;
public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
public static final int LIBLOAD_FAILED_JNI_CALL = 7;
public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8;
public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
private static String getWebViewPreparationErrorReason(int error) {
switch (error) {
case LIBLOAD_FAILED_WAITING_FOR_RELRO:
return "Time out waiting for Relro files being created";
case LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES:
return "No WebView installed";
case LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN:
return "Crashed for unknown reason";
}
return "Unknown";
}
static class MissingWebViewPackageException extends Exception {
public MissingWebViewPackageException(String message) { super(message); }
public MissingWebViewPackageException(Exception e) { super(e); }
}
private static boolean isWebViewSupported() {
if (sWebViewSupported == null) {
sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
}
return sWebViewSupported;
}
static void disableWebView() {
synchronized (sProviderLock) {
if (sProviderInstance != null) {
throw new IllegalStateException(
"Can't disable WebView: WebView already initialized");
}
sWebViewDisabled = true;
}
}
static void setDataDirectorySuffix(String suffix) {
synchronized (sProviderLock) {
if (sProviderInstance != null) {
throw new IllegalStateException(
"Can't set data directory suffix: WebView already initialized");
}
if (suffix.indexOf(File.separatorChar) >= 0) {
throw new IllegalArgumentException("Suffix " + suffix
+ " contains a path separator");
}
sDataDirectorySuffix = suffix;
}
}
static String getDataDirectorySuffix() {
synchronized (sProviderLock) {
return sDataDirectorySuffix;
}
}
public static String getWebViewLibrary(ApplicationInfo ai) {
if (ai.metaData != null)
return ai.metaData.getString("com.android.webview.WebViewLibrary");
return null;
}
public static PackageInfo getLoadedPackageInfo() {
synchronized (sProviderLock) {
return sPackageInfo;
}
}
public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
throws ClassNotFoundException {
return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
true, clazzLoader);
}
public static int loadWebViewNativeLibraryFromPackage(String packageName,
ClassLoader clazzLoader) {
if (!isWebViewSupported()) {
return LIBLOAD_WRONG_PACKAGE_NAME;
}
WebViewProviderResponse response = null;
try {
response = getUpdateService().waitForAndGetProvider();
} catch (RemoteException e) {
Log.e(LOGTAG, "error waiting for relro creation", e);
return LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN;
}
if (response.status != LIBLOAD_SUCCESS
&& response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
return response.status;
}
if (!response.packageInfo.packageName.equals(packageName)) {
return LIBLOAD_WRONG_PACKAGE_NAME;
}
PackageManager packageManager = AppGlobals.getInitialApplication().getPackageManager();
String libraryFileName;
try {
PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
libraryFileName = getWebViewLibrary(packageInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOGTAG, "Couldn't find package " + packageName);
return LIBLOAD_WRONG_PACKAGE_NAME;
}
int loadNativeRet = WebViewLibraryLoader.loadNativeLibrary(clazzLoader, libraryFileName);
if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
return loadNativeRet;
}
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
|| uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
|| uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
if (!isWebViewSupported()) {
throw new UnsupportedOperationException();
}
if (sWebViewDisabled) {
throw new IllegalStateException(
"WebView.disableWebView() was called: WebView is disabled");
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
Class<WebViewFactoryProvider> providerClass = getProviderClass();
Method staticFactory = null;
try {
staticFactory = providerClass.getMethod(
CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
} catch (Exception e) {
if (DEBUG) {
Log.w(LOGTAG, "error instantiating provider with static factory method", e);
}
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
try {
sProviderInstance = (WebViewFactoryProvider)
staticFactory.invoke(null, new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
Log.e(LOGTAG, "error instantiating provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}
private static boolean signaturesEquals(Signature[] s1, Signature[] s2) {
if (s1 == null) {
return s2 == null;
}
if (s2 == null) return false;
ArraySet<Signature> set1 = new ArraySet<>();
for(Signature signature : s1) {
set1.add(signature);
}
ArraySet<Signature> set2 = new ArraySet<>();
for(Signature signature : s2) {
set2.add(signature);
}
return set1.equals(set2);
}
private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)
throws MissingWebViewPackageException {
if (!chosen.packageName.equals(toUse.packageName)) {
throw new MissingWebViewPackageException("Failed to verify WebView provider, "
+ "packageName mismatch, expected: "
+ chosen.packageName + " actual: " + toUse.packageName);
}
if (chosen.getLongVersionCode() > toUse.getLongVersionCode()) {
throw new MissingWebViewPackageException("Failed to verify WebView provider, "
+ "version code is lower than expected: " + chosen.getLongVersionCode()
+ " actual: " + toUse.getLongVersionCode());
}
if (getWebViewLibrary(toUse.applicationInfo) == null) {
throw new MissingWebViewPackageException("Tried to load an invalid WebView provider: "
+ toUse.packageName);
}
if (!signaturesEquals(chosen.signatures, toUse.signatures)) {
throw new MissingWebViewPackageException("Failed to verify WebView provider, "
+ "signature mismatch");
}
}
private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)
throws MissingWebViewPackageException {
String donorPackageName = null;
if (ai.metaData != null) {
donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
}
if (donorPackageName != null) {
PackageInfo donorPackage;
try {
donorPackage = pm.getPackageInfo(
donorPackageName,
PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_DEBUG_TRIAGED_MISSING
| PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_FACTORY_ONLY);
} catch (PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException("Failed to find donor package: " +
donorPackageName);
}
ApplicationInfo donorInfo = donorPackage.applicationInfo;
ai.sourceDir = donorInfo.sourceDir;
ai.splitSourceDirs = donorInfo.splitSourceDirs;
ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
}
}
private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
WebViewProviderResponse response = null;
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"WebViewUpdateService.waitForAndGetProvider()");
try {
response = getUpdateService().waitForAndGetProvider();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
if (response.status != LIBLOAD_SUCCESS
&& response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
throw new MissingWebViewPackageException("Failed to load WebView provider: "
+ getWebViewPreparationErrorReason(response.status));
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "ActivityManager.addPackageDependency()");
try {
ActivityManager.getService().addPackageDependency(
response.packageInfo.packageName);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
PackageInfo newPackageInfo = null;
PackageManager pm = initialApplication.getPackageManager();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
try {
newPackageInfo = pm.getPackageInfo(
response.packageInfo.packageName,
PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_DEBUG_TRIAGED_MISSING
| PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.GET_SIGNATURES
| PackageManager.GET_META_DATA);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
verifyPackageInfo(response.packageInfo, newPackageInfo);
ApplicationInfo ai = newPackageInfo.applicationInfo;
fixupStubApplicationInfo(ai, pm);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"initialApplication.createApplicationContext");
try {
Context webViewContext = initialApplication.createApplicationContext(
ai,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
sPackageInfo = newPackageInfo;
return webViewContext;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (RemoteException | PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException("Failed to load WebView provider: " + e);
}
}
private static Class<WebViewFactoryProvider> getProviderClass() {
Context webViewContext = null;
Application initialApplication = AppGlobals.getInitialApplication();
try {
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"WebViewFactory.getWebViewContextAndSetProvider()");
try {
webViewContext = getWebViewContextAndSetProvider();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
sPackageInfo.versionName + " (code " + sPackageInfo.getLongVersionCode() + ")");
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
initialApplication.getAssets().addAssetPathAsSharedLibrary(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
getWebViewLibrary(sPackageInfo.applicationInfo));
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
try {
return getWebViewProviderClass(clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (ClassNotFoundException e) {
Log.e(LOGTAG, "error loading provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (MissingWebViewPackageException e) {
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
}
public static void prepareWebViewInZygote() {
try {
WebViewLibraryLoader.reserveAddressSpaceInZygote();
} catch (Throwable t) {
Log.e(LOGTAG, "error preparing native loader", t);
}
}
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
int startedRelroProcesses = 0;
ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
fixupStubApplicationInfo(packageInfo.applicationInfo,
AppGlobals.getInitialApplication().getPackageManager());
startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
Log.e(LOGTAG, "error preparing webview native library", t);
}
WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
return startedRelroProcesses;
}
private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
public static IWebViewUpdateService getUpdateService() {
if (isWebViewSupported()) {
return getUpdateServiceUnchecked();
} else {
return null;
}
}
static IWebViewUpdateService getUpdateServiceUnchecked() {
return IWebViewUpdateService.Stub.asInterface(
ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
}
}