1 /* 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 18 package com.android.browser; 19 20 import android.app.Activity; 21 import android.app.SearchManager; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.net.Uri; 26 import android.nfc.NfcAdapter; 27 import android.os.AsyncTask; 28 import android.os.Bundle; 29 import android.provider.Browser; 30 import android.provider.MediaStore; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.util.Patterns; 34 35 import com.android.browser.UI.ComboViews; 36 import com.android.browser.search.SearchEngine; 37 import com.android.common.Search; 38 39 import java.util.HashMap; 40 import java.util.Iterator; 41 import java.util.Locale; 42 import java.util.Map; 43 44 /** 45 * Handle all browser related intents 46 */ 47 public class IntentHandler { 48 49 private static final String TAG = "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 56 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); 57 58 private static final String[] SCHEME_WHITELIST = { 59 "http", 60 "https", 61 "about", 62 }; 63 64 private Activity mActivity; 65 private Controller mController; 66 private TabControl mTabControl; 67 private BrowserSettings mSettings; 68 69 public IntentHandler(Activity browser, Controller controller) { 70 mActivity = browser; 71 mController = controller; 72 mTabControl = mController.getTabControl(); 73 mSettings = controller.getSettings(); 74 } 75 76 void onNewIntent(Intent intent) { 77 Uri uri = intent.getData(); 78 if (uri != null && isForbiddenUri(uri)) { 79 Log.e(TAG, "Aborting intent with forbidden uri, \"" + uri + "\""); 80 return; 81 } 82 83 Tab current = mTabControl.getCurrentTab(); 84 // When a tab is closed on exit, the current tab index is set to -1. 85 // Reset before proceed as Browser requires the current tab to be set. 86 if (current == null) { 87 // Try to reset the tab in case the index was incorrect. 88 current = mTabControl.getTab(0); 89 if (current == null) { 90 // No tabs at all so just ignore this intent. 91 return; 92 } 93 mController.setActiveTab(current); 94 } 95 final String action = intent.getAction(); 96 final int flags = intent.getFlags(); 97 if (Intent.ACTION_MAIN.equals(action) || 98 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { 99 // just resume the browser 100 return; 101 } 102 if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(action)) { 103 mController.bookmarksOrHistoryPicker(ComboViews.Bookmarks); 104 return; 105 } 106 107 // In case the SearchDialog is open. 108 ((SearchManager) mActivity.getSystemService(Context.SEARCH_SERVICE)) 109 .stopSearch(); 110 if (Intent.ACTION_VIEW.equals(action) 111 || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) 112 || Intent.ACTION_SEARCH.equals(action) 113 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 114 || Intent.ACTION_WEB_SEARCH.equals(action)) { 115 // If this was a search request (e.g. search query directly typed into the address bar), 116 // pass it on to the default web search provider. 117 if (handleWebSearchIntent(mActivity, mController, intent)) { 118 return; 119 } 120 121 UrlData urlData = getUrlDataFromIntent(intent); 122 if (urlData.isEmpty()) { 123 urlData = new UrlData(mSettings.getHomePage()); 124 } 125 126 if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false) 127 || urlData.isPreloaded()) { 128 Tab t = mController.openTab(urlData); 129 return; 130 } 131 /* 132 * If the URL is already opened, switch to that tab 133 * phone: Reuse tab with same appId 134 * tablet: Open new tab 135 */ 136 final String appId = intent 137 .getStringExtra(Browser.EXTRA_APPLICATION_ID); 138 if (Intent.ACTION_VIEW.equals(action) 139 && (appId != null) 140 && appId.startsWith(mActivity.getPackageName())) { 141 Tab appTab = mTabControl.getTabFromAppId(appId); 142 if ((appTab != null) && (appTab == mController.getCurrentTab())) { 143 mController.switchToTab(appTab); 144 mController.loadUrlDataIn(appTab, urlData); 145 return; 146 } 147 } 148 if (Intent.ACTION_VIEW.equals(action) 149 && !mActivity.getPackageName().equals(appId)) { 150 if (!BrowserActivity.isTablet(mActivity) 151 && !mSettings.allowAppTabs()) { 152 Tab appTab = mTabControl.getTabFromAppId(appId); 153 if (appTab != null) { 154 mController.reuseTab(appTab, urlData); 155 return; 156 } 157 } 158 // No matching application tab, try to find a regular tab 159 // with a matching url. 160 Tab appTab = mTabControl.findTabWithUrl(urlData.mUrl); 161 if (appTab != null) { 162 // Transfer ownership 163 appTab.setAppId(appId); 164 if (current != appTab) { 165 mController.switchToTab(appTab); 166 } 167 // Otherwise, we are already viewing the correct tab. 168 } else { 169 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url 170 // will be opened in a new tab unless we have reached 171 // MAX_TABS. Then the url will be opened in the current 172 // tab. If a new tab is created, it will have "true" for 173 // exit on close. 174 Tab tab = mController.openTab(urlData); 175 if (tab != null) { 176 tab.setAppId(appId); 177 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { 178 tab.setCloseOnBack(true); 179 } 180 } 181 } 182 } else { 183 // Get rid of the subwindow if it exists 184 mController.dismissSubWindow(current); 185 // If the current Tab is being used as an application tab, 186 // remove the association, since the new Intent means that it is 187 // no longer associated with that application. 188 current.setAppId(null); 189 mController.loadUrlDataIn(current, urlData); 190 } 191 } 192 } 193 194 protected static UrlData getUrlDataFromIntent(Intent intent) { 195 String url = ""; 196 Map<String, String> headers = null; 197 PreloadedTabControl preloaded = null; 198 String preloadedSearchBoxQuery = null; 199 if (intent != null 200 && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) { 201 final String action = intent.getAction(); 202 if (Intent.ACTION_VIEW.equals(action) || 203 NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { 204 url = UrlUtils.smartUrlFilter(intent.getData()); 205 if (url != null && url.startsWith("http")) { 206 final Bundle pairs = intent 207 .getBundleExtra(Browser.EXTRA_HEADERS); 208 if (pairs != null && !pairs.isEmpty()) { 209 Iterator<String> iter = pairs.keySet().iterator(); 210 headers = new HashMap<String, String>(); 211 while (iter.hasNext()) { 212 String key = iter.next(); 213 headers.put(key, pairs.getString(key)); 214 } 215 } 216 } 217 if (intent.hasExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID)) { 218 String id = intent.getStringExtra(PreloadRequestReceiver.EXTRA_PRELOAD_ID); 219 preloadedSearchBoxQuery = intent.getStringExtra( 220 PreloadRequestReceiver.EXTRA_SEARCHBOX_SETQUERY); 221 preloaded = Preloader.getInstance().getPreloadedTab(id); 222 } 223 } else if (Intent.ACTION_SEARCH.equals(action) 224 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 225 || Intent.ACTION_WEB_SEARCH.equals(action)) { 226 url = intent.getStringExtra(SearchManager.QUERY); 227 if (url != null) { 228 // In general, we shouldn't modify URL from Intent. 229 // But currently, we get the user-typed URL from search box as well. 230 url = UrlUtils.fixUrl(url); 231 url = UrlUtils.smartUrlFilter(url); 232 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&"; 233 if (url.contains(searchSource)) { 234 String source = null; 235 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); 236 if (appData != null) { 237 source = appData.getString(Search.SOURCE); 238 } 239 if (TextUtils.isEmpty(source)) { 240 source = GOOGLE_SEARCH_SOURCE_UNKNOWN; 241 } 242 url = url.replace(searchSource, "&source=android-"+source+"&"); 243 } 244 } 245 } 246 } 247 return new UrlData(url, headers, intent, preloaded, preloadedSearchBoxQuery); 248 } 249 250 /** 251 * Launches the default web search activity with the query parameters if the given intent's data 252 * are identified as plain search terms and not URLs/shortcuts. 253 * @return true if the intent was handled and web search activity was launched, false if not. 254 */ 255 static boolean handleWebSearchIntent(Activity activity, 256 Controller controller, Intent intent) { 257 if (intent == null) return false; 258 259 String url = null; 260 final String action = intent.getAction(); 261 if (Intent.ACTION_VIEW.equals(action)) { 262 Uri data = intent.getData(); 263 if (data != null) url = data.toString(); 264 } else if (Intent.ACTION_SEARCH.equals(action) 265 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 266 || Intent.ACTION_WEB_SEARCH.equals(action)) { 267 url = intent.getStringExtra(SearchManager.QUERY); 268 } 269 return handleWebSearchRequest(activity, controller, url, 270 intent.getBundleExtra(SearchManager.APP_DATA), 271 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); 272 } 273 274 /** 275 * Launches the default web search activity with the query parameters if the given url string 276 * was identified as plain search terms and not URL/shortcut. 277 * @return true if the request was handled and web search activity was launched, false if not. 278 */ 279 private static boolean handleWebSearchRequest(Activity activity, 280 Controller controller, String inUrl, Bundle appData, 281 String extraData) { 282 if (inUrl == null) return false; 283 284 // In general, we shouldn't modify URL from Intent. 285 // But currently, we get the user-typed URL from search box as well. 286 String url = UrlUtils.fixUrl(inUrl).trim(); 287 if (TextUtils.isEmpty(url)) return false; 288 289 // URLs are handled by the regular flow of control, so 290 // return early. 291 if (Patterns.WEB_URL.matcher(url).matches() 292 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) { 293 return false; 294 } 295 296 final ContentResolver cr = activity.getContentResolver(); 297 final String newUrl = url; 298 if (controller == null || controller.getTabControl() == null 299 || controller.getTabControl().getCurrentWebView() == null 300 || !controller.getTabControl().getCurrentWebView() 301 .isPrivateBrowsingEnabled()) { 302 new AsyncTask<Void, Void, Void>() { 303 @Override 304 protected Void doInBackground(Void... unused) { 305 Browser.addSearchUrl(cr, newUrl); 306 return null; 307 } 308 }.execute(); 309 } 310 311 SearchEngine searchEngine = BrowserSettings.getInstance().getSearchEngine(); 312 if (searchEngine == null) return false; 313 searchEngine.startSearch(activity, url, appData, extraData); 314 315 return true; 316 } 317 318 private static boolean isForbiddenUri(Uri uri) { 319 String scheme = uri.getScheme(); 320 // Allow URIs with no scheme 321 if (scheme == null) { 322 return false; 323 } 324 325 scheme = scheme.toLowerCase(Locale.US); 326 for (String allowed : SCHEME_WHITELIST) { 327 if (allowed.equals(scheme)) { 328 return false; 329 } 330 } 331 return true; 332 } 333 334 /** 335 * A UrlData class to abstract how the content will be sent to WebView. 336 * This base class uses loadUrl to show the content. 337 */ 338 static class UrlData { 339 final String mUrl; 340 final Map<String, String> mHeaders; 341 final PreloadedTabControl mPreloadedTab; 342 final String mSearchBoxQueryToSubmit; 343 final boolean mDisableUrlOverride; 344 345 UrlData(String url) { 346 this.mUrl = url; 347 this.mHeaders = null; 348 this.mPreloadedTab = null; 349 this.mSearchBoxQueryToSubmit = null; 350 this.mDisableUrlOverride = false; 351 } 352 353 UrlData(String url, Map<String, String> headers, Intent intent) { 354 this(url, headers, intent, null, null); 355 } 356 357 UrlData(String url, Map<String, String> headers, Intent intent, 358 PreloadedTabControl preloaded, String searchBoxQueryToSubmit) { 359 this.mUrl = url; 360 this.mHeaders = headers; 361 this.mPreloadedTab = preloaded; 362 this.mSearchBoxQueryToSubmit = searchBoxQueryToSubmit; 363 if (intent != null) { 364 mDisableUrlOverride = intent.getBooleanExtra( 365 BrowserActivity.EXTRA_DISABLE_URL_OVERRIDE, false); 366 } else { 367 mDisableUrlOverride = false; 368 } 369 } 370 371 boolean isEmpty() { 372 return (mUrl == null || mUrl.length() == 0); 373 } 374 375 boolean isPreloaded() { 376 return mPreloadedTab != null; 377 } 378 379 PreloadedTabControl getPreloadedTab() { 380 return mPreloadedTab; 381 } 382 383 String getSearchBoxQueryToSubmit() { 384 return mSearchBoxQueryToSubmit; 385 } 386 } 387 388 } 389