Add visual state callbacks to the WebView.

Registering a visual state callback allows the caller to be notified
after the commit, activation and swap of the current (or future) state
of the DOM tree has occurred. At the point at which the callback is
called, the caller can be sure that any DOM updates made prior to
the registration are ready to be drawn in the next WebView#onDraw.

We also provide a convenience callback related to the visual state:

* WebViewClient.onPageCommitVisible; called at the earliest point at
  which the next draw will not render contents from the previously
  loaded page.

Bug: 6375170
Change-Id: I17e706b6e6ba4a8c28c835552687c9f7a4623024
diff --git a/api/current.txt b/api/current.txt
index 7bac613..c0466fc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37444,6 +37444,7 @@
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
+    method public void insertVisualStateCallback(long, android.webkit.WebView.VisualStateCallback);
     method public void invokeZoomPicker();
     method public boolean isPrivateBrowsingEnabled();
     method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -37518,6 +37519,11 @@
     method public abstract deprecated void onNewPicture(android.webkit.WebView, android.graphics.Picture);
   }
 
+  public static abstract class WebView.VisualStateCallback {
+    ctor public WebView.VisualStateCallback();
+    method public abstract void onComplete(long);
+  }
+
   public class WebView.WebViewTransport {
     ctor public WebView.WebViewTransport();
     method public synchronized android.webkit.WebView getWebView();
@@ -37529,6 +37535,7 @@
     method public void doUpdateVisitedHistory(android.webkit.WebView, java.lang.String, boolean);
     method public void onFormResubmission(android.webkit.WebView, android.os.Message, android.os.Message);
     method public void onLoadResource(android.webkit.WebView, java.lang.String);
+    method public void onPageCommitVisible(android.webkit.WebView, java.lang.String);
     method public void onPageFinished(android.webkit.WebView, java.lang.String);
     method public void onPageStarted(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
     method public void onReceivedClientCertRequest(android.webkit.WebView, android.webkit.ClientCertRequest);
diff --git a/api/system-current.txt b/api/system-current.txt
index 89c0460e..d16386d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -39876,6 +39876,7 @@
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
+    method public void insertVisualStateCallback(long, android.webkit.WebView.VisualStateCallback);
     method public void invokeZoomPicker();
     method public boolean isPrivateBrowsingEnabled();
     method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -39980,6 +39981,11 @@
     method public void super_setLayoutParams(android.view.ViewGroup.LayoutParams);
   }
 
+  public static abstract class WebView.VisualStateCallback {
+    ctor public WebView.VisualStateCallback();
+    method public abstract void onComplete(long);
+  }
+
   public class WebView.WebViewTransport {
     ctor public WebView.WebViewTransport();
     method public synchronized android.webkit.WebView getWebView();
@@ -39991,6 +39997,7 @@
     method public void doUpdateVisitedHistory(android.webkit.WebView, java.lang.String, boolean);
     method public void onFormResubmission(android.webkit.WebView, android.os.Message, android.os.Message);
     method public void onLoadResource(android.webkit.WebView, java.lang.String);
+    method public void onPageCommitVisible(android.webkit.WebView, java.lang.String);
     method public void onPageFinished(android.webkit.WebView, java.lang.String);
     method public void onPageStarted(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
     method public void onReceivedClientCertRequest(android.webkit.WebView, android.webkit.ClientCertRequest);
@@ -40137,6 +40144,7 @@
     method public abstract void goBackOrForward(int);
     method public abstract void goForward();
     method public abstract void init(java.util.Map<java.lang.String, java.lang.Object>, boolean);
+    method public abstract void insertVisualStateCallback(long, android.webkit.WebView.VisualStateCallback);
     method public abstract void invokeZoomPicker();
     method public abstract boolean isPaused();
     method public abstract boolean isPrivateBrowsingEnabled();
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 67ad642..6711a6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -364,6 +364,20 @@
     }
 
     /**
+     * Callback interface supplied to {@link #insertVisualStateCallback} for receiving
+     * notifications about the visual state.
+     */
+    public static abstract class VisualStateCallback {
+        /**
+         * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
+         *
+         * @param requestId the id supplied to the corresponding {@link #insertVisualStateCallback}
+         * request
+         */
+        public abstract void onComplete(long requestId);
+    }
+
+    /**
      * Interface to listen for new pictures as they change.
      *
      * @deprecated This interface is now obsolete.
@@ -1144,6 +1158,60 @@
     }
 
     /**
+     * Inserts a {@link VisualStateCallback}.
+     *
+     * <p>Updates to the the DOM are reflected asynchronously such that when the DOM is updated the
+     * subsequent {@link WebView#onDraw} invocation might not reflect those updates. The
+     * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
+     * the DOM at the current time are ready to be drawn the next time the {@link WebView} draws.
+     * By current time we mean the time at which this API was called. The next draw after the
+     * callback completes is guaranteed to reflect all the updates to the DOM applied before the
+     * current time, but it may also contain updates applied after the current time.</p>
+     *
+     * <p>The state of the DOM covered by this API includes the following:
+     * <ul>
+     * <li>primitive HTML elements (div, img, span, etc..)</li>
+     * <li>images</li>
+     * <li>CSS animations</li>
+     * <li>WebGL</li>
+     * <li>canvas</li>
+     * </ul>
+     * It does not include the state of:
+     * <ul>
+     * <li>the video tag</li>
+     * </ul></p>
+     *
+     * <p>To guarantee that the {@link WebView} will successfully render the first frame
+     * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
+     * must be met:
+     * <ul>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
+     * the {@link WebView} must be attached to the view hierarchy.</li>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
+     * then the {@link WebView} must be attached to the view hierarchy and must be made
+     * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
+     * {@link WebView} must be attached to the view hierarchy and its
+     * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
+     * values and must be made {@link View#VISIBLE VISIBLE} from the
+     * {@link VisualStateCallback#onComplete} method.</li>
+     * </ul></p>
+     *
+     * <p>When using this API it is also recommended to enable pre-rasterization if the
+     * {@link WebView} is offscreen to avoid flickering. See WebSettings#setOffscreenPreRaster for
+     * more details and do consider its caveats.</p>
+     *
+     * @param requestId an id that will be returned in the callback to allow callers to match
+     * requests with callbacks.
+     * @param callback the callback to be invoked.
+     */
+    public void insertVisualStateCallback(long requestId, VisualStateCallback callback) {
+        checkThread();
+        if (TRACE) Log.d(LOGTAG, "insertVisualStateCallback");
+        mProvider.insertVisualStateCallback(requestId, callback);
+    }
+
+    /**
      * Clears this WebView so that onDraw() will draw nothing but white background,
      * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
      * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 34b8cf6..53c7e04 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -83,6 +83,32 @@
     }
 
     /**
+     * Notify the host application that the page commit is visible.
+     *
+     * <p>This is the earliest point at which we can guarantee that the contents of the previously
+     * loaded page will not longer be drawn in the next {@link WebView#onDraw}. The next draw will
+     * render the {@link WebView#setBackgroundColor background color} of the WebView or some of the
+     * contents from the committed page already. This callback may be useful when reusing
+     * {@link WebView}s to ensure that no stale content is shown. This method is only called for
+     * the main frame.</p>
+     *
+     * <p>This method is called when the state of the DOM at the point at which the
+     * body of the HTTP response (commonly the string of html) had started loading will be visible.
+     * If you set a background color for the page in the HTTP response body this will most likely
+     * be visible and perhaps some other elements. At that point no other resources had usually
+     * been loaded, so you can expect images for example to not be visible. If you want
+     * a finer level of granularity consider calling {@link WebView#insertVisualStateCallback}
+     * directly.</p>
+     *
+     * <p>Please note that all the conditions and recommendations presented in
+     * {@link WebView#insertVisualStateCallback} also apply to this API.<p>
+     *
+     * @param url the url of the committed page
+     */
+    public void onPageCommitVisible(WebView view, String url) {
+    }
+
+    /**
      * Notify the host application of a resource request and allow the
      * application to return the data.  If the return value is null, the WebView
      * will continue to load the resource as usual.  Otherwise, the return
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 0cdb875..fa2ce1b 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -40,6 +40,8 @@
 import android.view.inputmethod.InputConnection;
 import android.webkit.WebView.HitTestResult;
 import android.webkit.WebView.PictureListener;
+import android.webkit.WebView.VisualStateCallback;
+
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -146,6 +148,8 @@
 
     public boolean pageDown(boolean bottom);
 
+    public void insertVisualStateCallback(long requestId, VisualStateCallback callback);
+
     public void clearView();
 
     public Picture capturePicture();