blob: 783f11a2abaa53fec76e73c17817b46d0fe68bc7 [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;
John Reck8bcafc12011-08-29 16:43:02 -070026import android.provider.Browser;
Michael Kolb8233fac2010-10-26 16:08:53 -070027import android.util.Log;
kaiyizc4ada322013-07-30 09:58:07 +080028import android.widget.Toast;
Michael Kolb8233fac2010-10-26 16:08:53 -070029
Bijan Amirzada41242f22014-03-21 12:12:18 -070030import com.android.browser.R;
31import com.android.browser.reflect.ReflectHelper;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080032
kaiyizc4ada322013-07-30 09:58:07 +080033import java.io.UnsupportedEncodingException;
Michael Kolb8233fac2010-10-26 16:08:53 -070034import java.net.URISyntaxException;
John Reck95a49ff2011-02-08 18:23:22 -080035import java.util.List;
John Reckdb3d43d2011-02-11 11:56:38 -080036import java.util.regex.Matcher;
Michael Kolb8233fac2010-10-26 16:08:53 -070037
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080038import org.codeaurora.swe.WebView;
39
Michael Kolb8233fac2010-10-26 16:08:53 -070040public class UrlHandler {
41
kaiyizc4ada322013-07-30 09:58:07 +080042 private final static String TAG = "UrlHandler";
John Reck35e9dd62011-04-25 09:01:54 -070043
Michael Kolb8233fac2010-10-26 16:08:53 -070044 // Use in overrideUrlLoading
45 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
46 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
47 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
48 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
49
50 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
65 if (url.startsWith(SCHEME_WTAI)) {
66 // wtai://wp/mc;number
67 // number=string(phone-number)
68 if (url.startsWith(SCHEME_WTAI_MC)) {
69 Intent intent = new Intent(Intent.ACTION_VIEW,
70 Uri.parse(WebView.SCHEME_TEL +
71 url.substring(SCHEME_WTAI_MC.length())));
72 mActivity.startActivity(intent);
73 // before leaving BrowserActivity, close the empty child tab.
74 // If a new tab is created through JavaScript open to load this
75 // url, we would like to close it as we will load this url in a
76 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -070077 mController.closeEmptyTab();
Michael Kolb8233fac2010-10-26 16:08:53 -070078 return true;
79 }
80 // wtai://wp/sd;dtmf
81 // dtmf=string(dialstring)
82 if (url.startsWith(SCHEME_WTAI_SD)) {
83 // TODO: only send when there is active voice connection
84 return false;
85 }
86 // wtai://wp/ap;number;name
87 // number=string(phone-number)
88 // name=string
89 if (url.startsWith(SCHEME_WTAI_AP)) {
90 // TODO
91 return false;
92 }
93 }
94
95 // The "about:" schemes are internal to the browser; don't want these to
96 // be dispatched to other apps.
97 if (url.startsWith("about:")) {
98 return false;
99 }
100
kaiyiz6e5b3e02013-08-19 20:02:01 +0800101 if (url.startsWith("ae://") && url.endsWith("add-fav")) {
102 mController.startAddMyNavigation(url);
103 return true;
104 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700105
kaiyizc4ada322013-07-30 09:58:07 +0800106 // add for carrier wap2estore feature
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800107 Object[] params = {new String("persist.env.browser.wap2estore"),
108 Boolean.valueOf(false)};
109 Class[] type = new Class[] {String.class, boolean.class};
110 Boolean wap2estore = (Boolean)ReflectHelper.invokeStaticMethod(
111 "android.os.SystemProperties", "getBoolean", type, params);
kaiyizc4ada322013-07-30 09:58:07 +0800112 if (wap2estore && isEstoreTypeUrl(url)) {
113 handleEstoreTypeUrl(url);
114 return true;
115 }
116
John Reck8bcafc12011-08-29 16:43:02 -0700117 if (startActivityForUrl(tab, url)) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800118 return true;
Michael Kolb8233fac2010-10-26 16:08:53 -0700119 }
120
Russell Brenner14e1ae22011-01-12 14:54:23 -0800121 if (handleMenuClick(tab, url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700122 return true;
123 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800124
Michael Kolb8233fac2010-10-26 16:08:53 -0700125 return false;
126 }
127
kaiyizc4ada322013-07-30 09:58:07 +0800128 private boolean isEstoreTypeUrl(String url) {
129 String utf8Url = null;
130 try {
131 utf8Url = new String(url.getBytes("UTF-8"), "UTF-8");
132 } catch (UnsupportedEncodingException e) {
133 Log.e(TAG, "err " + e);
134 }
135 if (utf8Url != null && utf8Url.startsWith("estore:")) {
136 return true;
137 }
138 return false;
139 }
140
141 private void handleEstoreTypeUrl(String url) {
142 String utf8Url = null, finalUrl = null;
143 try {
144 utf8Url = new String(url.getBytes("UTF-8"), "UTF-8");
145 } catch (UnsupportedEncodingException e) {
146 Log.e(TAG, "err " + e);
147 }
148 if (utf8Url != null) {
149 finalUrl = utf8Url;
150 } else {
151 finalUrl = url;
152 }
153 if (finalUrl.replaceFirst("estore:", "").length() > 256) {
154 Toast.makeText(mActivity, R.string.estore_url_warning, Toast.LENGTH_LONG).show();
155 return;
156 }
157 Intent intent = new Intent(Intent.ACTION_VIEW);
158 intent.setData(Uri.parse(finalUrl));
159 try {
160 mActivity.startActivity(intent);
161 } catch (ActivityNotFoundException ex) {
162 String downloadUrl = mActivity.getResources().getString(R.string.estore_homepage);
163 mController.loadUrl(mController.getCurrentTab(), downloadUrl);
164 Toast.makeText(mActivity, R.string.download_estore_app, Toast.LENGTH_LONG).show();
165 }
166 }
167
John Reck8bcafc12011-08-29 16:43:02 -0700168 boolean startActivityForUrl(Tab tab, String url) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800169 Intent intent;
170 // perform generic parsing of the URI to turn it into an Intent.
171 try {
172 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
173 } catch (URISyntaxException ex) {
174 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
175 return false;
176 }
177
178 // check whether the intent can be resolved. If not, we will see
179 // whether we can download it from the Market.
180 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
181 String packagename = intent.getPackage();
182 if (packagename != null) {
183 intent = new Intent(Intent.ACTION_VIEW, Uri
184 .parse("market://search?q=pname:" + packagename));
185 intent.addCategory(Intent.CATEGORY_BROWSABLE);
186 mActivity.startActivity(intent);
187 // before leaving BrowserActivity, close the empty child tab.
188 // If a new tab is created through JavaScript open to load this
189 // url, we would like to close it as we will load this url in a
190 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -0700191 mController.closeEmptyTab();
Russell Brennerd4afde12011-01-07 11:09:36 -0800192 return true;
193 } else {
194 return false;
195 }
196 }
197
198 // sanitize the Intent, ensuring web pages can not bypass browser
199 // security (only access to BROWSABLE activities).
200 intent.addCategory(Intent.CATEGORY_BROWSABLE);
201 intent.setComponent(null);
John Reck8bcafc12011-08-29 16:43:02 -0700202 // Re-use the existing tab if the intent comes back to us
203 if (tab != null) {
204 if (tab.getAppId() == null) {
Michael Kolbf1286a42012-04-17 14:10:52 -0700205 tab.setAppId(mActivity.getPackageName() + "-" + tab.getId());
John Reck8bcafc12011-08-29 16:43:02 -0700206 }
207 intent.putExtra(Browser.EXTRA_APPLICATION_ID, tab.getAppId());
208 }
John Reckdb3d43d2011-02-11 11:56:38 -0800209 // Make sure webkit can handle it internally before checking for specialized
210 // handlers. If webkit can't handle it internally, we need to call
211 // startActivityIfNeeded
212 Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
213 if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
John Reck95a49ff2011-02-08 18:23:22 -0800214 return false;
215 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800216 try {
John Reck38b39652012-06-05 09:22:59 -0700217 intent.putExtra(BrowserActivity.EXTRA_DISABLE_URL_OVERRIDE, true);
Russell Brennerd4afde12011-01-07 11:09:36 -0800218 if (mActivity.startActivityIfNeeded(intent, -1)) {
219 // before leaving BrowserActivity, close the empty child tab.
220 // If a new tab is created through JavaScript open to load this
221 // url, we would like to close it as we will load this url in a
222 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -0700223 mController.closeEmptyTab();
Russell Brennerd4afde12011-01-07 11:09:36 -0800224 return true;
225 }
226 } catch (ActivityNotFoundException ex) {
227 // ignore the error. If no application can handle the URL,
228 // eg about:blank, assume the browser can handle it.
229 }
230
231 return false;
232 }
233
John Reck95a49ff2011-02-08 18:23:22 -0800234 /**
235 * Search for intent handlers that are specific to this URL
236 * aka, specialized apps like google maps or youtube
237 */
238 private boolean isSpecializedHandlerAvailable(Intent intent) {
239 PackageManager pm = mActivity.getPackageManager();
240 List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
241 PackageManager.GET_RESOLVED_FILTER);
242 if (handlers == null || handlers.size() == 0) {
243 return false;
244 }
245 for (ResolveInfo resolveInfo : handlers) {
246 IntentFilter filter = resolveInfo.filter;
247 if (filter == null) {
248 // No intent filter matches this intent?
249 // Error on the side of staying in the browser, ignore
250 continue;
251 }
John Reck1e6a2872011-12-15 14:35:02 -0800252 if (filter.countDataAuthorities() == 0 && filter.countDataPaths() == 0) {
John Reck95a49ff2011-02-08 18:23:22 -0800253 // Generic handler, skip
254 continue;
255 }
256 return true;
257 }
258 return false;
259 }
260
Russell Brenner14e1ae22011-01-12 14:54:23 -0800261 // In case a physical keyboard is attached, handle clicks with the menu key
262 // depressed by opening in a new tab
Russell Brennerca9898e2011-01-21 13:34:02 -0800263 boolean handleMenuClick(Tab tab, String url) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800264 if (mController.isMenuDown()) {
Michael Kolb7bcafde2011-05-09 13:55:59 -0700265 mController.openTab(url,
266 (tab != null) && tab.isPrivateBrowsingEnabled(),
267 !BrowserSettings.getInstance().openInBackground(), true);
Russell Brenner14e1ae22011-01-12 14:54:23 -0800268 mActivity.closeOptionsMenu();
269 return true;
270 }
271
272 return false;
273 }
274
Michael Kolb8233fac2010-10-26 16:08:53 -0700275}