diff options
author | Mathew Inwood <mathewi@google.com> | 2011-09-05 18:58:48 +0100 |
---|---|---|
committer | Mathew Inwood <mathewi@google.com> | 2011-09-06 12:22:26 +0100 |
commit | b4e831bf1ff62945199b89a77ad039be7bd16545 (patch) | |
tree | a3a7dcf7f0de94487e03ec9b9904ab9cbdaa67ba | |
parent | 012dcd474cd3980aa206e4b7a31fa67114f56d54 (diff) | |
download | packages_apps_Browser-b4e831bf1ff62945199b89a77ad039be7bd16545.zip packages_apps_Browser-b4e831bf1ff62945199b89a77ad039be7bd16545.tar.gz packages_apps_Browser-b4e831bf1ff62945199b89a77ad039be7bd16545.tar.bz2 |
Support for measuring page load times.
An app can attached a pending intent to an intent sent to the browser
which will be send when the load completes. The browser will fill in
the timestamp of when the page load completed, and details of preloading
success or otherwise if this was used.
Bug: 5259031
Change-Id: I2d025caabd6055ac25f06e69897a03d5c57c4f41
-rw-r--r-- | src/com/android/browser/Controller.java | 34 | ||||
-rw-r--r-- | src/com/android/browser/IntentHandler.java | 66 | ||||
-rw-r--r-- | src/com/android/browser/PreloadedTabControl.java | 32 | ||||
-rw-r--r-- | src/com/android/browser/Tab.java | 37 |
4 files changed, 158 insertions, 11 deletions
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java index 67c42dd..d05a845 100644 --- a/src/com/android/browser/Controller.java +++ b/src/com/android/browser/Controller.java @@ -18,6 +18,7 @@ package com.android.browser; import android.app.Activity; import android.app.DownloadManager; +import android.app.PendingIntent; import android.app.SearchManager; import android.content.ClipboardManager; import android.content.ContentProvider; @@ -93,6 +94,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Controller for browser @@ -761,6 +763,7 @@ public class Controller public void stopLoading() { mLoadStopped = true; Tab tab = mTabControl.getCurrentTab(); + tab.clearPageLoadCompleteListener(); WebView w = getCurrentTopWebView(); w.stopLoading(); mUi.onPageStopped(tab); @@ -2154,7 +2157,8 @@ public class Controller final PreloadedTabControl tabControl = urlData.getPreloadedTab(); final String sbQuery = urlData.getSearchBoxQueryToSubmit(); if (sbQuery != null) { - if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders)) { + if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders, + urlData.getOnLoadCompletePendingIntent())) { // Could not submit query. Fallback to regular tab creation tabControl.destroy(); return null; @@ -2172,6 +2176,18 @@ public class Controller mTabControl.addPreloadedTab(t); addTab(t); setActiveTab(t); + if (sbQuery == null) { + // if the searchbox query is set, the load complete notification is handled within + // the preloaded tab controller. + if (t.inPageLoad()) { + requestLoadCompleteNotification(urlData.mOnLoadCompletePendingIntent, t, + urlData.mUrl, true, true); + } else { + // the page is already fully loaded + IntentHandler.sendPageLoadCompletePendingIntent(mActivity, + urlData.mOnLoadCompletePendingIntent, true, true); + } + } return t; } @@ -2365,10 +2381,26 @@ public class Controller // this isn't called for preloaded tabs } else { loadUrl(t, data.mUrl, data.mHeaders); + requestLoadCompleteNotification(data.mOnLoadCompletePendingIntent, t, data.mUrl, + null, null); } } } + private void requestLoadCompleteNotification(final PendingIntent loadCompletePendingIntent, + Tab t, String forUrl, final Boolean preloaded, final Boolean preloadSuccess) { + if (loadCompletePendingIntent != null) { + Pattern urlMatch = Pattern.compile(Pattern.quote(forUrl)); + t.setOnPageLoadCompleteListener(urlMatch, new Tab.OnPageLoadCompleteListener() { + @Override + public void onPageLoadComplete() { + IntentHandler.sendPageLoadCompletePendingIntent(mActivity, + loadCompletePendingIntent, preloaded, preloadSuccess); + } + }); + } + } + @Override public void onUserCanceledSsl(Tab tab) { // TODO: Figure out the "right" behavior diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java index cc6b57c..178ba62 100644 --- a/src/com/android/browser/IntentHandler.java +++ b/src/com/android/browser/IntentHandler.java @@ -18,6 +18,8 @@ package com.android.browser; import android.app.Activity; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; import android.app.SearchManager; import android.content.ContentResolver; import android.content.Context; @@ -30,6 +32,7 @@ import android.provider.Browser; import android.provider.MediaStore; import android.speech.RecognizerResultsIntent; import android.text.TextUtils; +import android.util.Log; import android.util.Patterns; import com.android.browser.search.SearchEngine; @@ -50,6 +53,22 @@ public class IntentHandler { // "source" parameter for Google search from unknown source final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; + // Pending intent extra attached to browser intents that is broadcast when the page load + // completes. + // TODO move to android.provider.Browser & make public? + private static final String EXTRA_LOAD_COMPLETE_PENDINGINTENT = "load_complete_intent"; + // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating the + // time at which the load completed. + public static final String EXTRA_LOAD_COMPLETION_TIME = "completets"; + // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating if + // preloading was attempted. + public static final String EXTRA_PREFETCH_ATTEMPTED = "prefattempt"; + // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating if + // preloading succeeded. + public static final String EXTRA_PREFETCH_SUCCESS = "prefsuccess"; + + + /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); private Activity mActivity; @@ -224,11 +243,39 @@ public class IntentHandler { } } + /** + * Send a pending intent received in a page view intent. This should be called when the page + * has finished loading. + * + * @param prefetchAttempted Indicates if prefetching was attempted, {@code null} if prefetching + * was not requested or is disabled. + * @param prefetchSucceeded Indicates if prefetching succeeded, {@code null} if prefetching + * was not requested or is disabled. + */ + public static void sendPageLoadCompletePendingIntent(Context context, PendingIntent pi, + Boolean prefetchAttempted, Boolean prefetchSucceeded) { + if (pi == null) return; + Intent fillIn = new Intent(); + fillIn.putExtra(EXTRA_LOAD_COMPLETION_TIME, System.currentTimeMillis()); + if (prefetchAttempted != null) { + fillIn.putExtra(EXTRA_PREFETCH_ATTEMPTED, prefetchAttempted.booleanValue()); + } + if (prefetchSucceeded != null) { + fillIn.putExtra(EXTRA_PREFETCH_SUCCESS, prefetchSucceeded.booleanValue()); + } + try { + pi.send(context, Activity.RESULT_OK, fillIn); + } catch (CanceledException e) { + // ignore + } + } + protected static UrlData getUrlDataFromIntent(Intent intent) { String url = ""; Map<String, String> headers = null; PreloadedTabControl preloaded = null; String preloadedSearchBoxQuery = null; + PendingIntent loadCompletePendingIntent = null; if (intent != null && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) { final String action = intent.getAction(); @@ -253,6 +300,10 @@ public class IntentHandler { PreloadRequestReceiver.EXTRA_SEARCHBOX_SETQUERY); preloaded = Preloader.getInstance().getPreloadedTab(id); } + if (intent.hasExtra(EXTRA_LOAD_COMPLETE_PENDINGINTENT)) { + loadCompletePendingIntent = + intent.getParcelableExtra(EXTRA_LOAD_COMPLETE_PENDINGINTENT); + } } else if (Intent.ACTION_SEARCH.equals(action) || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) || Intent.ACTION_WEB_SEARCH.equals(action)) { @@ -277,7 +328,8 @@ public class IntentHandler { } } } - return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery); + return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery, + loadCompletePendingIntent); } /** @@ -362,6 +414,7 @@ public class IntentHandler { final Intent mVoiceIntent; final PreloadedTabControl mPreloadedTab; final String mSearchBoxQueryToSubmit; + final PendingIntent mOnLoadCompletePendingIntent; UrlData(String url) { this.mUrl = url; @@ -369,14 +422,16 @@ public class IntentHandler { this.mVoiceIntent = null; this.mPreloadedTab = null; this.mSearchBoxQueryToSubmit = null; + this.mOnLoadCompletePendingIntent = null; } UrlData(String url, Map<String, String> headers, Intent intent) { - this(url, headers, intent, null, null); + this(url, headers, intent, null, null, null); } UrlData(String url, Map<String, String> headers, Intent intent, - PreloadedTabControl preloaded, String searchBoxQueryToSubmit) { + PreloadedTabControl preloaded, String searchBoxQueryToSubmit, + PendingIntent onLoadCompletePendingIntent) { this.mUrl = url; this.mHeaders = headers; if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS @@ -387,6 +442,7 @@ public class IntentHandler { } this.mPreloadedTab = preloaded; this.mSearchBoxQueryToSubmit = searchBoxQueryToSubmit; + this.mOnLoadCompletePendingIntent = onLoadCompletePendingIntent; } boolean isEmpty() { @@ -404,6 +460,10 @@ public class IntentHandler { String getSearchBoxQueryToSubmit() { return mSearchBoxQueryToSubmit; } + + PendingIntent getOnLoadCompletePendingIntent() { + return mOnLoadCompletePendingIntent; + } } } diff --git a/src/com/android/browser/PreloadedTabControl.java b/src/com/android/browser/PreloadedTabControl.java index 4ffe6b4..b0eff63 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.app.PendingIntent; import android.net.Uri; import android.text.TextUtils; import android.util.Log; @@ -39,7 +40,7 @@ public class PreloadedTabControl { mTab = t; } - private void maybeSetQuery(final String query, SearchBox sb) { + private boolean 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); @@ -55,25 +56,27 @@ public class PreloadedTabControl { } } }); + return true; } else { if (LOGD_ENABLED) Log.d(LOGTAG, "Cannot set query: no searchbox interface"); } } + return false; } public void setQuery(String query) { maybeSetQuery(query, mTab.getWebView().getSearchBox()); } - public boolean searchBoxSubmit(final String query, - final String fallbackUrl, final Map<String, String> fallbackHeaders) { + public boolean searchBoxSubmit(final String query, final String fallbackUrl, + final Map<String, String> fallbackHeaders, final PendingIntent onLoadCompleteIntent) { final SearchBox sb = mTab.getWebView().getSearchBox(); if (sb == null) { // no searchbox, cannot submit. Fallback to regular tab creation if (LOGD_ENABLED) Log.d(LOGTAG, "No searchbox, cannot submit query"); return false; } - maybeSetQuery(query, sb); + final boolean newQuery = maybeSetQuery(query, sb); if (LOGD_ENABLED) Log.d(LOGTAG, "Submitting query " + query); final String currentUrl = mTab.getUrl(); sb.onsubmit(new SearchBox.SearchBoxListener() { @@ -84,9 +87,14 @@ public class PreloadedTabControl { if (!called) { if (LOGD_ENABLED) Log.d(LOGTAG, "Query not submitted; falling back"); loadUrl(fallbackUrl, fallbackHeaders); + // make sure that the failed, preloaded URL is cleared from the back stack mTab.clearBackStackWhenItemAdded(Pattern.compile( "^" + Pattern.quote(fallbackUrl) + "$")); + // When setting the search box query, preloadAttempted=true implies that the + // the query was prefetched using the searchbox API. This is the case if we + // the query is not new. + registerLoadCompleteListener(!newQuery, false, onLoadCompleteIntent); } else { // ignore the next fragment change, to avoid leaving a blank page in the browser // after the query has been submitted. @@ -100,11 +108,27 @@ public class PreloadedTabControl { Pattern.quote(currentWithoutFragment) + "(\\#.*)?" + "$")); + registerLoadCompleteListener(!newQuery, true, onLoadCompleteIntent); } }}); return true; } + private void registerLoadCompleteListener( + final boolean queryPreloaded, + final boolean preloadSucceeded, + final PendingIntent pendingIntent) { + if (pendingIntent == null) { + return; + } + mTab.setOnPageLoadCompleteListener(null, new Tab.OnPageLoadCompleteListener(){ + @Override + public void onPageLoadComplete() { + IntentHandler.sendPageLoadCompletePendingIntent(mTab.mContext, pendingIntent, + queryPreloaded, preloadSucceeded); + }}); + } + public void searchBoxCancel() { SearchBox sb = mTab.getWebView().getSearchBox(); if (sb != null) { diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 8c9dc02..8dcc54b 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -66,8 +66,6 @@ import android.webkit.WebView; import android.webkit.WebView.PictureListener; import android.webkit.WebViewClient; import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; import com.android.browser.TabControl.OnThumbnailUpdatedListener; @@ -174,10 +172,13 @@ class Tab implements PictureListener { private Handler mHandler; /** - * See {@link #clearBackStackWhenItemAdded(String)}. + * See {@link #clearBackStackWhenItemAdded(Pattern)}. */ private Pattern mClearHistoryUrlPattern; + private OnPageLoadCompleteListener mOnPageLoadCompleteListener; + private Pattern mOnPageLoadCompleteUrlMatch; + private static synchronized Bitmap getDefaultFavicon(Context context) { if (sDefaultFavicon == null) { sDefaultFavicon = BitmapFactory.decodeResource( @@ -532,6 +533,26 @@ class Tab implements PictureListener { } } + public interface OnPageLoadCompleteListener { + void onPageLoadComplete(); + } + + /** + * Requests a notification when the next page load completes. This is a one shot notification, + * the listener will be discarded after the first callback, or if the page load is cancelled. + * @param listener + */ + public void setOnPageLoadCompleteListener(Pattern urlMatch, + OnPageLoadCompleteListener listener) { + mOnPageLoadCompleteListener = listener; + mOnPageLoadCompleteUrlMatch = urlMatch; + } + + public void clearPageLoadCompleteListener() { + mOnPageLoadCompleteListener = null; + mOnPageLoadCompleteUrlMatch = null; + } + // ------------------------------------------------------------------------- // WebViewClient implementation for the main WebView // ------------------------------------------------------------------------- @@ -595,6 +616,13 @@ class Tab implements PictureListener { @Override public void onPageFinished(WebView view, String url) { + if (mOnPageLoadCompleteListener != null) { + if (mOnPageLoadCompleteUrlMatch == null + || mOnPageLoadCompleteUrlMatch.matcher(url).matches()) + mOnPageLoadCompleteListener.onPageLoadComplete(); + mOnPageLoadCompleteListener = null; + mOnPageLoadCompleteUrlMatch = null; + } if (!mInPageLoad) { // In page navigation links (www.something.com#footer) will // trigger an onPageFinished which we don't care about. @@ -2042,6 +2070,7 @@ class Tab implements PictureListener { mCurrentState = new PageState(mContext, false, url, null); mWebViewController.onPageStarted(this, mMainView, null); mMainView.loadUrl(url, headers); + clearPageLoadCompleteListener(); } } @@ -2093,12 +2122,14 @@ class Tab implements PictureListener { public void goBack() { if (mMainView != null) { + clearPageLoadCompleteListener(); mMainView.goBack(); } } public void goForward() { if (mMainView != null) { + clearPageLoadCompleteListener(); mMainView.goForward(); } } |