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