diff options
author | Mikhail Naganov <mnaganov@google.com> | 2016-01-22 14:09:18 -0800 |
---|---|---|
committer | Mikhail Naganov <mnaganov@google.com> | 2016-01-22 15:49:56 -0800 |
commit | 9aa1af8ab126fb6a364291c259a5a37532e8f0ef (patch) | |
tree | 0140196a2d151c426138a086812b5023b29162fc | |
parent | 7af69b1b552b13da5941c35c430f3adaf4132875 (diff) | |
download | Browser2-nougat-mr1-wear-release.tar.gz |
Update Browser with the current code from the Chromium treeandroid-wear-n-preview-3android-wear-n-preview-2android-wear-n-preview-1android-wear-7.1.1_r1android-n-preview-5android-n-preview-4android-n-preview-3android-n-preview-2android-n-preview-1android-n-iot-preview-2nougat-mr1-wear-releasen-iot-preview-2
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.xml | 18 | ||||
-rw-r--r-- | res/layout/activity_webview_browser.xml | 4 | ||||
-rw-r--r-- | res/menu/main_menu.xml | 4 | ||||
-rw-r--r-- | res/values/strings.xml | 2 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/WebViewBrowserActivity.java | 344 |
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); + } } |