blob: 167d4105ce37d442ef6760e8aff873794e330a2b [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
17package com.android.browser;
18
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.database.Cursor;
26import android.net.Uri;
Ben Murdocha941f6e2010-12-07 16:09:16 +000027import android.os.AsyncTask;
John Reck8bcafc12011-08-29 16:43:02 -070028import android.provider.Browser;
Michael Kolb8233fac2010-10-26 16:08:53 -070029import android.util.Log;
30import android.webkit.WebView;
31
32import 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
36/**
37 *
38 */
39public class UrlHandler {
40
John Reck35e9dd62011-04-25 09:01:54 -070041 static final String RLZ_PROVIDER = "com.google.android.partnersetup.rlzappprovider";
42 static final Uri RLZ_PROVIDER_URI = Uri.parse("content://" + RLZ_PROVIDER + "/");
43
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
53 private Boolean mIsProviderPresent = null;
54 private Uri mRlzUri = null;
55
56 public UrlHandler(Controller controller) {
57 mController = controller;
58 mActivity = mController.getActivity();
59 }
60
Michael Kolb18eb3772010-12-10 14:29:51 -080061 boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -070062 if (view.isPrivateBrowsingEnabled()) {
63 // Don't allow urls to leave the browser app when in
64 // private browsing mode
Patrick Scottd05faa62010-12-16 09:15:34 -050065 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -070066 }
67
68 if (url.startsWith(SCHEME_WTAI)) {
69 // wtai://wp/mc;number
70 // number=string(phone-number)
71 if (url.startsWith(SCHEME_WTAI_MC)) {
72 Intent intent = new Intent(Intent.ACTION_VIEW,
73 Uri.parse(WebView.SCHEME_TEL +
74 url.substring(SCHEME_WTAI_MC.length())));
75 mActivity.startActivity(intent);
76 // before leaving BrowserActivity, close the empty child tab.
77 // If a new tab is created through JavaScript open to load this
78 // url, we would like to close it as we will load this url in a
79 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -070080 mController.closeEmptyTab();
Michael Kolb8233fac2010-10-26 16:08:53 -070081 return true;
82 }
83 // wtai://wp/sd;dtmf
84 // dtmf=string(dialstring)
85 if (url.startsWith(SCHEME_WTAI_SD)) {
86 // TODO: only send when there is active voice connection
87 return false;
88 }
89 // wtai://wp/ap;number;name
90 // number=string(phone-number)
91 // name=string
92 if (url.startsWith(SCHEME_WTAI_AP)) {
93 // TODO
94 return false;
95 }
96 }
97
98 // The "about:" schemes are internal to the browser; don't want these to
99 // be dispatched to other apps.
100 if (url.startsWith("about:")) {
101 return false;
102 }
103
104 // If this is a Google search, attempt to add an RLZ string
105 // (if one isn't already present).
106 if (rlzProviderPresent()) {
107 Uri siteUri = Uri.parse(url);
108 if (needsRlzString(siteUri)) {
Ben Murdocha941f6e2010-12-07 16:09:16 +0000109 // Need to look up the RLZ info from a database, so do it in an
110 // AsyncTask. Although we are not overriding the URL load synchronously,
111 // we guarantee that we will handle this URL load after the task executes,
112 // so it's safe to just return true to WebCore now to stop its own loading.
Russell Brenner14e1ae22011-01-12 14:54:23 -0800113 new RLZTask(tab, siteUri, view).execute();
Michael Kolb8233fac2010-10-26 16:08:53 -0700114 return true;
115 }
116 }
117
John Reck8bcafc12011-08-29 16:43:02 -0700118 if (startActivityForUrl(tab, url)) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800119 return true;
Michael Kolb8233fac2010-10-26 16:08:53 -0700120 }
121
Russell Brenner14e1ae22011-01-12 14:54:23 -0800122 if (handleMenuClick(tab, url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700123 return true;
124 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800125
Michael Kolb8233fac2010-10-26 16:08:53 -0700126 return false;
127 }
128
John Reck8bcafc12011-08-29 16:43:02 -0700129 boolean startActivityForUrl(Tab tab, String url) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800130 Intent intent;
131 // perform generic parsing of the URI to turn it into an Intent.
132 try {
133 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
134 } catch (URISyntaxException ex) {
135 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
136 return false;
137 }
138
139 // check whether the intent can be resolved. If not, we will see
140 // whether we can download it from the Market.
141 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
142 String packagename = intent.getPackage();
143 if (packagename != null) {
144 intent = new Intent(Intent.ACTION_VIEW, Uri
145 .parse("market://search?q=pname:" + packagename));
146 intent.addCategory(Intent.CATEGORY_BROWSABLE);
147 mActivity.startActivity(intent);
148 // before leaving BrowserActivity, close the empty child tab.
149 // If a new tab is created through JavaScript open to load this
150 // url, we would like to close it as we will load this url in a
151 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -0700152 mController.closeEmptyTab();
Russell Brennerd4afde12011-01-07 11:09:36 -0800153 return true;
154 } else {
155 return false;
156 }
157 }
158
159 // sanitize the Intent, ensuring web pages can not bypass browser
160 // security (only access to BROWSABLE activities).
161 intent.addCategory(Intent.CATEGORY_BROWSABLE);
162 intent.setComponent(null);
John Reck8bcafc12011-08-29 16:43:02 -0700163 // Re-use the existing tab if the intent comes back to us
164 if (tab != null) {
165 if (tab.getAppId() == null) {
Michael Kolbf1286a42012-04-17 14:10:52 -0700166 tab.setAppId(mActivity.getPackageName() + "-" + tab.getId());
John Reck8bcafc12011-08-29 16:43:02 -0700167 }
168 intent.putExtra(Browser.EXTRA_APPLICATION_ID, tab.getAppId());
169 }
John Reckdb3d43d2011-02-11 11:56:38 -0800170 // Make sure webkit can handle it internally before checking for specialized
171 // handlers. If webkit can't handle it internally, we need to call
172 // startActivityIfNeeded
173 Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
174 if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
John Reck95a49ff2011-02-08 18:23:22 -0800175 return false;
176 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800177 try {
John Reck38b39652012-06-05 09:22:59 -0700178 intent.putExtra(BrowserActivity.EXTRA_DISABLE_URL_OVERRIDE, true);
Russell Brennerd4afde12011-01-07 11:09:36 -0800179 if (mActivity.startActivityIfNeeded(intent, -1)) {
180 // before leaving BrowserActivity, close the empty child tab.
181 // If a new tab is created through JavaScript open to load this
182 // url, we would like to close it as we will load this url in a
183 // different Activity.
John Reck8bcafc12011-08-29 16:43:02 -0700184 mController.closeEmptyTab();
Russell Brennerd4afde12011-01-07 11:09:36 -0800185 return true;
186 }
187 } catch (ActivityNotFoundException ex) {
188 // ignore the error. If no application can handle the URL,
189 // eg about:blank, assume the browser can handle it.
190 }
191
192 return false;
193 }
194
John Reck95a49ff2011-02-08 18:23:22 -0800195 /**
196 * Search for intent handlers that are specific to this URL
197 * aka, specialized apps like google maps or youtube
198 */
199 private boolean isSpecializedHandlerAvailable(Intent intent) {
200 PackageManager pm = mActivity.getPackageManager();
201 List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
202 PackageManager.GET_RESOLVED_FILTER);
203 if (handlers == null || handlers.size() == 0) {
204 return false;
205 }
206 for (ResolveInfo resolveInfo : handlers) {
207 IntentFilter filter = resolveInfo.filter;
208 if (filter == null) {
209 // No intent filter matches this intent?
210 // Error on the side of staying in the browser, ignore
211 continue;
212 }
John Reck1e6a2872011-12-15 14:35:02 -0800213 if (filter.countDataAuthorities() == 0 && filter.countDataPaths() == 0) {
John Reck95a49ff2011-02-08 18:23:22 -0800214 // Generic handler, skip
215 continue;
216 }
217 return true;
218 }
219 return false;
220 }
221
Russell Brenner14e1ae22011-01-12 14:54:23 -0800222 // In case a physical keyboard is attached, handle clicks with the menu key
223 // depressed by opening in a new tab
Russell Brennerca9898e2011-01-21 13:34:02 -0800224 boolean handleMenuClick(Tab tab, String url) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800225 if (mController.isMenuDown()) {
Michael Kolb7bcafde2011-05-09 13:55:59 -0700226 mController.openTab(url,
227 (tab != null) && tab.isPrivateBrowsingEnabled(),
228 !BrowserSettings.getInstance().openInBackground(), true);
Russell Brenner14e1ae22011-01-12 14:54:23 -0800229 mActivity.closeOptionsMenu();
230 return true;
231 }
232
233 return false;
234 }
235
Russell Brennerca9898e2011-01-21 13:34:02 -0800236 // TODO: Move this class into Tab, where it can be properly stopped upon
237 // closure of the tab
Ben Murdocha941f6e2010-12-07 16:09:16 +0000238 private class RLZTask extends AsyncTask<Void, Void, String> {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800239 private Tab mTab;
Ben Murdocha941f6e2010-12-07 16:09:16 +0000240 private Uri mSiteUri;
241 private WebView mWebView;
242
Russell Brenner14e1ae22011-01-12 14:54:23 -0800243 public RLZTask(Tab tab, Uri uri, WebView webView) {
244 mTab = tab;
Ben Murdocha941f6e2010-12-07 16:09:16 +0000245 mSiteUri = uri;
246 mWebView = webView;
247 }
248
249 protected String doInBackground(Void... unused) {
250 String result = mSiteUri.toString();
251 Cursor cur = null;
252 try {
253 cur = mActivity.getContentResolver()
254 .query(getRlzUri(), null, null, null, null);
255 if (cur != null && cur.moveToFirst() && !cur.isNull(0)) {
256 result = mSiteUri.buildUpon()
257 .appendQueryParameter("rlz", cur.getString(0))
258 .build().toString();
259 }
260 } finally {
261 if (cur != null) {
262 cur.close();
263 }
264 }
265 return result;
266 }
267
268 protected void onPostExecute(String result) {
Michael Kolbe57c7092011-07-19 11:56:56 -0700269 // abort if we left browser already
270 if (mController.isActivityPaused()) return;
Russell Brennerca9898e2011-01-21 13:34:02 -0800271 // Make sure the Tab was not closed while handling the task
Michael Kolbc831b632011-05-11 09:30:34 -0700272 if (mController.getTabControl().getTabPosition(mTab) != -1) {
Russell Brennerca9898e2011-01-21 13:34:02 -0800273 // If the Activity Manager is not invoked, load the URL directly
John Reck8bcafc12011-08-29 16:43:02 -0700274 if (!startActivityForUrl(mTab, result)) {
Russell Brennerca9898e2011-01-21 13:34:02 -0800275 if (!handleMenuClick(mTab, result)) {
John Reck26b18322011-06-21 13:08:58 -0700276 mController.loadUrl(mTab, result);
Russell Brennerca9898e2011-01-21 13:34:02 -0800277 }
Russell Brenner14e1ae22011-01-12 14:54:23 -0800278 }
279 }
Ben Murdocha941f6e2010-12-07 16:09:16 +0000280 }
281 }
282
Michael Kolb8233fac2010-10-26 16:08:53 -0700283 // Determine whether the RLZ provider is present on the system.
284 private boolean rlzProviderPresent() {
285 if (mIsProviderPresent == null) {
286 PackageManager pm = mActivity.getPackageManager();
John Reck35e9dd62011-04-25 09:01:54 -0700287 mIsProviderPresent = pm.resolveContentProvider(RLZ_PROVIDER, 0) != null;
Michael Kolb8233fac2010-10-26 16:08:53 -0700288 }
289 return mIsProviderPresent;
290 }
291
292 // Retrieve the RLZ access point string and cache the URI used to
293 // retrieve RLZ values.
294 private Uri getRlzUri() {
295 if (mRlzUri == null) {
296 String ap = mActivity.getResources()
297 .getString(R.string.rlz_access_point);
John Reck35e9dd62011-04-25 09:01:54 -0700298 mRlzUri = Uri.withAppendedPath(RLZ_PROVIDER_URI, ap);
Michael Kolb8233fac2010-10-26 16:08:53 -0700299 }
300 return mRlzUri;
301 }
302
303 // Determine if this URI appears to be for a Google search
304 // and does not have an RLZ parameter.
305 // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
306 private static boolean needsRlzString(Uri uri) {
307 String scheme = uri.getScheme();
308 if (("http".equals(scheme) || "https".equals(scheme)) &&
309 (uri.getQueryParameter("q") != null) &&
310 (uri.getQueryParameter("rlz") == null)) {
311 String host = uri.getHost();
312 if (host == null) {
313 return false;
314 }
315 String[] hostComponents = host.split("\\.");
316
317 if (hostComponents.length < 2) {
318 return false;
319 }
320 int googleComponent = hostComponents.length - 2;
321 String component = hostComponents[googleComponent];
322 if (!"google".equals(component)) {
323 if (hostComponents.length < 3 ||
324 (!"co".equals(component) && !"com".equals(component))) {
325 return false;
326 }
327 googleComponent = hostComponents.length - 3;
328 if (!"google".equals(hostComponents[googleComponent])) {
329 return false;
330 }
331 }
332
333 // Google corp network handling.
334 if (googleComponent > 0 && "corp".equals(
335 hostComponents[googleComponent - 1])) {
336 return false;
337 }
338
339 return true;
340 }
341 return false;
342 }
343
344}