Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(376)

Side by Side Diff: android_webview/tools/WebViewShell/src/org/chromium/webview_shell/WebViewBrowserActivity.java

Issue 1785283005: [WebView] Reorganize all of the WebView Shell apks into their own dir. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.webview_shell;
6
7 import android.Manifest;
8 import android.app.Activity;
9 import android.app.AlertDialog;
10 import android.content.ActivityNotFoundException;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.content.IntentFilter;
14 import android.content.pm.PackageManager;
15 import android.content.pm.ResolveInfo;
16 import android.graphics.Bitmap;
17 import android.graphics.Color;
18 import android.net.Uri;
19 import android.os.Build;
20 import android.os.Bundle;
21 import android.provider.Browser;
22 import android.util.SparseArray;
23
24 import android.view.KeyEvent;
25 import android.view.MenuItem;
26 import android.view.View;
27 import android.view.View.OnKeyListener;
28 import android.view.ViewGroup;
29 import android.view.ViewGroup.LayoutParams;
30 import android.view.inputmethod.InputMethodManager;
31
32 import android.webkit.GeolocationPermissions;
33 import android.webkit.PermissionRequest;
34 import android.webkit.WebChromeClient;
35 import android.webkit.WebSettings;
36 import android.webkit.WebView;
37 import android.webkit.WebViewClient;
38
39 import android.widget.EditText;
40 import android.widget.PopupMenu;
41 import android.widget.TextView;
42
43 import org.chromium.base.Log;
44
45 import java.lang.reflect.InvocationTargetException;
46 import java.lang.reflect.Method;
47
48 import java.net.URI;
49 import java.net.URISyntaxException;
50
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56
57 /**
58 * This activity is designed for starting a "mini-browser" for manual testing of WebView.
59 * It takes an optional URL as an argument, and displays the page. There is a UR L bar
60 * on top of the webview for manually specifying URLs to load.
61 */
62 public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu ItemClickListener {
63 private static final String TAG = "WebViewShell";
64
65 // Our imaginary Android permission to associate with the WebKit geo permiss ion
66 private static final String RESOURCE_GEO = "RESOURCE_GEO";
67 // Our imaginary WebKit permission to request when loading a file:// URL
68 private static final String RESOURCE_FILE_URL = "RESOURCE_FILE_URL";
69 // WebKit permissions with no corresponding Android permission can always be granted
70 private static final String NO_ANDROID_PERMISSION = "NO_ANDROID_PERMISSION";
71
72 // Map from WebKit permissions to Android permissions
73 private static final HashMap<String, String> sPermissions;
74 static {
75 sPermissions = new HashMap<String, String>();
76 sPermissions.put(RESOURCE_GEO, Manifest.permission.ACCESS_FINE_LOCATION) ;
77 sPermissions.put(RESOURCE_FILE_URL, Manifest.permission.READ_EXTERNAL_ST ORAGE);
78 sPermissions.put(PermissionRequest.RESOURCE_AUDIO_CAPTURE,
79 Manifest.permission.RECORD_AUDIO);
80 sPermissions.put(PermissionRequest.RESOURCE_MIDI_SYSEX, NO_ANDROID_PERMI SSION);
81 sPermissions.put(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID, NO_ANDRO ID_PERMISSION);
82 sPermissions.put(PermissionRequest.RESOURCE_VIDEO_CAPTURE,
83 Manifest.permission.CAMERA);
84 }
85
86 private static final Pattern WEBVIEW_VERSION_PATTERN =
87 Pattern.compile("(Chrome/)([\\d\\.]+)\\s");
88
89 private EditText mUrlBar;
90 private WebView mWebView;
91 private String mWebViewVersion;
92
93 // Each time we make a request, store it here with an int key. onRequestPerm issionsResult will
94 // look up the request in order to grant the approprate permissions.
95 private SparseArray<PermissionRequest> mPendingRequests = new SparseArray<Pe rmissionRequest>();
96 private int mNextRequestKey = 0;
97
98 // Work around our wonky API by wrapping a geo permission prompt inside a re gular
99 // PermissionRequest.
100 private static class GeoPermissionRequest extends PermissionRequest {
101 private String mOrigin;
102 private GeolocationPermissions.Callback mCallback;
103
104 public GeoPermissionRequest(String origin, GeolocationPermissions.Callba ck callback) {
105 mOrigin = origin;
106 mCallback = callback;
107 }
108
109 public Uri getOrigin() {
110 return Uri.parse(mOrigin);
111 }
112
113 public String[] getResources() {
114 return new String[] { WebViewBrowserActivity.RESOURCE_GEO };
115 }
116
117 public void grant(String[] resources) {
118 assert resources.length == 1;
119 assert WebViewBrowserActivity.RESOURCE_GEO.equals(resources[0]);
120 mCallback.invoke(mOrigin, true, false);
121 }
122
123 public void deny() {
124 mCallback.invoke(mOrigin, false, false);
125 }
126 }
127
128 // For simplicity, also treat the read access needed for file:// URLs as a r egular
129 // PermissionRequest.
130 private class FilePermissionRequest extends PermissionRequest {
131 private String mOrigin;
132
133 public FilePermissionRequest(String origin) {
134 mOrigin = origin;
135 }
136
137 public Uri getOrigin() {
138 return Uri.parse(mOrigin);
139 }
140
141 public String[] getResources() {
142 return new String[] { WebViewBrowserActivity.RESOURCE_FILE_URL };
143 }
144
145 public void grant(String[] resources) {
146 assert resources.length == 1;
147 assert WebViewBrowserActivity.RESOURCE_FILE_URL.equals(resources[0]) ;
148 // Try again now that we have read access.
149 WebViewBrowserActivity.this.mWebView.loadUrl(mOrigin);
150 }
151
152 public void deny() {
153 // womp womp
154 }
155 }
156
157 @Override
158 public void onCreate(Bundle savedInstanceState) {
159 super.onCreate(savedInstanceState);
160 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
161 WebView.setWebContentsDebuggingEnabled(true);
162 }
163 setContentView(R.layout.activity_webview_browser);
164 mUrlBar = (EditText) findViewById(R.id.url_field);
165 mUrlBar.setOnKeyListener(new OnKeyListener() {
166 public boolean onKey(View view, int keyCode, KeyEvent event) {
167 if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == Ke yEvent.ACTION_UP) {
168 loadUrlFromUrlBar(view);
169 return true;
170 }
171 return false;
172 }
173 });
174
175 createAndInitializeWebView();
176
177 String url = getUrlFromIntent(getIntent());
178 if (url != null) {
179 setUrlBarText(url);
180 setUrlFail(false);
181 loadUrlFromUrlBar(mUrlBar);
182 }
183 }
184
185 ViewGroup getContainer() {
186 return (ViewGroup) findViewById(R.id.container);
187 }
188
189 private void createAndInitializeWebView() {
190 WebView webview = new WebView(this);
191 WebSettings settings = webview.getSettings();
192 initializeSettings(settings);
193
194 Matcher matcher = WEBVIEW_VERSION_PATTERN.matcher(settings.getUserAgentS tring());
195 if (matcher.find()) {
196 mWebViewVersion = matcher.group(2);
197 } else {
198 mWebViewVersion = "-";
199 }
200 setTitle(getResources().getString(R.string.title_activity_browser) + " " + mWebViewVersion);
201
202 webview.setWebViewClient(new WebViewClient() {
203 @Override
204 public void onPageStarted(WebView view, String url, Bitmap favicon) {
205 setUrlBarText(url);
206 }
207
208 @Override
209 public void onPageFinished(WebView view, String url) {
210 setUrlBarText(url);
211 }
212
213 @Override
214 public boolean shouldOverrideUrlLoading(WebView webView, String url) {
215 // "about:" and "chrome:" schemes are internal to Chromium;
216 // don't want these to be dispatched to other apps.
217 if (url.startsWith("about:") || url.startsWith("chrome:")) {
218 return false;
219 }
220 return startBrowsingIntent(WebViewBrowserActivity.this, url);
221 }
222
223 @Override
224 public void onReceivedError(WebView view, int errorCode, String desc ription,
225 String failingUrl) {
226 setUrlFail(true);
227 }
228 });
229
230 webview.setWebChromeClient(new WebChromeClient() {
231 @Override
232 public Bitmap getDefaultVideoPoster() {
233 return Bitmap.createBitmap(
234 new int[] {Color.TRANSPARENT}, 1, 1, Bitmap.Config.ARGB_ 8888);
235 }
236
237 @Override
238 public void onGeolocationPermissionsShowPrompt(String origin,
239 GeolocationPermissions.Callback callback) {
240 onPermissionRequest(new GeoPermissionRequest(origin, callback));
241 }
242
243 @Override
244 public void onPermissionRequest(PermissionRequest request) {
245 WebViewBrowserActivity.this.requestPermissionsForPage(request);
246 }
247 });
248
249 mWebView = webview;
250 getContainer().addView(
251 webview, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParam s.MATCH_PARENT));
252 setUrlBarText("");
253 }
254
255 // WebKit permissions which can be granted because either they have no assoc iated Android
256 // permission or the associated Android permission has been granted
257 private boolean canGrant(String webkitPermission) {
258 String androidPermission = sPermissions.get(webkitPermission);
259 if (androidPermission == NO_ANDROID_PERMISSION) {
260 return true;
261 }
262 return PackageManager.PERMISSION_GRANTED == checkSelfPermission(androidP ermission);
263 }
264
265 private void requestPermissionsForPage(PermissionRequest request) {
266 // Deny any unrecognized permissions.
267 for (String webkitPermission : request.getResources()) {
268 if (!sPermissions.containsKey(webkitPermission)) {
269 Log.w(TAG, "Unrecognized WebKit permission: " + webkitPermission );
270 request.deny();
271 return;
272 }
273 }
274
275 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
276 request.grant(request.getResources());
277 return;
278 }
279
280 // Find what Android permissions we need before we can grant these WebKi t permissions.
281 ArrayList<String> androidPermissionsNeeded = new ArrayList<String>();
282 for (String webkitPermission : request.getResources()) {
283 if (!canGrant(webkitPermission)) {
284 // We already checked for unrecognized permissions, and canGrant will skip over
285 // NO_ANDROID_PERMISSION cases, so this is guaranteed to be a re gular Android
286 // permission.
287 String androidPermission = sPermissions.get(webkitPermission);
288 androidPermissionsNeeded.add(androidPermission);
289 }
290 }
291
292 // If there are no such Android permissions, grant the WebKit permission s immediately.
293 if (androidPermissionsNeeded.isEmpty()) {
294 request.grant(request.getResources());
295 return;
296 }
297
298 // Otherwise, file a new request
299 if (mNextRequestKey == Integer.MAX_VALUE) {
300 Log.e(TAG, "Too many permission requests");
301 return;
302 }
303 int requestCode = mNextRequestKey;
304 mNextRequestKey++;
305 mPendingRequests.append(requestCode, request);
306 requestPermissions(androidPermissionsNeeded.toArray(new String[0]), requ estCode);
307 }
308
309 @Override
310 public void onRequestPermissionsResult(int requestCode,
311 String permissions[], int[] grantResults) {
312 // Verify that we can now grant all the requested permissions. Note that although grant()
313 // takes a list of permissions, grant() is actually all-or-nothing. If t here are any
314 // requested permissions not included in the granted permissions, all wi ll be denied.
315 PermissionRequest request = mPendingRequests.get(requestCode);
316 for (String webkitPermission : request.getResources()) {
317 if (!canGrant(webkitPermission)) {
318 request.deny();
319 return;
320 }
321 }
322 request.grant(request.getResources());
323 mPendingRequests.delete(requestCode);
324 }
325
326 public void loadUrlFromUrlBar(View view) {
327 String url = mUrlBar.getText().toString();
328 try {
329 URI uri = new URI(url);
330 url = (uri.getScheme() == null) ? "http://" + uri.toString() : uri.t oString();
331 } catch (URISyntaxException e) {
332 String message = "<html><body>URISyntaxException: " + e.getMessage() + "</body></html>";
333 mWebView.loadData(message, "text/html", "UTF-8");
334 setUrlFail(true);
335 return;
336 }
337
338 setUrlBarText(url);
339 setUrlFail(false);
340 loadUrl(url);
341 hideKeyboard(mUrlBar);
342 }
343
344 public void showPopup(View v) {
345 PopupMenu popup = new PopupMenu(this, v);
346 popup.setOnMenuItemClickListener(this);
347 popup.inflate(R.menu.main_menu);
348 popup.show();
349 }
350
351 @Override
352 public boolean onMenuItemClick(MenuItem item) {
353 switch(item.getItemId()) {
354 case R.id.menu_reset_webview:
355 if (mWebView != null) {
356 ViewGroup container = getContainer();
357 container.removeView(mWebView);
358 mWebView.destroy();
359 mWebView = null;
360 }
361 createAndInitializeWebView();
362 return true;
363 case R.id.menu_clear_cache:
364 if (mWebView != null) {
365 mWebView.clearCache(true);
366 }
367 return true;
368 case R.id.menu_about:
369 about();
370 hideKeyboard(mUrlBar);
371 return true;
372 default:
373 return false;
374 }
375 }
376
377 private void initializeSettings(WebSettings settings) {
378 settings.setJavaScriptEnabled(true);
379
380 // configure local storage apis and their database paths.
381 settings.setAppCachePath(getDir("appcache", 0).getPath());
382 settings.setGeolocationDatabasePath(getDir("geolocation", 0).getPath());
383 settings.setDatabasePath(getDir("databases", 0).getPath());
384
385 settings.setAppCacheEnabled(true);
386 settings.setGeolocationEnabled(true);
387 settings.setDatabaseEnabled(true);
388 settings.setDomStorageEnabled(true);
389 }
390
391 private void about() {
392 WebSettings settings = mWebView.getSettings();
393 StringBuilder summary = new StringBuilder();
394 summary.append("WebView version : " + mWebViewVersion + "\n");
395
396 for (Method method : settings.getClass().getMethods()) {
397 if (!methodIsSimpleInspector(method)) continue;
398 try {
399 summary.append(method.getName() + " : " + method.invoke(settings ) + "\n");
400 } catch (IllegalAccessException e) {
401 } catch (InvocationTargetException e) { }
402 }
403
404 AlertDialog dialog = new AlertDialog.Builder(this)
405 .setTitle(getResources().getString(R.string.menu_about))
406 .setMessage(summary)
407 .setPositiveButton("OK", null)
408 .create();
409 dialog.show();
410 dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL _PARENT);
411 }
412
413 // Returns true is a method has no arguments and returns either a boolean or a String.
414 private boolean methodIsSimpleInspector(Method method) {
415 Class<?> returnType = method.getReturnType();
416 return ((returnType.equals(boolean.class) || returnType.equals(String.cl ass))
417 && method.getParameterTypes().length == 0);
418 }
419
420 private void loadUrl(String url) {
421 // Request read access if necessary
422 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
423 && "file".equals(Uri.parse(url).getScheme())
424 && PackageManager.PERMISSION_DENIED
425 == checkSelfPermission(Manifest.permission.READ_EXTERNAL _STORAGE)) {
426 requestPermissionsForPage(new FilePermissionRequest(url));
427 }
428
429 // If it is file:// and we don't have permission, they'll get the "Webpa ge not available"
430 // "net::ERR_ACCESS_DENIED" page. When we get permission, FilePermission Request.grant()
431 // will reload.
432 mWebView.loadUrl(url);
433 mWebView.requestFocus();
434 }
435
436 private void setUrlBarText(String url) {
437 mUrlBar.setText(url, TextView.BufferType.EDITABLE);
438 }
439
440 private void setUrlFail(boolean fail) {
441 mUrlBar.setTextColor(fail ? Color.RED : Color.BLACK);
442 }
443
444 /**
445 * Hides the keyboard.
446 * @param view The {@link View} that is currently accepting input.
447 * @return Whether the keyboard was visible before.
448 */
449 private static boolean hideKeyboard(View view) {
450 InputMethodManager imm = (InputMethodManager) view.getContext().getSyste mService(
451 Context.INPUT_METHOD_SERVICE);
452 return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
453 }
454
455 private static String getUrlFromIntent(Intent intent) {
456 return intent != null ? intent.getDataString() : null;
457 }
458
459 static final Pattern BROWSER_URI_SCHEMA = Pattern.compile(
460 "(?i)" // switch on case insensitive matching
461 + "(" // begin group for schema
462 + "(?:http|https|file):\\/\\/"
463 + "|(?:inline|data|about|chrome|javascript):"
464 + ")"
465 + "(.*)");
466
467 private static boolean startBrowsingIntent(Context context, String url) {
468 Intent intent;
469 // Perform generic parsing of the URI to turn it into an Intent.
470 try {
471 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
472 } catch (Exception ex) {
473 Log.w(TAG, "Bad URI %s", url, ex);
474 return false;
475 }
476 // Check for regular URIs that WebView supports by itself, but also
477 // check if there is a specialized app that had registered itself
478 // for this kind of an intent.
479 Matcher m = BROWSER_URI_SCHEMA.matcher(url);
480 if (m.matches() && !isSpecializedHandlerAvailable(context, intent)) {
481 return false;
482 }
483 // Sanitize the Intent, ensuring web pages can not bypass browser
484 // security (only access to BROWSABLE activities).
485 intent.addCategory(Intent.CATEGORY_BROWSABLE);
486 intent.setComponent(null);
487 Intent selector = intent.getSelector();
488 if (selector != null) {
489 selector.addCategory(Intent.CATEGORY_BROWSABLE);
490 selector.setComponent(null);
491 }
492
493 // Pass the package name as application ID so that the intent from the
494 // same application can be opened in the same tab.
495 intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
496 try {
497 context.startActivity(intent);
498 return true;
499 } catch (ActivityNotFoundException ex) {
500 Log.w(TAG, "No application can handle %s", url);
501 }
502 return false;
503 }
504
505 /**
506 * Search for intent handlers that are specific to the scheme of the URL in the intent.
507 */
508 private static boolean isSpecializedHandlerAvailable(Context context, Intent intent) {
509 PackageManager pm = context.getPackageManager();
510 List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
511 PackageManager.GET_RESOLVED_FILTER);
512 if (handlers == null || handlers.size() == 0) {
513 return false;
514 }
515 for (ResolveInfo resolveInfo : handlers) {
516 if (!isNullOrGenericHandler(resolveInfo.filter)) {
517 return true;
518 }
519 }
520 return false;
521 }
522
523 private static boolean isNullOrGenericHandler(IntentFilter filter) {
524 return filter == null
525 || (filter.countDataAuthorities() == 0 && filter.countDataPaths( ) == 0);
526 }
527 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698