Commit 6f5fc054 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Simplify asset and resource handling

This change makes it so we no longer need to add asset paths to the
applications context, and instead we wrap the context in a
ContextWrapper which overrides getAssets() and getResources() to
directly return the implementation assets and resources.

This means we can no longer use WebViewFactory.getProviderClass(),
since it adds asset paths internally. Instead we load the context from
the WebView provider using WVF.getWebViewContextAndSetProvider() on N+,
which takes care of signature verification, and load manually using
createPackageContext() below N. We also need to make sure the native
library is loaded so we can use the WebView shared relro implementation.

Change-Id: I082107a71d1bf71069e498843a9ecc00470e6cb8
Bug: 1032662
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1931617Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#723520}
parent c92e7ea2
......@@ -8,6 +8,8 @@ import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.view.LayoutInflater;
import org.chromium.base.ContextUtils;
......@@ -29,6 +31,16 @@ public class ClassLoaderContextWrapperFactory {
private static final WeakHashMap<Context, WeakReference<ClassLoaderContextWrapper>>
sCtxToWrapper = new WeakHashMap<>();
private static final Object sLock = new Object();
@SuppressWarnings("StaticFieldLeak")
private static Context sResourceOverrideContext;
/**
* Sets a context that will override the return values from getAssets(), getResources(), and
* getSystemService() when asking for layout inflater.
*/
public static void setResourceOverrideContext(Context context) {
sResourceOverrideContext = context;
}
public static Context get(Context ctx) {
// Avoid double-wrapping a context.
......@@ -76,6 +88,9 @@ public class ClassLoaderContextWrapperFactory {
@Override
public Object getSystemService(String name) {
if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
if (sResourceOverrideContext != null) {
return LayoutInflater.from(sResourceOverrideContext);
}
LayoutInflater i = (LayoutInflater) getBaseContext().getSystemService(name);
return i.cloneInContext(this);
} else {
......@@ -120,5 +135,21 @@ public class ClassLoaderContextWrapperFactory {
super.startActivity(intent);
}
@Override
public AssetManager getAssets() {
if (sResourceOverrideContext != null) {
return sResourceOverrideContext.getAssets();
}
return getBaseContext().getAssets();
}
@Override
public Resources getResources() {
if (sResourceOverrideContext != null) {
return sResourceOverrideContext.getResources();
}
return getBaseContext().getResources();
}
}
}
......@@ -25,8 +25,15 @@ import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
public final class ChildProcessServiceImpl extends IChildProcessService.Stub {
private ChildProcessService mService;
// This should only be called in M80 or below.
@UsedByReflection("WebLayer")
public static IBinder create(Service service, Context appContext) {
return create(service, appContext, WebLayerImpl.createRemoteContextV80(appContext));
}
@UsedByReflection("WebLayer")
public static IBinder create(Service service, Context appContext, Context remoteContext) {
ClassLoaderContextWrapperFactory.setResourceOverrideContext(remoteContext);
// Wrap the app context so that it can be used to load WebLayer implementation classes.
appContext = ClassLoaderContextWrapperFactory.get(appContext);
return new ChildProcessServiceImpl(service, appContext);
......
......@@ -20,7 +20,6 @@ import org.chromium.components.crash.browser.ChildProcessCrashObserver;
import org.chromium.components.minidump_uploader.CrashFileManager;
import org.chromium.weblayer_private.interfaces.ICrashReporterController;
import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import java.io.File;
......@@ -59,9 +58,7 @@ public final class CrashReporterControllerImpl extends ICrashReporterController.
});
}
public static CrashReporterControllerImpl getInstance(IObjectWrapper appContextWrapper) {
// This is a no-op if init has already happened.
WebLayerImpl.minimalInitForContext(appContextWrapper);
public static CrashReporterControllerImpl getInstance() {
return Holder.sInstance;
}
......
......@@ -4,15 +4,15 @@
package org.chromium.weblayer_private;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.FileProvider;
import android.util.AndroidRuntimeException;
import android.util.SparseArray;
import android.webkit.ValueCallback;
import android.webkit.WebViewDelegate;
......@@ -78,26 +78,17 @@ public final class WebLayerImpl extends IWebLayer.Stub {
WebLayerImpl() {}
/**
* Performs the minimal initialization needed for a context. This is used for example in
* CrashReporterControllerImpl, so it can be used before full WebLayer initialization.
*/
public static Context minimalInitForContext(IObjectWrapper appContextWrapper) {
if (ContextUtils.getApplicationContext() != null) {
return ContextUtils.getApplicationContext();
}
// Wrap the app context so that it can be used to load WebLayer implementation classes.
Context appContext = ClassLoaderContextWrapperFactory.get(
ObjectWrapper.unwrap(appContextWrapper, Context.class));
ContextUtils.initApplicationContext(appContext);
PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DIRECTORY_SUFFIX, PRIVATE_DIRECTORY_SUFFIX);
return appContext;
@Override
public void loadAsyncV80(
IObjectWrapper appContextWrapper, IObjectWrapper loadedCallbackWrapper) {
loadAsync(appContextWrapper, null, loadedCallbackWrapper);
}
@Override
public void loadAsync(IObjectWrapper appContextWrapper, IObjectWrapper loadedCallbackWrapper) {
public void loadAsync(IObjectWrapper appContextWrapper, IObjectWrapper remoteContextWrapper,
IObjectWrapper loadedCallbackWrapper) {
StrictModeWorkaround.apply();
init(appContextWrapper);
init(appContextWrapper, remoteContextWrapper);
final ValueCallback<Boolean> loadedCallback = (ValueCallback<Boolean>) ObjectWrapper.unwrap(
loadedCallbackWrapper, ValueCallback.class);
......@@ -107,8 +98,7 @@ public final class WebLayerImpl extends IWebLayer.Stub {
new BrowserStartupController.StartupCallback() {
@Override
public void onSuccess() {
CrashReporterControllerImpl.getInstance(appContextWrapper)
.notifyNativeInitialized();
CrashReporterControllerImpl.getInstance().notifyNativeInitialized();
loadedCallback.onReceiveValue(true);
}
@Override
......@@ -119,17 +109,22 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
@Override
public void loadSync(IObjectWrapper appContextWrapper) {
public void loadSyncV80(IObjectWrapper appContextWrapper) {
loadSync(appContextWrapper, null);
}
@Override
public void loadSync(IObjectWrapper appContextWrapper, IObjectWrapper remoteContextWrapper) {
StrictModeWorkaround.apply();
init(appContextWrapper);
init(appContextWrapper, remoteContextWrapper);
BrowserStartupController.get(LibraryProcessType.PROCESS_WEBLAYER)
.startBrowserProcessesSync(
/* singleProcess*/ false);
CrashReporterControllerImpl.getInstance(appContextWrapper).notifyNativeInitialized();
CrashReporterControllerImpl.getInstance().notifyNativeInitialized();
}
private void init(IObjectWrapper appContextWrapper) {
private void init(IObjectWrapper appContextWrapper, IObjectWrapper remoteContextWrapper) {
if (mInited) {
return;
}
......@@ -139,14 +134,15 @@ public final class WebLayerImpl extends IWebLayer.Stub {
LibraryLoader.getInstance().setLibraryProcessType(LibraryProcessType.PROCESS_WEBLAYER);
Context appContext = minimalInitForContext(appContextWrapper);
Context appContext = minimalInitForContext(appContextWrapper, remoteContextWrapper);
PackageInfo packageInfo = WebViewFactory.getLoadedPackageInfo();
// TODO: This can break some functionality of apps that are doing interesting things with
// Contexts, ideally we would find a better way to do this.
addWebViewAssetPath(appContext, packageInfo);
// If a remote context is not provided, the client is an older version that loads the native
// library on the client side.
if (remoteContextWrapper != null) {
loadNativeLibrary(packageInfo.packageName);
}
applySplitApkWorkaround(packageInfo.applicationInfo, appContext.getResources().getAssets());
BuildInfo.setBrowserPackageInfo(packageInfo);
int resourcesPackageId = getPackageId(appContext, packageInfo.packageName);
// TODO: The call to onResourcesLoaded() can be slow, we may need to parallelize this with
......@@ -214,28 +210,52 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
@Override
public ICrashReporterController getCrashReporterController(IObjectWrapper appContext) {
return CrashReporterControllerImpl.getInstance(appContext);
public ICrashReporterController getCrashReporterControllerV80(IObjectWrapper appContext) {
return getCrashReporterController(appContext, null);
}
@Override
public ICrashReporterController getCrashReporterController(
IObjectWrapper appContext, IObjectWrapper remoteContext) {
// This is a no-op if init has already happened.
WebLayerImpl.minimalInitForContext(appContext, remoteContext);
return CrashReporterControllerImpl.getInstance();
}
private static void addWebViewAssetPath(Context appContext, PackageInfo packageInfo) {
/**
* Creates a remote context. This should only be used for backwards compatibility when the
* client was not sending the remote context.
*/
public static Context createRemoteContextV80(Context appContext) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
Constructor constructor = WebViewDelegate.class.getDeclaredConstructor();
constructor.setAccessible(true);
WebViewDelegate delegate = (WebViewDelegate) constructor.newInstance();
delegate.addWebViewAssetPath(appContext);
} else {
// In L WebViewDelegate did not yet exist, so we have to poke AssetManager directly.
// Note: like the implementation in WebView's Api21CompatibilityDelegate this does
// not support split APKs.
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(appContext.getResources().getAssets(),
packageInfo.applicationInfo.sourceDir);
return appContext.createPackageContext(
WebViewFactory.getLoadedPackageInfo().packageName,
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
throw new AndroidRuntimeException(e);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
/**
* Performs the minimal initialization needed for a context. This is used for example in
* CrashReporterControllerImpl, so it can be used before full WebLayer initialization.
*/
private static Context minimalInitForContext(
IObjectWrapper appContextWrapper, IObjectWrapper remoteContextWrapper) {
if (ContextUtils.getApplicationContext() != null) {
return ContextUtils.getApplicationContext();
}
Context appContext = ObjectWrapper.unwrap(appContextWrapper, Context.class);
Context remoteContext = ObjectWrapper.unwrap(remoteContextWrapper, Context.class);
if (remoteContext == null) {
remoteContext = createRemoteContextV80(appContext);
}
ClassLoaderContextWrapperFactory.setResourceOverrideContext(remoteContext);
// Wrap the app context so that it can be used to load WebLayer implementation classes.
appContext = ClassLoaderContextWrapperFactory.get(appContext);
ContextUtils.initApplicationContext(appContext);
PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DIRECTORY_SUFFIX, PRIVATE_DIRECTORY_SUFFIX);
return appContext;
}
/**
......@@ -268,29 +288,18 @@ public final class WebLayerImpl extends IWebLayer.Stub {
}
}
/** Adds assets from split APKs on Android versions where this is broken. */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static void applySplitApkWorkaround(
ApplicationInfo applicationInfo, AssetManager assetManager) {
// Q already handles this correctly.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return;
}
if (applicationInfo.splitSourceDirs != null) {
try {
Method addAssetPath;
private void loadNativeLibrary(String packageName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
addAssetPath = AssetManager.class.getMethod(
"addAssetPathAsSharedLibrary", String.class);
WebViewFactory.loadWebViewNativeLibraryFromPackage(
packageName, getClass().getClassLoader());
} else {
addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
}
for (String path : applicationInfo.splitSourceDirs) {
addAssetPath.invoke(assetManager, path);
}
try {
Method loadNativeLibrary =
WebViewFactory.class.getDeclaredMethod("loadNativeLibrary");
loadNativeLibrary.setAccessible(true);
loadNativeLibrary.invoke(null);
} catch (ReflectiveOperationException e) {
Log.e(TAG, "Unable to load assets from split APK.", e);
Log.e(TAG, "Failed to load native library.", e);
}
}
}
......
......@@ -13,21 +13,12 @@ import org.chromium.weblayer_private.interfaces.IProfile;
import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient;
interface IWebLayer {
// Initializes WebLayer and starts loading.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param loadedCallback A ValueCallback that will be called when load completes.
void loadAsync(in IObjectWrapper appContext,
// Deprecated, use loadAsync().
void loadAsyncV80(in IObjectWrapper appContext,
in IObjectWrapper loadedCallback) = 1;
// Initializes WebLayer, starts loading and blocks until loading has completed.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
void loadSync(in IObjectWrapper appContext) = 2;
// Deprecated, use loadSync().
void loadSyncV80(in IObjectWrapper appContext) = 2;
// Creates the WebLayer counterpart to a BrowserFragment - a BrowserFragmentImpl
//
......@@ -47,8 +38,33 @@ interface IWebLayer {
// Returns whether or not the DevTools remote debugging server is enabled.
boolean isRemoteDebuggingEnabled() = 6;
// Deprecated, use getCrashReporterController().
ICrashReporterController getCrashReporterControllerV80(
in IObjectWrapper appContext) = 7;
// Initializes WebLayer and starts loading.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param remoteContext A Context that refers to the WebLayer provider package.
// @param loadedCallback A ValueCallback that will be called when load completes.
void loadAsync(in IObjectWrapper appContext,
in IObjectWrapper remoteContext,
in IObjectWrapper loadedCallback) = 8;
// Initializes WebLayer, starts loading and blocks until loading has completed.
//
// It is expected that either loadAsync or loadSync is called before anything else.
//
// @param appContext A Context that refers to the Application using WebLayer.
// @param remoteContext A Context that refers to the WebLayer provider package.
void loadSync(in IObjectWrapper appContext,
in IObjectWrapper remoteContext) = 9;
// Returns the singleton crash reporter controller. If WebLayer has not been
// initialized, does the minimum initialization needed for the crash reporter.
ICrashReporterController getCrashReporterController(
in IObjectWrapper appContext) = 7;
in IObjectWrapper appContext,
in IObjectWrapper remoteContext) = 10;
}
......@@ -28,13 +28,20 @@ public abstract class ChildProcessService extends Service {
super.onCreate();
try {
Context appContext = getApplicationContext();
ClassLoader remoteClassLoader =
WebLayer.getOrCreateRemoteClassLoaderForChildProcess(appContext);
Context remoteContext = WebLayer.getOrCreateRemoteContext(appContext);
if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
mImpl = IChildProcessService.Stub.asInterface(
(IBinder) remoteClassLoader
(IBinder) remoteContext.getClassLoader()
.loadClass("org.chromium.weblayer_private.ChildProcessServiceImpl")
.getMethod("create", Service.class, Context.class)
.invoke(null, this, appContext));
} else {
mImpl = IChildProcessService.Stub.asInterface(
(IBinder) remoteContext.getClassLoader()
.loadClass("org.chromium.weblayer_private.ChildProcessServiceImpl")
.getMethod("create", Service.class, Context.class, Context.class)
.invoke(null, this, appContext, remoteContext));
}
mImpl.onCreate();
} catch (Exception e) {
throw new APICallException(e);
......
......@@ -134,10 +134,17 @@ public final class CrashReporterController {
return this;
}
try {
if (WebLayer.getSupportedMajorVersion(appContext) < 81) {
mImpl = WebLayer.getIWebLayer(appContext)
.getCrashReporterController(ObjectWrapper.wrap(appContext));
.getCrashReporterControllerV80(ObjectWrapper.wrap(appContext));
} else {
mImpl = WebLayer.getIWebLayer(appContext)
.getCrashReporterController(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(
WebLayer.getOrCreateRemoteContext(appContext)));
}
mImpl.setClient(new CrashReporterControllerClientImpl());
} catch (RemoteException e) {
} catch (Exception e) {
throw new APICallException(e);
}
return this;
......
......@@ -10,7 +10,6 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.util.AndroidRuntimeException;
......@@ -44,8 +43,9 @@ public final class WebLayer {
// load the code. Do not set this in production APKs!
private static final String PACKAGE_MANIFEST_KEY = "org.chromium.weblayer.WebLayerPackage";
@SuppressWarnings("StaticFieldLeak")
@Nullable
private static ClassLoader sRemoteClassLoader;
private static Context sRemoteContext;
@Nullable
private static WebLayerLoader sLoader;
......@@ -168,7 +168,7 @@ public final class WebLayer {
int majorVersion = -1;
String version = "<unavailable>";
try {
remoteClassLoader = getOrCreateRemoteClassLoader(appContext);
remoteClassLoader = getOrCreateRemoteContext(appContext).getClassLoader();
Class factoryClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.WebLayerFactoryImpl");
// NOTE: the 20 comes from the previous scheme of incrementing versioning. It must
......@@ -219,12 +219,21 @@ public final class WebLayer {
return;
}
try {
if (getMajorVersion() < 81) {
getIWebLayer(appContext)
.loadAsyncV80(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
onWebLayerReady();
}));
} else {
getIWebLayer(appContext)
.loadAsync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
onWebLayerReady();
}));
} catch (RemoteException e) {
}
} catch (Exception e) {
throw new APICallException(e);
}
}
......@@ -239,10 +248,16 @@ public final class WebLayer {
return null;
}
try {
getIWebLayer(appContext).loadSync(ObjectWrapper.wrap(appContext));
if (getMajorVersion() < 81) {
getIWebLayer(appContext).loadSyncV80(ObjectWrapper.wrap(appContext));
} else {
getIWebLayer(appContext)
.loadSync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)));
}
onWebLayerReady();
return mWebLayer;
} catch (RemoteException e) {
} catch (Exception e) {
throw new APICallException(e);
}
}
......@@ -350,53 +365,37 @@ public final class WebLayer {
return getWebLayerLoader(appContext).getIWebLayer(appContext);
}
@SuppressWarnings("NewApi")
static ClassLoader getOrCreateRemoteClassLoaderForChildProcess(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sRemoteClassLoader != null) {
return sRemoteClassLoader;
}
if (getImplPackageName(appContext) == null && Process.isIsolated()
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
// In <= M, the WebView update service is not available in isolated processes. This
// causes a crash when trying to initialize WebView through the normal machinery, so we
// need to directly make the remote context here.
String packageName = (String) Class.forName("android.webkit.WebViewFactory")
.getMethod("getWebViewPackageName")
.invoke(null);
sRemoteClassLoader =
appContext
.createPackageContext(packageName,
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE)
.getClassLoader();
return sRemoteClassLoader;
}
return getOrCreateRemoteClassLoader(appContext);
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side.
* Creates a Context for the remote (weblayer implementation) side.
*/
static ClassLoader getOrCreateRemoteClassLoader(Context appContext)
static Context getOrCreateRemoteContext(Context appContext)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
if (sRemoteClassLoader != null) {
return sRemoteClassLoader;
if (sRemoteContext != null) {
return sRemoteContext;
}
Class<?> webViewFactoryClass = Class.forName("android.webkit.WebViewFactory");
String implPackageName = getImplPackageName(appContext);
if (implPackageName == null) {
sRemoteClassLoader = createRemoteClassLoaderFromWebViewFactory(appContext);
if (implPackageName != null) {
sRemoteContext = createRemoteContextFromPackageName(appContext, implPackageName);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Method getContext =
webViewFactoryClass.getDeclaredMethod("getWebViewContextAndSetProvider");
getContext.setAccessible(true);
sRemoteContext = (Context) getContext.invoke(null);
} else {
sRemoteClassLoader = createRemoteClassLoaderFromPackage(appContext, implPackageName);
implPackageName =
(String) webViewFactoryClass.getMethod("getWebViewPackageName").invoke(null);
sRemoteContext = createRemoteContextFromPackageName(appContext, implPackageName);
}
return sRemoteClassLoader;
return sRemoteContext;
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side
* Creates a Context for the remote (weblayer implementation) side
* using a specified package name as the implementation. This is only
* intended for testing, not production use.
*/
private static ClassLoader createRemoteClassLoaderFromPackage(
private static Context createRemoteContextFromPackageName(
Context appContext, String implPackageName)
throws PackageManager.NameNotFoundException, ReflectiveOperationException {
// Load the code for the target package.
......@@ -414,34 +413,7 @@ public final class WebLayer {
sPackageInfo.setAccessible(true);
sPackageInfo.set(null, implPackageInfo);
return remoteContext.getClassLoader();
}
/**
* Creates a ClassLoader for the remote (weblayer implementation) side
* using WebViewFactory to load the current WebView implementation.
*/
private static ClassLoader createRemoteClassLoaderFromWebViewFactory(Context appContext)
throws ReflectiveOperationException {
Class<?> webViewFactory = Class.forName("android.webkit.WebViewFactory");
Class<?> providerClass;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// In M+ this method loads the native library and the Java code, and adds the assets
// to the app.
Method getProviderClass = webViewFactory.getDeclaredMethod("getProviderClass");
getProviderClass.setAccessible(true);
providerClass = (Class) getProviderClass.invoke(null);
} else {
// In L we have to load the native library separately first.
Method loadNativeLibrary = webViewFactory.getDeclaredMethod("loadNativeLibrary");
loadNativeLibrary.setAccessible(true);
loadNativeLibrary.invoke(null);
// In L the method had a different name but still adds the assets to the app.
Method getFactoryClass = webViewFactory.getDeclaredMethod("getFactoryClass");
getFactoryClass.setAccessible(true);
providerClass = (Class) getFactoryClass.invoke(null);
}
return providerClass.getClassLoader();
return remoteContext;
}
private static String sanitizeProfileName(String profileName) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment