SearchBox preloading fixes.

It now seems to work pretty reliably. Nice. Requires changes to SearchBox
API in frameworks/base:
Depends on change: If283ecdfa62aecb1fa697b1a2cd43b771b908d72

Change-Id: I5af94c8df8f24dfafb02c4052381aa547c72684c
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 5b00179..b495cc0 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -679,9 +679,7 @@
         if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) {
             CookieSyncManager.getInstance().startSync();
             WebView w = tab.getWebView();
-            if (w != null) {
-                w.resumeTimers();
-            }
+            WebViewTimersControl.getInstance().onBrowserActivityResume(w);
         }
     }
 
@@ -695,10 +693,7 @@
             return true;
         } else if (!tab.inPageLoad()) {
             CookieSyncManager.getInstance().stopSync();
-            WebView w = getCurrentWebView();
-            if (w != null) {
-                w.pauseTimers();
-            }
+            WebViewTimersControl.getInstance().onBrowserActivityPause(getCurrentWebView());
             return true;
         }
         return false;
diff --git a/src/com/android/browser/GoogleAccountLogin.java b/src/com/android/browser/GoogleAccountLogin.java
index c7d1924..3b4f2aa 100644
--- a/src/com/android/browser/GoogleAccountLogin.java
+++ b/src/com/android/browser/GoogleAccountLogin.java
@@ -84,7 +84,7 @@
         // resumeWebViewTimers. So to avoid problems with timers not running, we
         // duplicate the work here using the off-screen WebView.
         CookieSyncManager.getInstance().startSync();
