blob: 178ba62c35a30458cb8f3eb7d290ad32a367f46f [file] [log] [blame]
Michael Kolb8233fac2010-10-26 16:08:53 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.browser;
19
Michael Kolb8233fac2010-10-26 16:08:53 -070020import android.app.Activity;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +010021import android.app.PendingIntent;
22import android.app.PendingIntent.CanceledException;
Michael Kolb8233fac2010-10-26 16:08:53 -070023import android.app.SearchManager;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.net.Uri;
Jeff Hamiltonfd77aaa2011-06-17 16:19:33 -050028import android.nfc.NfcAdapter;
Michael Kolb8233fac2010-10-26 16:08:53 -070029import android.os.AsyncTask;
30import android.os.Bundle;
31import android.provider.Browser;
32import android.provider.MediaStore;
33import android.speech.RecognizerResultsIntent;
34import android.text.TextUtils;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +010035import android.util.Log;
Michael Kolb8233fac2010-10-26 16:08:53 -070036import android.util.Patterns;
37
Michael Kolbe28b3472011-08-04 16:54:31 -070038import com.android.browser.search.SearchEngine;
39import com.android.common.Search;
40import com.android.common.speech.LoggingEvents;
41
Michael Kolb8233fac2010-10-26 16:08:53 -070042import java.util.HashMap;
43import java.util.Iterator;
44import java.util.Map;
45
46/**
47 * Handle all browser related intents
48 */
49public class IntentHandler {
50
51 // "source" parameter for Google search suggested by the browser
52 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
53 // "source" parameter for Google search from unknown source
54 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
55
Mathew Inwoodb4e831b2011-09-05 18:58:48 +010056 // Pending intent extra attached to browser intents that is broadcast when the page load
57 // completes.
58 // TODO move to android.provider.Browser & make public?
59 private static final String EXTRA_LOAD_COMPLETE_PENDINGINTENT = "load_complete_intent";
60 // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating the
61 // time at which the load completed.
62 public static final String EXTRA_LOAD_COMPLETION_TIME = "completets";
63 // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating if
64 // preloading was attempted.
65 public static final String EXTRA_PREFETCH_ATTEMPTED = "prefattempt";
66 // extra attached to intent received via EXTRA_LOAD_COMPLETE_PENDINGINTENT indicating if
67 // preloading succeeded.
68 public static final String EXTRA_PREFETCH_SUCCESS = "prefsuccess";
69
70
71
Michael Kolb8233fac2010-10-26 16:08:53 -070072 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
73
74 private Activity mActivity;
75 private Controller mController;
76 private TabControl mTabControl;
77 private BrowserSettings mSettings;
78
79 public IntentHandler(Activity browser, Controller controller) {
80 mActivity = browser;
81 mController = controller;
82 mTabControl = mController.getTabControl();
83 mSettings = controller.getSettings();
84 }
85
86 void onNewIntent(Intent intent) {
John Reck6c2e2f32011-08-22 13:41:23 -070087 mActivity.setIntent(null);
Michael Kolb8233fac2010-10-26 16:08:53 -070088 Tab current = mTabControl.getCurrentTab();
89 // When a tab is closed on exit, the current tab index is set to -1.
90 // Reset before proceed as Browser requires the current tab to be set.
91 if (current == null) {
92 // Try to reset the tab in case the index was incorrect.
93 current = mTabControl.getTab(0);
94 if (current == null) {
95 // No tabs at all so just ignore this intent.
96 return;
97 }
98 mController.setActiveTab(current);
Michael Kolb8233fac2010-10-26 16:08:53 -070099 }
100 final String action = intent.getAction();
101 final int flags = intent.getFlags();
102 if (Intent.ACTION_MAIN.equals(action) ||
103 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
104 // just resume the browser
105 return;
106 }
John Reck439c9a52010-12-14 10:04:39 -0800107 if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(action)) {
108 mController.bookmarksOrHistoryPicker(false);
109 return;
110 }
John Reck07af2b82011-01-18 14:24:43 -0800111
Michael Kolb8233fac2010-10-26 16:08:53 -0700112 // In case the SearchDialog is open.
113 ((SearchManager) mActivity.getSystemService(Context.SEARCH_SERVICE))
114 .stopSearch();
115 boolean activateVoiceSearch = RecognizerResultsIntent
116 .ACTION_VOICE_SEARCH_RESULTS.equals(action);
117 if (Intent.ACTION_VIEW.equals(action)
Jeff Hamiltonfd77aaa2011-06-17 16:19:33 -0500118 || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
Michael Kolb8233fac2010-10-26 16:08:53 -0700119 || Intent.ACTION_SEARCH.equals(action)
120 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
121 || Intent.ACTION_WEB_SEARCH.equals(action)
122 || activateVoiceSearch) {
123 if (current.isInVoiceSearchMode()) {
124 String title = current.getVoiceDisplayTitle();
125 if (title != null && title.equals(intent.getStringExtra(
126 SearchManager.QUERY))) {
127 // The user submitted the same search as the last voice
128 // search, so do nothing.
129 return;
130 }
131 if (Intent.ACTION_SEARCH.equals(action)
132 && current.voiceSearchSourceIsGoogle()) {
133 Intent logIntent = new Intent(
134 LoggingEvents.ACTION_LOG_EVENT);
135 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
136 LoggingEvents.VoiceSearch.QUERY_UPDATED);
137 logIntent.putExtra(
138 LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE,
139 intent.getDataString());
140 mActivity.sendBroadcast(logIntent);
141 // Note, onPageStarted will revert the voice title bar
142 // When http://b/issue?id=2379215 is fixed, we should update
143 // the title bar here.
144 }
145 }
146 // If this was a search request (e.g. search query directly typed into the address bar),
147 // pass it on to the default web search provider.
148 if (handleWebSearchIntent(mActivity, mController, intent)) {
149 return;
150 }
151
152 UrlData urlData = getUrlDataFromIntent(intent);
153 if (urlData.isEmpty()) {
154 urlData = new UrlData(mSettings.getHomePage());
155 }
156
Michael Kolb14612442011-06-24 13:06:29 -0700157 if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false)
158 || urlData.isPreloaded()) {
159 Tab t = mController.openTab(urlData);
Leon Scroggins2ed6e312011-02-22 16:37:23 -0500160 return;
161 }
John Reckdb22ec42011-06-29 11:31:24 -0700162 /*
163 * TODO: Don't allow javascript URIs
164 * 0) If this is a javascript: URI, *always* open a new tab
165 * 1) If this is a voice search, re-use tab for appId
166 * If there is no appId, use current tab
167 * 2) If the URL is already opened, switch to that tab
168 * 3-phone) Reuse tab with same appId
169 * 3-tablet) Open new tab
170 */
Michael Kolb8233fac2010-10-26 16:08:53 -0700171 final String appId = intent
172 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
John Reck26b18322011-06-21 13:08:58 -0700173 if (!TextUtils.isEmpty(urlData.mUrl) &&
174 urlData.mUrl.startsWith("javascript:")) {
175 // Always open javascript: URIs in new tabs
176 mController.openTab(urlData);
177 return;
178 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700179 if ((Intent.ACTION_VIEW.equals(action)
180 // If a voice search has no appId, it means that it came
181 // from the browser. In that case, reuse the current tab.
182 || (activateVoiceSearch && appId != null))
John Reckdb22ec42011-06-29 11:31:24 -0700183 && !mActivity.getPackageName().equals(appId)) {
184 if (activateVoiceSearch || !BrowserActivity.isTablet(mActivity)) {
Michael Kolbc831b632011-05-11 09:30:34 -0700185 Tab appTab = mTabControl.getTabFromAppId(appId);
Michael Kolbaf0d3342011-03-11 11:30:16 -0800186 if (appTab != null) {
John Reck26b18322011-06-21 13:08:58 -0700187 mController.reuseTab(appTab, urlData);
Michael Kolbaf0d3342011-03-11 11:30:16 -0800188 return;
Michael Kolbaf0d3342011-03-11 11:30:16 -0800189 }
John Reckdb22ec42011-06-29 11:31:24 -0700190 }
191 // No matching application tab, try to find a regular tab
192 // with a matching url.
193 Tab appTab = mTabControl.findTabWithUrl(urlData.mUrl);
194 if (appTab != null) {
195 // Transfer ownership
196 appTab.setAppId(appId);
197 if (current != appTab) {
198 mController.switchToTab(appTab);
199 }
200 // Otherwise, we are already viewing the correct tab.
Michael Kolb8233fac2010-10-26 16:08:53 -0700201 } else {
John Reckdb22ec42011-06-29 11:31:24 -0700202 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
203 // will be opened in a new tab unless we have reached
204 // MAX_TABS. Then the url will be opened in the current
205 // tab. If a new tab is created, it will have "true" for
206 // exit on close.
207 Tab tab = mController.openTab(urlData);
208 if (tab != null) {
209 tab.setAppId(appId);
Michael Kolbe28b3472011-08-04 16:54:31 -0700210 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
211 tab.setCloseOnBack(true);
212 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700213 }
214 }
215 } else {
216 if (!urlData.isEmpty()
217 && urlData.mUrl.startsWith("about:debug")) {
218 if ("about:debug.dom".equals(urlData.mUrl)) {
219 current.getWebView().dumpDomTree(false);
220 } else if ("about:debug.dom.file".equals(urlData.mUrl)) {
221 current.getWebView().dumpDomTree(true);
222 } else if ("about:debug.render".equals(urlData.mUrl)) {
223 current.getWebView().dumpRenderTree(false);
224 } else if ("about:debug.render.file".equals(urlData.mUrl)) {
225 current.getWebView().dumpRenderTree(true);
226 } else if ("about:debug.display".equals(urlData.mUrl)) {
227 current.getWebView().dumpDisplayTree();
Cary Clark4f4cb6b2011-01-18 13:03:42 -0500228 } else if ("about:debug.nav".equals(urlData.mUrl)) {
229 current.getWebView().debugDump();
Michael Kolb8233fac2010-10-26 16:08:53 -0700230 } else {
John Reck35e9dd62011-04-25 09:01:54 -0700231 mSettings.toggleDebugSettings();
Michael Kolb8233fac2010-10-26 16:08:53 -0700232 }
233 return;
234 }
235 // Get rid of the subwindow if it exists
236 mController.dismissSubWindow(current);
237 // If the current Tab is being used as an application tab,
238 // remove the association, since the new Intent means that it is
239 // no longer associated with that application.
240 current.setAppId(null);
241 mController.loadUrlDataIn(current, urlData);
242 }
243 }
244 }
245
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100246 /**
247 * Send a pending intent received in a page view intent. This should be called when the page
248 * has finished loading.
249 *
250 * @param prefetchAttempted Indicates if prefetching was attempted, {@code null} if prefetching
251 * was not requested or is disabled.
252 * @param prefetchSucceeded Indicates if prefetching succeeded, {@code null} if prefetching
253 * was not requested or is disabled.
254 */
255 public static void sendPageLoadCompletePendingIntent(Context context, PendingIntent pi,
256 Boolean prefetchAttempted, Boolean prefetchSucceeded) {
257 if (pi == null) return;
258 Intent fillIn = new Intent();
259 fillIn.putExtra(EXTRA_LOAD_COMPLETION_TIME, System.currentTimeMillis());
260 if (prefetchAttempted != null) {
261 fillIn.putExtra(EXTRA_PREFETCH_ATTEMPTED, prefetchAttempted.booleanValue());
262 }
263 if (prefetchSucceeded != null) {
264 fillIn.putExtra(EXTRA_PREFETCH_SUCCESS, prefetchSucceeded.booleanValue());
265 }
266 try {
267 pi.send(context, Activity.RESULT_OK, fillIn);
268 } catch (CanceledException e) {
269 // ignore
270 }
271 }
272
Michael Kolb14612442011-06-24 13:06:29 -0700273 protected static UrlData getUrlDataFromIntent(Intent intent) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700274 String url = "";
275 Map<String, String> headers = null;
Mathew Inwood29721c22011-06-29 17:55:24 +0100276 PreloadedTabControl preloaded = null;
277 String preloadedSearchBoxQuery = null;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100278 PendingIntent loadCompletePendingIntent = null;
Leon Scrogginse1cab102011-01-04 10:50:13 -0500279 if (intent != null
280 && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700281 final String action = intent.getAction();
Jeff Hamiltonfd77aaa2011-06-17 16:19:33 -0500282 if (Intent.ACTION_VIEW.equals(action) ||
283 NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700284 url = UrlUtils.smartUrlFilter(intent.getData());
Michael Kolb8233fac2010-10-26 16:08:53 -0700285 if (url != null && url.startsWith("http")) {
286 final Bundle pairs = intent
287 .getBundleExtra(Browser.EXTRA_HEADERS);
288 if (pairs != null && !pairs.isEmpty()) {
289 Iterator<String> iter = pairs.keySet().iterator();
290 headers = new HashMap<String, String>();
291 while (iter.hasNext()) {
292 String key = iter.next();
293 headers.put(key, pairs.getString(key));
294 }
295 }
296 }
Michael Kolb14612442011-06-24 13:06:29 -0700297 if (intent.hasExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID)) {
298 String id = intent.getStringExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID);
Mathew Inwood29721c22011-06-29 17:55:24 +0100299 preloadedSearchBoxQuery = intent.getStringExtra(
300 PreloadRequestReceiver.EXTRA_SEARCHBOX_SETQUERY);
Michael Kolb14612442011-06-24 13:06:29 -0700301 preloaded = Preloader.getInstance().getPreloadedTab(id);
302 }
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100303 if (intent.hasExtra(EXTRA_LOAD_COMPLETE_PENDINGINTENT)) {
304 loadCompletePendingIntent =
305 intent.getParcelableExtra(EXTRA_LOAD_COMPLETE_PENDINGINTENT);
306 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700307 } else if (Intent.ACTION_SEARCH.equals(action)
308 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
309 || Intent.ACTION_WEB_SEARCH.equals(action)) {
310 url = intent.getStringExtra(SearchManager.QUERY);
311 if (url != null) {
312 // In general, we shouldn't modify URL from Intent.
313 // But currently, we get the user-typed URL from search box as well.
314 url = UrlUtils.fixUrl(url);
315 url = UrlUtils.smartUrlFilter(url);
Michael Kolb8233fac2010-10-26 16:08:53 -0700316 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
317 if (url.contains(searchSource)) {
318 String source = null;
319 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
320 if (appData != null) {
321 source = appData.getString(Search.SOURCE);
322 }
323 if (TextUtils.isEmpty(source)) {
324 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
325 }
326 url = url.replace(searchSource, "&source=android-"+source+"&");
327 }
328 }
329 }
330 }
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100331 return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery,
332 loadCompletePendingIntent);
Michael Kolb8233fac2010-10-26 16:08:53 -0700333 }
334
335 /**
336 * Launches the default web search activity with the query parameters if the given intent's data
337 * are identified as plain search terms and not URLs/shortcuts.
338 * @return true if the intent was handled and web search activity was launched, false if not.
339 */
340 static boolean handleWebSearchIntent(Activity activity,
341 Controller controller, Intent intent) {
342 if (intent == null) return false;
343
344 String url = null;
345 final String action = intent.getAction();
346 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals(
347 action)) {
348 return false;
349 }
350 if (Intent.ACTION_VIEW.equals(action)) {
351 Uri data = intent.getData();
352 if (data != null) url = data.toString();
353 } else if (Intent.ACTION_SEARCH.equals(action)
354 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
355 || Intent.ACTION_WEB_SEARCH.equals(action)) {
356 url = intent.getStringExtra(SearchManager.QUERY);
357 }
358 return handleWebSearchRequest(activity, controller, url,
359 intent.getBundleExtra(SearchManager.APP_DATA),
360 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
361 }
362
363 /**
364 * Launches the default web search activity with the query parameters if the given url string
365 * was identified as plain search terms and not URL/shortcut.
366 * @return true if the request was handled and web search activity was launched, false if not.
367 */
368 private static boolean handleWebSearchRequest(Activity activity,
369 Controller controller, String inUrl, Bundle appData,
370 String extraData) {
John Reck99141272010-12-21 11:34:12 -0800371 if (inUrl == null) return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700372
373 // In general, we shouldn't modify URL from Intent.
374 // But currently, we get the user-typed URL from search box as well.
375 String url = UrlUtils.fixUrl(inUrl).trim();
John Reck99141272010-12-21 11:34:12 -0800376 if (TextUtils.isEmpty(url)) return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700377
378 // URLs are handled by the regular flow of control, so
379 // return early.
380 if (Patterns.WEB_URL.matcher(url).matches()
381 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
382 return false;
383 }
384
385 final ContentResolver cr = activity.getContentResolver();
386 final String newUrl = url;
387 if (controller == null || controller.getTabControl() == null
388 || controller.getTabControl().getCurrentWebView() == null
389 || !controller.getTabControl().getCurrentWebView()
390 .isPrivateBrowsingEnabled()) {
391 new AsyncTask<Void, Void, Void>() {
392 @Override
393 protected Void doInBackground(Void... unused) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700394 Browser.addSearchUrl(cr, newUrl);
395 return null;
396 }
397 }.execute();
398 }
399
400 SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine();
401 if (searchEngine == null) return false;
402 searchEngine.startSearch(activity, url, appData, extraData);
403
404 return true;
405 }
406
407 /**
408 * A UrlData class to abstract how the content will be set to WebView.
409 * This base class uses loadUrl to show the content.
410 */
411 static class UrlData {
412 final String mUrl;
413 final Map<String, String> mHeaders;
414 final Intent mVoiceIntent;
Mathew Inwood29721c22011-06-29 17:55:24 +0100415 final PreloadedTabControl mPreloadedTab;
416 final String mSearchBoxQueryToSubmit;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100417 final PendingIntent mOnLoadCompletePendingIntent;
Michael Kolb8233fac2010-10-26 16:08:53 -0700418
419 UrlData(String url) {
420 this.mUrl = url;
421 this.mHeaders = null;
422 this.mVoiceIntent = null;
Michael Kolb14612442011-06-24 13:06:29 -0700423 this.mPreloadedTab = null;
Mathew Inwood29721c22011-06-29 17:55:24 +0100424 this.mSearchBoxQueryToSubmit = null;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100425 this.mOnLoadCompletePendingIntent = null;
Michael Kolb8233fac2010-10-26 16:08:53 -0700426 }
427
428 UrlData(String url, Map<String, String> headers, Intent intent) {
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100429 this(url, headers, intent, null, null, null);
Michael Kolb14612442011-06-24 13:06:29 -0700430 }
431
Mathew Inwood29721c22011-06-29 17:55:24 +0100432 UrlData(String url, Map<String, String> headers, Intent intent,
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100433 PreloadedTabControl preloaded, String searchBoxQueryToSubmit,
434 PendingIntent onLoadCompletePendingIntent) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700435 this.mUrl = url;
436 this.mHeaders = headers;
437 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
438 .equals(intent.getAction())) {
439 this.mVoiceIntent = intent;
440 } else {
441 this.mVoiceIntent = null;
442 }
Mathew Inwood29721c22011-06-29 17:55:24 +0100443 this.mPreloadedTab = preloaded;
444 this.mSearchBoxQueryToSubmit = searchBoxQueryToSubmit;
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100445 this.mOnLoadCompletePendingIntent = onLoadCompletePendingIntent;
Michael Kolb8233fac2010-10-26 16:08:53 -0700446 }
447
448 boolean isEmpty() {
449 return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
450 }
Michael Kolb14612442011-06-24 13:06:29 -0700451
452 boolean isPreloaded() {
453 return mPreloadedTab != null;
454 }
455
Mathew Inwood29721c22011-06-29 17:55:24 +0100456 PreloadedTabControl getPreloadedTab() {
Michael Kolb14612442011-06-24 13:06:29 -0700457 return mPreloadedTab;
458 }
Mathew Inwood29721c22011-06-29 17:55:24 +0100459
460 String getSearchBoxQueryToSubmit() {
461 return mSearchBoxQueryToSubmit;
462 }
Mathew Inwoodb4e831b2011-09-05 18:58:48 +0100463
464 PendingIntent getOnLoadCompletePendingIntent() {
465 return mOnLoadCompletePendingIntent;
466 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700467 }
468
469}