| OLD | NEW |
| (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 } | |
| OLD | NEW |