diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java index 361f0d47fc61a..bb76449a85290 100644 --- a/services/core/java/com/android/server/webkit/SystemImpl.java +++ b/services/core/java/com/android/server/webkit/SystemImpl.java @@ -270,5 +270,6 @@ public class SystemImpl implements SystemInterface { // flags declaring we want extra info from the package manager for webview providers private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA - | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING; + | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING + | PackageManager.MATCH_UNINSTALLED_PACKAGES; } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index 9b971e0557f84..846169cbf9c30 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -63,6 +63,7 @@ public class WebViewUpdateService extends SystemService { mWebViewUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); switch (intent.getAction()) { case Intent.ACTION_PACKAGE_REMOVED: // When a package is replaced we will receive two intents, one @@ -73,24 +74,22 @@ public class WebViewUpdateService extends SystemService { // run the update-logic twice. if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return; mImpl.packageStateChanged(packageNameFromIntent(intent), - PACKAGE_REMOVED); + PACKAGE_REMOVED, userId); break; case Intent.ACTION_PACKAGE_CHANGED: // Ensure that we only heed PACKAGE_CHANGED intents if they change an // entire package, not just a component if (entirePackageChanged(intent)) { mImpl.packageStateChanged(packageNameFromIntent(intent), - PACKAGE_CHANGED); + PACKAGE_CHANGED, userId); } break; case Intent.ACTION_PACKAGE_ADDED: mImpl.packageStateChanged(packageNameFromIntent(intent), (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING) - ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED)); + ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED), userId); break; case Intent.ACTION_USER_ADDED: - int userId = - intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); mImpl.handleNewUser(userId); break; } @@ -105,11 +104,14 @@ public class WebViewUpdateService extends SystemService { for (WebViewProviderInfo provider : mImpl.getWebViewPackages()) { filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL); } - getContext().registerReceiver(mWebViewUpdatedReceiver, filter); + + getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, filter, + null /* broadcast permission */, null /* handler */); IntentFilter userAddedFilter = new IntentFilter(); userAddedFilter.addAction(Intent.ACTION_USER_ADDED); - getContext().registerReceiver(mWebViewUpdatedReceiver, userAddedFilter); + getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, + userAddedFilter, null /* broadcast permission */, null /* handler */); publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index ecab009ed12c4..2cf17229a5bf5 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -20,6 +20,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; +import android.os.UserHandle; import android.util.Base64; import android.util.Slog; import android.webkit.WebViewFactory; @@ -49,7 +50,10 @@ public class WebViewUpdateServiceImpl { mWebViewUpdater = new WebViewUpdater(mContext, mSystemInterface); } - void packageStateChanged(String packageName, int changedState) { + void packageStateChanged(String packageName, int changedState, int userId) { + // We don't early out here in different cases where we could potentially early-out (e.g. if + // we receive PACKAGE_CHANGED for another user than the system user) since that would + // complicate this logic further and open up for more edge cases. updateFallbackStateOnPackageChange(packageName, changedState); mWebViewUpdater.packageStateChanged(packageName, changedState); } @@ -64,7 +68,7 @@ public class WebViewUpdateServiceImpl { if (provider.availableByDefault && !provider.isFallback) { try { PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider); - if (isEnabledPackage(packageInfo) + if (isInstalledPackage(packageInfo) && isEnabledPackage(packageInfo) && mWebViewUpdater.isValidProvider(provider, packageInfo)) { return true; } @@ -103,7 +107,7 @@ public class WebViewUpdateServiceImpl { } WebViewProviderInfo[] getValidWebViewPackages() { - return mWebViewUpdater.getValidWebViewPackages(); + return mWebViewUpdater.getValidAndInstalledWebViewPackages(); } WebViewProviderInfo[] getWebViewPackages() { @@ -254,6 +258,12 @@ public class WebViewUpdateServiceImpl { // (not if it has been enabled/disabled). return; } + if (newPackage.packageName.equals(oldProviderName) + && (newPackage.lastUpdateTime + == mCurrentWebViewPackage.lastUpdateTime)) { + // If the chosen package hasn't been updated, then early-out + return; + } } // Only trigger update actions if the updated package is the one // that will be used, or the one that was in use before the @@ -373,14 +383,15 @@ public class WebViewUpdateServiceImpl { } } - private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { + private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) { WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages(); List providers = new ArrayList<>(); for(int n = 0; n < allProviders.length; n++) { try { PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(allProviders[n]); - if (isValidProvider(allProviders[n], packageInfo)) { + if ((!onlyInstalled || isInstalledPackage(packageInfo)) + && isValidProvider(allProviders[n], packageInfo)) { providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); } } catch (NameNotFoundException e) { @@ -393,8 +404,9 @@ public class WebViewUpdateServiceImpl { /** * Fetch only the currently valid WebView packages. **/ - public WebViewProviderInfo[] getValidWebViewPackages() { - ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); + public WebViewProviderInfo[] getValidAndInstalledWebViewPackages() { + ProviderAndPackageInfo[] providersAndPackageInfos = + getValidWebViewPackagesAndInfos(true /* only fetch installed packages */); WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length]; for(int n = 0; n < providersAndPackageInfos.length; n++) { @@ -421,29 +433,33 @@ public class WebViewUpdateServiceImpl { * */ private PackageInfo findPreferredWebViewPackage() { - ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); + ProviderAndPackageInfo[] providers = + getValidWebViewPackagesAndInfos(false /* onlyInstalled */); String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); // If the user has chosen provider, use that for (ProviderAndPackageInfo providerAndPackage : providers) { if (providerAndPackage.provider.packageName.equals(userChosenProvider) + && isInstalledPackage(providerAndPackage.packageInfo) && isEnabledPackage(providerAndPackage.packageInfo)) { return providerAndPackage.packageInfo; } } // User did not choose, or the choice failed; use the most stable provider that is - // enabled and available by default (not through user choice). + // installed and enabled for the device owner, 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; } } - // Could not find any enabled package either, use the most stable and default-available - // provider. + // Could not find any installed and enabled package either, use the most stable and + // default-available provider. for (ProviderAndPackageInfo providerAndPackage : providers) { if (providerAndPackage.provider.availableByDefault) { return providerAndPackage.packageInfo; @@ -642,4 +658,13 @@ public class WebViewUpdateServiceImpl { return packageInfo.applicationInfo.enabled; } + /** + * 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)); + } + } 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 c03324aa9e325..b7370331ad0ef 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -86,7 +86,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { for(WebViewProviderInfo wpi : providers) { mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */, - true /* valid */)); + true /* valid */, true /* installed */)); } } @@ -137,12 +137,17 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { } private static PackageInfo createPackageInfo( - String packageName, boolean enabled, boolean valid) { + String packageName, boolean enabled, boolean valid, boolean installed) { PackageInfo p = new PackageInfo(); p.packageName = packageName; p.applicationInfo = new ApplicationInfo(); p.applicationInfo.enabled = enabled; p.applicationInfo.metaData = new Bundle(); + if (installed) { + p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; + } else { + p.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; + } if (valid) { // no flag means invalid p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah"); @@ -150,10 +155,23 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { return p; } - private static PackageInfo createPackageInfo( - String packageName, boolean enabled, boolean valid, Signature[] signatures) { - PackageInfo p = createPackageInfo(packageName, enabled, valid); + private static PackageInfo createPackageInfo(String packageName, boolean enabled, boolean valid, + boolean installed, Signature[] signatures, long updateTime) { + PackageInfo p = createPackageInfo(packageName, enabled, valid, installed); p.signatures = signatures; + p.lastUpdateTime = updateTime; + return p; + } + + private static PackageInfo createPackageInfo(String packageName, boolean enabled, boolean valid, + boolean installed, Signature[] signatures, long updateTime, boolean hidden) { + PackageInfo p = + createPackageInfo(packageName, enabled, valid, installed, signatures, updateTime); + if (hidden) { + p.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN; + } else { + p.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN; + } return p; } @@ -223,9 +241,11 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, false /* isDebuggable */); mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */, - true /* valid */, new Signature[]{invalidPackageSignature})); + true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature} + , 0 /* updateTime */)); mTestSystemImpl.setPackageInfo(createPackageInfo(validPackage, true /* enabled */, - true /* valid */, new Signature[]{validSignature})); + true /* valid */, true /* installed */, new Signature[]{validSignature} + , 0 /* updateTime */)); mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); @@ -273,7 +293,8 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi}; setupWithPackages(packages); mTestSystemImpl.setPackageInfo( - createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */)); + createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); @@ -285,9 +306,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Verify that we can recover from failing to list webview packages. mTestSystemImpl.setPackageInfo( - createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */)); + createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); checkPreparationPhasesForPackage(wpi.packageName, 1); } @@ -345,7 +367,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // 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 */)); + false /* enabled */, true /* valid */, true /* installed */)); } } @@ -371,7 +393,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { } }).start(); try { - Thread.sleep(1000); // Let the new thread run / be blocked + Thread.sleep(500); // Let the new thread run / be blocked } catch (InterruptedException e) { } @@ -380,9 +402,9 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { } else { // Switch provider by enabling the second one mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, - true /* valid */)); + true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - secondPackage, WebViewUpdateService.PACKAGE_CHANGED); + secondPackage, WebViewUpdateService.PACKAGE_CHANGED, 0); } mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); // first package done, should start on second @@ -432,9 +454,9 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Enable fallback package mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, - true /* valid */)); + true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED); + fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, 0); if (fallbackLogicEnabled) { // Check that we have now disabled the fallback package twice @@ -463,7 +485,8 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* isFallbackLogicEnabled */); mTestSystemImpl.setPackageInfo( - createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */)); + createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( @@ -474,9 +497,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Install primary package mTestSystemImpl.setPackageInfo( - createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */)); + createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); // Verify fallback disabled, primary package used as provider, and fallback package killed Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( @@ -507,9 +531,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Disable primary package and ensure fallback becomes enabled and used mTestSystemImpl.setPackageInfo( - createPackageInfo(primaryPackage, false /* enabled */, true /* valid */)); + createPackageInfo(primaryPackage, false /* enabled */, true /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_CHANGED); + WebViewUpdateService.PACKAGE_CHANGED, 0); Mockito.verify(mTestSystemImpl).enablePackageForUser( Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, @@ -520,9 +545,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Again enable primary package and verify primary is used and fallback becomes disabled mTestSystemImpl.setPackageInfo( - createPackageInfo(primaryPackage, true /* enabled */, true /* valid */)); + createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, - WebViewUpdateService.PACKAGE_CHANGED); + WebViewUpdateService.PACKAGE_CHANGED, 0); // Verify fallback is disabled a second time when primary package becomes enabled Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser( @@ -593,9 +619,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Make packages invalid to cause exception to be thrown mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, - false /* valid */)); + false /* valid */, true /* installed */, null /* signatures */, + 0 /* updateTime */)); mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, - false /* valid */)); + false /* valid */, true /* installed */)); // This shouldn't throw an exception! mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); @@ -605,10 +632,11 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Now make a package valid again and verify that we can switch back to that mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, - true /* valid */)); + true /* valid */, true /* installed */, null /* signatures */, + 1 /* updateTime */ )); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); // Ensure we use firstPackage checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */); @@ -634,16 +662,16 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Remove second package (invalidate it) and verify that first package is used mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, - false /* valid */)); + false /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED); + WebViewUpdateService.PACKAGE_ADDED, 0); 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 /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED); + WebViewUpdateService.PACKAGE_ADDED, 0); checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */); } @@ -663,7 +691,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { setupWithPackages(packages); // Only 'install' nonChosenPackage mTestSystemImpl.setPackageInfo( - createPackageInfo(nonChosenPackage, true /* enabled */, true /* valid */)); + createPackageInfo(nonChosenPackage, true /* enabled */, true /* valid */, true /* installed */)); // Set user-chosen package mTestSystemImpl.updateUserSetting(null, chosenPackage); @@ -702,16 +730,16 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Make both packages invalid so that we fail listing WebView packages mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, - false /* valid */)); + false /* valid */, true /* installed */)); mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, - false /* valid */)); + false /* valid */, true /* installed */)); // Change package to hit the webview packages listing problem. if (settingsChange) { mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); } else { mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); } WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); @@ -719,10 +747,10 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Make second package valid and verify that we can load it again mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, - true /* valid */)); + true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); checkPreparationPhasesForPackage(secondPackage, 1); @@ -749,13 +777,14 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { // Replace or remove the current webview package if (replaced) { mTestSystemImpl.setPackageInfo( - createPackageInfo(firstPackage, true /* enabled */, false /* valid */)); + createPackageInfo(firstPackage, true /* enabled */, false /* valid */, + true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_ADDED_REPLACED); + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); } else { mTestSystemImpl.removePackageInfo(firstPackage); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, - WebViewUpdateService.PACKAGE_REMOVED); + WebViewUpdateService.PACKAGE_REMOVED, 0); } checkPreparationPhasesForPackage(secondPackage, 1); @@ -806,7 +835,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { checkPreparationPhasesForPackage(thirdPackage, 1); mTestSystemImpl.setPackageInfo( - createPackageInfo(secondPackage, true /* enabled */, false /* valid */)); + createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); // Try to switch to the invalid second package, this should result in switching to the first // package, since that is more preferred than the third one. @@ -817,4 +846,229 @@ public class WebViewUpdateServiceTest extends AndroidTestCase { Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(thirdPackage)); } + + // Ensure that the update service uses an uninstalled package if that is the only package + // available. + public void testWithSingleUninstalledPackage() { + String testPackageName = "test.package.name"; + WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { + new WebViewProviderInfo(testPackageName, "", + true /*default available*/, false /* fallback */, null)}; + setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); + mTestSystemImpl.setPackageInfo(createPackageInfo(testPackageName, true /* enabled */, + true /* valid */, false /* installed */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */); + } + + public void testNonhiddenPackageUserOverHidden() { + checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */); + } + + public void testInstalledPackageUsedOverUninstalled() { + checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */); + } + + private void checkVisiblePackageUserOverNonVisible(boolean uninstalledNotHidden) { + boolean testUninstalled = uninstalledNotHidden; + boolean testHidden = !uninstalledNotHidden; + String installedPackage = "installedPackage"; + String uninstalledPackage = "uninstalledPackage"; + WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(installedPackage, "", true /* available by default */, + 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))); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); + } + + public void testCantSwitchToHiddenPackage () { + checkCantSwitchToNonVisiblePackage(false /* true == uninstalled, false == hidden */); + } + + + public void testCantSwitchToUninstalledPackage () { + checkCantSwitchToNonVisiblePackage(true /* true == uninstalled, false == hidden */); + } + + /** + * 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). + */ + private void checkCantSwitchToNonVisiblePackage(boolean uninstalledNotHidden) { + boolean testUninstalled = uninstalledNotHidden; + boolean testHidden = !uninstalledNotHidden; + String installedPackage = "installedPackage"; + String uninstalledPackage = "uninstalledPackage"; + WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(installedPackage, "", true /* available by default */, + 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 */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + 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)); + + // We should only have called onWebViewProviderChanged once (before calling + // changeProviderAndSetting + Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(installedPackage))); + } + + public void testHiddenPackageNotPrioritizedEvenIfChosen() { + checkNonvisiblePackageNotPrioritizedEvenIfChosen( + false /* true == uninstalled, false == hidden */); + } + + public void testUninstalledPackageNotPrioritizedEvenIfChosen() { + checkNonvisiblePackageNotPrioritizedEvenIfChosen( + true /* true == uninstalled, false == hidden */); + } + + public void checkNonvisiblePackageNotPrioritizedEvenIfChosen(boolean uninstalledNotHidden) { + boolean testUninstalled = uninstalledNotHidden; + boolean testHidden = !uninstalledNotHidden; + String installedPackage = "installedPackage"; + String uninstalledPackage = "uninstalledPackage"; + WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(installedPackage, "", true /* available by default */, + 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 */)); + + // Start with the setting pointing to the uninstalled package + mTestSystemImpl.updateUserSetting(null, uninstalledPackage); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); + } + + /** + * Ensures that fallback becomes enabled if the primary package is uninstalled for the current + * user. + */ + public void testFallbackEnabledIfPrimaryUninstalled() { + 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 /* fallback logic enabled */); + mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, + true /* valid */, false /* installed */)); + mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, + true /* valid */, true /* installed */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + // Verify that we enable the fallback package + Mockito.verify(mTestSystemImpl).enablePackageForAllUsers( + Mockito.anyObject(), Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */); + + checkPreparationPhasesForPackage(fallbackPackage, 1 /* first preparation phase */); + } + + public void testPreparationRunsIffNewPackage() { + 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 /* fallback logic enabled */); + mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, + true /* valid */, true /* installed */, null /* signatures */, + 10 /* lastUpdateTime*/ )); + mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, + true /* valid */, true /* installed */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */); + Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt() /* user */); + + + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0 /* userId */); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 1 /* userId */); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 2 /* userId */); + // package still has the same update-time so we shouldn't run preparation here + Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); + Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt() /* user */); + + // Ensure we can still load the package + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(primaryPackage, response.packageInfo.packageName); + + + mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, + true /* valid */, true /* installed */, null /* signatures */, + 20 /* lastUpdateTime*/ )); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); + // The package has now changed - ensure that we have run the preparation phase a second time + checkPreparationPhasesForPackage(primaryPackage, 2 /* second preparation phase */); + + + mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, + true /* valid */, true /* installed */, null /* signatures */, + 50 /* lastUpdateTime*/ )); + // Receive intent for different user + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED_REPLACED, 2); + + checkPreparationPhasesForPackage(primaryPackage, 3 /* third preparation phase */); + } + }