Move WebViewUpdater to its own file.

WebViewUpdater is a large portion of WebViewUpdateServiceImpl, split it
into its own file.
Also restructure the members of WebViewUpdater to give them a logical
order.

Bug: 36094936
Test: Build and flash an Angler build.
Change-Id: If11cc46d73ceeaf54de34f0648ed1d2df57d9a1c
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 9e95eec..114c362 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -18,20 +18,14 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
-import android.os.Handler;
 import android.os.UserHandle;
-import android.util.Base64;
 import android.util.Slog;
 import android.webkit.UserPackage;
-import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -104,7 +98,7 @@
                 // userPackages can contain null objects.
                 List<UserPackage> userPackages =
                         mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
-                if (isInstalledAndEnabledForAllUsers(userPackages) &&
+                if (WebViewUpdater.isInstalledAndEnabledForAllUsers(userPackages) &&
                         // Checking validity of the package for the system user (rather than all
                         // users) since package validity depends not on the user but on the package
                         // itself.
@@ -211,7 +205,7 @@
             mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext,
                     fallbackProvider.packageName);
         } else if (!existsValidNonFallbackProvider
-                && !isInstalledAndEnabledForAllUsers(userPackages)) {
+                && !WebViewUpdater.isInstalledAndEnabledForAllUsers(userPackages)) {
             // Enable the fallback package for all users.
             mSystemInterface.enablePackageForAllUsers(mContext,
                     fallbackProvider.packageName, true);
@@ -271,593 +265,6 @@
         }
     }
 
