blob: d372ec3d0adbb2b925119615216b25425053f903 [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
Bijan Amirzada41242f22014-03-21 12:12:18 -070017package com.android.browser;
Michael Kolb8233fac2010-10-26 16:08:53 -070018
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.Intent;
John Reck95a49ff2011-02-08 18:23:22 -080022import android.content.IntentFilter;
Michael Kolb8233fac2010-10-26 16:08:53 -070023import android.content.pm.PackageManager;
John Reck95a49ff2011-02-08 18:23:22 -080024import android.content.pm.ResolveInfo;
Michael Kolb8233fac2010-10-26 16:08:53 -070025import android.net.Uri;
26import android.util.Log;
kaiyizc4ada322013-07-30 09:58:07 +080027import android.widget.Toast;
Michael Kolb8233fac2010-10-26 16:08:53 -070028
Bijan Amirzada41242f22014-03-21 12:12:18 -070029import com.android.browser.R;
Bijan Amirzada3f04dc72014-06-25 11:48:36 -070030import com.android.browser.platformsupport.Browser;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080031
Michael Kolb8233fac2010-10-26 16:08:53 -070032import java.net.URISyntaxException;
John Reck95a49ff2011-02-08 18:23:22 -080033import java.util.List;
John Reckdb3d43d2011-02-11 11:56:38 -080034import java.util.regex.Matcher;
Michael Kolb8233fac2010-10-26 16:08:53 -070035
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080036import org.codeaurora.swe.WebView;
Vivek Sekhar6b276892016-02-05 13:59:50 +010037import org.codeaurora.swe.util.SWEUrlUtils;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080038
Michael Kolb8233fac2010-10-26 16:08:53 -070039public class UrlHandler {
40
kaiyizc4ada322013-07-30 09:58:07 +080041 private final static String TAG = "UrlHandler";
John Reck35e9dd62011-04-25 09:01:54 -070042
Michael Kolb8233fac2010-10-26 16:08:53 -070043 // Use in overrideUrlLoading
44 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
45 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
46 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
47 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
Vivek Sekharb54614f2014-05-01 19:03:37 -070048 /* package */ final static String SCHEME_MAILTO = "mailto:";
Vivek Sekhar6b276892016-02-05 13:59:50 +010049 public static final String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
Michael Kolb8233fac2010-10-26 16:08:53 -070050 Controller mController;
51 Activity mActivity;
52
Michael Kolb8233fac2010-10-26 16:08:53 -070053 public UrlHandler(Controller controller) {
54 mController = controller;
55 mActivity = mController.getActivity();
56 }
57
Michael Kolb18eb3772010-12-10 14:29:51 -080058 boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -070059 if (view.isPrivateBrowsingEnabled()) {
60 // Don't allow urls to leave the browser app when in
61 // private browsing mode
Patrick Scottd05faa62010-12-16 09:15:34 -050062 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -070063 }
64
Vivek Sekharcb3f7232015-12-10 20:02:46 -080065 if (url.startsWith(WebView.SCHEME_TEL)) {
66 Intent intent = new Intent(Intent.ACTION_VIEW,
67 Uri.parse(WebView.SCHEME_TEL +
68 Uri.encode(url.substring(WebView.SCHEME_TEL.length()))));
69 mActivity.startActivity(intent);
70 return true;
71 }
Michael Kolb8233fac2010-10-26 16:08:53 -070072 if (url.startsWith(SCHEME_WTAI)) {
73 // wtai://wp/mc;number
74 // number=string(phone-number)
75 if (url.startsWith(SCHEME_WTAI_MC)) {
76 Intent intent = new Intent(Intent.ACTION_VIEW,
77 Uri.parse(WebView.SCHEME_TEL +
Vivek Sekharcb3f7232015-12-10 20:02:46 -080078 Uri.encode(url.substring(SCHEME_WTAI_MC.length()))));
Michael Kolb8233fac2010-10-26 16:08:53 -070079 mActivity.startActivity(intent);
80 // before leaving BrowserActivity, close the empty child tab.
81 // If a new tab is created through JavaScript open to load this
82 // url, we would like to close it as we will load this url in a
83 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -070084 mController.closeEmptyTab();
Michael Kolb8233fac2010-10-26 16:08:53 -070085 return true;
86 }
87 // wtai://wp/sd;dtmf
88 // dtmf=string(dialstring)
89 if (url.startsWith(SCHEME_WTAI_SD)) {
90 // TODO: only send when there is active voice connection
91 return false;
92 }
93 // wtai://wp/ap;number;name
94 // number=string(phone-number)
95 // name=string
96 if (url.startsWith(SCHEME_WTAI_AP)) {
97 // TODO
98 return false;
99 }
100 }
101
102 // The "about:" schemes are internal to the browser; don't want these to
103 // be dispatched to other apps.
104 if (url.startsWith("about:")) {
105 return false;
106 }
107
kaiyiz6e5b3e02013-08-19 20:02:01 +0800108 if (url.startsWith("ae://") && url.endsWith("add-fav")) {
109 mController.startAddMyNavigation(url);
110 return true;
111 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700112
Vivek Sekharb54614f2014-05-01 19:03:37 -0700113 // add for carrier feature - recognize additional website format
114 // here add to support "mailto:" scheme
115 if (url.startsWith(SCHEME_MAILTO) && handleMailtoTypeUrl(url)) {
116 return true;
117 }
118
119 // add for carrier feature - wap2estore
Panos Thomas4bdb5252014-11-13 16:20:11 -0800120 boolean wap2estore = BrowserConfig.getInstance(mController.getContext())
121 .hasFeature(BrowserConfig.Feature.WAP2ESTORE);
Vivek Sekharb54614f2014-05-01 19:03:37 -0700122 if (wap2estore && isEstoreTypeUrl(url) && handleEstoreTypeUrl(url)) {
kaiyizc4ada322013-07-30 09:58:07 +0800123 return true;
124 }
125
John Reck8bcafc12011-08-29 16:43:02 -0700126 if (startActivityForUrl(tab, url)) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800127 return true;
Michael Kolb8233fac2010-10-26 16:08:53 -0700128 }
129
Russell Brenner14e1ae22011-01-12 14:54:23 -0800130 if (handleMenuClick(tab, url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700131 return true;
132 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800133
Michael Kolb8233fac2010-10-26 16:08:53 -0700134 return false;
135 }
136
Vivek Sekharb54614f2014-05-01 19:03:37 -0700137 private boolean handleMailtoTypeUrl(String url) {
138 Intent intent;
139 // perform generic parsing of the URI to turn it into an Intent.
kaiyizc4ada322013-07-30 09:58:07 +0800140 try {
Vivek Sekharb54614f2014-05-01 19:03:37 -0700141 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
142 mActivity.startActivity(intent);
143 } catch (URISyntaxException ex) {
Vivek Sekhar6b276892016-02-05 13:59:50 +0100144 Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage());
Vivek Sekharb54614f2014-05-01 19:03:37 -0700145 return false;
146 } catch (ActivityNotFoundException ex) {
Vivek Sekhar6b276892016-02-05 13:59:50 +0100147 Log.w(TAG, "No Activity Found for " + url);
kaiyizc4ada322013-07-30 09:58:07 +0800148 }
Vivek Sekharb54614f2014-05-01 19:03:37 -0700149
150 return true;
151 }
152
153 private boolean isEstoreTypeUrl(String url) {
154 if (url != null && url.startsWith("estore:")) {
kaiyizc4ada322013-07-30 09:58:07 +0800155 return true;
156 }
157 return false;
158 }
159
Vivek Sekharb54614f2014-05-01 19:03:37 -0700160 private boolean handleEstoreTypeUrl(String url) {
161 if (url.getBytes().length > 256) {
kaiyizc4ada322013-07-30 09:58:07 +0800162 Toast.makeText(mActivity, R.string.estore_url_warning, Toast.LENGTH_LONG).show();
Vivek Sekharb54614f2014-05-01 19:03:37 -0700163 return false;
kaiyizc4ada322013-07-30 09:58:07 +0800164 }
Vivek Sekharb54614f2014-05-01 19:03:37 -0700165
166 Intent intent;
167 // perform generic parsing of the URI to turn it into an Intent.
kaiyizc4ada322013-07-30 09:58:07 +0800168 try {
Vivek Sekharb54614f2014-05-01 19:03:37 -0700169 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
kaiyizc4ada322013-07-30 09:58:07 +0800170 mActivity.startActivity(intent);
Vivek Sekharb54614f2014-05-01 19:03:37 -0700171 } catch (URISyntaxException ex) {
Vivek Sekhar6b276892016-02-05 13:59:50 +0100172 Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage());
Vivek Sekharb54614f2014-05-01 19:03:37 -0700173 return false;
kaiyizc4ada322013-07-30 09:58:07 +0800174 } catch (ActivityNotFoundException ex) {
175 String downloadUrl = mActivity.getResources().getString(R.string.estore_homepage);
176 mController.loadUrl(mController.getCurrentTab(), downloadUrl);
177 Toast.makeText(mActivity, R.string.download_estore_app, Toast.LENGTH_LONG).show();
178 }
Vivek Sekharb54614f2014-05-01 19:03:37 -0700179
180 return true;
kaiyizc4ada322013-07-30 09:58:07 +0800181 }
182
Vivek Sekharb54614f2014-05-01 19:03:37 -0700183
John Reck8bcafc12011-08-29 16:43:02 -0700184 boolean startActivityForUrl(Tab tab, String url) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800185 Intent intent;
186 // perform generic parsing of the URI to turn it into an Intent.
187 try {
188 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
189 } catch (URISyntaxException ex) {
Vivek Sekhar6b276892016-02-05 13:59:50 +0100190 Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage());
Russell Brennerd4afde12011-01-07 11:09:36 -0800191 return false;
192 }
193
Vivek Sekhar6b276892016-02-05 13:59:50 +0100194 // handle fallback url for deep linking apps
195 String browserFallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
196 if (browserFallbackUrl != null
197 && SWEUrlUtils.isValidForIntentFallbackNavigation(browserFallbackUrl)) {
198 mController.loadUrl(mController.getCurrentTab(), browserFallbackUrl);
199 mController.closeEmptyTab();
200 return true;
201 }
202
Russell Brennerd4afde12011-01-07 11:09:36 -0800203 // check whether the intent can be resolved. If not, we will see
204 // whether we can download it from the Market.
205 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
206 String packagename = intent.getPackage();
207 if (packagename != null) {
Vivek Sekhar40713382014-06-11 14:29:32 -0700208 try {
209 intent = new Intent(Intent.ACTION_VIEW, Uri
Vivek Sekhar6b276892016-02-05 13:59:50 +0100210 .parse("market://details?id=" + packagename));
Vivek Sekhar40713382014-06-11 14:29:32 -0700211 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Vivek Sekhar6b276892016-02-05 13:59:50 +0100212 intent.setPackage("com.android.vending");
Vivek Sekhar40713382014-06-11 14:29:32 -0700213 mActivity.startActivity(intent);
Vivek Sekhar6b276892016-02-05 13:59:50 +0100214 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Vivek Sekhar40713382014-06-11 14:29:32 -0700215 // before leaving BrowserActivity, close the empty child tab.
216 // If a new tab is created through JavaScript open to load this
217 // url, we would like to close it as we will load this url in a
218 // different Activity.
219 mController.closeEmptyTab();
220 return true;
221 } catch (ActivityNotFoundException e) {
222 Log.w(TAG, "Play store not found while searching for : " + packagename);
223 CharSequence alert = mActivity.getResources().getString(
224 R.string.msg_no_google_play);
225 Toast t = Toast.makeText(mActivity , alert, Toast.LENGTH_SHORT);
226 t.show();
227 return false;
228 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800229 } else {
230 return false;
231 }
232 }
233
234 // sanitize the Intent, ensuring web pages can not bypass browser
235 // security (only access to BROWSABLE activities).
236 intent.addCategory(Intent.CATEGORY_BROWSABLE);
237 intent.setComponent(null);
John Reck8bcafc12011-08-29 16:43:02 -0700238 // Re-use the existing tab if the intent comes back to us
239 if (tab != null) {
240 if (tab.getAppId() == null) {
Michael Kolbf1286a42012-04-17 14:10:52 -0700241 tab.setAppId(mActivity.getPackageName() + "-" + tab.getId());
John Reck8bcafc12011-08-29 16:43:02 -0700242 }
243 intent.putExtra(Browser.EXTRA_APPLICATION_ID, tab.getAppId());
244 }
John Reckdb3d43d2011-02-11 11:56:38 -0800245 // Make sure webkit can handle it internally before checking for specialized
246 // handlers. If webkit can't handle it internally, we need to call
247 // startActivityIfNeeded
248 Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
249 if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
John Reck95a49ff2011-02-08 18:23:22 -0800250 return false;
251 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800252 try {
John Reck38b39652012-06-05 09:22:59 -0700253 intent.putExtra(BrowserActivity.EXTRA_DISABLE_URL_OVERRIDE, true);
Russell Brennerd4afde12011-01-07 11:09:36 -0800254 if (mActivity.startActivityIfNeeded(intent, -1)) {
255 // before leaving BrowserActivity, close the empty child tab.
256 // If a new tab is created through JavaScript open to load this
257 // url, we would like to close it as we will load this url in a
258 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -0700259 mController.closeEmptyTab();
Russell Brennerd4afde12011-01-07 11:09:36 -0800260 return true;
261 }
262 } catch (ActivityNotFoundException ex) {
263 // ignore the error. If no application can handle the URL,
264 // eg about:blank, assume the browser can handle it.
265 }
266
267 return false;
268 }
269
John Reck95a49ff2011-02-08 18:23:22 -0800270 /**
271 * Search for intent handlers that are specific to this URL
272 * aka, specialized apps like google maps or youtube
273 */
274 private boolean isSpecializedHandlerAvailable(Intent intent) {
275 PackageManager pm = mActivity.getPackageManager();
276 List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
277 PackageManager.GET_RESOLVED_FILTER);
278 if (handlers == null || handlers.size() == 0) {
279 return false;
280 }
281 for (ResolveInfo resolveInfo : handlers) {
282 IntentFilter filter = resolveInfo.filter;
283 if (filter == null) {
284 // No intent filter matches this intent?
285 // Error on the side of staying in the browser, ignore
286 continue;
287 }
John Reck1e6a2872011-12-15 14:35:02 -0800288 if (filter.countDataAuthorities() == 0 && filter.countDataPaths() == 0) {
John Reck95a49ff2011-02-08 18:23:22 -0800289 // Generic handler, skip
290 continue;
291 }
292 return true;
293 }
294 return false;
295 }
296
Russell Brenner14e1ae22011-01-12 14:54:23 -0800297 // In case a physical keyboard is attached, handle clicks with the menu key
298 // depressed by opening in a new tab
Russell Brennerca9898e2011-01-21 13:34:02 -0800299 boolean handleMenuClick(Tab tab, String url) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800300 if (mController.isMenuDown()) {
Michael Kolb7bcafde2011-05-09 13:55:59 -0700301 mController.openTab(url,
302 (tab != null) && tab.isPrivateBrowsingEnabled(),
303 !BrowserSettings.getInstance().openInBackground(), true);
Russell Brenner14e1ae22011-01-12 14:54:23 -0800304 mActivity.closeOptionsMenu();
305 return true;
306 }
307
308 return false;
309 }
310
Michael Kolb8233fac2010-10-26 16:08:53 -0700311}