-        mWebView.resumeTimers();
+        WebViewTimersControl.getInstance().onBrowserActivityResume(mWebView);
 
         mWebView.setWebViewClient(new WebViewClient() {
             @Override
diff --git a/src/com/android/browser/InstantSearchEngine.java b/src/com/android/browser/InstantSearchEngine.java
index e2e9c8a..c913494 100644
--- a/src/com/android/browser/InstantSearchEngine.java
+++ b/src/com/android/browser/InstantSearchEngine.java
@@ -85,10 +85,10 @@
 
         mSearchBox.setQuery(query);
         mSearchBox.setVerbatim(true);
-        mSearchBox.onsubmit();
+        mSearchBox.onsubmit(null);
     }
 
-    private final class BrowserSearchboxListener implements SearchBox.SearchBoxListener {
+    private final class BrowserSearchboxListener extends SearchBox.SearchBoxListener {
         /*
          * The maximum number of out of order suggestions we accept
          * before giving up the wait.
@@ -253,7 +253,7 @@
         }
 
         mSearchBox.setDimensions(0, 0, 0, mHeight);
-        mSearchBox.onresize();
+        mSearchBox.onresize(null);
 
         if (TextUtils.isEmpty(query)) {
             // To force the SRP to render an empty (no results) page.
@@ -262,7 +262,7 @@
             mSearchBox.setVerbatim(false);
         }
         mSearchBox.setQuery(query);
-        mSearchBox.onchange();
+        mSearchBox.onchange(null);
 
         // Don't bother waiting for suggestions for an empty query. We still
         // set the query so that the SRP clears itself.
@@ -327,7 +327,7 @@
             mHeight = rescaledHeight;
             if (mSearchBox != null) {
                 mSearchBox.setDimensions(0, 0, 0, rescaledHeight);
-                mSearchBox.onresize();
+                mSearchBox.onresize(null);
             }
         }
     }
diff --git a/src/com/android/browser/PreloadController.java b/src/com/android/browser/PreloadController.java
index 6528410..11586ea 100644
--- a/src/com/android/browser/PreloadController.java
+++ b/src/com/android/browser/PreloadController.java
@@ -22,6 +22,7 @@
 import android.net.Uri;
 import android.net.http.SslError;
 import android.os.Message;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.webkit.HttpAuthHandler;
@@ -34,6 +35,9 @@
 
 public class PreloadController implements WebViewController {
 
+    private static final boolean LOGD_ENABLED = false;
+    private static final String LOGTAG = "PreloadController";
+
     private Context mContext;
 
     public PreloadController(Context ctx) {
@@ -48,177 +52,214 @@
 
     @Override
     public Activity getActivity() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getActivity()");
         return null;
     }
 
     @Override
     public TabControl getTabControl() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getTabControl()");
         return null;
     }
 
     @Override
     public WebViewFactory getWebViewFactory() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getWebViewFactory()");
         return null;
     }
 
     @Override
     public void onSetWebView(Tab tab, WebView view) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onSetWebView()");
     }
 
     @Override
     public void createSubWindow(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "createSubWindow()");
     }
 
     @Override
     public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onPageStarted()");
     }
 
     @Override
     public void onPageFinished(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onPageFinished()");
     }
 
     @Override
     public void onProgressChanged(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onProgressChanged()");
     }
 
     @Override
     public void onReceivedTitle(Tab tab, String title) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onReceivedTitle()");
     }
 
     @Override
     public void onFavicon(Tab tab, WebView view, Bitmap icon) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onFavicon()");
     }
 
     @Override
     public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "shouldOverrideUrlLoading()");
         return false;
     }
 
     @Override
     public boolean shouldOverrideKeyEvent(KeyEvent event) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "shouldOverrideKeyEvent()");
         return false;
     }
 
     @Override
     public void onUnhandledKeyEvent(KeyEvent event) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onUnhandledKeyEvent()");
     }
 
     @Override
     public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "doUpdateVisitedHistory()");
     }
 
     @Override
     public void getVisitedHistory(ValueCallback<String[]> callback) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getVisitedHistory()");
     }
 
     @Override
     public void onReceivedHttpAuthRequest(Tab tab, WebView view,
                                     HttpAuthHandler handler, String host,
                                     String realm) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onReceivedHttpAuthRequest()");
     }
 
     @Override
     public void onDownloadStart(Tab tab, String url, String useragent,
                                     String contentDisposition, String mimeType,
                                     long contentLength) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onDownloadStart()");
     }
 
     @Override
     public void showCustomView(Tab tab, View view, int requestedOrientation,
                                     CustomViewCallback callback) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "showCustomView()");
     }
 
     @Override
     public void hideCustomView() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "hideCustomView()");
     }
 
     @Override
     public Bitmap getDefaultVideoPoster() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getDefaultVideoPoster()");
         return null;
     }
 
     @Override
     public View getVideoLoadingProgressView() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "getVideoLoadingProgressView()");
         return null;
     }
 
     @Override
     public void showSslCertificateOnError(WebView view,
                                     SslErrorHandler handler, SslError error) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "showSslCertificateOnError()");
     }
 
     @Override
     public void onUserCanceledSsl(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onUserCanceledSsl()");
     }
 
     @Override
     public void activateVoiceSearchMode(String title, List<String> results) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "activateVoiceSearchMode()");
     }
 
     @Override
     public void revertVoiceSearchMode(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "revertVoiceSearchMode()");
     }
 
     @Override
     public boolean shouldShowErrorConsole() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "shouldShowErrorConsole()");
         return false;
     }
 
     @Override
     public void onUpdatedLockIcon(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onUpdatedLockIcon()");
     }
 
     @Override
     public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "openFileChooser()");
     }
 
     @Override
     public void endActionMode() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "endActionMode()");
     }
 
     @Override
     public void attachSubWindow(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "attachSubWindow()");
     }
 
     @Override
     public void dismissSubWindow(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "dismissSubWindow()");
     }
 
     @Override
-    public Tab openTab(String url, boolean incognito, boolean setActive,
-                                    boolean useCurrent) {
+    public Tab openTab(String url, boolean incognito, boolean setActive, boolean useCurrent) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "openTab()");
         return null;
     }
 
     @Override
-    public Tab openTab(String url, Tab parent, boolean setActive,
-                                    boolean useCurrent) {
+    public Tab openTab(String url, Tab parent, boolean setActive, boolean useCurrent) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "openTab()");
         return null;
     }
 
     @Override
     public boolean switchToTab(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "switchToTab()");
         return false;
     }
 
     @Override
     public void closeTab(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "closeTab()");
     }
 
     @Override
     public void setupAutoFill(Message message) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "setupAutoFill()");
     }
 
     @Override
     public void bookmarkedStatusHasChanged(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "bookmarkedStatusHasChanged()");
     }
 
     @Override
     public void showAutoLogin(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "showAutoLogin()");
     }
 
     @Override
     public void hideAutoLogin(Tab tab) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "hideAutoLogin()");
     }
 
 }
diff --git a/src/com/android/browser/PreloadedTabControl.java b/src/com/android/browser/PreloadedTabControl.java
index 99592fb..d2482a4 100644
--- a/src/com/android/browser/PreloadedTabControl.java
+++ b/src/com/android/browser/PreloadedTabControl.java
@@ -15,6 +15,7 @@
  */
 package com.android.browser;
 
+import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.SearchBox;
@@ -25,7 +26,7 @@
  * Class to manage the controlling of preloaded tab.
  */
 public class PreloadedTabControl {
-    private static final boolean LOGD_ENABLED = true;//com.android.browser.Browser.LOGD_ENABLED;
+    private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
     private static final String LOGTAG = "PreloadedTabControl";
 
     final Tab mTab;
@@ -33,17 +34,26 @@
     private boolean mDestroyed;
 
     public PreloadedTabControl(Tab t) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "PreloadedTabControl.<init>");
         mTab = t;
     }
 
-    private void maybeSetQuery(String query, SearchBox sb) {
+    private void maybeSetQuery(final String query, SearchBox sb) {
         if (!TextUtils.equals(mLastQuery, query)) {
             if (sb != null) {
                 if (LOGD_ENABLED) Log.d(LOGTAG, "Changing searchbox query to " + query);
                 sb.setVerbatim(true);
                 sb.setQuery(query);
-                sb.onchange();
-                mLastQuery = query;
+                sb.onchange(new SearchBox.SearchBoxListener() {
+                    @Override
+                    public void onChangeComplete(boolean called) {
+                        if (mDestroyed) return;
+                        if (LOGD_ENABLED) Log.d(LOGTAG, "Changed searchbox query: " + called);
+                        if (called) {
+                            mLastQuery = query;
+                        }
+                    }
+                });
             } else {
                 if (LOGD_ENABLED) Log.d(LOGTAG, "Cannot set query: no searchbox interface");
             }
@@ -62,30 +72,34 @@
             if (LOGD_ENABLED) Log.d(LOGTAG, "No searchbox, cannot submit query");
             return false;
         }
-        sb.isSupported(new SearchBox.IsSupportedCallback() {
+        maybeSetQuery(query, sb);
+        if (LOGD_ENABLED) Log.d(LOGTAG, "Submitting query " + query);
+        sb.onsubmit(new SearchBox.SearchBoxListener() {
             @Override
-            public void searchBoxIsSupported(boolean supported) {
-                if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox supported: " + supported);
-                if (mDestroyed) {
-                    if (LOGD_ENABLED) Log.d(LOGTAG, "tab has been destroyed");
-                    return;
-                }
-                if (supported) {
-                    maybeSetQuery(query, sb);
-                    if (LOGD_ENABLED) Log.d(LOGTAG, "Submitting query " + query);
-                    sb.onsubmit();
-                } else {
-                    if (LOGD_ENABLED) Log.d(LOGTAG, "SearchBox not supported; falling back");
+            public void onSubmitComplete(boolean called) {
+                if (mDestroyed) return;
+                if (LOGD_ENABLED) Log.d(LOGTAG, "Query submitted: " + called);
+                if (!called) {
+                    if (LOGD_ENABLED) Log.d(LOGTAG, "Query not submitted; falling back");
                     loadUrl(fallbackUrl, fallbackHeaders);
                 }
                 mTab.getWebView().clearHistory();
-            }
-        });
+            }});
         return true;
     }
 
     public void loadUrlIfChanged(String url, Map<String, String> headers) {
-        if (!TextUtils.equals(url, mTab.getUrl())) {
+        String currentUrl = mTab.getUrl();
+        if (!TextUtils.isEmpty(currentUrl)) {
+            try {
+                // remove fragment:
+                currentUrl = Uri.parse(currentUrl).buildUpon().fragment(null).build().toString();
+            } catch (UnsupportedOperationException e) {
+                // carry on
+            }
+        }
+        if (LOGD_ENABLED) Log.d(LOGTAG, "loadUrlIfChanged\nnew: " + url + "\nold: " +currentUrl);
+        if (!TextUtils.equals(url, currentUrl)) {
             loadUrl(url, headers);
         }
     }
@@ -96,6 +110,7 @@
     }
 
     public void destroy() {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "PreloadedTabControl.destroy");
         mDestroyed = true;
         mTab.destroy();
     }
diff --git a/src/com/android/browser/Preloader.java b/src/com/android/browser/Preloader.java
index 336b77a..bc84523 100644
--- a/src/com/android/browser/Preloader.java
+++ b/src/com/android/browser/Preloader.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
+import android.webkit.WebView;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -62,6 +63,7 @@
             if (LOGD_ENABLED) Log.d(LOGTAG, "Create new preload session " + id);
             s = new PreloaderSession(id);
             mSessions.put(id, s);
+            WebViewTimersControl.getInstance().onPrerenderStart(s.getWebView());
         }
         return s;
     }
@@ -71,6 +73,9 @@
         if (s != null) {
             s.cancelTimeout();
         }
+        if (mSessions.size() == 0) {
+            WebViewTimersControl.getInstance().onPrerenderDone(s == null ? null : s.getWebView());
+        }
         return s;
     }
 
