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