aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Naganov <mnaganov@google.com>2016-01-22 14:09:18 -0800
committerMikhail Naganov <mnaganov@google.com>2016-01-22 15:49:56 -0800
commit9aa1af8ab126fb6a364291c259a5a37532e8f0ef (patch)
tree0140196a2d151c426138a086812b5023b29162fc
parent7af69b1b552b13da5941c35c430f3adaf4132875 (diff)
downloadBrowser2-nougat-mr1-wear-release.tar.gz
Synced WebViewBrowserActivity, manifest and resources up to the state of the Chromium tree at refs/heads/master@{#371018} Differences from the Chromium version: -- WebViewBrowserActivity uses android.util.Log instead of org.chromium.base.Log, thus difference in call parameters; -- relative class names are used for Activity classes in the manifest file. Change-Id: I9af67200e82c17d76347a1ca4ffb060be3e777bf
-rw-r--r--AndroidManifest.xml18
-rw-r--r--res/layout/activity_webview_browser.xml4
-rw-r--r--res/menu/main_menu.xml4
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/org/chromium/webview_shell/WebViewBrowserActivity.java344
5 files changed, 333 insertions, 39 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d313c81..6f7a444 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -10,16 +10,23 @@
android:versionCode="1"
android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="22" />
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
+ <!-- "Normal" permissions which do not require user prompt -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+ <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+
+ <!-- "Dangerous" permissions which require user prompt -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
- <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/ic_launcher"
@@ -51,7 +58,8 @@
<activity
android:name=".WebViewBrowserActivity"
android:label="@string/title_activity_browser"
- android:exported="true">
+ android:exported="true"
+ android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/res/layout/activity_webview_browser.xml b/res/layout/activity_webview_browser.xml
index d583562..07483c5 100644
--- a/res/layout/activity_webview_browser.xml
+++ b/res/layout/activity_webview_browser.xml
@@ -36,8 +36,4 @@
android:contentDescription="@string/menu_about"
android:onClick="showPopup" />
</LinearLayout>
- <WebView
- android:id="@+id/webview"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
</LinearLayout>
diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml
index abddef3..a22610d 100644
--- a/res/menu/main_menu.xml
+++ b/res/menu/main_menu.xml
@@ -4,6 +4,8 @@
found in the LICENSE file. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/menu_reset_webview"
+ android:title="@string/menu_reset_webview"/>
<item android:id="@+id/menu_about"
android:title="@string/menu_about"/>
-</menu> \ No newline at end of file
+</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b8058ea..82ccaea 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11,6 +11,8 @@
<string name="title_activity_startup_time">WebView Startup Time Tester</string>
<string name="title_activity_browser">WebView Browser Tester</string>
<string name="title_activity_layout_test">WebView Layout Test</string>
+ <string name="title_activity_page_cycler">WebView Page Cycler Test</string>
+ <string name="menu_reset_webview">Destroy and create new WebView</string>
<string name="menu_about">About WebView</string>
<string name="load_url">Load URL</string>
</resources>
diff --git a/src/org/chromium/webview_shell/WebViewBrowserActivity.java b/src/org/chromium/webview_shell/WebViewBrowserActivity.java
index fbe41e3..0e1cbb1 100644
--- a/src/org/chromium/webview_shell/WebViewBrowserActivity.java
+++ b/src/org/chromium/webview_shell/WebViewBrowserActivity.java
@@ -4,18 +4,30 @@
package org.chromium.webview_shell;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Browser;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.webkit.GeolocationPermissions;
@@ -26,7 +38,6 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
-import android.widget.LinearLayout.LayoutParams;
import android.widget.PopupMenu;
import android.widget.TextView;
@@ -36,6 +47,9 @@ import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -45,25 +59,135 @@ import java.util.regex.Pattern;
* on top of the webview for manually specifying URLs to load.
*/
public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenuItemClickListener {
+ private static final String TAG = "WebViewShell";
+
+ // Our imaginary Android permission to associate with the WebKit geo permission
+ private static final String RESOURCE_GEO = "RESOURCE_GEO";
+ // Our imaginary WebKit permission to request when loading a file:// URL
+ private static final String RESOURCE_FILE_URL = "RESOURCE_FILE_URL";
+ // WebKit permissions with no corresponding Android permission can always be granted
+ private static final String NO_ANDROID_PERMISSION = "NO_ANDROID_PERMISSION";
+
+ // Map from WebKit permissions to Android permissions
+ private static final HashMap<String, String> sPermissions;
+ static {
+ sPermissions = new HashMap<String, String>();
+ sPermissions.put(RESOURCE_GEO, Manifest.permission.ACCESS_FINE_LOCATION);
+ sPermissions.put(RESOURCE_FILE_URL, Manifest.permission.READ_EXTERNAL_STORAGE);
+ sPermissions.put(PermissionRequest.RESOURCE_AUDIO_CAPTURE,
+ Manifest.permission.RECORD_AUDIO);
+ sPermissions.put(PermissionRequest.RESOURCE_MIDI_SYSEX, NO_ANDROID_PERMISSION);
+ sPermissions.put(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID, NO_ANDROID_PERMISSION);
+ sPermissions.put(PermissionRequest.RESOURCE_VIDEO_CAPTURE,
+ Manifest.permission.CAMERA);
+ }
+
+ private static final Pattern WEBVIEW_VERSION_PATTERN =
+ Pattern.compile("(Chrome/)([\\d\\.]+)\\s");
+
private EditText mUrlBar;
private WebView mWebView;
private String mWebViewVersion;
- private static final Pattern WEBVIEW_VERSION_PATTERN =
- Pattern.compile("(Chrome/)([\\d\\.]+)\\s");
+ // Each time we make a request, store it here with an int key. onRequestPermissionsResult will
+ // look up the request in order to grant the approprate permissions.
+ private SparseArray<PermissionRequest> mPendingRequests = new SparseArray<PermissionRequest>();
+ private int mNextRequestKey = 0;
+
+ // Work around our wonky API by wrapping a geo permission prompt inside a regular
+ // PermissionRequest.
+ private static class GeoPermissionRequest extends PermissionRequest {
+ private String mOrigin;
+ private GeolocationPermissions.Callback mCallback;
+
+ public GeoPermissionRequest(String origin, GeolocationPermissions.Callback callback) {
+ mOrigin = origin;
+ mCallback = callback;
+ }
+
+ public Uri getOrigin() {
+ return Uri.parse(mOrigin);
+ }
+
+ public String[] getResources() {
+ return new String[] { WebViewBrowserActivity.RESOURCE_GEO };
+ }
+
+ public void grant(String[] resources) {
+ assert resources.length == 1;
+ assert WebViewBrowserActivity.RESOURCE_GEO.equals(resources[0]);
+ mCallback.invoke(mOrigin, true, false);
+ }
+
+ public void deny() {
+ mCallback.invoke(mOrigin, false, false);
+ }
+ }
- // TODO(michaelbai) : Replace "android.webkit.resoruce.MIDI_SYSEX" with
- // PermissionRequest.RESOURCE_MIDI_SYSEX once Android M SDK is used.
- private static final String[] AUTOMATICALLY_GRANT =
- { PermissionRequest.RESOURCE_VIDEO_CAPTURE, PermissionRequest.RESOURCE_AUDIO_CAPTURE,
- "android.webkit.resource.MIDI_SYSEX" };
+ // For simplicity, also treat the read access needed for file:// URLs as a regular
+ // PermissionRequest.
+ private class FilePermissionRequest extends PermissionRequest {
+ private String mOrigin;
+
+ public FilePermissionRequest(String origin) {
+ mOrigin = origin;
+ }
+
+ public Uri getOrigin() {
+ return Uri.parse(mOrigin);
+ }
+
+ public String[] getResources() {
+ return new String[] { WebViewBrowserActivity.RESOURCE_FILE_URL };
+ }
+
+ public void grant(String[] resources) {
+ assert resources.length == 1;
+ assert WebViewBrowserActivity.RESOURCE_FILE_URL.equals(resources[0]);
+ // Try again now that we have read access.
+ WebViewBrowserActivity.this.mWebView.loadUrl(mOrigin);
+ }
+
+ public void deny() {
+ // womp womp
+ }
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ WebView.setWebContentsDebuggingEnabled(true);
+ }
setContentView(R.layout.activity_webview_browser);
- mWebView = (WebView) findViewById(R.id.webview);
- WebSettings settings = mWebView.getSettings();
+ mUrlBar = (EditText) findViewById(R.id.url_field);
+ mUrlBar.setOnKeyListener(new OnKeyListener() {
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+ loadUrlFromUrlBar(view);
+ return true;
+ }
+ return false;
+ }
+ });
+
+ createAndInitializeWebView();
+
+ String url = getUrlFromIntent(getIntent());
+ if (url != null) {
+ setUrlBarText(url);
+ setUrlFail(false);
+ loadUrlFromUrlBar(mUrlBar);
+ }
+ }
+
+ ViewGroup getContainer() {
+ return (ViewGroup) findViewById(R.id.container);
+ }
+
+ private void createAndInitializeWebView() {
+ WebView webview = new WebView(this);
+ WebSettings settings = webview.getSettings();
initializeSettings(settings);
Matcher matcher = WEBVIEW_VERSION_PATTERN.matcher(settings.getUserAgentString());
@@ -74,10 +198,25 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
}
setTitle(getResources().getString(R.string.title_activity_browser) + " " + mWebViewVersion);
- mWebView.setWebViewClient(new WebViewClient() {
+ webview.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ setUrlBarText(url);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ setUrlBarText(url);
+ }
+
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
- return false;
+ // "about:" and "chrome:" schemes are internal to Chromium;
+ // don't want these to be dispatched to other apps.
+ if (url.startsWith("about:") || url.startsWith("chrome:")) {
+ return false;
+ }
+ return startBrowsingIntent(WebViewBrowserActivity.this, url);
}
@Override
@@ -87,7 +226,7 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
}
});
- mWebView.setWebChromeClient(new WebChromeClient() {
+ webview.setWebChromeClient(new WebChromeClient() {
@Override
public Bitmap getDefaultVideoPoster() {
return Bitmap.createBitmap(
@@ -97,32 +236,90 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
- callback.invoke(origin, true, false);
+ onPermissionRequest(new GeoPermissionRequest(origin, callback));
}
@Override
public void onPermissionRequest(PermissionRequest request) {
- request.grant(AUTOMATICALLY_GRANT);
+ WebViewBrowserActivity.this.requestPermissionsForPage(request);
}
});
- mUrlBar = (EditText) findViewById(R.id.url_field);
- mUrlBar.setOnKeyListener(new OnKeyListener() {
- public boolean onKey(View view, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
- loadUrlFromUrlBar(view);
- return true;
- }
- return false;
+ mWebView = webview;
+ getContainer().addView(
+ webview, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setUrlBarText("");
+ }
+
+ // WebKit permissions which can be granted because either they have no associated Android
+ // permission or the associated Android permission has been granted
+ private boolean canGrant(String webkitPermission) {
+ String androidPermission = sPermissions.get(webkitPermission);
+ if (androidPermission == NO_ANDROID_PERMISSION) {
+ return true;
+ }
+ return PackageManager.PERMISSION_GRANTED == checkSelfPermission(androidPermission);
+ }
+
+ private void requestPermissionsForPage(PermissionRequest request) {
+ // Deny any unrecognized permissions.
+ for (String webkitPermission : request.getResources()) {
+ if (!sPermissions.containsKey(webkitPermission)) {
+ Log.w(TAG, "Unrecognized WebKit permission: " + webkitPermission);
+ request.deny();
+ return;
}
- });
+ }
- String url = getUrlFromIntent(getIntent());
- if (url != null) {
- setUrlBarText(url);
- setUrlFail(false);
- loadUrlFromUrlBar(mUrlBar);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ request.grant(request.getResources());
+ return;
+ }
+
+ // Find what Android permissions we need before we can grant these WebKit permissions.
+ ArrayList<String> androidPermissionsNeeded = new ArrayList<String>();
+ for (String webkitPermission : request.getResources()) {
+ if (!canGrant(webkitPermission)) {
+ // We already checked for unrecognized permissions, and canGrant will skip over
+ // NO_ANDROID_PERMISSION cases, so this is guaranteed to be a regular Android
+ // permission.
+ String androidPermission = sPermissions.get(webkitPermission);
+ androidPermissionsNeeded.add(androidPermission);
+ }
+ }
+
+ // If there are no such Android permissions, grant the WebKit permissions immediately.
+ if (androidPermissionsNeeded.isEmpty()) {
+ request.grant(request.getResources());
+ return;
+ }
+
+ // Otherwise, file a new request
+ if (mNextRequestKey == Integer.MAX_VALUE) {
+ Log.e(TAG, "Too many permission requests");
+ return;
+ }
+ int requestCode = mNextRequestKey;
+ mNextRequestKey++;
+ mPendingRequests.append(requestCode, request);
+ requestPermissions(androidPermissionsNeeded.toArray(new String[0]), requestCode);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ String permissions[], int[] grantResults) {
+ // Verify that we can now grant all the requested permissions. Note that although grant()
+ // takes a list of permissions, grant() is actually all-or-nothing. If there are any
+ // requested permissions not included in the granted permissions, all will be denied.
+ PermissionRequest request = mPendingRequests.get(requestCode);
+ for (String webkitPermission : request.getResources()) {
+ if (!canGrant(webkitPermission)) {
+ request.deny();
+ return;
+ }
}
+ request.grant(request.getResources());
+ mPendingRequests.delete(requestCode);
}
public void loadUrlFromUrlBar(View view) {
@@ -153,6 +350,15 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
@Override
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId()) {
+ case R.id.menu_reset_webview:
+ if (mWebView != null) {
+ ViewGroup container = getContainer();
+ container.removeView(mWebView);
+ mWebView.destroy();
+ mWebView = null;
+ }
+ createAndInitializeWebView();
+ return true;
case R.id.menu_about:
about();
hideKeyboard(mUrlBar);
@@ -206,6 +412,17 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
}
private void loadUrl(String url) {
+ // Request read access if necessary
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && "file".equals(Uri.parse(url).getScheme())
+ && PackageManager.PERMISSION_DENIED
+ == checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ requestPermissionsForPage(new FilePermissionRequest(url));
+ }
+
+ // If it is file:// and we don't have permission, they'll get the "Webpage not available"
+ // "net::ERR_ACCESS_DENIED" page. When we get permission, FilePermissionRequest.grant()
+ // will reload.
mWebView.loadUrl(url);
mWebView.requestFocus();
}
@@ -232,4 +449,73 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu
private static String getUrlFromIntent(Intent intent) {
return intent != null ? intent.getDataString() : null;
}
+
+ static final Pattern BROWSER_URI_SCHEMA = Pattern.compile(
+ "(?i)" // switch on case insensitive matching
+ + "(" // begin group for schema
+ + "(?:http|https|file):\\/\\/"
+ + "|(?:inline|data|about|chrome|javascript):"
+ + ")"
+ + "(.*)");
+
+ private static boolean startBrowsingIntent(Context context, String url) {
+ Intent intent;
+ // Perform generic parsing of the URI to turn it into an Intent.
+ try {
+ intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
+ } catch (Exception ex) {
+ Log.w(TAG, "Bad URI " + url, ex);
+ return false;
+ }
+ // Check for regular URIs that WebView supports by itself, but also
+ // check if there is a specialized app that had registered itself
+ // for this kind of an intent.
+ Matcher m = BROWSER_URI_SCHEMA.matcher(url);
+ if (m.matches() && !isSpecializedHandlerAvailable(context, intent)) {
+ return false;
+ }
+ // Sanitize the Intent, ensuring web pages can not bypass browser
+ // security (only access to BROWSABLE activities).
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.setComponent(null);
+ Intent selector = intent.getSelector();
+ if (selector != null) {
+ selector.addCategory(Intent.CATEGORY_BROWSABLE);
+ selector.setComponent(null);
+ }
+
+ // Pass the package name as application ID so that the intent from the
+ // same application can be opened in the same tab.
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ try {
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException ex) {
+ Log.w(TAG, "No application can handle " + url);
+ }
+ return false;
+ }
+
+ /**
+ * Search for intent handlers that are specific to the scheme of the URL in the intent.
+ */
+ private static boolean isSpecializedHandlerAvailable(Context context, Intent intent) {
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
+ PackageManager.GET_RESOLVED_FILTER);
+ if (handlers == null || handlers.size() == 0) {
+ return false;
+ }
+ for (ResolveInfo resolveInfo : handlers) {
+ if (!isNullOrGenericHandler(resolveInfo.filter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isNullOrGenericHandler(IntentFilter filter) {
+ return filter == null
+ || (filter.countDataAuthorities() == 0 && filter.countDataPaths() == 0);
+ }
}