From 8b17926a78cc686504739730516c9b057d0672da Mon Sep 17 00:00:00 2001 From: Gustav Sennton Date: Mon, 14 Mar 2016 11:31:14 +0000 Subject: [PATCH] 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 --- .../android/webkit/IWebViewUpdateService.aidl | 5 + core/java/android/webkit/WebViewFactory.java | 91 +--------- .../android/webkit/WebViewProviderInfo.java | 4 + .../server/webkit/WebViewUpdateService.java | 62 +++---- .../server/webkit/WebViewUtilityImpl.java | 161 ++++++++++++++++++ .../webkit/WebViewUtilityInterface.java | 37 ++++ 6 files changed, 233 insertions(+), 127 deletions(-) create mode 100644 services/core/java/com/android/server/webkit/WebViewUtilityImpl.java create mode 100644 services/core/java/com/android/server/webkit/WebViewUtilityInterface.java diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl index 5697dfc0188c0..9434f0ccac4e7 100644 --- a/core/java/android/webkit/IWebViewUpdateService.aidl +++ b/core/java/android/webkit/IWebViewUpdateService.aidl @@ -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. */ diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 0751ab009c806..1580441281e0c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -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 signatures = new ArrayList(); - 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 webViewProviders = new ArrayList(); - 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; } /** diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index 64c2caa58fd5f..75ccf355ecd7f 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -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); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index 2db6b5d4bf904..181b807481578 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -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) { + if (oldPackage != null) { + 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 webViewProviders = - new ArrayList(Arrays.asList(WebViewFactory.getWebViewPackages())); + new ArrayList(Arrays.asList(mWebViewUtility.getWebViewPackages())); Iterator 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 diff --git a/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java b/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java new file mode 100644 index 0000000000000..4dbd02d1ede5e --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java @@ -0,0 +1,161 @@ +/* + * 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 webViewProviders = new ArrayList(); + 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 signatures = new ArrayList(); + 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) { + } + } +} diff --git a/services/core/java/com/android/server/webkit/WebViewUtilityInterface.java b/services/core/java/com/android/server/webkit/WebViewUtilityInterface.java new file mode 100644 index 0000000000000..1919f400c29ad --- /dev/null +++ b/services/core/java/com/android/server/webkit/WebViewUtilityInterface.java @@ -0,0 +1,37 @@ +/* + * 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.webkit.WebViewProviderInfo; +import android.content.Context; +import android.content.pm.PackageInfo; + +/** + * Utility interface for the WebViewUpdateService. + * This interface provides a way to test the WebView preparation mechanism - during normal use this + * interface is implemented using calls to the Android framework, but by providing an alternative + * implementation we can test the WebView preparation logic without reaching other framework code. + * @hide + */ +public interface WebViewUtilityInterface { + public WebViewProviderInfo[] getWebViewPackages(); + public int onWebViewProviderChanged(PackageInfo packageInfo); + + public String getUserChosenWebViewProvider(Context context); + public void updateUserSetting(Context context, String newProviderName); + public void killPackageDependents(String packageName); +}