@@ -139,6 +144,11 @@
             return mTabControl;
         }
 
+        public WebView getWebView() {
+            Tab t = mTabControl.getTab();
+            return t == null? null : t.getWebView();
+        }
+
     }
 
 }
diff --git a/src/com/android/browser/WebViewTimersControl.java b/src/com/android/browser/WebViewTimersControl.java
new file mode 100644
index 0000000..d6d1726
--- /dev/null
+++ b/src/com/android/browser/WebViewTimersControl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.browser;
+
+import android.os.Looper;
+import android.util.Log;
+import android.webkit.WebView;
+
+/**
+ * Centralised point for controlling WebView timers pausing and resuming.
+ *
+ * All methods on this class should only be called from the UI thread.
+ */
+public class WebViewTimersControl {
+
+    private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+    private static final String LOGTAG = "WebViewTimersControl";
+
+    private static WebViewTimersControl sInstance;
+
+    private boolean mBrowserActive;
+    private boolean mPrerenderActive;
+
+    /**
+     * Get the static instance. Must be called from UI thread.
+     */
+    public static WebViewTimersControl getInstance() {
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new IllegalStateException("WebViewTimersControl.get() called on wrong thread");
+        }
+        if (sInstance == null) {
+            sInstance = new WebViewTimersControl();
+        }
+        return sInstance;
+    }
+
+    private WebViewTimersControl() {
+    }
+
+    private void resumeTimers(WebView wv) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "Resuming webview timers, view=" + wv);
+        if (wv != null) {
+            wv.resumeTimers();
+        }
+    }
+
+    private void maybePauseTimers(WebView wv) {
+        if (!mBrowserActive && !mPrerenderActive && wv != null) {
+            if (LOGD_ENABLED) Log.d(LOGTAG, "Pausing webview timers, view=" + wv);
+            wv.pauseTimers();
+        }
+    }
+
+    public void onBrowserActivityResume(WebView wv) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onBrowserActivityResume");
+        mBrowserActive = true;
+        resumeTimers(wv);
+    }
+
+    public void onBrowserActivityPause(WebView wv) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onBrowserActivityPause");
+        mBrowserActive = false;
+        maybePauseTimers(wv);
+    }
+
+    public void onPrerenderStart(WebView wv) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onPrerenderStart");
+        mPrerenderActive = true;
+        resumeTimers(wv);
+    }
+
+    public void onPrerenderDone(WebView wv) {
+        if (LOGD_ENABLED) Log.d(LOGTAG, "onPrerenderDone");
+        mPrerenderActive = false;
+        maybePauseTimers(wv);
+    }
+
+}