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

Commit 8b17926a authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Add utility interface for WebView preparation logic.

To make the WebView preparation mechanism testable we add a utility
interface that can be overridden during a test to avoid calling the
Android framework and to provide custom WebView packages.

With this change we also split some of the code from the WebViewFactory
(code unrelated to WebView loading) into a separate utility class.

Bug: 27635535
Change-Id: I265ecd42b24ad5383637e125b3654ff339c9df9c
parent 54f4a630
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -53,6 +53,11 @@ interface IWebViewUpdateService {
     */
    WebViewProviderInfo[] getValidWebViewPackages();

    /**
     * Fetch all packages that could potentially implement WebView.
     */
    WebViewProviderInfo[] getAllWebViewPackages();

    /**
     * Used by DevelopmentSetting to get the name of the WebView provider currently in use.
     */
+1 −90
Original line number Diff line number Diff line
@@ -131,98 +131,9 @@ public final class WebViewFactory {
        public MissingWebViewPackageException(Exception e) { super(e); }
    }

    private static String TAG_START = "webviewproviders";
    private static String TAG_WEBVIEW_PROVIDER = "webviewprovider";
    private static String TAG_PACKAGE_NAME = "packageName";
    private static String TAG_DESCRIPTION = "description";
    // Whether or not the provider must be explicitly chosen by the user to be used.
    private static String TAG_AVAILABILITY = "availableByDefault";
    private static String TAG_SIGNATURE = "signature";
    private static String TAG_FALLBACK = "isFallback";

    /**
     * Reads all signatures at the current depth (within the current provider) from the XML parser.
     */
    private static String[] readSignatures(XmlResourceParser parser) throws IOException,
            XmlPullParserException {
        List<String> signatures = new ArrayList<String>();
        int outerDepth = parser.getDepth();
        while(XmlUtils.nextElementWithin(parser, outerDepth)) {
            if (parser.getName().equals(TAG_SIGNATURE)) {
                // Parse the value within the signature tag
                String signature = parser.nextText();
                signatures.add(signature);
            } else {
                Log.e(LOGTAG, "Found an element in a webview provider that is not a signature");
            }
        }
        return signatures.toArray(new String[signatures.size()]);
    }

    /**
     * Returns all packages declared in the framework resources as potential WebView providers.
     * @hide
     * */
    public static WebViewProviderInfo[] getWebViewPackages() {
        int numFallbackPackages = 0;
        XmlResourceParser parser = null;
        List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
        try {
            parser = AppGlobals.getInitialApplication().getResources().getXml(
                    com.android.internal.R.xml.config_webview_packages);
            XmlUtils.beginDocument(parser, TAG_START);
            while(true) {
                XmlUtils.nextElement(parser);
                String element = parser.getName();
                if (element == null) {
                    break;
                }
                if (element.equals(TAG_WEBVIEW_PROVIDER)) {
                    String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME);
                    if (packageName == null) {
                        throw new MissingWebViewPackageException(
                                "WebView provider in framework resources missing package name");
                    }
                    String description = parser.getAttributeValue(null, TAG_DESCRIPTION);
                    if (description == null) {
                        throw new MissingWebViewPackageException(
                                "WebView provider in framework resources missing description");
                    }
                    boolean availableByDefault = "true".equals(
                            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()) {
                        numFallbackPackages++;
                        if (numFallbackPackages > 1) {
                            throw new AndroidRuntimeException(
                                    "There can be at most one webview fallback package.");
                        }
                    }
                    webViewProviders.add(currentProvider);
                }
                else {
                    Log.e(LOGTAG, "Found an element that is not a webview provider");
                }
            }
        } catch(XmlPullParserException e) {
            throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
        } catch(IOException e) {
            throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
        } finally {
            if (parser != null) parser.close();
        }
        return webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
    }


    // TODO (gsennton) remove when committing webview xts test change
    public static String getWebViewPackageName() {
        WebViewProviderInfo[] providers = getWebViewPackages();
        return providers[0].packageName;
        return null;
    }

    /**
+4 −0
Original line number Diff line number Diff line
@@ -150,6 +150,8 @@ public class WebViewProviderInfo implements Parcelable {
    private WebViewProviderInfo(Parcel in) {
        packageName = in.readString();
        description = in.readString();
        availableByDefault = (in.readInt() > 0);
        isFallback = (in.readInt() > 0);
        signatures = in.createStringArray();
        packageInfo = null;
    }
@@ -163,6 +165,8 @@ public class WebViewProviderInfo implements Parcelable {
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(packageName);
        out.writeString(description);
        out.writeInt(availableByDefault ? 1 : 0);
        out.writeInt(isFallback ? 1 : 0);
        out.writeStringArray(signatures);
    }

+25 −37
Original line number Diff line number Diff line
@@ -35,14 +35,14 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
import android.webkit.WebViewFactory;

import com.android.server.SystemService;

@@ -78,9 +78,11 @@ public class WebViewUpdateService extends SystemService {
    private WebViewProviderInfo[] mCurrentValidWebViewPackages = null;

    private BroadcastReceiver mWebViewUpdatedReceiver;
    private WebViewUtilityInterface mWebViewUtility;

    public WebViewUpdateService(Context context) {
        super(context);
        mWebViewUtility = new WebViewUtilityImpl();
    }

    @Override
@@ -125,7 +127,7 @@ public class WebViewUpdateService extends SystemService {

                    updateFallbackState(context, intent);

                    for (WebViewProviderInfo provider : WebViewFactory.getWebViewPackages()) {
                    for (WebViewProviderInfo provider : mWebViewUtility.getWebViewPackages()) {
                        String webviewPackage = "package:" + provider.packageName;

                        if (webviewPackage.equals(intent.getDataString())) {
@@ -164,11 +166,7 @@ public class WebViewUpdateService extends SystemService {
                                // 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.
                                try {
                                    ActivityManagerNative.getDefault().killPackageDependents(
                                        oldProviderName, UserHandle.USER_ALL);
                                } catch (RemoteException e) {
                                }
                                mWebViewUtility.killPackageDependents(oldProviderName);
                            }
                            return;
                        }
@@ -181,7 +179,7 @@ public class WebViewUpdateService extends SystemService {
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        // Make sure we only receive intents for WebView packages from our config file.
        for (WebViewProviderInfo provider : WebViewFactory.getWebViewPackages()) {
        for (WebViewProviderInfo provider : mWebViewUtility.getWebViewPackages()) {
            filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
        }
        getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
@@ -221,7 +219,7 @@ public class WebViewUpdateService extends SystemService {
    void handleNewUser(int userId) {
        if (!isFallbackLogicEnabled()) return;

        WebViewProviderInfo[] webviewProviders = WebViewFactory.getWebViewPackages();
        WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();
        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
        if (fallbackProvider == null) return;
        boolean existsValidNonFallbackProvider =
@@ -239,7 +237,7 @@ public class WebViewUpdateService extends SystemService {
    void updateFallbackState(final Context context, final Intent intent) {
        if (!isFallbackLogicEnabled()) return;

        WebViewProviderInfo[] webviewProviders = WebViewFactory.getWebViewPackages();
        WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();

        if (intent != null && (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)
                    || intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED))) {
@@ -330,10 +328,10 @@ public class WebViewUpdateService extends SystemService {
        return false;
    }

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

        WebViewProviderInfo[] webviewPackages = WebViewFactory.getWebViewPackages();
        WebViewProviderInfo[] webviewPackages = mWebViewUtility.getWebViewPackages();
        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewPackages);
        return (fallbackProvider != null
                && packageName.equals(fallbackProvider.packageName));
@@ -370,13 +368,13 @@ public class WebViewUpdateService extends SystemService {
        PackageInfo newPackage = null;
        synchronized(this) {
            oldPackage = mCurrentWebViewPackage;
            updateUserSetting(newProviderName);
            mWebViewUtility.updateUserSetting(getContext(), newProviderName);

            try {
                newPackage = findPreferredWebViewPackage();
                if (oldPackage != null && newPackage.packageName.equals(oldPackage.packageName)) {
                    // If we don't perform the user change, revert the settings change.
                    updateUserSetting(newPackage.packageName);
                    mWebViewUtility.updateUserSetting(getContext(), newPackage.packageName);
                    return newPackage.packageName;
                }
            } catch (WebViewFactory.MissingWebViewPackageException e) {
@@ -389,12 +387,8 @@ public class WebViewUpdateService extends SystemService {
            onWebViewProviderChanged(newPackage);
        }
        // Kill apps using the old provider
        try {
        if (oldPackage != null) {
                ActivityManagerNative.getDefault().killPackageDependents(
                        oldPackage.packageName, UserHandle.USER_ALL);
            }
        } catch (RemoteException e) {
            mWebViewUtility.killPackageDependents(oldPackage.packageName);
        }
        return newPackage.packageName;
    }
@@ -411,14 +405,14 @@ public class WebViewUpdateService extends SystemService {
            mCurrentProviderBeingReplaced = false;
            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
                mCurrentWebViewPackage = newPackage;
                updateUserSetting(newPackage.packageName);
                mWebViewUtility.updateUserSetting(getContext(), newPackage.packageName);

                // 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 = WebViewFactory.onWebViewProviderChanged(newPackage);
                mNumRelroCreationsStarted = mWebViewUtility.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();
@@ -435,7 +429,7 @@ public class WebViewUpdateService extends SystemService {
     * */
    private void updateValidWebViewPackages() {
        List<WebViewProviderInfo> webViewProviders  =
            new ArrayList<WebViewProviderInfo>(Arrays.asList(WebViewFactory.getWebViewPackages()));
            new ArrayList<WebViewProviderInfo>(Arrays.asList(mWebViewUtility.getWebViewPackages()));
        Iterator<WebViewProviderInfo> it = webViewProviders.iterator();
        // remove non-valid packages
        while(it.hasNext()) {
@@ -449,17 +443,6 @@ public class WebViewUpdateService extends SystemService {
        }
    }

    private static String getUserChosenWebViewProvider() {
        return Settings.Global.getString(AppGlobals.getInitialApplication().getContentResolver(),
                Settings.Global.WEBVIEW_PROVIDER);
    }

    private void updateUserSetting(String newProviderName) {
        Settings.Global.putString(getContext().getContentResolver(),
                Settings.Global.WEBVIEW_PROVIDER,
                newProviderName == null ? "" : newProviderName);
    }

    /**
     * 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,
@@ -470,7 +453,7 @@ public class WebViewUpdateService extends SystemService {
    private PackageInfo findPreferredWebViewPackage() {
        WebViewProviderInfo[] providers = mCurrentValidWebViewPackages;

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

        // If the user has chosen provider, use that
        for (WebViewProviderInfo provider : providers) {
@@ -640,6 +623,11 @@ public class WebViewUpdateService extends SystemService {
            }
        }

        @Override // Binder call
        public WebViewProviderInfo[] getAllWebViewPackages() {
            return WebViewUpdateService.this.mWebViewUtility.getWebViewPackages();
        }

        @Override // Binder call
        public String getCurrentWebViewPackageName() {
            synchronized(WebViewUpdateService.this) {
@@ -651,7 +639,7 @@ public class WebViewUpdateService extends SystemService {

        @Override // Binder call
        public boolean isFallbackPackage(String packageName) {
            return WebViewUpdateService.isFallbackPackage(packageName);
            return WebViewUpdateService.this.isFallbackPackage(packageName);
        }

        @Override // Binder call
+161 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.XmlResourceParser;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactory.MissingWebViewPackageException;
import android.webkit.WebViewProviderInfo;

import com.android.internal.util.XmlUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParserException;

/**
 * Default implementation for the WebView preparation Utility interface.
 * @hide
 */
public class WebViewUtilityImpl implements WebViewUtilityInterface {
    private static final String TAG = WebViewUtilityImpl.class.getSimpleName();
    private static final String TAG_START = "webviewproviders";
    private static final String TAG_WEBVIEW_PROVIDER = "webviewprovider";
    private static final String TAG_PACKAGE_NAME = "packageName";
    private static final String TAG_DESCRIPTION = "description";
    // Whether or not the provider must be explicitly chosen by the user to be used.
    private static final String TAG_AVAILABILITY = "availableByDefault";
    private static final String TAG_SIGNATURE = "signature";
    private static final String TAG_FALLBACK = "isFallback";

    /**
     * Returns all packages declared in the framework resources as potential WebView providers.
     * @hide
     * */
    @Override
    public WebViewProviderInfo[] getWebViewPackages() {
        int numFallbackPackages = 0;
        XmlResourceParser parser = null;
        List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
        try {
            parser = AppGlobals.getInitialApplication().getResources().getXml(
                    com.android.internal.R.xml.config_webview_packages);
            XmlUtils.beginDocument(parser, TAG_START);
            while(true) {
                XmlUtils.nextElement(parser);
                String element = parser.getName();
                if (element == null) {
                    break;
                }
                if (element.equals(TAG_WEBVIEW_PROVIDER)) {
                    String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME);
                    if (packageName == null) {
                        throw new MissingWebViewPackageException(
                                "WebView provider in framework resources missing package name");
                    }
                    String description = parser.getAttributeValue(null, TAG_DESCRIPTION);
                    if (description == null) {
                        throw new MissingWebViewPackageException(
                                "WebView provider in framework resources missing description");
                    }
                    boolean availableByDefault = "true".equals(
                            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()) {
                        numFallbackPackages++;
                        if (numFallbackPackages > 1) {
                            throw new AndroidRuntimeException(
                                    "There can be at most one webview fallback package.");
                        }
                    }
                    webViewProviders.add(currentProvider);
                }
                else {
                    Log.e(TAG, "Found an element that is not a webview provider");
                }
            }
        } catch(XmlPullParserException e) {
            throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
        } catch(IOException e) {
            throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e);
        } finally {
            if (parser != null) parser.close();
        }
        return webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
    }

    /**
     * Reads all signatures at the current depth (within the current provider) from the XML parser.
     */
    private static String[] readSignatures(XmlResourceParser parser) throws IOException,
            XmlPullParserException {
        List<String> signatures = new ArrayList<String>();
        int outerDepth = parser.getDepth();
        while(XmlUtils.nextElementWithin(parser, outerDepth)) {
            if (parser.getName().equals(TAG_SIGNATURE)) {
                // Parse the value within the signature tag
                String signature = parser.nextText();
                signatures.add(signature);
            } else {
                Log.e(TAG, "Found an element in a webview provider that is not a signature");
            }
        }
        return signatures.toArray(new String[signatures.size()]);
    }

    @Override
    public int onWebViewProviderChanged(PackageInfo packageInfo) {
        return WebViewFactory.onWebViewProviderChanged(packageInfo);
    }

    @Override
    public String getUserChosenWebViewProvider(Context context) {
        return Settings.Global.getString(context.getContentResolver(),
                Settings.Global.WEBVIEW_PROVIDER);
    }

    @Override
    public void updateUserSetting(Context context, String newProviderName) {
        Settings.Global.putString(context.getContentResolver(),
                Settings.Global.WEBVIEW_PROVIDER,
                newProviderName == null ? "" : newProviderName);
    }

    @Override
    public void killPackageDependents(String packageName) {
        try {
            ActivityManagerNative.getDefault().killPackageDependents(packageName,
                    UserHandle.USER_ALL);
        } catch (RemoteException e) {
        }
    }
}
Loading