Merge "Merge WebKit at r65615 : Ignore http/tests/appcache/origin-quota.html"
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index b021ded..e972c24 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -38,6 +38,8 @@
 
 import junit.framework.Assert;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.net.URLEncoder;
@@ -658,6 +660,96 @@
     }
 
     /**
+     * Called by JNI.
+     * Read from an InputStream into a supplied byte[]
+     * This method catches any exceptions so they don't crash the JVM.
+     * @param inputStream InputStream to read from.
+     * @param output Bytearray that gets the output.
+     * @return the number of bytes read, or -i if then end of stream has been reached
+     */
+    private static int readFromStream(InputStream inputStream, byte[] output) {
+        try {
+            return inputStream.read(output);
+        } catch(java.io.IOException e) {
+            // If we get an exception, return end of stream
+            return -1;
+        }
+    }
+
+    /**
+     * Get the InputStream for an Android resource
+     * There are three different kinds of android resources:
+     * - file:///android_res
+     * - file:///android_asset
+     * - content://
+     * @param url The url to load.
+     * @return An InputStream to the android resource
+     */
+    private InputStream inputStreamForAndroidResource(String url, int type) {
+        final int RESOURCE = 1;
+        final int ASSET = 2;
+        final int CONTENT = 3;
+
+        if (type == RESOURCE) {
+            // file:///android_res
+            if (url == null || url.length() == 0) {
+                Log.e(LOGTAG, "url has length 0 " + url);
+                return null;
+            }
+            int slash = url.indexOf('/');
+            int dot = url.indexOf('.', slash);
+            if (slash == -1 || dot == -1) {
+                Log.e(LOGTAG, "Incorrect res path: " + url);
+                return null;
+            }
+            String subClassName = url.substring(0, slash);
+            String fieldName = url.substring(slash + 1, dot);
+            String errorMsg = null;
+            try {
+                final Class<?> d = mContext.getApplicationContext()
+                        .getClassLoader().loadClass(
+                                mContext.getPackageName() + ".R$"
+                                        + subClassName);
+                final java.lang.reflect.Field field = d.getField(fieldName);
+                final int id = field.getInt(null);
+                TypedValue value = new TypedValue();
+                mContext.getResources().getValue(id, value, true);
+                if (value.type == TypedValue.TYPE_STRING) {
+                    return mContext.getAssets().openNonAsset(
+                            value.assetCookie, value.string.toString(),
+                            AssetManager.ACCESS_STREAMING);
+                } else {
+                    // Old stack only supports TYPE_STRING for res files
+                    Log.e(LOGTAG, "not of type string: " + url);
+                    return null;
+                }
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Exception: " + url);
+                return null;
+            }
+
+        } else if (type == ASSET) {
+            // file:///android_asset
+            try {
+                AssetManager assets = mContext.getAssets();
+                return assets.open(url, AssetManager.ACCESS_STREAMING);
+            } catch (IOException e) {
+                return null;
+            }
+        } else if (type == CONTENT) {
+            try {
+                Uri uri = Uri.parse(url);
+                return mContext.getContentResolver().openInputStream(uri);
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Exception: " + url);
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Start loading a resource.
      * @param loaderHandle The native ResourceLoader that is the target of the
      *                     data.
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index cdcb662..f7d1134 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -149,41 +149,11 @@
                     continue;
                 }
 
-                // check if the plugin has the required permissions
-                String permissions[] = pkgInfo.requestedPermissions;
-                if (permissions == null) {
+                // check if the plugin has the required permissions and
+                // signatures
+                if (!containsPluginPermissionAndSignatures(pkgInfo)) {
                     continue;
                 }
-                boolean permissionOk = false;
-                for (String permit : permissions) {
-                    if (PLUGIN_PERMISSION.equals(permit)) {
-                        permissionOk = true;
-                        break;
-                    }
-                }
-                if (!permissionOk) {
-                    continue;
-                }
-
-                // check to ensure the plugin is properly signed
-                Signature signatures[] = pkgInfo.signatures;
-                if (signatures == null) {
-                    continue;
-                }
-                if (SystemProperties.getBoolean("ro.secure", false)) {
-                    boolean signatureMatch = false;
-                    for (Signature signature : signatures) {
-                        for (int i = 0; i < SIGNATURES.length; i++) {
-                            if (SIGNATURES[i].equals(signature)) {
-                                signatureMatch = true;
-                                break;
-                            }
-                        }
-                    }
-                    if (!signatureMatch) {
-                        continue;
-                    }
-                }
 
                 // determine the type of plugin from the manifest
                 if (serviceInfo.metaData == null) {
@@ -226,6 +196,64 @@
     }
 
     /* package */
+    boolean containsPluginPermissionAndSignatures(String pluginAPKName) {
+        PackageManager pm = mContext.getPackageManager();
+
+        // retrieve information from the plugin's manifest
+        try {
+            PackageInfo pkgInfo = pm.getPackageInfo(pluginAPKName, PackageManager.GET_PERMISSIONS
+                    | PackageManager.GET_SIGNATURES);
+            if (pkgInfo != null) {
+                return containsPluginPermissionAndSignatures(pkgInfo);
+            }
+        } catch (NameNotFoundException e) {
+            Log.w(LOGTAG, "Can't find plugin: " + pluginAPKName);
+        }
+        return false;
+    }
+
+    private static boolean containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
+
+        // check if the plugin has the required permissions
+        String permissions[] = pkgInfo.requestedPermissions;
+        if (permissions == null) {
+            return false;
+        }
+        boolean permissionOk = false;
+        for (String permit : permissions) {
+            if (PLUGIN_PERMISSION.equals(permit)) {
+                permissionOk = true;
+                break;
+            }
+        }
+        if (!permissionOk) {
+            return false;
+        }
+
+        // check to ensure the plugin is properly signed
+        Signature signatures[] = pkgInfo.signatures;
+        if (signatures == null) {
+            return false;
+        }
+        if (SystemProperties.getBoolean("ro.secure", false)) {
+            boolean signatureMatch = false;
+            for (Signature signature : signatures) {
+                for (int i = 0; i < SIGNATURES.length; i++) {
+                    if (SIGNATURES[i].equals(signature)) {
+                        signatureMatch = true;
+                        break;
+                    }
+                }
+            }
+            if (!signatureMatch) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* package */
     String getPluginsAPKName(String pluginLib) {
 
         // basic error checking on input params
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4823407..5d9ab90 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -18,10 +18,14 @@
 
 import android.annotation.Widget;
 import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.IntentFilter;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.Intent;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
@@ -40,6 +44,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -92,6 +97,7 @@
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -877,6 +883,7 @@
          * such as the mZoomManager.
          */
         init();
+        setupPackageListener(context);
         updateMultiTouchSupport(context);
 
         if (privateBrowsing) {
@@ -884,6 +891,96 @@
         }
     }
 
+    /*
+     * The intent receiver that monitors for changes to relevant packages (e.g.,
+     * sGoogleApps) and notifies WebViewCore of their existence.
+     */
+    private static BroadcastReceiver sPackageInstallationReceiver = null;
+
+    /*
+     * A set of Google packages we monitor for the
+     * navigator.isApplicationInstalled() API. Add additional packages as
+     * needed.
+     */
+    private static Set<String> sGoogleApps;
+    static {
+        sGoogleApps = new HashSet<String>();
+        sGoogleApps.add("com.google.android.youtube");
+    }
+
+    private void setupPackageListener(Context context) {
+
+        /*
+         * we must synchronize the instance check and the creation of the
+         * receiver to ensure that only ONE receiver exists for all WebView
+         * instances.
+         */
+        synchronized (WebView.class) {
+
+            // if the receiver already exists then we do not need to register it
+            // again
+            if (sPackageInstallationReceiver != null) {
+                return;
+            }
+
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addDataScheme("package");
+            sPackageInstallationReceiver = new BroadcastReceiver() {
+
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    final String packageName = intent.getData().getSchemeSpecificPart();
+                    final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                    if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
+                        // if it is replacing, refreshPlugins() when adding
+                        return;
+                    }
+
+                    if (sGoogleApps.contains(packageName)) {
+                        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                            mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName);
+                        } else {
+                            mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
+                        }
+                    }
+
+                    PluginManager pm = PluginManager.getInstance(context);
+                    if (pm.containsPluginPermissionAndSignatures(packageName)) {
+                        pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
+                    }
+                }
+            };
+
+            context.getApplicationContext().registerReceiver(sPackageInstallationReceiver, filter);
+        }
+
+        // check if any of the monitored apps are already installed
+        AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
+
+            @Override
+            protected Set<String> doInBackground(Void... unused) {
+                Set<String> installedPackages = new HashSet<String>();
+                PackageManager pm = mContext.getPackageManager();
+                List<PackageInfo> packages = pm.getInstalledPackages(0);
+                for (PackageInfo p : packages) {
+                    if (sGoogleApps.contains(p.packageName)) {
+                        installedPackages.add(p.packageName);
+                    }
+                }
+                return installedPackages;
+            }
+
+            // Executes on the UI thread
+            @Override
+            protected void onPostExecute(Set<String> installedPackages) {
+                mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
+            }
+        };
+        task.execute();
+    }
+
     void updateMultiTouchSupport(Context context) {
         mZoomManager.updateMultiTouchSupport(context);
     }
@@ -3076,45 +3173,6 @@
         return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
     }
 
-    /**
-     * Use this method to inform the webview about packages that are installed
-     * in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageNames is a set of package names that are known to be
-     * installed in the system.
-     *
-     * @hide not a public API
-     */
-    public void addPackageNames(Set<String> packageNames) {
-        mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, packageNames);
-    }
-
-    /**
-     * Use this method to inform the webview about single packages that are
-     * installed in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageName is the name of a package that is known to be
-     * installed in the system.
-     *
-     * @hide not a public API
-     */
-    public void addPackageName(String packageName) {
-        mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName);
-    }
-
-    /**
-     * Use this method to inform the webview about packages that are uninstalled
-     * in the system. This information will be used by the
-     * navigator.isApplicationInstalled() API.
-     * @param packageName is the name of a package that has been uninstalled in
-     * the system.
-     *
-     * @hide not a public API
-     */
-    public void removePackageName(String packageName) {
-        mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
-    }
-
    /**
     * Return the list of currently loaded plugins.
     * @return The list of currently loaded plugins.