-    /**
-     * Class that decides what WebView implementation to use and prepares that implementation for
-     * use.
-     */
-    private static class WebViewUpdater {
-        private Context mContext;
-        private SystemInterface mSystemInterface;
-        private int mMinimumVersionCode = -1;
-
-        public WebViewUpdater(Context context, SystemInterface systemInterface) {
-            mContext = context;
-            mSystemInterface = systemInterface;
-        }
-
-        private static class WebViewPackageMissingException extends Exception {
-            public WebViewPackageMissingException(String message) { super(message); }
-            public WebViewPackageMissingException(Exception e) { super(e); }
-        }
-
-        private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
-
-        // Keeps track of the number of running relro creations
-        private int mNumRelroCreationsStarted = 0;
-        private int mNumRelroCreationsFinished = 0;
-        // Implies that we need to rerun relro creation because we are using an out-of-date package
-        private boolean mWebViewPackageDirty = false;
-        private boolean mAnyWebViewInstalled = false;
-
-        private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
-
-        // The WebView package currently in use (or the one we are preparing).
-        private PackageInfo mCurrentWebViewPackage = null;
-
-        private Object mLock = new Object();
-
-        public void packageStateChanged(String packageName, int changedState) {
-            for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
-                String webviewPackage = provider.packageName;
-
-                if (webviewPackage.equals(packageName)) {
-                    boolean updateWebView = false;
-                    boolean removedOrChangedOldPackage = false;
-                    String oldProviderName = null;
-                    PackageInfo newPackage = null;
-                    synchronized(mLock) {
-                        try {
-                            newPackage = findPreferredWebViewPackage();
-                            if (mCurrentWebViewPackage != null) {
-                                oldProviderName = mCurrentWebViewPackage.packageName;
-                                if (changedState == WebViewUpdateService.PACKAGE_CHANGED
-                                        && newPackage.packageName.equals(oldProviderName)) {
-                                    // If we don't change package name we should only rerun the
-                                    // preparation phase if the current package has been replaced
-                                    // (not if it has been enabled/disabled).
-                                    return;
-                                }
-                                if (newPackage.packageName.equals(oldProviderName)
-                                        && (newPackage.lastUpdateTime
-                                            == mCurrentWebViewPackage.lastUpdateTime)) {
-                                    // If the chosen package hasn't been updated, then early-out
-                                    return;
-                                }
-                            }
-                            // Only trigger update actions if the updated package is the one
-                            // that will be used, or the one that was in use before the
-                            // update, or if we haven't seen a valid WebView package before.
-                            updateWebView =
-                                provider.packageName.equals(newPackage.packageName)
-                                || provider.packageName.equals(oldProviderName)
-                                || mCurrentWebViewPackage == null;
-                            // We removed the old package if we received an intent to remove
-                            // or replace the old package.
-                            removedOrChangedOldPackage =
-                                provider.packageName.equals(oldProviderName);
-                            if (updateWebView) {
-                                onWebViewProviderChanged(newPackage);
-                            }
-                        } catch (WebViewPackageMissingException e) {
-                            mCurrentWebViewPackage = null;
-                            Slog.e(TAG, "Could not find valid WebView package to create " +
-                                    "relro with " + e);
-                        }
-                    }
-                    if(updateWebView && !removedOrChangedOldPackage
-                            && oldProviderName != null) {
-                        // If the provider change is the result of adding or replacing a
-                        // package that was not the previous provider then we must kill
-                        // packages dependent on the old package ourselves. The framework
-                        // only kills dependents of packages that are being removed.
-                        mSystemInterface.killPackageDependents(oldProviderName);
-                    }
-                    return;
-                }
-            }
-        }
-
-        public void prepareWebViewInSystemServer() {
-            try {
-                synchronized(mLock) {
-                    mCurrentWebViewPackage = findPreferredWebViewPackage();
-                    // Don't persist the user-chosen setting across boots if the package being
-                    // chosen is not used (could be disabled or uninstalled) so that the user won't
-                    // be surprised by the device switching to using a certain webview package,
-                    // that was uninstalled/disabled a long time ago, if it is installed/enabled
-                    // again.
-                    mSystemInterface.updateUserSetting(mContext,
-                            mCurrentWebViewPackage.packageName);
-                    onWebViewProviderChanged(mCurrentWebViewPackage);
-                }
-            } catch (Throwable t) {
-                // Log and discard errors at this stage as we must not crash the system server.
-                Slog.e(TAG, "error preparing webview provider from system server", t);
-            }
-        }
-
-        /**
-         * Change WebView provider and provider setting and kill packages using the old provider.
-         * Return the new provider (in case we are in the middle of creating relro files, or
-         * replacing that provider it will not be in use directly, but will be used when the relros
-         * or the replacement are done).
-         */
-        public String changeProviderAndSetting(String newProviderName) {
-            PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
-            if (newPackage == null) return "";
-            return newPackage.packageName;
-        }
-
-        /**
-         * This is called when we change WebView provider, either when the current provider is
-         * updated or a new provider is chosen / takes precedence.
-         */
-        private void onWebViewProviderChanged(PackageInfo newPackage) {
-            synchronized(mLock) {
-                mAnyWebViewInstalled = true;
-                if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
-                    mCurrentWebViewPackage = newPackage;
-
-                    // The relro creations might 'finish' (not start at all) before
-                    // WebViewFactory.onWebViewProviderChanged which means we might not know the
-                    // number of started creations before they finish.
-                    mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
-                    mNumRelroCreationsFinished = 0;
-                    mNumRelroCreationsStarted =
-                        mSystemInterface.onWebViewProviderChanged(newPackage);
-                    // If the relro creations finish before we know the number of started creations
-                    // we will have to do any cleanup/notifying here.
-                    checkIfRelrosDoneLocked();
-                } else {
-                    mWebViewPackageDirty = true;
-                }
-            }
-        }
-
-        private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
-            WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
-            List<ProviderAndPackageInfo> providers = new ArrayList<>();
-            for(int n = 0; n < allProviders.length; n++) {
-                try {
-                    PackageInfo packageInfo =
-                        mSystemInterface.getPackageInfoForProvider(allProviders[n]);
-                    if (isValidProvider(allProviders[n], packageInfo)) {
-                        providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
-                    }
-                } catch (NameNotFoundException e) {
-                    // Don't add non-existent packages
-                }
-            }
-            return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
-        }
-
-        /**
-         * Fetch only the currently valid WebView packages.
-         **/
-        public WebViewProviderInfo[] getValidWebViewPackages() {
-            ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
-            WebViewProviderInfo[] providers =
-                new WebViewProviderInfo[providersAndPackageInfos.length];
-            for(int n = 0; n < providersAndPackageInfos.length; n++) {
-                providers[n] = providersAndPackageInfos[n].provider;
-            }
-            return providers;
-        }
-
-
-        private static class ProviderAndPackageInfo {
-            public final WebViewProviderInfo provider;
-            public final PackageInfo packageInfo;
-
-            public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
-                this.provider = provider;
-                this.packageInfo = packageInfo;
-            }
-        }
-
-        /**
-         * Returns either the package info of the WebView provider determined in the following way:
-         * If the user has chosen a provider then use that if it is valid,
-         * otherwise use the first package in the webview priority list that is valid.
-         *
-         */
-        private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
-            ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
-            String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
-
-            // If the user has chosen provider, use that (if it's installed and enabled for all
-            // users).
-            for (ProviderAndPackageInfo providerAndPackage : providers) {
-                if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
-                    // userPackages can contain null objects.
-                    List<UserPackage> userPackages =
-                            mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
-                                    providerAndPackage.provider);
-                    if (isInstalledAndEnabledForAllUsers(userPackages)) {
-                        return providerAndPackage.packageInfo;
-                    }
-                }
-            }
-
-            // User did not choose, or the choice failed; use the most stable provider that is
-            // installed and enabled for all users, and available by default (not through
-            // user choice).
-            for (ProviderAndPackageInfo providerAndPackage : providers) {
-                if (providerAndPackage.provider.availableByDefault) {
-                    // userPackages can contain null objects.
-                    List<UserPackage> userPackages =
-                            mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
-                                    providerAndPackage.provider);
-                    if (isInstalledAndEnabledForAllUsers(userPackages)) {
-                        return providerAndPackage.packageInfo;
-                    }
-                }
-            }
-
-            // Could not find any installed and enabled package either, use the most stable and
-            // default-available provider.
-            // TODO(gsennton) remove this when we have a functional WebView stub.
-            for (ProviderAndPackageInfo providerAndPackage : providers) {
-                if (providerAndPackage.provider.availableByDefault) {
-                    return providerAndPackage.packageInfo;
-                }
-            }
-
-            // This should never happen during normal operation (only with modified system images).
-            mAnyWebViewInstalled = false;
-            throw new WebViewPackageMissingException("Could not find a loadable WebView package");
-        }
-
-        public void notifyRelroCreationCompleted() {
-            synchronized (mLock) {
-                mNumRelroCreationsFinished++;
-                checkIfRelrosDoneLocked();
-            }
-        }
-
-        public WebViewProviderResponse waitForAndGetProvider() {
-            PackageInfo webViewPackage = null;
-            final long NS_PER_MS = 1000000;
-            final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
-            boolean webViewReady = false;
-            int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
-            synchronized (mLock) {
-                webViewReady = webViewIsReadyLocked();
-                while (!webViewReady) {
-                    final long timeNowMs = System.nanoTime() / NS_PER_MS;
-                    if (timeNowMs >= timeoutTimeMs) break;
-                    try {
-                        mLock.wait(timeoutTimeMs - timeNowMs);
-                    } catch (InterruptedException e) {}
-                    webViewReady = webViewIsReadyLocked();
-                }
-                // Make sure we return the provider that was used to create the relro file
-                webViewPackage = mCurrentWebViewPackage;
-                if (webViewReady) {
-                } else if (!mAnyWebViewInstalled) {
-                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
-                } else {
-                    // Either the current relro creation  isn't done yet, or the new relro creatioin
-                    // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
-                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
-                    Slog.e(TAG, "Timed out waiting for relro creation, relros started "
-                            + mNumRelroCreationsStarted
-                            + " relros finished " + mNumRelroCreationsFinished
-                            + " package dirty? " + mWebViewPackageDirty);
-                }
-            }
-            if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
-            return new WebViewProviderResponse(webViewPackage, webViewStatus);
-        }
-
-        public PackageInfo getCurrentWebViewPackage() {
-            synchronized(mLock) {
-                return mCurrentWebViewPackage;
-            }
-        }
-
-        /**
-         * Returns whether WebView is ready and is not going to go through its preparation phase
-         * again directly.
-         */
-        private boolean webViewIsReadyLocked() {
-            return !mWebViewPackageDirty
-                && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
-                // The current package might be replaced though we haven't received an intent
-                // declaring this yet, the following flag makes anyone loading WebView to wait in
-                // this case.
-                && mAnyWebViewInstalled;
-        }
-
-        private void checkIfRelrosDoneLocked() {
-            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
-                if (mWebViewPackageDirty) {
-                    mWebViewPackageDirty = false;
-                    // If we have changed provider since we started the relro creation we need to
-                    // redo the whole process using the new package instead.
-                    try {
-                        PackageInfo newPackage = findPreferredWebViewPackage();
-                        onWebViewProviderChanged(newPackage);
-                    } catch (WebViewPackageMissingException e) {
-                        mCurrentWebViewPackage = null;
-                        // If we can't find any valid WebView package we are now in a state where
-                        // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
-                        // should simply wait until we receive an intent declaring a new package was
-                        // installed.
-                    }
-                } else {
-                    mLock.notifyAll();
-                }
-            }
-        }
-
-        /**
-         * Both versionCodes should be from a WebView provider package implemented by Chromium.
-         * VersionCodes from other kinds of packages won't make any sense in this method.
-         *
-         * An introduction to Chromium versionCode scheme:
-         * "BBBBPPPAX"
-         * BBBB: 4 digit branch number. It monotonically increases over time.
-         * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
-         * may change their meaning in the future.
-         * A: architecture digit.
-         * X: A digit to differentiate APKs for other reasons.
-         *
-         * This method takes the "BBBB" of versionCodes and compare them.
-         *
-         * @return true if versionCode1 is higher than or equal to versionCode2.
-         */
-        private static boolean versionCodeGE(int versionCode1, int versionCode2) {
-            int v1 = versionCode1 / 100000;
-            int v2 = versionCode2 / 100000;
-
-            return v1 >= v2;
-        }
-
-        private final static int VALIDITY_OK = 0;
-        private final static int VALIDITY_INCORRECT_SDK_VERSION = 1;
-        private final static int VALIDITY_INCORRECT_VERSION_CODE = 2;
-        private final static int VALIDITY_INCORRECT_SIGNATURE = 3;
-        private final static int VALIDITY_NO_LIBRARY_FLAG = 4;
-
-        private static String getInvalidityReason(int invalidityReason) {
-            switch (invalidityReason) {
-                case VALIDITY_INCORRECT_SDK_VERSION:
-                    return "SDK version too low";
-                case VALIDITY_INCORRECT_VERSION_CODE:
-                    return "Version code too low";
-                case VALIDITY_INCORRECT_SIGNATURE:
-                    return "Incorrect signature";
-                case VALIDITY_NO_LIBRARY_FLAG:
-                    return "No WebView-library manifest flag";
-                default:
-                    return "Unexcepted validity-reason";
-            }
-        }
-
-        private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
-            // Ensure the provider targets this framework release (or a later one).
-            if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
-                return VALIDITY_INCORRECT_SDK_VERSION;
-            }
-            if (!versionCodeGE(packageInfo.versionCode, getMinimumVersionCode())
-                    && !mSystemInterface.systemIsDebuggable()) {
-                // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
-                // minimum version code. This check is only enforced for user builds.
-                return VALIDITY_INCORRECT_VERSION_CODE;
-            }
-            if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
-                return VALIDITY_INCORRECT_SIGNATURE;
-            }
-            if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
-                return VALIDITY_NO_LIBRARY_FLAG;
-            }
-            return VALIDITY_OK;
-        }
-
-        /**
-         * Returns whether this provider is valid for use as a WebView provider.
-         */
-        public boolean isValidProvider(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
-            return VALIDITY_OK == validityResult(configInfo, packageInfo);
-        }
-
-        /**
-         * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
-         * of all available-by-default and non-fallback WebView provider packages. If there is no
-         * such WebView provider package on the system, then return -1, which means all positive
-         * versionCode WebView packages are accepted.
-         *
-         * Note that this is a private method in WebViewUpdater that handles a variable
-         * (mMinimumVersionCode) which is shared between threads. Furthermore, this method does not
-         * hold mLock meaning that we must take extra care to ensure this method is thread-safe.
-         */
-        private int getMinimumVersionCode() {
-            if (mMinimumVersionCode > 0) {
-                return mMinimumVersionCode;
-            }
-
-            int minimumVersionCode = -1;
-            for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
-                if (provider.availableByDefault && !provider.isFallback) {
-                    try {
-                        int versionCode =
-                            mSystemInterface.getFactoryPackageVersion(provider.packageName);
-                        if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
-                            minimumVersionCode = versionCode;
-                        }
-                    } catch (NameNotFoundException e) {
-                        // Safe to ignore.
-                    }
-                }
-            }
-
-            mMinimumVersionCode = minimumVersionCode;
-            return mMinimumVersionCode;
-        }
-
-        public void dumpState(PrintWriter pw) {
-            synchronized (mLock) {
-                if (mCurrentWebViewPackage == null) {
-                    pw.println("  Current WebView package is null");
-                } else {
-                    pw.println(String.format("  Current WebView package (name, version): (%s, %s)",
-                            mCurrentWebViewPackage.packageName,
-                            mCurrentWebViewPackage.versionName));
-                }
-                pw.println(String.format("  Minimum WebView version code: %d",
-                      mMinimumVersionCode));
-                pw.println(String.format("  Number of relros started: %d",
-                        mNumRelroCreationsStarted));
-                pw.println(String.format("  Number of relros finished: %d",
-                            mNumRelroCreationsFinished));
-                pw.println(String.format("  WebView package dirty: %b", mWebViewPackageDirty));
-                pw.println(String.format("  Any WebView package installed: %b",
-                        mAnyWebViewInstalled));
-
-                try {
-                    PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
-                    pw.println(String.format(
-                            "  Preferred WebView package (name, version): (%s, %s)",
-                            preferredWebViewPackage.packageName,
-                            preferredWebViewPackage.versionName));
-                } catch (WebViewPackageMissingException e) {
-                    pw.println(String.format("  Preferred WebView package: none"));
-                }
-
-                dumpAllPackageInformationLocked(pw);
-            }
-        }
-
-        private void dumpAllPackageInformationLocked(PrintWriter pw) {
-            WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
-            pw.println("  WebView packages:");
-            for (WebViewProviderInfo provider : allProviders) {
-                List<UserPackage> userPackages =
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
-                PackageInfo systemUserPackageInfo =
-                        userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
-                if (systemUserPackageInfo == null) {
-                    continue;
-                }
-
-                int validity = validityResult(provider, systemUserPackageInfo);
-                String packageDetails = String.format(
-                        "versionName: %s, versionCode: %d, targetSdkVersion: %d",
-                        systemUserPackageInfo.versionName,
-                        systemUserPackageInfo.versionCode,
-                        systemUserPackageInfo.applicationInfo.targetSdkVersion);
-                if (validity == VALIDITY_OK) {
-                    boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
-                            mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
-                    pw.println(String.format(
-                            "    Valid package %s (%s) is %s installed/enabled for all users",
-                            systemUserPackageInfo.packageName,
-                            packageDetails,
-                            installedForAllUsers ? "" : "NOT"));
-                } else {
-                    pw.println(String.format("    Invalid package %s (%s), reason: %s",
-                            systemUserPackageInfo.packageName,
-                            packageDetails,
-                            getInvalidityReason(validity)));
-                }
-            }
-        }
-
-        /**
-         * Update the current WebView package.
-         * @param newProviderName the package to switch to, null if no package has been explicitly
-         * chosen.
-         */
-        public PackageInfo updateCurrentWebViewPackage(String newProviderName) {
-            PackageInfo oldPackage = null;
-            PackageInfo newPackage = null;
-            boolean providerChanged = false;
-            synchronized(mLock) {
-                oldPackage = mCurrentWebViewPackage;
-
-                if (newProviderName != null) {
-                    mSystemInterface.updateUserSetting(mContext, newProviderName);
-                }
-
-                try {
-                    newPackage = findPreferredWebViewPackage();
-                    providerChanged = (oldPackage == null)
-                            || !newPackage.packageName.equals(oldPackage.packageName);
-                } catch (WebViewPackageMissingException e) {
-                    // If updated the Setting but don't have an installed WebView package, the
-                    // Setting will be used when a package is available.
-                    mCurrentWebViewPackage = null;
-                    Slog.e(TAG, "Couldn't find WebView package to use " + e);
-                    return null;
-                }
-                // Perform the provider change if we chose a new provider
-                if (providerChanged) {
-                    onWebViewProviderChanged(newPackage);
-                }
-            }
-            // Kill apps using the old provider only if we changed provider
-            if (providerChanged && oldPackage != null) {
-                mSystemInterface.killPackageDependents(oldPackage.packageName);
-            }
-            // Return the new provider, this is not necessarily the one we were asked to switch to,
-            // but the persistent setting will now be pointing to the provider we were asked to
-            // switch to anyway.
-            return newPackage;
-        }
-    }
-
-    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
-            PackageInfo packageInfo, SystemInterface systemInterface) {
-        if (systemInterface.systemIsDebuggable()) {
-            return true;
-        }
-        Signature[] packageSignatures;
-        // If no signature is declared, instead check whether the package is included in the
-        // system.
-        if (provider.signatures == null || provider.signatures.length == 0) {
-            return packageInfo.applicationInfo.isSystemApp();
-        }
-        packageSignatures = packageInfo.signatures;
-        if (packageSignatures.length != 1)
-            return false;
-
-        final byte[] packageSignature = packageSignatures[0].toByteArray();
-        // Return whether the package signature matches any of the valid signatures
-        for (String signature : provider.signatures) {
-            final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT);
-            if (Arrays.equals(packageSignature, validSignature))
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * Return true iff {@param packageInfos} point to only installed and enabled packages.
-     * The given packages {@param packageInfos} should all be pointing to the same package, but each
-     * PackageInfo representing a different user's package.
-     */
-    private static boolean isInstalledAndEnabledForAllUsers(
-            List<UserPackage> userPackages) {
-        for (UserPackage userPackage : userPackages) {
-            if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private static boolean isDisabledForAllUsers(List<UserPackage> userPackages) {
         for (UserPackage userPackage : userPackages) {
             if (userPackage.getPackageInfo() != null && userPackage.isEnabledPackage()) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdater.java b/services/core/java/com/android/server/webkit/WebViewUpdater.java
new file mode 100644
index 0000000..56311a3
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdater.java
@@ -0,0 +1,622 @@
+/*
+ * 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.server.webkit;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.UserHandle;
+import android.util.Base64;
+import android.util.Slog;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class that decides what WebView implementation to use and prepares that implementation for
+ * use.
+ */
+class WebViewUpdater {
+    private static final String TAG = WebViewUpdater.class.getSimpleName();
+
+    private static class WebViewPackageMissingException extends Exception {
+        public WebViewPackageMissingException(String message) { super(message); }
+        public WebViewPackageMissingException(Exception e) { super(e); }
+    }
+
+    private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
+
+    private final static int VALIDITY_OK = 0;
+    private final static int VALIDITY_INCORRECT_SDK_VERSION = 1;
+    private final static int VALIDITY_INCORRECT_VERSION_CODE = 2;
+    private final static int VALIDITY_INCORRECT_SIGNATURE = 3;
+    private final static int VALIDITY_NO_LIBRARY_FLAG = 4;
+
+    private Context mContext;
+    private SystemInterface mSystemInterface;
+    private int mMinimumVersionCode = -1;
+
+    // Keeps track of the number of running relro creations
+    private int mNumRelroCreationsStarted = 0;
+    private int mNumRelroCreationsFinished = 0;
+    // Implies that we need to rerun relro creation because we are using an out-of-date package
+    private boolean mWebViewPackageDirty = false;
+    private boolean mAnyWebViewInstalled = false;
+
+    private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+    // The WebView package currently in use (or the one we are preparing).
+    private PackageInfo mCurrentWebViewPackage = null;
+
+    private Object mLock = new Object();
+
+    WebViewUpdater(Context context, SystemInterface systemInterface) {
+        mContext = context;
+        mSystemInterface = systemInterface;
+    }
+
+    void packageStateChanged(String packageName, int changedState) {
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            String webviewPackage = provider.packageName;
+
+            if (webviewPackage.equals(packageName)) {
+                boolean updateWebView = false;
+                boolean removedOrChangedOldPackage = false;
+                String oldProviderName = null;
+                PackageInfo newPackage = null;
+                synchronized(mLock) {
+                    try {
+                        newPackage = findPreferredWebViewPackage();
+                        if (mCurrentWebViewPackage != null) {
+                            oldProviderName = mCurrentWebViewPackage.packageName;
+                            if (changedState == WebViewUpdateService.PACKAGE_CHANGED
+                                    && newPackage.packageName.equals(oldProviderName)) {
+                                // If we don't change package name we should only rerun the
+                                // preparation phase if the current package has been replaced
+                                // (not if it has been enabled/disabled).
+                                return;
+                            }
+                            if (newPackage.packageName.equals(oldProviderName)
+                                    && (newPackage.lastUpdateTime
+                                        == mCurrentWebViewPackage.lastUpdateTime)) {
+                                // If the chosen package hasn't been updated, then early-out
+                                return;
+                            }
+                        }
+                        // Only trigger update actions if the updated package is the one
+                        // that will be used, or the one that was in use before the
+                        // update, or if we haven't seen a valid WebView package before.
+                        updateWebView =
+                            provider.packageName.equals(newPackage.packageName)
+                            || provider.packageName.equals(oldProviderName)
+                            || mCurrentWebViewPackage == null;
+                        // We removed the old package if we received an intent to remove
+                        // or replace the old package.
+                        removedOrChangedOldPackage =
+                            provider.packageName.equals(oldProviderName);
+                        if (updateWebView) {
+                            onWebViewProviderChanged(newPackage);
+                        }
+                    } catch (WebViewPackageMissingException e) {
+                        mCurrentWebViewPackage = null;
+                        Slog.e(TAG, "Could not find valid WebView package to create " +
+                                "relro with " + e);
+                    }
+                }
+                if(updateWebView && !removedOrChangedOldPackage
+                        && oldProviderName != null) {
+                    // If the provider change is the result of adding or replacing a
+                    // package that was not the previous provider then we must kill
+                    // packages dependent on the old package ourselves. The framework
+                    // only kills dependents of packages that are being removed.
+                    mSystemInterface.killPackageDependents(oldProviderName);
+                }
+                return;
+            }
+        }
+    }
+
+    void prepareWebViewInSystemServer() {
+        try {
+            synchronized(mLock) {
+                mCurrentWebViewPackage = findPreferredWebViewPackage();
+                // Don't persist the user-chosen setting across boots if the package being
+                // chosen is not used (could be disabled or uninstalled) so that the user won't
+                // be surprised by the device switching to using a certain webview package,
+                // that was uninstalled/disabled a long time ago, if it is installed/enabled
+                // again.
+                mSystemInterface.updateUserSetting(mContext,
+                        mCurrentWebViewPackage.packageName);
+                onWebViewProviderChanged(mCurrentWebViewPackage);
+            }
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Slog.e(TAG, "error preparing webview provider from system server", t);
+        }
+    }
+
+    /**
+     * Change WebView provider and provider setting and kill packages using the old provider.
+     * Return the new provider (in case we are in the middle of creating relro files, or
+     * replacing that provider it will not be in use directly, but will be used when the relros
+     * or the replacement are done).
+     */
+    String changeProviderAndSetting(String newProviderName) {
+        PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+        if (newPackage == null) return "";
+        return newPackage.packageName;
+    }
+
+    /**
+     * Update the current WebView package.
+     * @param newProviderName the package to switch to, null if no package has been explicitly
+     * chosen.
+     */
+    PackageInfo updateCurrentWebViewPackage(String newProviderName) {
+        PackageInfo oldPackage = null;
+        PackageInfo newPackage = null;
+        boolean providerChanged = false;
+        synchronized(mLock) {
+            oldPackage = mCurrentWebViewPackage;
+
+            if (newProviderName != null) {
+                mSystemInterface.updateUserSetting(mContext, newProviderName);
+            }
+
+            try {
+                newPackage = findPreferredWebViewPackage();
+                providerChanged = (oldPackage == null)
+                        || !newPackage.packageName.equals(oldPackage.packageName);
+            } catch (WebViewPackageMissingException e) {
+                // If updated the Setting but don't have an installed WebView package, the
+                // Setting will be used when a package is available.
+                mCurrentWebViewPackage = null;
+                Slog.e(TAG, "Couldn't find WebView package to use " + e);
+                return null;
+            }
+            // Perform the provider change if we chose a new provider
+            if (providerChanged) {
+                onWebViewProviderChanged(newPackage);
+            }
+        }
+        // Kill apps using the old provider only if we changed provider
+        if (providerChanged && oldPackage != null) {
+            mSystemInterface.killPackageDependents(oldPackage.packageName);
+        }
+        // Return the new provider, this is not necessarily the one we were asked to switch to,
+        // but the persistent setting will now be pointing to the provider we were asked to
+        // switch to anyway.
+        return newPackage;
+    }
+
+    /**
+     * This is called when we change WebView provider, either when the current provider is
+     * updated or a new provider is chosen / takes precedence.
+     */
+    private void onWebViewProviderChanged(PackageInfo newPackage) {
+        synchronized(mLock) {
+            mAnyWebViewInstalled = true;
+            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+                mCurrentWebViewPackage = newPackage;
+
+                // The relro creations might 'finish' (not start at all) before
+                // WebViewFactory.onWebViewProviderChanged which means we might not know the
+                // number of started creations before they finish.
+                mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+                mNumRelroCreationsFinished = 0;
+                mNumRelroCreationsStarted =
+                    mSystemInterface.onWebViewProviderChanged(newPackage);
+                // If the relro creations finish before we know the number of started creations
+                // we will have to do any cleanup/notifying here.
+                checkIfRelrosDoneLocked();
+            } else {
+                mWebViewPackageDirty = true;
+            }
+        }
+    }
+
+    /**
+     * Fetch only the currently valid WebView packages.
+     **/
+    WebViewProviderInfo[] getValidWebViewPackages() {
+        ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+        WebViewProviderInfo[] providers =
+            new WebViewProviderInfo[providersAndPackageInfos.length];
+        for(int n = 0; n < providersAndPackageInfos.length; n++) {
+            providers[n] = providersAndPackageInfos[n].provider;
+        }
+        return providers;
+    }
+
+    private static class ProviderAndPackageInfo {
+        public final WebViewProviderInfo provider;
+        public final PackageInfo packageInfo;
+
+        public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+            this.provider = provider;
+            this.packageInfo = packageInfo;
+        }
+    }
+
+    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        List<ProviderAndPackageInfo> providers = new ArrayList<>();
+        for(int n = 0; n < allProviders.length; n++) {
+            try {
+                PackageInfo packageInfo =
+                    mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+                if (isValidProvider(allProviders[n], packageInfo)) {
+                    providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+                }
+            } catch (NameNotFoundException e) {
+                // Don't add non-existent packages
+            }
+        }
+        return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+    }
+
+    /**
+     * Returns either the package info of the WebView provider determined in the following way:
+     * If the user has chosen a provider then use that if it is valid,
+     * otherwise use the first package in the webview priority list that is valid.
+     *
+     */
+    private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+        // If the user has chosen provider, use that (if it's installed and enabled for all
+        // users).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // User did not choose, or the choice failed; use the most stable provider that is
+        // installed and enabled for all users, and available by default (not through
+        // user choice).
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.availableByDefault) {
+                // userPackages can contain null objects.
+                List<UserPackage> userPackages =
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+                                providerAndPackage.provider);
+                if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                    return providerAndPackage.packageInfo;
+                }
+            }
+        }
+
+        // Could not find any installed and enabled package either, use the most stable and
+        // default-available provider.
+        // TODO(gsennton) remove this when we have a functional WebView stub.
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.availableByDefault) {
+                return providerAndPackage.packageInfo;
+            }
+        }
+
+        // This should never happen during normal operation (only with modified system images).
+        mAnyWebViewInstalled = false;
+        throw new WebViewPackageMissingException("Could not find a loadable WebView package");
+    }
+
+    /**
+     * Return true iff {@param packageInfos} point to only installed and enabled packages.
+     * The given packages {@param packageInfos} should all be pointing to the same package, but each
+     * PackageInfo representing a different user's package.
+     */
+    static boolean isInstalledAndEnabledForAllUsers(
+            List<UserPackage> userPackages) {
+        for (UserPackage userPackage : userPackages) {
+            if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void notifyRelroCreationCompleted() {
+        synchronized (mLock) {
+            mNumRelroCreationsFinished++;
+            checkIfRelrosDoneLocked();
+        }
+    }
+
+    WebViewProviderResponse waitForAndGetProvider() {
+        PackageInfo webViewPackage = null;
+        final long NS_PER_MS = 1000000;
+        final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+        boolean webViewReady = false;
+        int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+        synchronized (mLock) {
+            webViewReady = webViewIsReadyLocked();
+            while (!webViewReady) {
+                final long timeNowMs = System.nanoTime() / NS_PER_MS;
+                if (timeNowMs >= timeoutTimeMs) break;
+                try {
+                    mLock.wait(timeoutTimeMs - timeNowMs);
+                } catch (InterruptedException e) {}
+                webViewReady = webViewIsReadyLocked();
+            }
+            // Make sure we return the provider that was used to create the relro file
+            webViewPackage = mCurrentWebViewPackage;
+            if (webViewReady) {
+            } else if (!mAnyWebViewInstalled) {
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+            } else {
+                // Either the current relro creation  isn't done yet, or the new relro creatioin
+                // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+                webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+                Slog.e(TAG, "Timed out waiting for relro creation, relros started "
+                        + mNumRelroCreationsStarted
+                        + " relros finished " + mNumRelroCreationsFinished
+                        + " package dirty? " + mWebViewPackageDirty);
+            }
+        }
+        if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+        return new WebViewProviderResponse(webViewPackage, webViewStatus);
+    }
+
+    PackageInfo getCurrentWebViewPackage() {
+        synchronized(mLock) {
+            return mCurrentWebViewPackage;
+        }
+    }
+
+    /**
+     * Returns whether WebView is ready and is not going to go through its preparation phase
+     * again directly.
+     */
+    private boolean webViewIsReadyLocked() {
+        return !mWebViewPackageDirty
+            && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+            // The current package might be replaced though we haven't received an intent
+            // declaring this yet, the following flag makes anyone loading WebView to wait in
+            // this case.
+            && mAnyWebViewInstalled;
+    }
+
+    private void checkIfRelrosDoneLocked() {
+        if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+            if (mWebViewPackageDirty) {
+                mWebViewPackageDirty = false;
+                // If we have changed provider since we started the relro creation we need to
+                // redo the whole process using the new package instead.
+                try {
+                    PackageInfo newPackage = findPreferredWebViewPackage();
+                    onWebViewProviderChanged(newPackage);
+                } catch (WebViewPackageMissingException e) {
+                    mCurrentWebViewPackage = null;
+                    // If we can't find any valid WebView package we are now in a state where
+                    // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
+                    // should simply wait until we receive an intent declaring a new package was
+                    // installed.
+                }
+            } else {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Returns whether this provider is valid for use as a WebView provider.
+     */
+    boolean isValidProvider(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+        return VALIDITY_OK == validityResult(configInfo, packageInfo);
+    }
+
+    private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+        // Ensure the provider targets this framework release (or a later one).
+        if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
+            return VALIDITY_INCORRECT_SDK_VERSION;
+        }
+        if (!versionCodeGE(packageInfo.versionCode, getMinimumVersionCode())
+                && !mSystemInterface.systemIsDebuggable()) {
+            // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
+            // minimum version code. This check is only enforced for user builds.
+            return VALIDITY_INCORRECT_VERSION_CODE;
+        }
+        if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
+            return VALIDITY_INCORRECT_SIGNATURE;
+        }
+        if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
+            return VALIDITY_NO_LIBRARY_FLAG;
+        }
+        return VALIDITY_OK;
+    }
+
+    /**
+     * Both versionCodes should be from a WebView provider package implemented by Chromium.
+     * VersionCodes from other kinds of packages won't make any sense in this method.
+     *
+     * An introduction to Chromium versionCode scheme:
+     * "BBBBPPPAX"
+     * BBBB: 4 digit branch number. It monotonically increases over time.
+     * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
+     * may change their meaning in the future.
+     * A: architecture digit.
+     * X: A digit to differentiate APKs for other reasons.
+     *
+     * This method takes the "BBBB" of versionCodes and compare them.
+     *
+     * @return true if versionCode1 is higher than or equal to versionCode2.
+     */
+    private static boolean versionCodeGE(int versionCode1, int versionCode2) {
+        int v1 = versionCode1 / 100000;
+        int v2 = versionCode2 / 100000;
+
+        return v1 >= v2;
+    }
+
+    /**
+     * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+     * of all available-by-default and non-fallback WebView provider packages. If there is no
+     * such WebView provider package on the system, then return -1, which means all positive
+     * versionCode WebView packages are accepted.
+     *
+     * Note that this is a private method in WebViewUpdater that handles a variable
+     * (mMinimumVersionCode) which is shared between threads. Furthermore, this method does not
+     * hold mLock meaning that we must take extra care to ensure this method is thread-safe.
+     */
+    private int getMinimumVersionCode() {
+        if (mMinimumVersionCode > 0) {
+            return mMinimumVersionCode;
+        }
+
+        int minimumVersionCode = -1;
+        for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+            if (provider.availableByDefault && !provider.isFallback) {
+                try {
+                    int versionCode =
+                        mSystemInterface.getFactoryPackageVersion(provider.packageName);
+                    if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
+                        minimumVersionCode = versionCode;
+                    }
+                } catch (NameNotFoundException e) {
+                    // Safe to ignore.
+                }
+            }
+        }
+
+        mMinimumVersionCode = minimumVersionCode;
+        return mMinimumVersionCode;
+    }
+
+    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+            PackageInfo packageInfo, SystemInterface systemInterface) {
+        if (systemInterface.systemIsDebuggable()) {
+            return true;
+        }
+        Signature[] packageSignatures;
+        // If no signature is declared, instead check whether the package is included in the
+        // system.
+        if (provider.signatures == null || provider.signatures.length == 0) {
+            return packageInfo.applicationInfo.isSystemApp();
+        }
+        packageSignatures = packageInfo.signatures;
+        if (packageSignatures.length != 1)
+            return false;
+
+        final byte[] packageSignature = packageSignatures[0].toByteArray();
+        // Return whether the package signature matches any of the valid signatures
+        for (String signature : provider.signatures) {
+            final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT);
+            if (Arrays.equals(packageSignature, validSignature))
+                return true;
+        }
+        return false;
+    }
+
+    void dumpState(PrintWriter pw) {
+        synchronized (mLock) {
+            if (mCurrentWebViewPackage == null) {
+                pw.println("  Current WebView package is null");
+            } else {
+                pw.println(String.format("  Current WebView package (name, version): (%s, %s)",
+                        mCurrentWebViewPackage.packageName,
+                        mCurrentWebViewPackage.versionName));
+            }
+            pw.println(String.format("  Minimum WebView version code: %d",
+                  mMinimumVersionCode));
+            pw.println(String.format("  Number of relros started: %d",
+                    mNumRelroCreationsStarted));
+            pw.println(String.format("  Number of relros finished: %d",
+                        mNumRelroCreationsFinished));
+            pw.println(String.format("  WebView package dirty: %b", mWebViewPackageDirty));
+            pw.println(String.format("  Any WebView package installed: %b",
+                    mAnyWebViewInstalled));
+
+            try {
+                PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
+                pw.println(String.format(
+                        "  Preferred WebView package (name, version): (%s, %s)",
+                        preferredWebViewPackage.packageName,
+                        preferredWebViewPackage.versionName));
+            } catch (WebViewPackageMissingException e) {
+                pw.println(String.format("  Preferred WebView package: none"));
+            }
+
+            dumpAllPackageInformationLocked(pw);
+        }
+    }
+
+    private void dumpAllPackageInformationLocked(PrintWriter pw) {
+        WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+        pw.println("  WebView packages:");
+        for (WebViewProviderInfo provider : allProviders) {
+            List<UserPackage> userPackages =
+                    mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+            PackageInfo systemUserPackageInfo =
+                    userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
+            if (systemUserPackageInfo == null) {
+                continue;
+            }
+
+            int validity = validityResult(provider, systemUserPackageInfo);
+            String packageDetails = String.format(
+                    "versionName: %s, versionCode: %d, targetSdkVersion: %d",
+                    systemUserPackageInfo.versionName,
+                    systemUserPackageInfo.versionCode,
+                    systemUserPackageInfo.applicationInfo.targetSdkVersion);
+            if (validity == VALIDITY_OK) {
+                boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
+                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+                pw.println(String.format(
+                        "    Valid package %s (%s) is %s installed/enabled for all users",
+                        systemUserPackageInfo.packageName,
+                        packageDetails,
+                        installedForAllUsers ? "" : "NOT"));
+            } else {
+                pw.println(String.format("    Invalid package %s (%s), reason: %s",
+                        systemUserPackageInfo.packageName,
+                        packageDetails,
+                        getInvalidityReason(validity)));
+            }
+        }
+    }
+
+    private static String getInvalidityReason(int invalidityReason) {
+        switch (invalidityReason) {
+            case VALIDITY_INCORRECT_SDK_VERSION:
+                return "SDK version too low";
+            case VALIDITY_INCORRECT_VERSION_CODE:
+                return "Version code too low";
+            case VALIDITY_INCORRECT_SIGNATURE:
+                return "Incorrect signature";
+            case VALIDITY_NO_LIBRARY_FLAG:
+                return "No WebView-library manifest flag";
+            default:
+                return "Unexcepted validity-reason";
+        }
+    }
+
+}