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";
+ }
+ }
+
+}