Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dbf5eb04 authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Simplify WebViewProviderInfo - move its logic into WebViewUpdateService.

The WebViewProviderInfo should now be ready to be added as an API to be
fetched from XTS tests (to avoid using reflection).

Move the logic for validation, signature checking and package info
fetching out of WebViewProviderInfo so that we can mock the coupling
between that logic and the system (e.g. the package manager).

Note: with this patch we stop caching valid webview packages in the
update service (we would still refetch them anyway when anything
important happened).

Bug: 27635535
Bug: 27736084

Change-Id: Ia455202d2fd5bc4e03dce0fd917d262bf942d1a3
parent 2298bb19
Loading
Loading
Loading
Loading
+8 −118
Original line number Diff line number Diff line
@@ -16,32 +16,16 @@

package android.webkit;

import android.app.AppGlobals;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AndroidRuntimeException;
import android.util.Base64;

import java.util.Arrays;

/** @hide */
public class WebViewProviderInfo implements Parcelable {
public final class WebViewProviderInfo implements Parcelable {

    /**
     * @hide
     */
    public static class WebViewPackageNotFoundException extends AndroidRuntimeException {
        public WebViewPackageNotFoundException(String message) { super(message); }
        public WebViewPackageNotFoundException(Exception e) { super(e); }
    }

    public WebViewProviderInfo(String packageName, String description, boolean availableByDefault,
            boolean isFallback, String[] signatures) {
    public WebViewProviderInfo(String packageName, String description,
            boolean availableByDefault, boolean isFallback, String[] signatures) {
        this.packageName = packageName;
        this.description = description;
        this.availableByDefault = availableByDefault;
@@ -49,92 +33,6 @@ public class WebViewProviderInfo implements Parcelable {
        this.signatures = signatures;
    }

    private boolean hasValidSignature() {
        if (Build.IS_DEBUGGABLE)
            return true;
        Signature[] packageSignatures;
        try {
            // If no signature is declared, instead check whether the package is included in the
            // system.
            if (signatures == null || signatures.length == 0)
                return getPackageInfo().applicationInfo.isSystemApp();

            packageSignatures = getPackageInfo().signatures;
        } catch (WebViewPackageNotFoundException e) {
            return false;
        }
        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 : signatures) {
            final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT);
            if (Arrays.equals(packageSignature, validSignature))
                return true;
        }
        return false;
    }

    /**
     * Returns whether this provider is valid for use as a WebView provider.
     */
    public boolean isValidProvider() {
        ApplicationInfo applicationInfo;
        try {
            applicationInfo = getPackageInfo().applicationInfo;
        } catch (WebViewPackageNotFoundException e) {
            return false;
        }
        if (hasValidSignature() && WebViewFactory.getWebViewLibrary(applicationInfo) != null) {
            return true;
        }
        return false;
    }

    /**
     * Returns whether this package is enabled.
     * This state can be changed by the user from Settings->Apps
     */
    public boolean isEnabled() {
        try {
            // Explicitly fetch up-to-date package info here since the enabled-state of the package
            // might have changed since we last fetched its package info.
            updatePackageInfo();
            return getPackageInfo().applicationInfo.enabled;
        } catch (WebViewPackageNotFoundException e) {
            return false;
        }
    }

    /**
     * Returns whether the provider is always available as long as it is valid.
     * If this returns false, the provider will only be used if the user chose this provider.
     */
    public boolean isAvailableByDefault() {
        return availableByDefault;
    }

    public boolean isFallbackPackage() {
        return isFallback;
    }

    private void updatePackageInfo() {
        try {
            PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
            packageInfo = pm.getPackageInfo(packageName, PACKAGE_FLAGS);
        } catch (PackageManager.NameNotFoundException e) {
            throw new WebViewPackageNotFoundException(e);
        }
    }

    public PackageInfo getPackageInfo() {
        if (packageInfo == null) {
            updatePackageInfo();
        }
        return packageInfo;
    }

