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.