From 364e16029017a4e16ed727a5e501f70363d04e5a Mon Sep 17 00:00:00 2001 From: Gustav Sennton Date: Wed, 14 Dec 2016 09:10:50 +0000 Subject: [PATCH] Change WebView fallback mechanism to consider all users' package states. MultiProcess WebView will use a Service to start a separate process (the renderer process). This Service can only be started if the WebView package is enabled for the current user. To ensure that the current WebView package is usable by all users on a single device we now only use a WebView package as WebView implementation if that package is enabled and installed for all user on the device. This also means that the WebView-fallback mechanism will trigger when disabling the primary WebView package for any user (not just the system user). Also add multi-user unit tests to cover this new change. Bug: 32894152 Test: run unit tests in WebViewUpdateServiceTest Test: ensure the standalone WebView package becomes enabled (for all device users) when disabling Chrome for a secondary user. Test: load WebView (both using Monochrome, and using the standalone WebView). Change-Id: Iad3fc48aa50273062c2f29ae48a343c2dea38116 --- core/java/android/webkit/UserPackage.java | 94 ++++++ .../com/android/server/webkit/SystemImpl.java | 7 + .../server/webkit/SystemInterface.java | 11 + .../server/webkit/WebViewUpdateService.java | 4 + .../webkit/WebViewUpdateServiceImpl.java | 221 ++++++------ .../android/server/webkit/TestSystemImpl.java | 75 ++++- .../webkit/WebViewUpdateServiceTest.java | 314 +++++++++++++----- 7 files changed, 537 insertions(+), 189 deletions(-) create mode 100644 core/java/android/webkit/UserPackage.java diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java new file mode 100644 index 0000000000000..404bcf453aaf9 --- /dev/null +++ b/core/java/android/webkit/UserPackage.java @@ -0,0 +1,94 @@ +/* + * 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 android.webkit; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.os.UserManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for storing a (user,PackageInfo) mapping. + * @hide + */ +public class UserPackage { + private final UserInfo mUserInfo; + private final PackageInfo mPackageInfo; + + public UserPackage(UserInfo user, PackageInfo packageInfo) { + this.mUserInfo = user; + this.mPackageInfo = packageInfo; + } + + /** + * Returns a list of (User,PackageInfo) pairs corresponding to the PackageInfos for all + * device users for the package named {@param packageName}. + */ + public static List getPackageInfosAllUsers(Context context, + String packageName, int packageFlags) { + List users = getAllUsers(context); + List userPackages = new ArrayList(users.size()); + for (UserInfo user : users) { + PackageInfo packageInfo = null; + try { + packageInfo = context.getPackageManager().getPackageInfoAsUser( + packageName, packageFlags, user.id); + } catch (NameNotFoundException e) { + } + userPackages.add(new UserPackage(user, packageInfo)); + } + return userPackages; + } + + /** + * Returns whether the given package is enabled. + * This state can be changed by the user from Settings->Apps + */ + public boolean isEnabledPackage() { + if (mPackageInfo == null) return false; + return mPackageInfo.applicationInfo.enabled; + } + + /** + * Return true if the package is installed and not hidden + */ + public boolean isInstalledPackage() { + if (mPackageInfo == null) return false; + return (((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0) + && ((mPackageInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0)); + } + + public UserInfo getUserInfo() { + return mUserInfo; + } + + public PackageInfo getPackageInfo() { + return mPackageInfo; + } + + + private static List getAllUsers(Context context) { + UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + return userManager.getUsers(false); + } + +} diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java index 02b46ecfc258e..4fd51b2274dfd 100644 --- a/services/core/java/com/android/server/webkit/SystemImpl.java +++ b/services/core/java/com/android/server/webkit/SystemImpl.java @@ -35,6 +35,7 @@ import android.provider.Settings.Global; import android.provider.Settings; import android.util.AndroidRuntimeException; import android.util.Log; +import android.webkit.UserPackage; import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewZygote; @@ -270,6 +271,12 @@ public class SystemImpl implements SystemInterface { return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS); } + @Override + public List getPackageInfoForProviderAllUsers(Context context, + WebViewProviderInfo configInfo) { + return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS); + } + @Override public int getMultiProcessSetting(Context context) { return Settings.Global.getInt(context.getContentResolver(), diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java index fd137eb245d47..b06f829712476 100644 --- a/services/core/java/com/android/server/webkit/SystemInterface.java +++ b/services/core/java/com/android/server/webkit/SystemInterface.java @@ -20,8 +20,11 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; +import android.webkit.UserPackage; import android.webkit.WebViewProviderInfo; +import java.util.List; + /** * System interface for the WebViewUpdateService. * This interface provides a way to test the WebView preparation mechanism - during normal use this @@ -49,6 +52,14 @@ public interface SystemInterface { public boolean systemIsDebuggable(); public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo) throws NameNotFoundException; + /** + * Get the PackageInfos of all users for the package represented by {@param configInfo}. + * @return an array of UserPackages for a certain package, each UserPackage being belonging to a + * certain user. The returned array can contain null PackageInfos if the given package + * is uninstalled for some user. + */ + public List getPackageInfoForProviderAllUsers(Context context, + WebViewProviderInfo configInfo); public int getMultiProcessSetting(Context context); public void setMultiProcessSetting(Context context, int value); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index 311570e893cff..4a105e18e9bae 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -94,6 +94,9 @@ public class WebViewUpdateService extends SystemService { case Intent.ACTION_USER_ADDED: mImpl.handleNewUser(userId); break; + case Intent.ACTION_USER_REMOVED: + mImpl.handleUserRemoved(userId); + break; } } }; @@ -112,6 +115,7 @@ public class WebViewUpdateService extends SystemService { IntentFilter userAddedFilter = new IntentFilter(); userAddedFilter.addAction(Intent.ACTION_USER_ADDED); + userAddedFilter.addAction(Intent.ACTION_USER_REMOVED); getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, userAddedFilter, null /* broadcast permission */, null /* handler */); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index edfb11c6634ba..f016830a26735 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -24,6 +24,7 @@ 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; @@ -100,32 +101,41 @@ public class WebViewUpdateServiceImpl { private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) { for (WebViewProviderInfo provider : providers) { if (provider.availableByDefault && !provider.isFallback) { - try { - PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider); - if (isInstalledPackage(packageInfo) && isEnabledPackage(packageInfo) - && mWebViewUpdater.isValidProvider(provider, packageInfo)) { - return true; - } - } catch (NameNotFoundException e) { - // A non-existent provider is neither valid nor enabled + // userPackages can contain null objects. + List userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider); + if (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. + mWebViewUpdater.isValidProvider(provider, + userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo())) { + return true; } } } return false; } - /** - * Called when a new user has been added to update the state of its fallback package. - */ void handleNewUser(int userId) { - if (!mSystemInterface.isFallbackLogicEnabled()) return; + handleUserChange(); + } - WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); - WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); - if (fallbackProvider == null) return; + void handleUserRemoved(int userId) { + handleUserChange(); + } - mSystemInterface.enablePackageForUser(fallbackProvider.packageName, - !existsValidNonFallbackProvider(webviewProviders), userId); + /** + * Called when a user was added or removed to ensure fallback logic and WebView preparation are + * triggered. This has to be done since the WebView package we use depends on the enabled-state + * of packages for all users (so adding or removing a user might cause us to change package). + */ + private void handleUserChange() { + if (mSystemInterface.isFallbackLogicEnabled()) { + updateFallbackState(mSystemInterface.getWebViewPackages()); + } + // Potentially trigger package-changing logic. + mWebViewUpdater.updateCurrentWebViewPackage(null); } void notifyRelroCreationCompleted() { @@ -141,7 +151,7 @@ public class WebViewUpdateServiceImpl { } WebViewProviderInfo[] getValidWebViewPackages() { - return mWebViewUpdater.getValidAndInstalledWebViewPackages(); + return mWebViewUpdater.getValidWebViewPackages(); } WebViewProviderInfo[] getWebViewPackages() { @@ -160,7 +170,7 @@ public class WebViewUpdateServiceImpl { if (!mSystemInterface.isFallbackLogicEnabled()) return; WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); - updateFallbackState(webviewProviders, true); + updateFallbackState(webviewProviders); } /** @@ -185,35 +195,23 @@ public class WebViewUpdateServiceImpl { } } if (!changedPackageAvailableByDefault) return; - updateFallbackState(webviewProviders, false); + updateFallbackState(webviewProviders); } - private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) { + private void updateFallbackState(WebViewProviderInfo[] webviewProviders) { // If there exists a valid and enabled non-fallback package - disable the fallback // package, otherwise, enable it. WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); if (fallbackProvider == null) return; boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders); - boolean isFallbackEnabled = false; - try { - isFallbackEnabled = isEnabledPackage( - mSystemInterface.getPackageInfoForProvider(fallbackProvider)); - } catch (NameNotFoundException e) { - // No fallback package installed -> early out. - return; - } - - 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. - && (isFallbackEnabled || isBoot)) { + List userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers(mContext, fallbackProvider); + if (existsValidNonFallbackProvider && !isDisabledForAllUsers(userPackages)) { mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext, fallbackProvider.packageName); } 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. - && (!isFallbackEnabled || isBoot)) { + && !isInstalledAndEnabledForAllUsers(userPackages)) { // Enable the fallback package for all users. mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, true); @@ -376,38 +374,8 @@ public class WebViewUpdateServiceImpl { * or the replacement are done). */ public String changeProviderAndSetting(String newProviderName) { - PackageInfo oldPackage = null; - PackageInfo newPackage = null; - boolean providerChanged = false; - synchronized(mLock) { - oldPackage = mCurrentWebViewPackage; - mSystemInterface.updateUserSetting(mContext, newProviderName); - - try { - newPackage = findPreferredWebViewPackage(); - providerChanged = (oldPackage == null) - || !newPackage.packageName.equals(oldPackage.packageName); - } catch (WebViewPackageMissingException e) { - mCurrentWebViewPackage = null; - Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " + - "package " + e); - // If we don't perform the user change but don't have an installed WebView - // package, we will have changed the setting and it will be used when a package - // is available. - return ""; - } - // 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 + PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName); + if (newPackage == null) return ""; return newPackage.packageName; } @@ -437,15 +405,14 @@ public class WebViewUpdateServiceImpl { } } - private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) { + private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); List providers = new ArrayList<>(); for(int n = 0; n < allProviders.length; n++) { try { PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(allProviders[n]); - if ((!onlyInstalled || isInstalledPackage(packageInfo)) - && isValidProvider(allProviders[n], packageInfo)) { + if (isValidProvider(allProviders[n], packageInfo)) { providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); } } catch (NameNotFoundException e) { @@ -458,9 +425,8 @@ public class WebViewUpdateServiceImpl { /** * Fetch only the currently valid WebView packages. **/ - public WebViewProviderInfo[] getValidAndInstalledWebViewPackages() { - ProviderAndPackageInfo[] providersAndPackageInfos = - getValidWebViewPackagesAndInfos(true /* only fetch installed packages */); + public WebViewProviderInfo[] getValidWebViewPackages() { + ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length]; for(int n = 0; n < providersAndPackageInfos.length; n++) { @@ -487,39 +453,49 @@ public class WebViewUpdateServiceImpl { * */ private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException { - ProviderAndPackageInfo[] providers = - getValidWebViewPackagesAndInfos(false /* onlyInstalled */); + ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); - // If the user has chosen provider, use that + // 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) - && isInstalledPackage(providerAndPackage.packageInfo) - && isEnabledPackage(providerAndPackage.packageInfo)) { - return providerAndPackage.packageInfo; + if (providerAndPackage.provider.packageName.equals(userChosenProvider)) { + // userPackages can contain null objects. + List 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 the device owner, and available by default (not through + // installed and enabled for all users, and available by default (not through // user choice). for (ProviderAndPackageInfo providerAndPackage : providers) { - if (providerAndPackage.provider.availableByDefault - && isInstalledPackage(providerAndPackage.packageInfo) - && isEnabledPackage(providerAndPackage.packageInfo)) { - return providerAndPackage.packageInfo; + if (providerAndPackage.provider.availableByDefault) { + // userPackages can contain null objects. + List 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"); } @@ -702,6 +678,48 @@ public class WebViewUpdateServiceImpl { mAnyWebViewInstalled)); } } + + /** + * 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, @@ -730,20 +748,27 @@ public class WebViewUpdateServiceImpl { } /** - * Returns whether the given package is enabled. - * This state can be changed by the user from Settings->Apps + * 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 isEnabledPackage(PackageInfo packageInfo) { - return packageInfo.applicationInfo.enabled; + private static boolean isInstalledAndEnabledForAllUsers( + List userPackages) { + for (UserPackage userPackage : userPackages) { + if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) { + return false; + } + } + return true; } - /** - * Return true if the package is installed and not hidden - */ - private static boolean isInstalledPackage(PackageInfo packageInfo) { - return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0) - && ((packageInfo.applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0)); + private static boolean isDisabledForAllUsers(List userPackages) { + for (UserPackage userPackage : userPackages) { + if (userPackage.getPackageInfo() != null && userPackage.isEnabledPackage()) { + return false; + } + } + return true; } /** diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java index 83a61ca99d513..67e78d4a2108e 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -19,25 +19,39 @@ package com.android.server.webkit; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.webkit.UserPackage; import android.webkit.WebViewProviderInfo; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; public class TestSystemImpl implements SystemInterface { private String mUserProvider = null; private final WebViewProviderInfo[] mPackageConfigs; - HashMap mPackages = new HashMap(); + List mUsers = new ArrayList<>(); + // Package -> [user, package] + Map> mPackages = new HashMap(); private boolean mFallbackLogicEnabled; private final int mNumRelros; private final boolean mIsDebuggable; private int mMultiProcessSetting; + public static final int PRIMARY_USER_ID = 0; + public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable) { mPackageConfigs = packageConfigs; mFallbackLogicEnabled = fallbackLogicEnabled; mNumRelros = numRelros; mIsDebuggable = isDebuggable; + mUsers.add(PRIMARY_USER_ID); + } + + public void addUser(int userId) { + mUsers.add(userId); } @Override @@ -78,17 +92,20 @@ public class TestSystemImpl implements SystemInterface { @Override public void enablePackageForAllUsers(Context context, String packageName, boolean enable) { - enablePackageForUser(packageName, enable, 0); + for(int userId : mUsers) { + enablePackageForUser(packageName, enable, userId); + } } @Override public void enablePackageForUser(String packageName, boolean enable, int userId) { - PackageInfo packageInfo = mPackages.get(packageName); - if (packageInfo == null) { + Map userPackages = mPackages.get(packageName); + if (userPackages == null) { throw new IllegalArgumentException("There is no package called " + packageName); } + PackageInfo packageInfo = userPackages.get(userId); packageInfo.applicationInfo.enabled = enable; - setPackageInfo(packageInfo); + setPackageInfoForUser(userId, packageInfo); } @Override @@ -97,23 +114,61 @@ public class TestSystemImpl implements SystemInterface { @Override public PackageInfo getPackageInfoForProvider(WebViewProviderInfo info) throws NameNotFoundException { - PackageInfo ret = mPackages.get(info.packageName); + Map userPackages = mPackages.get(info.packageName); + if (userPackages == null) throw new NameNotFoundException(info.packageName); + PackageInfo ret = userPackages.get(PRIMARY_USER_ID); if (ret == null) throw new NameNotFoundException(info.packageName); return ret; } - public void setPackageInfo(PackageInfo pi) { - mPackages.put(pi.packageName, pi); + @Override + public List getPackageInfoForProviderAllUsers( + Context context, WebViewProviderInfo info) { + Map userPackages = mPackages.get(info.packageName); + List ret = new ArrayList(); + // Loop over defined users, and find the corresponding package for each user. + for (int userId : mUsers) { + ret.add(new UserPackage(createUserInfo(userId), + userPackages == null ? null : userPackages.get(userId))); + } + return ret; } + private static UserInfo createUserInfo(int userId) { + return new UserInfo(userId, "User nr. " + userId, 0 /* flags */); + } + + /** + * Set package for primary user. + */ + public void setPackageInfo(PackageInfo pi) { + setPackageInfoForUser(PRIMARY_USER_ID, pi); + } + + public void setPackageInfoForUser(int userId, PackageInfo pi) { + if (!mUsers.contains(userId)) { + throw new IllegalArgumentException("User nr. " + userId + " doesn't exist"); + } + if (!mPackages.containsKey(pi.packageName)) { + mPackages.put(pi.packageName, new HashMap()); + } + mPackages.get(pi.packageName).put(userId, pi); + } + + /** + * Removes the package {@param packageName} for the primary user. + */ public void removePackageInfo(String packageName) { - mPackages.remove(packageName); + mPackages.get(packageName).remove(PRIMARY_USER_ID); } @Override public int getFactoryPackageVersion(String packageName) throws NameNotFoundException { PackageInfo pi = null; - pi = mPackages.get(packageName); + Map userPackages = mPackages.get(packageName); + if (userPackages == null) throw new NameNotFoundException(); + + pi = userPackages.get(PRIMARY_USER_ID); if (pi != null && pi.applicationInfo.isSystemApp()) { return pi.applicationInfo.versionCode; } diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index 05194488b08a1..33cedfa41e517 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -92,9 +92,15 @@ public class WebViewUpdateServiceTest { } private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { + // Set package infos for the primary user (user 0). + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, providers); + } + + private void setEnabledAndValidPackageInfosForUser(int userId, + WebViewProviderInfo[] providers) { for(WebViewProviderInfo wpi : providers) { - mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */, - true /* valid */, true /* installed */)); + mTestSystemImpl.setPackageInfoForUser(userId, createPackageInfo(wpi.packageName, + true /* enabled */, true /* valid */, true /* installed */)); } } @@ -335,7 +341,7 @@ public class WebViewUpdateServiceTest { setEnabledAndValidPackageInfos(packages); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, - WebViewUpdateService.PACKAGE_ADDED, 0); + WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(singlePackage, 1 /* number of finished preparations */); assertEquals(singlePackage, @@ -344,7 +350,7 @@ public class WebViewUpdateServiceTest { // Remove the package again mTestSystemImpl.removePackageInfo(singlePackage); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, - WebViewUpdateService.PACKAGE_ADDED, 0); + WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); // Package removed - ensure our interface states that there is no package response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); @@ -374,7 +380,7 @@ public class WebViewUpdateServiceTest { createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(wpi.packageName, 1); } @@ -429,16 +435,8 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo(firstPackage, "", true, false, null), new WebViewProviderInfo(secondPackage, "", true, false, null)}; setupWithPackages(packages); - if (settingsChange) { - // Have all packages be enabled, so that we can change provider however we want to - setEnabledAndValidPackageInfos(packages); - } else { - // Have all packages be disabled so that we can change one to enabled later - for(WebViewProviderInfo wpi : packages) { - mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, - false /* enabled */, true /* valid */, true /* installed */)); - } - } + // Have all packages be enabled, so that we can change provider however we want to + setEnabledAndValidPackageInfos(packages); CountDownLatch countdown = new CountDownLatch(1); @@ -457,8 +455,12 @@ public class WebViewUpdateServiceTest { mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status); assertEquals(secondPackage, threadResponse.packageInfo.packageName); - // Verify that we killed the first package - Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); + // Verify that we killed the first package if we performed a settings change - + // otherwise we had to disable the first package, in which case its dependents + // should have been killed by the framework. + if (settingsChange) { + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); + } countdown.countDown(); } }).start(); @@ -470,11 +472,21 @@ public class WebViewUpdateServiceTest { if (settingsChange) { mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); } else { - // Switch provider by enabling the second one + // Enable the second provider mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - secondPackage, WebViewUpdateService.PACKAGE_CHANGED, 0); + secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); + + // Ensure we haven't changed package yet. + assertEquals(firstPackage, + mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); + + // Switch provider by disabling the first one + mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */, + true /* valid */, true /* installed */)); + mWebViewUpdateServiceImpl.packageStateChanged( + firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); } mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); // first package done, should start on second @@ -528,7 +540,7 @@ public class WebViewUpdateServiceTest { mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, 0); + fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); if (fallbackLogicEnabled) { // Check that we have now disabled the fallback package twice @@ -573,7 +585,7 @@ public class WebViewUpdateServiceTest { createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); // Verify fallback disabled, primary package used as provider, and fallback package killed Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( @@ -583,7 +595,31 @@ public class WebViewUpdateServiceTest { } @Test - public void testFallbackChangesEnabledState() { + public void testFallbackChangesEnabledStateSingleUser() { + for (PackageRemovalType removalType : REMOVAL_TYPES) { + checkFallbackChangesEnabledState(false /* multiUser */, removalType); + } + } + + @Test + public void testFallbackChangesEnabledStateMultiUser() { + for (PackageRemovalType removalType : REMOVAL_TYPES) { + checkFallbackChangesEnabledState(true /* multiUser */, removalType); + } + } + + /** + * Represents how to remove a package during a tests (disabling it / uninstalling it / hiding + * it). + */ + private enum PackageRemovalType { + UNINSTALL, DISABLE, HIDE + } + + private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants(); + + public void checkFallbackChangesEnabledState(boolean multiUser, + PackageRemovalType removalType) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { @@ -592,46 +628,68 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallbackLogicEnabled */); - setEnabledAndValidPackageInfos(packages); + int secondaryUserId = 10; + int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID; + if (multiUser) { + mTestSystemImpl.addUser(secondaryUserId); + setEnabledAndValidPackageInfosForUser(secondaryUserId, packages); + } + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); runWebViewBootPreparationOnMainSync(); // Verify fallback disabled at boot when primary package enabled - Mockito.verify(mTestSystemImpl).enablePackageForUser( - Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, - Matchers.anyInt()); + checkEnablePackageForUserCalled(fallbackPackage, false, multiUser + ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} + : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */); checkPreparationPhasesForPackage(primaryPackage, 1); + boolean enabled = !(removalType == PackageRemovalType.DISABLE); + boolean installed = !(removalType == PackageRemovalType.UNINSTALL); + boolean hidden = (removalType == PackageRemovalType.HIDE); // Disable primary package and ensure fallback becomes enabled and used - mTestSystemImpl.setPackageInfo( - createPackageInfo(primaryPackage, false /* enabled */, true /* valid */, - true /* installed */)); + mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, + createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */, + installed /* installed */, null /* signature */, 0 /* updateTime */, + hidden /* hidden */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_CHANGED, 0); + removalType == PackageRemovalType.DISABLE + ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED, + userIdToChangePackageFor); // USER ID - Mockito.verify(mTestSystemImpl).enablePackageForUser( - Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, - Matchers.anyInt()); + checkEnablePackageForUserCalled(fallbackPackage, true, multiUser + ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} + : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */); checkPreparationPhasesForPackage(fallbackPackage, 1); // Again enable primary package and verify primary is used and fallback becomes disabled - mTestSystemImpl.setPackageInfo( + mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_CHANGED, 0); + removalType == PackageRemovalType.DISABLE + ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED, + userIdToChangePackageFor); // Verify fallback is disabled a second time when primary package becomes enabled - Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser( - Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, - Matchers.anyInt()); + checkEnablePackageForUserCalled(fallbackPackage, false, multiUser + ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} + : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 2 /* numUsages */); checkPreparationPhasesForPackage(primaryPackage, 2); } + private void checkEnablePackageForUserCalled(String packageName, boolean expectEnabled, + int[] userIds, int numUsages) { + for (int userId : userIds) { + Mockito.verify(mTestSystemImpl, Mockito.times(numUsages)).enablePackageForUser( + Mockito.eq(packageName), Mockito.eq(expectEnabled), Mockito.eq(userId)); + } + } + @Test public void testAddUserWhenFallbackLogicEnabled() { checkAddingNewUser(true); @@ -651,8 +709,10 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, fallbackLogicEnabled); - setEnabledAndValidPackageInfos(packages); + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); int newUser = 100; + mTestSystemImpl.addUser(newUser); + setEnabledAndValidPackageInfosForUser(newUser, packages); mWebViewUpdateServiceImpl.handleNewUser(newUser); if (fallbackLogicEnabled) { // Verify fallback package becomes disabled for new user @@ -667,6 +727,42 @@ public class WebViewUpdateServiceTest { } } + /** + * Ensures that adding a new user for which the current WebView package is uninstalled causes a + * change of WebView provider. + */ + @Test + public void testAddingNewUserWithUninstalledPackage() { + String primaryPackage = "primary"; + String fallbackPackage = "fallback"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo( + primaryPackage, "", true /* default available */, false /* fallback */, null), + new WebViewProviderInfo( + fallbackPackage, "", true /* default available */, true /* fallback */, null)}; + setupWithPackages(packages, true /* fallbackLogicEnabled */); + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); + int newUser = 100; + mTestSystemImpl.addUser(newUser); + // Let the primary package be uninstalled for the new user + mTestSystemImpl.setPackageInfoForUser(newUser, + createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, + false /* installed */)); + mTestSystemImpl.setPackageInfoForUser(newUser, + createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */, + true /* installed */)); + mWebViewUpdateServiceImpl.handleNewUser(newUser); + // Verify fallback package doesn't become disabled for the primary user. + Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser( + Mockito.anyObject(), Mockito.eq(false) /* enable */, + Mockito.eq(TestSystemImpl.PRIMARY_USER_ID) /* user */); + // Verify that we enable the fallback package for the secondary user. + Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, + Mockito.eq(newUser) /* user */); + checkPreparationPhasesForPackage(fallbackPackage, 1 /* numRelros */); + } + /** * Timing dependent test where we verify that the list of valid webview packages becoming empty * at a certain point doesn't crash us or break our state. @@ -713,7 +809,7 @@ public class WebViewUpdateServiceTest { 1 /* updateTime */ )); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); // Ensure we use firstPackage checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */); @@ -742,14 +838,14 @@ public class WebViewUpdateServiceTest { mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED, 0); + WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(firstPackage, 2 /* second time for this package */); // Now make the second package valid again and verify that it is used again mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED, 0); + WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */); } @@ -820,7 +916,7 @@ public class WebViewUpdateServiceTest { mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); } else { mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); } WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); @@ -831,7 +927,7 @@ public class WebViewUpdateServiceTest { true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(secondPackage, 1); @@ -863,11 +959,11 @@ public class WebViewUpdateServiceTest { createPackageInfo(firstPackage, true /* enabled */, false /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); } else { mTestSystemImpl.removePackageInfo(firstPackage); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_REMOVED, 0); + WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID); } checkPreparationPhasesForPackage(secondPackage, 1); @@ -1098,8 +1194,10 @@ public class WebViewUpdateServiceTest { } } - // Ensure that the update service uses an uninstalled package if that is the only package - // available. + /** + * Ensure that the update service does use an uninstalled package when that is the only + * package available. + */ @Test public void testWithSingleUninstalledPackage() { String testPackageName = "test.package.name"; @@ -1113,21 +1211,32 @@ public class WebViewUpdateServiceTest { runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */); + // TODO(gsennton) change this logic to use the code below when we have created a functional + // stub. + //Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( + // Matchers.anyObject()); + //WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + //assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + //assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); } @Test public void testNonhiddenPackageUserOverHidden() { - checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */); + checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE); + checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.HIDE); } @Test public void testInstalledPackageUsedOverUninstalled() { - checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */); + checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.UNINSTALL); + checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.UNINSTALL); } - private void checkVisiblePackageUserOverNonVisible(boolean uninstalledNotHidden) { - boolean testUninstalled = uninstalledNotHidden; - boolean testHidden = !uninstalledNotHidden; + private void checkVisiblePackageUserOverNonVisible(boolean multiUser, + PackageRemovalType removalType) { + assert removalType != PackageRemovalType.DISABLE; + boolean testUninstalled = removalType == PackageRemovalType.UNINSTALL; + boolean testHidden = removalType == PackageRemovalType.HIDE; String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { @@ -1137,11 +1246,25 @@ public class WebViewUpdateServiceTest { false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); - mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */, - true /* valid */, true /* installed */)); - mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, + int secondaryUserId = 5; + if (multiUser) { + mTestSystemImpl.addUser(secondaryUserId); + // Install all packages for the primary user. + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo( + installedPackage, true /* enabled */, true /* valid */, true /* installed */)); + // Hide or uninstall the primary package for the second user + mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, (testUninstalled ? false : true) /* installed */, null /* signatures */, 0 /* updateTime */, (testHidden ? true : false))); + } else { + mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */, + true /* valid */, true /* installed */)); + // Hide or uninstall the primary package + mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, + true /* valid */, (testUninstalled ? false : true) /* installed */, + null /* signatures */, 0 /* updateTime */, (testHidden ? true : false))); + } runWebViewBootPreparationOnMainSync(); @@ -1160,9 +1283,7 @@ public class WebViewUpdateServiceTest { } /** - * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen, - * and that an uninstalled (or hidden) package is not considered valid (in the - * getValidWebViewPackages() API). + * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen. */ private void checkCantSwitchToNonVisiblePackage(boolean uninstalledNotHidden) { boolean testUninstalled = uninstalledNotHidden; @@ -1176,27 +1297,31 @@ public class WebViewUpdateServiceTest { false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); - mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */, - true /* valid */, true /* installed */)); - mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, - true /* valid */, (testUninstalled ? false : true) /* installed */, - null /* signatures */, 0 /* updateTime */, - (testHidden ? true : false) /* hidden */)); + int secondaryUserId = 412; + mTestSystemImpl.addUser(secondaryUserId); + + // Let all packages be installed and enabled for the primary user. + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); + // Only uninstall the 'uninstalled package' for the secondary user. + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage, + true /* enabled */, true /* valid */, true /* installed */)); + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage, + true /* enabled */, true /* valid */, !testUninstalled /* installed */, + null /* signatures */, 0 /* updateTime */, testHidden /* hidden */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); - // Ensure that only the installed package is considered valid - WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages(); - assertEquals(1, validPackages.length); - assertEquals(installedPackage, validPackages[0].packageName); - // ensure that we don't switch to the uninstalled package (it will be used if it becomes // installed later) assertEquals(installedPackage, mWebViewUpdateServiceImpl.changeProviderAndSetting(uninstalledPackage)); + // Ensure both packages are considered valid. + assertEquals(2, mWebViewUpdateServiceImpl.getValidWebViewPackages().length); + + // We should only have called onWebViewProviderChanged once (before calling // changeProviderAndSetting Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged( @@ -1227,12 +1352,16 @@ public class WebViewUpdateServiceTest { false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); - mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */, - true /* valid */, true /* installed */)); - mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, - true /* valid */, (testUninstalled ? false : true) /* installed */, - null /* signatures */, 0 /* updateTime */, - (testHidden ? true : false) /* hidden */)); + int secondaryUserId = 4; + mTestSystemImpl.addUser(secondaryUserId); + + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage, + true /* enabled */, true /* valid */, true /* installed */)); + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage, + true /* enabled */, true /* valid */, + (testUninstalled ? false : true) /* installed */, null /* signatures */, + 0 /* updateTime */, (testHidden ? true : false) /* hidden */)); // Start with the setting pointing to the uninstalled package mTestSystemImpl.updateUserSetting(null, uninstalledPackage); @@ -1242,12 +1371,21 @@ public class WebViewUpdateServiceTest { checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); } + @Test + public void testFallbackEnabledIfPrimaryUninstalledSingleUser() { + checkFallbackEnabledIfPrimaryUninstalled(false /* multiUser */); + } + + @Test + public void testFallbackEnabledIfPrimaryUninstalledMultiUser() { + checkFallbackEnabledIfPrimaryUninstalled(true /* multiUser */); + } + /** - * Ensures that fallback becomes enabled if the primary package is uninstalled for the current + * Ensures that fallback becomes enabled at boot if the primary package is uninstalled for some * user. */ - @Test - public void testFallbackEnabledIfPrimaryUninstalled() { + private void checkFallbackEnabledIfPrimaryUninstalled(boolean multiUser) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { @@ -1256,10 +1394,24 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallback logic enabled */); - mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, + int secondaryUserId = 5; + if (multiUser) { + mTestSystemImpl.addUser(secondaryUserId); + // Install all packages for the primary user. + setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); + // Only install fallback package for secondary user. + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, + createPackageInfo(primaryPackage, true /* enabled */, + true /* valid */, false /* installed */)); + mTestSystemImpl.setPackageInfoForUser(secondaryUserId, + createPackageInfo(fallbackPackage, false /* enabled */, + true /* valid */, true /* installed */)); + } else { + mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, false /* installed */)); - mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, + mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */, true /* installed */)); + } runWebViewBootPreparationOnMainSync(); // Verify that we enable the fallback package