    // aidl stuff
    public static final Parcelable.Creator<WebViewProviderInfo> CREATOR =
        new Parcelable.Creator<WebViewProviderInfo>() {
@@ -153,7 +51,6 @@ public class WebViewProviderInfo implements Parcelable {
        availableByDefault = (in.readInt() > 0);
        isFallback = (in.readInt() > 0);
        signatures = in.createStringArray();
        packageInfo = null;
    }

    @Override
@@ -171,16 +68,9 @@ public class WebViewProviderInfo implements Parcelable {
    }

    // fields read from framework resource
    public String packageName;
    public String description;
    private boolean availableByDefault;
    private boolean isFallback;

    private String[] signatures;

    private PackageInfo packageInfo;

    // flags declaring we want extra info from the package manager
    private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
    public final String packageName;
    public final String description;
    public final boolean availableByDefault;
    public final boolean isFallback;
    public final String[] signatures;
}
+124 −55
Original line number Diff line number Diff line
@@ -26,9 +26,11 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Build;
import android.os.PatternMatcher;
import android.os.Process;
import android.os.RemoteException;
@@ -38,6 +40,7 @@ import android.os.UserManager;
import android.provider.Settings.Global;
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Base64;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
import android.webkit.WebViewFactory;
@@ -72,8 +75,6 @@ public class WebViewUpdateService extends SystemService {

    // The WebView package currently in use (or the one we are preparing).
    private PackageInfo mCurrentWebViewPackage = null;
    // The WebView providers that are currently available.
    private WebViewProviderInfo[] mCurrentValidWebViewPackages = null;

    private BroadcastReceiver mWebViewUpdatedReceiver;
    private WebViewUtilityInterface mWebViewUtility;
@@ -126,7 +127,6 @@ public class WebViewUpdateService extends SystemService {
                            PackageInfo newPackage = null;
                            synchronized(WebViewUpdateService.this) {
                                try {
                                    updateValidWebViewPackages();
                                    newPackage = findPreferredWebViewPackage();
                                    if (mCurrentWebViewPackage != null)
                                        oldProviderName = mCurrentWebViewPackage.packageName;
@@ -180,12 +180,18 @@ public class WebViewUpdateService extends SystemService {
        publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
    }

    private static boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
    private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
        for (WebViewProviderInfo provider : providers) {
            if (provider.isAvailableByDefault() && provider.isEnabled()
                    && provider.isValidProvider() && !provider.isFallbackPackage()) {
            if (provider.availableByDefault && !provider.isFallback) {
                try {
                    PackageInfo packageInfo = getPackageInfoForProvider(provider);
                    if (isEnabledPackage(packageInfo) && isValidProvider(provider, packageInfo)) {
                        return true;
                    }
                } catch (NameNotFoundException e) {
                    // A non-existent provider is neither valid nor enabled
                }
            }
        }
        return false;
    }
@@ -211,11 +217,9 @@ public class WebViewUpdateService extends SystemService {
        WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();
        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
        if (fallbackProvider == null) return;
        boolean existsValidNonFallbackProvider =
            existsValidNonFallbackProvider(webviewProviders);

        enablePackageForUser(fallbackProvider.packageName, !existsValidNonFallbackProvider,
                userId);
        enablePackageForUser(fallbackProvider.packageName,
                !existsValidNonFallbackProvider(webviewProviders), userId);
    }

    /**
@@ -236,7 +240,7 @@ public class WebViewUpdateService extends SystemService {
            for (WebViewProviderInfo provider : webviewProviders) {
                String webviewPackage = "package:" + provider.packageName;
                if (webviewPackage.equals(intent.getDataString())) {
                    if (provider.isAvailableByDefault()) {
                    if (provider.availableByDefault) {
                        changedPackage = provider.packageName;
                    }
                    break;
@@ -251,10 +255,16 @@ public class WebViewUpdateService extends SystemService {
        if (fallbackProvider == null) return;
        boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);

        boolean isFallbackEnabled = false;
        try {
            isFallbackEnabled = isEnabledPackage(getPackageInfoForProvider(fallbackProvider));
        } catch (NameNotFoundException e) {
        }

        if (existsValidNonFallbackProvider
                // During an OTA the primary user's WebView state might differ from other users', so
                // ignore the state of that user during boot.
                && (fallbackProvider.isEnabled() || intent == null)) {
                && (isFallbackEnabled || intent == null)) {
            // Uninstall and disable fallback package for all users.
            context.getPackageManager().deletePackage(fallbackProvider.packageName,
                    new IPackageDeleteObserver.Stub() {
@@ -273,7 +283,7 @@ public class WebViewUpdateService extends SystemService {
        } else if (!existsValidNonFallbackProvider
                // During an OTA the primary user's WebView state might differ from other users', so
                // ignore the state of that user during boot.
                && (!fallbackProvider.isEnabled() || intent==null)) {
                && (!isFallbackEnabled || intent==null)) {
            // Enable the fallback package for all users.
            UserManager userManager =
                (UserManager)context.getSystemService(Context.USER_SERVICE);
@@ -299,24 +309,13 @@ public class WebViewUpdateService extends SystemService {
     */
    private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
        for (WebViewProviderInfo provider : webviewPackages) {
            if (provider.isFallbackPackage()) {
            if (provider.isFallback) {
                return provider;
            }
        }
        return null;
    }

    private static boolean containsAvailableNonFallbackProvider(
            WebViewProviderInfo[] webviewPackages) {
        for (WebViewProviderInfo provider : webviewPackages) {
            if (provider.isAvailableByDefault() && provider.isEnabled()
                    && provider.isValidProvider() && !provider.isFallbackPackage()) {
                return true;
            }
        }
        return false;
    }

    private boolean isFallbackPackage(String packageName) {
        if (packageName == null || !isFallbackLogicEnabled()) return false;

@@ -336,7 +335,6 @@ public class WebViewUpdateService extends SystemService {
        updateFallbackState(getContext(), null);
        try {
            synchronized(this) {
                updateValidWebViewPackages();
                mCurrentWebViewPackage = findPreferredWebViewPackage();
                onWebViewProviderChanged(mCurrentWebViewPackage);
            }
@@ -408,24 +406,41 @@ public class WebViewUpdateService extends SystemService {
        }
    }

    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
        WebViewProviderInfo[] allProviders = mWebViewUtility.getWebViewPackages();
        List<ProviderAndPackageInfo> providers = new ArrayList<>();
        for(int n = 0; n < allProviders.length; n++) {
            try {
                PackageInfo packageInfo = 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()]);
    }

    /**
     * Updates the currently valid WebView provider packages.
     * Should be used when a provider has been installed or removed.
     * @hide
     * Fetch only the currently valid WebView packages.
     **/
    private void updateValidWebViewPackages() {
        List<WebViewProviderInfo> webViewProviders  =
            new ArrayList<WebViewProviderInfo>(Arrays.asList(mWebViewUtility.getWebViewPackages()));
        Iterator<WebViewProviderInfo> it = webViewProviders.iterator();
        // remove non-valid packages
        while(it.hasNext()) {
            WebViewProviderInfo current = it.next();
            if (!current.isValidProvider())
                it.remove();
    private WebViewProviderInfo[] getValidWebViewPackages() {
        ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
        WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length];
        for(int n = 0; n < providersAndPackageInfos.length; n++) {
            providers[n] = providersAndPackageInfos[n].provider;
        }
        synchronized(this) {
            mCurrentValidWebViewPackages =
                webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
        return providers;
    }

    private class ProviderAndPackageInfo {
        public final WebViewProviderInfo provider;
        public final PackageInfo packageInfo;

        public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
            this.provider = provider;
            this.packageInfo = packageInfo;
        }
    }

@@ -437,28 +452,30 @@ public class WebViewUpdateService extends SystemService {
     * @hide
     */
    private PackageInfo findPreferredWebViewPackage() {
        WebViewProviderInfo[] providers = mCurrentValidWebViewPackages;
        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();

        String userChosenProvider = mWebViewUtility.getUserChosenWebViewProvider(getContext());

        // If the user has chosen provider, use that
        for (WebViewProviderInfo provider : providers) {
            if (provider.packageName.equals(userChosenProvider) && provider.isEnabled()) {
                return provider.getPackageInfo();
        for (ProviderAndPackageInfo providerAndPackage : providers) {
            if (providerAndPackage.provider.packageName.equals(userChosenProvider)
                    && isEnabledPackage(providerAndPackage.packageInfo)) {
                return providerAndPackage.packageInfo;
            }
        }

        // User did not choose, or the choice failed; use the most stable provider that is
        // enabled and available by default (not through user choice).
        for (WebViewProviderInfo provider : providers) {
            if (provider.isAvailableByDefault() && provider.isEnabled()) {
                return provider.getPackageInfo();
        for (ProviderAndPackageInfo providerAndPackage : providers) {
            if (providerAndPackage.provider.availableByDefault
                    && isEnabledPackage(providerAndPackage.packageInfo)) {
                return providerAndPackage.packageInfo;
            }
        }

        // Could not find any enabled package either, use the most stable provider.
        for (WebViewProviderInfo provider : providers) {
            return provider.getPackageInfo();
        for (ProviderAndPackageInfo providerAndPackage : providers) {
            return providerAndPackage.packageInfo;
        }

        mAnyWebViewInstalled = false;
@@ -466,6 +483,60 @@ public class WebViewUpdateService extends SystemService {
                "Could not find a loadable WebView package");
    }

    /**
     * Returns whether this provider is valid for use as a WebView provider.
     */
    private static boolean isValidProvider(WebViewProviderInfo configInfo,
            PackageInfo packageInfo) {
        if (providerHasValidSignature(configInfo, packageInfo) &&
                WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
            return true;
        }
        return false;
    }

    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
            PackageInfo packageInfo) {
        if (Build.IS_DEBUGGABLE)
            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;
    }

    /**
     * Returns whether the given package is enabled.
     * This state can be changed by the user from Settings->Apps
     */
    private static boolean isEnabledPackage(PackageInfo packageInfo) {
        return packageInfo.applicationInfo.enabled;
    }

    private static PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
            throws NameNotFoundException {
        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
        return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
    }

    // flags declaring we want extra info from the package manager for webview providers
    private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;

    /**
     * Returns whether WebView is ready and is not going to go through its preparation phase again
     * directly.
@@ -593,9 +664,7 @@ public class WebViewUpdateService extends SystemService {

        @Override // Binder call
        public WebViewProviderInfo[] getValidWebViewPackages() {
            synchronized(WebViewUpdateService.this) {
                return mCurrentValidWebViewPackages;
            }
            return WebViewUpdateService.this.getValidWebViewPackages();
        }

        @Override // Binder call
+4 −4
Original line number Diff line number Diff line
@@ -87,10 +87,10 @@ public class WebViewUtilityImpl implements WebViewUtilityInterface {
                            parser.getAttributeValue(null, TAG_AVAILABILITY));
                    boolean isFallback = "true".equals(
                            parser.getAttributeValue(null, TAG_FALLBACK));
                    WebViewProviderInfo currentProvider =
                            new WebViewProviderInfo(packageName, description, availableByDefault,
                                isFallback, readSignatures(parser));
                    if (currentProvider.isFallbackPackage()) {
                    WebViewProviderInfo currentProvider = new WebViewProviderInfo(
                            packageName, description, availableByDefault, isFallback,
                            readSignatures(parser));
                    if (currentProvider.isFallback) {
                        numFallbackPackages++;
                        if (numFallbackPackages > 1) {
                            throw new AndroidRuntimeException(