Simplify WebViewProviderInfo - move its logic into WebViewUpdateService.

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

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

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

Bug: 27635535
Bug: 27736084

Change-Id: Ia455202d2fd5bc4e03dce0fd917d262bf942d1a3
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 50699f8..c4f9cc1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -26,9 +26,11 @@
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.os.Binder;
+import android.os.Build;
 import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.RemoteException;
@@ -38,6 +40,7 @@
 import android.provider.Settings.Global;
 import android.provider.Settings;
 import android.util.AndroidRuntimeException;
+import android.util.Base64;
 import android.util.Slog;
 import android.webkit.IWebViewUpdateService;
 import android.webkit.WebViewFactory;
@@ -72,8 +75,6 @@
 
     // The WebView package currently in use (or the one we are preparing).
     private PackageInfo mCurrentWebViewPackage = null;
-    // The WebView providers that are currently available.
-    private WebViewProviderInfo[] mCurrentValidWebViewPackages = null;
 
     private BroadcastReceiver mWebViewUpdatedReceiver;
     private WebViewUtilityInterface mWebViewUtility;
@@ -126,7 +127,6 @@
                             PackageInfo newPackage = null;
                             synchronized(WebViewUpdateService.this) {
                                 try {
-                                    updateValidWebViewPackages();
                                     newPackage = findPreferredWebViewPackage();
                                     if (mCurrentWebViewPackage != null)
                                         oldProviderName = mCurrentWebViewPackage.packageName;
@@ -180,11 +180,17 @@
         publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
     }
 
-    private static boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
+    private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
         for (WebViewProviderInfo provider : providers) {
-            if (provider.isAvailableByDefault() && provider.isEnabled()
-                    && provider.isValidProvider() && !provider.isFallbackPackage()) {
-                return true;
+            if (provider.availableByDefault && !provider.isFallback) {
+                try {
+                    PackageInfo packageInfo = getPackageInfoForProvider(provider);
+                    if (isEnabledPackage(packageInfo) && isValidProvider(provider, packageInfo)) {
+                        return true;
+                    }
+                } catch (NameNotFoundException e) {
+                    // A non-existent provider is neither valid nor enabled
+                }
             }
         }
         return false;
@@ -211,11 +217,9 @@
         WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages();
         WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
         if (fallbackProvider == null) return;
-        boolean existsValidNonFallbackProvider =
-            existsValidNonFallbackProvider(webviewProviders);
 
-        enablePackageForUser(fallbackProvider.packageName, !existsValidNonFallbackProvider,
-                userId);
+        enablePackageForUser(fallbackProvider.packageName,
+                !existsValidNonFallbackProvider(webviewProviders), userId);
     }
 
     /**
@@ -236,7 +240,7 @@
             for (WebViewProviderInfo provider : webviewProviders) {
                 String webviewPackage = "package:" + provider.packageName;
                 if (webviewPackage.equals(intent.getDataString())) {
-                    if (provider.isAvailableByDefault()) {
+                    if (provider.availableByDefault) {
                         changedPackage = provider.packageName;
                     }
                     break;
@@ -251,10 +255,16 @@
         if (fallbackProvider == null) return;
         boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);
 
+        boolean isFallbackEnabled = false;
+        try {
+            isFallbackEnabled = isEnabledPackage(getPackageInfoForProvider(fallbackProvider));
+        } catch (NameNotFoundException e) {
+        }
+
         if (existsValidNonFallbackProvider
                 // During an OTA the primary user's WebView state might differ from other users', so
                 // ignore the state of that user during boot.
-                && (fallbackProvider.isEnabled() || intent == null)) {
+                && (isFallbackEnabled || intent == null)) {
             // Uninstall and disable fallback package for all users.
             context.getPackageManager().deletePackage(fallbackProvider.packageName,
                     new IPackageDeleteObserver.Stub() {
@@ -273,7 +283,7 @@
         } else if (!existsValidNonFallbackProvider
                 // During an OTA the primary user's WebView state might differ from other users', so
                 // ignore the state of that user during boot.
-                && (!fallbackProvider.isEnabled() || intent==null)) {
+                && (!isFallbackEnabled || intent==null)) {
             // Enable the fallback package for all users.
             UserManager userManager =
                 (UserManager)context.getSystemService(Context.USER_SERVICE);
@@ -299,24 +309,13 @@
      */
     private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
         for (WebViewProviderInfo provider : webviewPackages) {
-            if (provider.isFallbackPackage()) {
+            if (provider.isFallback) {
                 return provider;
             }
         }
         return null;
     }
 
-    private static boolean containsAvailableNonFallbackProvider(
-            WebViewProviderInfo[] webviewPackages) {
-        for (WebViewProviderInfo provider : webviewPackages) {
-            if (provider.isAvailableByDefault() && provider.isEnabled()
-                    && provider.isValidProvider() && !provider.isFallbackPackage()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private boolean isFallbackPackage(String packageName) {
         if (packageName == null || !isFallbackLogicEnabled()) return false;
 
@@ -336,7 +335,6 @@
         updateFallbackState(getContext(), null);
         try {
             synchronized(this) {
-                updateValidWebViewPackages();
                 mCurrentWebViewPackage = findPreferredWebViewPackage();
                 onWebViewProviderChanged(mCurrentWebViewPackage);
             }
@@ -408,24 +406,41 @@
         }
     }
 
-    /**
-     * Updates the currently valid WebView provider packages.
-     * Should be used when a provider has been installed or removed.
-     * @hide
-     * */
-    private void updateValidWebViewPackages() {
-        List<WebViewProviderInfo> webViewProviders  =
-            new ArrayList<WebViewProviderInfo>(Arrays.asList(mWebViewUtility.getWebViewPackages()));
-        Iterator<WebViewProviderInfo> it = webViewProviders.iterator();
-        // remove non-valid packages
-        while(it.hasNext()) {
-            WebViewProviderInfo current = it.next();
-            if (!current.isValidProvider())
-                it.remove();
+    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+        WebViewProviderInfo[] allProviders = mWebViewUtility.getWebViewPackages();
+        List<ProviderAndPackageInfo> providers = new ArrayList<>();
+        for(int n = 0; n < allProviders.length; n++) {
+            try {
+                PackageInfo packageInfo = getPackageInfoForProvider(allProviders[n]);
+                if (isValidProvider(allProviders[n], packageInfo)) {
+                    providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+                }
+            } catch (NameNotFoundException e) {
+                // Don't add non-existent packages
+            }
         }
-        synchronized(this) {
-            mCurrentValidWebViewPackages =
-                webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
+        return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+    }
+
+    /**
+     * Fetch only the currently valid WebView packages.
+     **/
+    private WebViewProviderInfo[] getValidWebViewPackages() {
+        ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+        WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length];
+        for(int n = 0; n < providersAndPackageInfos.length; n++) {
+            providers[n] = providersAndPackageInfos[n].provider;
+        }
+        return providers;
+    }
+
+    private class ProviderAndPackageInfo {
+        public final WebViewProviderInfo provider;
+        public final PackageInfo packageInfo;
+
+        public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+            this.provider = provider;
+            this.packageInfo = packageInfo;
         }
     }
 
@@ -437,28 +452,30 @@
      * @hide
      */
     private PackageInfo findPreferredWebViewPackage() {
-        WebViewProviderInfo[] providers = mCurrentValidWebViewPackages;
+        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
 
         String userChosenProvider = mWebViewUtility.getUserChosenWebViewProvider(getContext());
 
         // If the user has chosen provider, use that
-        for (WebViewProviderInfo provider : providers) {
-            if (provider.packageName.equals(userChosenProvider) && provider.isEnabled()) {
-                return provider.getPackageInfo();
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.packageName.equals(userChosenProvider)
+                    && isEnabledPackage(providerAndPackage.packageInfo)) {
+                return providerAndPackage.packageInfo;
             }
         }
 
         // User did not choose, or the choice failed; use the most stable provider that is
         // enabled and available by default (not through user choice).
-        for (WebViewProviderInfo provider : providers) {
-            if (provider.isAvailableByDefault() && provider.isEnabled()) {
-                return provider.getPackageInfo();
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            if (providerAndPackage.provider.availableByDefault
+                    && isEnabledPackage(providerAndPackage.packageInfo)) {
+                return providerAndPackage.packageInfo;
             }
         }
 
         // Could not find any enabled package either, use the most stable provider.
-        for (WebViewProviderInfo provider : providers) {
-            return provider.getPackageInfo();
+        for (ProviderAndPackageInfo providerAndPackage : providers) {
+            return providerAndPackage.packageInfo;
         }
 
         mAnyWebViewInstalled = false;
@@ -467,6 +484,60 @@
     }
 
     /**
+     * Returns whether this provider is valid for use as a WebView provider.
+     */
+    private static boolean isValidProvider(WebViewProviderInfo configInfo,
+            PackageInfo packageInfo) {
+        if (providerHasValidSignature(configInfo, packageInfo) &&
+                WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+            PackageInfo packageInfo) {
+        if (Build.IS_DEBUGGABLE)
+            return true;
+        Signature[] packageSignatures;
+        // If no signature is declared, instead check whether the package is included in the
+        // system.
+        if (provider.signatures == null || provider.signatures.length == 0) {
+            return packageInfo.applicationInfo.isSystemApp();
+        }
+        packageSignatures = packageInfo.signatures;
+        if (packageSignatures.length != 1)
+            return false;
+
+        final byte[] packageSignature = packageSignatures[0].toByteArray();
+        // Return whether the package signature matches any of the valid signatures
+        for (String signature : provider.signatures) {
+            final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT);
+            if (Arrays.equals(packageSignature, validSignature))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether the given package is enabled.
+     * This state can be changed by the user from Settings->Apps
+     */
+    private static boolean isEnabledPackage(PackageInfo packageInfo) {
+        return packageInfo.applicationInfo.enabled;
+    }
+
+    private static PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
+            throws NameNotFoundException {
+        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+        return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
+    }
+
+    // flags declaring we want extra info from the package manager for webview providers
+    private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
+            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+
+    /**
      * Returns whether WebView is ready and is not going to go through its preparation phase again
      * directly.
      */
@@ -593,9 +664,7 @@
 
         @Override // Binder call
         public WebViewProviderInfo[] getValidWebViewPackages() {
-            synchronized(WebViewUpdateService.this) {
-                return mCurrentValidWebViewPackages;
-            }
+            return WebViewUpdateService.this.getValidWebViewPackages();
         }
 
         @Override // Binder call