/* * Copyright (C) 2012 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 android.webkit.Flags.updateServiceV2; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.UptimeMillisLong; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; 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.content.res.Resources; import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.Log; import android.util.Slog; import java.io.File; import java.lang.reflect.Method; /** * Top level factory, used creating all the main WebView implementation classes. * * @hide */ @SystemApi public final class WebViewFactory { // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. /** @hide */ private static final String CHROMIUM_WEBVIEW_FACTORY = "com.android.webview.chromium.WebViewChromiumFactoryProviderForT"; private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; private static final String LOGTAG = "WebViewFactory"; private static final boolean DEBUG = false; // Cache the factory both for efficiency, and ensure any one process gets all webviews from the // same provider. @UnsupportedAppUsage private static WebViewFactoryProvider sProviderInstance; private static final Object sProviderLock = new Object(); @UnsupportedAppUsage private static PackageInfo sPackageInfo; private static Boolean sWebViewSupported; private static boolean sWebViewDisabled; private static String sDataDirectorySuffix; // stored here so it can be set without loading WV // Error codes for loadWebViewNativeLibraryFromPackage 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; // error codes for waiting for WebView preparation public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3; public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // native relro loading error codes 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; // more error codes for waiting for WebView preparation public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8; // error for namespace lookup public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10; // generic error for future use static final int LIBLOAD_FAILED_OTHER = 11; /** * Stores the timestamps at which various WebView startup events occurred in this process. */ public static class StartupTimestamps { long mWebViewLoadStart; long mCreateContextStart; long mCreateContextEnd; long mAddAssetsStart; long mAddAssetsEnd; long mGetClassLoaderStart; long mGetClassLoaderEnd; long mNativeLoadStart; long mNativeLoadEnd; long mProviderClassForNameStart; long mProviderClassForNameEnd; StartupTimestamps() {} /** When the overall WebView provider load began. */ @UptimeMillisLong public long getWebViewLoadStart() { return mWebViewLoadStart; } /** Before creating the WebView APK Context. */ @UptimeMillisLong public long getCreateContextStart() { return mCreateContextStart; } /** After creating the WebView APK Context. */ @UptimeMillisLong public long getCreateContextEnd() { return mCreateContextEnd; } /** Before adding WebView assets to AssetManager. */ @UptimeMillisLong public long getAddAssetsStart() { return mAddAssetsStart; } /** After adding WebView assets to AssetManager. */ @UptimeMillisLong public long getAddAssetsEnd() { return mAddAssetsEnd; } /** Before creating the WebView ClassLoader. */ @UptimeMillisLong public long getGetClassLoaderStart() { return mGetClassLoaderStart; } /** After creating the WebView ClassLoader. */ @UptimeMillisLong public long getGetClassLoaderEnd() { return mGetClassLoaderEnd; } /** Before preloading the WebView native library. */ @UptimeMillisLong public long getNativeLoadStart() { return mNativeLoadStart; } /** After preloading the WebView native library. */ @UptimeMillisLong public long getNativeLoadEnd() { return mNativeLoadEnd; } /** Before looking up the WebView provider class. */ @UptimeMillisLong public long getProviderClassForNameStart() { return mProviderClassForNameStart; } /** After looking up the WebView provider class. */ @UptimeMillisLong public long getProviderClassForNameEnd() { return mProviderClassForNameEnd; } } static final StartupTimestamps sTimestamps = new StartupTimestamps(); @NonNull static StartupTimestamps getStartupTimestamps() { return sTimestamps; } 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() { // No lock; this is a benign race as Boolean's state is final and the PackageManager call // will always return the same value. if (sWebViewSupported == null) { sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_WEBVIEW); } return sWebViewSupported; } /** * @hide */ static void disableWebView() { synchronized (sProviderLock) { if (sProviderInstance != null) { throw new IllegalStateException( "Can't disable WebView: WebView already initialized"); } sWebViewDisabled = true; } } /** * @hide */ 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; } } /** * @hide */ static String getDataDirectorySuffix() { synchronized (sProviderLock) { return sDataDirectorySuffix; } } /** * @hide */ 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; } } /** * @hide */ public static Class getWebViewProviderClass(ClassLoader clazzLoader) throws ClassNotFoundException { return (Class) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true, clazzLoader); } /** * Load the native library for the given package name if that package * name is the same as the one providing the webview. */ public static int loadWebViewNativeLibraryFromPackage(String packageName, ClassLoader clazzLoader) { if (!isWebViewSupported()) { return LIBLOAD_WRONG_PACKAGE_NAME; } Application initialApplication = AppGlobals.getInitialApplication(); WebViewProviderResponse response = null; try { if (Flags.updateServiceIpcWrapper()) { response = initialApplication.getSystemService(WebViewUpdateManager.class) .waitForAndGetProvider(); } else { response = getUpdateService().waitForAndGetProvider(); } } catch (Exception 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 = initialApplication.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 we failed waiting for relro we want to return that fact even if we successfully // load the relro file. if (loadNativeRet == LIBLOAD_SUCCESS) return response.status; return loadNativeRet; } @UnsupportedAppUsage static WebViewFactoryProvider getProvider() { synchronized (sProviderLock) { // For now the main purpose of this function (and the factory abstraction) is to keep // us honest and minimize usage of WebView internals when binding the proxy. if (sProviderInstance != null) return sProviderInstance; sTimestamps.mWebViewLoadStart = SystemClock.uptimeMillis(); 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()) { // Device doesn't support WebView; don't try to load it, just throw. 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 providerClass = getProviderClass(); Method staticFactory = providerClass.getMethod( CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class); 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; } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } catch (Exception e) { Log.e(LOGTAG, "error instantiating provider", e); throw new AndroidRuntimeException(e); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } } /** * Returns {@code true} if the signatures match, {@code false} otherwise */ private static boolean signaturesEquals(Signature[] s1, Signature[] s2) { if (s1 == null) { return s2 == null; } if (s2 == null) return false; ArraySet set1 = new ArraySet<>(); for(Signature signature : s1) { set1.add(signature); } ArraySet set2 = new ArraySet<>(); for(Signature signature : s2) { set2.add(signature); } return set1.equals(set2); } // Throws MissingWebViewPackageException on failure 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"); } } // Returns whether the given package is enabled. // This state can be changed by the user from Settings->Apps private static boolean isEnabledPackage(PackageInfo packageInfo) { if (packageInfo == null) return false; return packageInfo.applicationInfo.enabled; } // Return {@code true} if the package is installed and not hidden private static boolean isInstalledPackage(PackageInfo packageInfo) { if (packageInfo == null) return false; return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0) && ((packageInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0)); } @UnsupportedAppUsage private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException { Application initialApplication = AppGlobals.getInitialApplication(); try { WebViewProviderResponse response = null; Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewUpdateService.waitForAndGetProvider()"); try { if (Flags.updateServiceIpcWrapper()) { response = initialApplication.getSystemService(WebViewUpdateManager.class) .waitForAndGetProvider(); } else { 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)); } // Register to be killed before fetching package info - so that we will be // killed if the package info goes out-of-date. Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "ActivityManager.addPackageDependency()"); try { ActivityManager.getService().addPackageDependency( response.packageInfo.packageName); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } // Fetch package info and verify it against the chosen package 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 // Make sure that we fetch the current provider even if its not // installed for the current user | PackageManager.MATCH_UNINSTALLED_PACKAGES // Fetch signatures for verification | PackageManager.GET_SIGNATURES // Get meta-data for meta data flag verification | PackageManager.GET_META_DATA); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } if (updateServiceV2() && !isInstalledPackage(newPackageInfo)) { throw new MissingWebViewPackageException( TextUtils.formatSimple( "Current WebView Package (%s) is not installed for the current " + "user", newPackageInfo.packageName)); } if (updateServiceV2() && !isEnabledPackage(newPackageInfo)) { throw new MissingWebViewPackageException( TextUtils.formatSimple( "Current WebView Package (%s) is not enabled for the current user", newPackageInfo.packageName)); } // Validate the newly fetched package info, throws MissingWebViewPackageException on // failure verifyPackageInfo(response.packageInfo, newPackageInfo); ApplicationInfo ai = newPackageInfo.applicationInfo; Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "initialApplication.createApplicationContext"); sTimestamps.mCreateContextStart = SystemClock.uptimeMillis(); try { // Construct an app context to load the Java code into the current app. Context webViewContext = initialApplication.createApplicationContext( ai, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); sPackageInfo = newPackageInfo; return webViewContext; } finally { sTimestamps.mCreateContextEnd = SystemClock.uptimeMillis(); Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } catch (RemoteException | PackageManager.NameNotFoundException e) { throw new MissingWebViewPackageException("Failed to load WebView provider: " + e); } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static Class 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 { sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis(); if (android.content.res.Flags.registerResourcePaths()) { Resources.registerResourcePaths(webViewContext.getPackageName(), webViewContext.getApplicationInfo()); } else { for (String newAssetPath : webViewContext.getApplicationInfo() .getAllApkPaths()) { initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath); } } sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart = SystemClock.uptimeMillis(); ClassLoader clazzLoader = webViewContext.getClassLoader(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); sTimestamps.mGetClassLoaderEnd = sTimestamps.mNativeLoadStart = SystemClock.uptimeMillis(); WebViewLibraryLoader.loadNativeLibrary(clazzLoader, getWebViewLibrary(sPackageInfo.applicationInfo)); Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()"); sTimestamps.mNativeLoadEnd = sTimestamps.mProviderClassForNameStart = SystemClock.uptimeMillis(); try { return getWebViewProviderClass(clazzLoader); } finally { sTimestamps.mProviderClassForNameEnd = SystemClock.uptimeMillis(); 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); } } /** * Perform any WebView loading preparations that must happen in the zygote. * Currently, this means allocating address space to load the real JNI library later. */ public static void prepareWebViewInZygote() { try { WebViewLibraryLoader.reserveAddressSpaceInZygote(); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the zygote. Log.e(LOGTAG, "error preparing native loader", t); } } /** * @hide */ public static int onWebViewProviderChanged(PackageInfo packageInfo) { int startedRelroProcesses = 0; try { startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Slog.wtf(LOGTAG, "error preparing webview native library", t); } WebViewZygote.onWebViewProviderChanged(packageInfo); return startedRelroProcesses; } private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate"; /** @hide */ @UnsupportedAppUsage public static IWebViewUpdateService getUpdateService() { if (isWebViewSupported()) { return getUpdateServiceUnchecked(); } else { return null; } } /** @hide */ static IWebViewUpdateService getUpdateServiceUnchecked() { return IWebViewUpdateService.Stub.asInterface( ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME)); } }