Key art for blog post "What You Need to Know About the WebView in L "

What You Need to Know About the WebView in L

WebView Updated via Play Store

OMG Reaction Gif

With the L release, the WebView can be updated by the Play Store, this is a huge achievement!

Major New Features

WebRTC in the WebView

The following features are now supported in the latest WebView:

  1. WebRTC (Full Sample Here)
  2. WebAudio
  3. WebGL
  4. Native support for all web components APIs

Subtle Behaviour Changes

There are a few things that have changed which aren't obvious at first.

  1. Using wrap_contents has always caused the WebView to ignore viewport meta tags, however in L, viewports are now taken into account.

  2. To be as secure as possible by default, third party cookies are now off by default. If you need to turn them on then you can do so with this little snippet of code.

    // AppRTC requires third party cookies to work
    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.setAcceptThirdPartyCookies(mWebView, true);
  3. Fullscreen video has caused a few issues where developers hadn't properly implemented the callbacks. I've put a sample up on GitHub to show how the callbacks can be used. This includes when to show / not show the fullscreen button based on whether the callbacks onShowCustomView and onHideCustomView have been implemented or not.

Fullscreen Video Sample

New API's

With the release of L, there have been some additional API's added.

Permissions

Web API's like getUserMedia(), need to have access to the devices Microphone and Camera, which means the WebView needs a permission model to grant or deny these requests.

The key new API to control this is the onPermissionRequest() method in WebChromeClient.

Below is a simple example that grants any permission that comes from http://apprtc-m.appspot.com/.

mWebView.setWebChromeClient(new WebChromeClient() {

    @Override
    public void onPermissionRequest(final PermissionRequest request) {
        Log.d(TAG, "onPermissionRequest");
        getActivity().runOnUiThread(new Runnable() {
            @TargetApi(Build.VERSION_CODES.L)
            @Override
            public void run() {
                if(request.getOrigin().toString().equals("https://apprtc-m.appspot.com/")) {
                    request.grant(request.getResources());
                } else {
                    request.deny();
                }
            }
        });
    }
});

Note: Your native app needs the AndroidManifest permissions before it can grant permission forward to a WebView.

The above was taken from the WebRTC sample on GitHub.

File Upload API

Input File Sample App

A long standing feature request has been for support of the input field for files.

In L, we've added the [onShowFileChooser() method](http://developer.android.com/reference/android/webkit/WebChromeClient.html#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams)). This gets called when an input field is clicked and you can show a file selection UI as a result of this.

You can find a full demo application here.

Below is an example implementation of the onShowFileChooser() method which sets up an Intent chooser to take a photo or get an existing image from the gallery.

mWebView.setWebChromeClient(new WebChromeClient() {
  public boolean onShowFileChooser(
      WebView webView, ValueCallback<Uri[]> filePathCallback,
      WebChromeClient.FileChooserParams fileChooserParams) {

    // Double check that we don't have any existing callbacks
    if(mFilePathCallback != null) {
        mFilePathCallback.onReceiveValue(null);
    }
    mFilePathCallback = filePathCallback;

    // Set up the take picture intent
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
      // Create the File where the photo should go
      File photoFile = null;
      try {
        photoFile = createImageFile();
        takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
      } catch (IOException ex) {
        // Error occurred while creating the File
        Log.e(TAG, "Unable to create Image File", ex);
      }

      // Continue only if the File was successfully created
      if (photoFile != null) {
        mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
            Uri.fromFile(photoFile));
      } else {
        takePictureIntent = null;
      }
    }

    // Set up the intent to get an existing image
    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType("image/*");

    // Set up the intents for the Intent chooser
    Intent[] intentArray;
    if(takePictureIntent != null) {
      intentArray = new Intent[]{takePictureIntent};
    } else {
      intentArray = new Intent[0];
    }

    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

    startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);

    return true;
  }
});

Notice that at the very end we call startActivityForResult(), this means we just need the corresponding method onActivityResult() where we can call the filePathCallback.onReceiveValue() once we've finished.

@Override
public void onActivityResult (int requestCode, int resultCode, Intent data) {
  if(requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
    super.onActivityResult(requestCode, resultCode, data);
    return;
  }

  Uri[] results = null;

  // Check that the response is a good one
  if(resultCode == Activity.RESULT_OK) {
    if(data == null) {
      // If there is not data, then we may have taken a photo
      if(mCameraPhotoPath != null) {
        results = new Uri[]{Uri.parse(mCameraPhotoPath)};
      }
    } else {
      String dataString = data.getDataString();
      if (dataString != null) {
        results = new Uri[]{Uri.parse(dataString)};
      }
    }
  }

  mFilePathCallback.onReceiveValue(results);
  mFilePathCallback = null;
  return;
}

You can find the full sample on GitHub.