blob: 9e41990be347854dd4a31cb5980ba11d5a8d51b5 [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;
22import android.content.pm.PackageManager;
23import android.database.Cursor;
24import android.net.Uri;
Ben Murdocha941f6e2010-12-07 16:09:16 +000025import android.os.AsyncTask;
Patrick Scottd05faa62010-12-16 09:15:34 -050026import android.os.Bundle;
Michael Kolb8233fac2010-10-26 16:08:53 -070027import android.util.Log;
28import android.webkit.WebView;
29
30import java.net.URISyntaxException;
31
32/**
33 *
34 */
35public class UrlHandler {
36
37 // Use in overrideUrlLoading
38 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
39 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
40 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
41 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
42
43 Controller mController;
44 Activity mActivity;
45
46 private Boolean mIsProviderPresent = null;
47 private Uri mRlzUri = null;
48
49 public UrlHandler(Controller controller) {
50 mController = controller;
51 mActivity = mController.getActivity();
52 }
53
Michael Kolb18eb3772010-12-10 14:29:51 -080054 boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -070055 if (view.isPrivateBrowsingEnabled()) {
56 // Don't allow urls to leave the browser app when in
57 // private browsing mode
Patrick Scottd05faa62010-12-16 09:15:34 -050058 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -070059 }
60
61 if (url.startsWith(SCHEME_WTAI)) {
62 // wtai://wp/mc;number
63 // number=string(phone-number)
64 if (url.startsWith(SCHEME_WTAI_MC)) {
65 Intent intent = new Intent(Intent.ACTION_VIEW,
66 Uri.parse(WebView.SCHEME_TEL +
67 url.substring(SCHEME_WTAI_MC.length())));
68 mActivity.startActivity(intent);
69 // before leaving BrowserActivity, close the empty child tab.
70 // If a new tab is created through JavaScript open to load this
71 // url, we would like to close it as we will load this url in a
72 // different Activity.
73 mController.closeEmptyChildTab();
74 return true;
75 }
76 // wtai://wp/sd;dtmf
77 // dtmf=string(dialstring)
78 if (url.startsWith(SCHEME_WTAI_SD)) {
79 // TODO: only send when there is active voice connection
80 return false;
81 }
82 // wtai://wp/ap;number;name
83 // number=string(phone-number)
84 // name=string
85 if (url.startsWith(SCHEME_WTAI_AP)) {
86 // TODO
87 return false;
88 }
89 }
90
91 // The "about:" schemes are internal to the browser; don't want these to
92 // be dispatched to other apps.
93 if (url.startsWith("about:")) {
94 return false;
95 }
96
97 // If this is a Google search, attempt to add an RLZ string
98 // (if one isn't already present).
99 if (rlzProviderPresent()) {
100 Uri siteUri = Uri.parse(url);
101 if (needsRlzString(siteUri)) {
Ben Murdocha941f6e2010-12-07 16:09:16 +0000102 // Need to look up the RLZ info from a database, so do it in an
103 // AsyncTask. Although we are not overriding the URL load synchronously,
104 // we guarantee that we will handle this URL load after the task executes,
105 // so it's safe to just return true to WebCore now to stop its own loading.
Russell Brenner14e1ae22011-01-12 14:54:23 -0800106 new RLZTask(tab, siteUri, view).execute();
Michael Kolb8233fac2010-10-26 16:08:53 -0700107 return true;
108 }
109 }
110
Russell Brennerd4afde12011-01-07 11:09:36 -0800111 if (startActivityForUrl(url)) {
112 return true;
Michael Kolb8233fac2010-10-26 16:08:53 -0700113 }
114
Russell Brenner14e1ae22011-01-12 14:54:23 -0800115 if (handleMenuClick(tab, url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700116 return true;
117 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800118
Michael Kolb8233fac2010-10-26 16:08:53 -0700119 return false;
120 }
121
Russell Brennerd4afde12011-01-07 11:09:36 -0800122 boolean startActivityForUrl(String url)
123 {
124 Intent intent;
125 // perform generic parsing of the URI to turn it into an Intent.
126 try {
127 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
128 } catch (URISyntaxException ex) {
129 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
130 return false;
131 }
132
133 // check whether the intent can be resolved. If not, we will see
134 // whether we can download it from the Market.
135 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
136 String packagename = intent.getPackage();
137 if (packagename != null) {
138 intent = new Intent(Intent.ACTION_VIEW, Uri
139 .parse("market://search?q=pname:" + packagename));
140 intent.addCategory(Intent.CATEGORY_BROWSABLE);
141 mActivity.startActivity(intent);
142 // before leaving BrowserActivity, close the empty child tab.
143 // If a new tab is created through JavaScript open to load this
144 // url, we would like to close it as we will load this url in a
145 // different Activity.
146 mController.closeEmptyChildTab();
147 return true;
148 } else {
149 return false;
150 }
151 }
152
153 // sanitize the Intent, ensuring web pages can not bypass browser
154 // security (only access to BROWSABLE activities).
155 intent.addCategory(Intent.CATEGORY_BROWSABLE);
156 intent.setComponent(null);
157 try {
158 if (mActivity.startActivityIfNeeded(intent, -1)) {
159 // before leaving BrowserActivity, close the empty child tab.
160 // If a new tab is created through JavaScript open to load this
161 // url, we would like to close it as we will load this url in a
162 // different Activity.
163 mController.closeEmptyChildTab();
164 return true;
165 }
166 } catch (ActivityNotFoundException ex) {
167 // ignore the error. If no application can handle the URL,
168 // eg about:blank, assume the browser can handle it.
169 }
170
171 return false;
172 }
173
Russell Brenner14e1ae22011-01-12 14:54:23 -0800174 // In case a physical keyboard is attached, handle clicks with the menu key
175 // depressed by opening in a new tab
176 boolean handleMenuClick(Tab tab, String url)
177 {
178 if (mController.isMenuDown()) {
179 mController.openTab(tab, url, false);
180 mActivity.closeOptionsMenu();
181 return true;
182 }
183
184 return false;
185 }
186
Ben Murdocha941f6e2010-12-07 16:09:16 +0000187 private class RLZTask extends AsyncTask<Void, Void, String> {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800188 private Tab mTab;
Ben Murdocha941f6e2010-12-07 16:09:16 +0000189 private Uri mSiteUri;
190 private WebView mWebView;
191
Russell Brenner14e1ae22011-01-12 14:54:23 -0800192 public RLZTask(Tab tab, Uri uri, WebView webView) {
193 mTab = tab;
Ben Murdocha941f6e2010-12-07 16:09:16 +0000194 mSiteUri = uri;
195 mWebView = webView;
196 }
197
198 protected String doInBackground(Void... unused) {
199 String result = mSiteUri.toString();
200 Cursor cur = null;
201 try {
202 cur = mActivity.getContentResolver()
203 .query(getRlzUri(), null, null, null, null);
204 if (cur != null && cur.moveToFirst() && !cur.isNull(0)) {
205 result = mSiteUri.buildUpon()
206 .appendQueryParameter("rlz", cur.getString(0))
207 .build().toString();
208 }
209 } finally {
210 if (cur != null) {
211 cur.close();
212 }
213 }
214 return result;
215 }
216
217 protected void onPostExecute(String result) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800218 // If the Activity Manager is not invoked, load the URL directly
219 if (!startActivityForUrl(result)) {
220 if (!handleMenuClick(mTab, result)) {
221 mController.loadUrl(mWebView, result);
222 }
223 }
Ben Murdocha941f6e2010-12-07 16:09:16 +0000224 }
225 }
226
Michael Kolb8233fac2010-10-26 16:08:53 -0700227 // Determine whether the RLZ provider is present on the system.
228 private boolean rlzProviderPresent() {
229 if (mIsProviderPresent == null) {
230 PackageManager pm = mActivity.getPackageManager();
231 mIsProviderPresent = pm.resolveContentProvider(
232 BrowserSettings.RLZ_PROVIDER, 0) != null;
233 }
234 return mIsProviderPresent;
235 }
236
237 // Retrieve the RLZ access point string and cache the URI used to
238 // retrieve RLZ values.
239 private Uri getRlzUri() {
240 if (mRlzUri == null) {
241 String ap = mActivity.getResources()
242 .getString(R.string.rlz_access_point);
243 mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap);
244 }
245 return mRlzUri;
246 }
247
248 // Determine if this URI appears to be for a Google search
249 // and does not have an RLZ parameter.
250 // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
251 private static boolean needsRlzString(Uri uri) {
252 String scheme = uri.getScheme();
253 if (("http".equals(scheme) || "https".equals(scheme)) &&
254 (uri.getQueryParameter("q") != null) &&
255 (uri.getQueryParameter("rlz") == null)) {
256 String host = uri.getHost();
257 if (host == null) {
258 return false;
259 }
260 String[] hostComponents = host.split("\\.");
261
262 if (hostComponents.length < 2) {
263 return false;
264 }
265 int googleComponent = hostComponents.length - 2;
266 String component = hostComponents[googleComponent];
267 if (!"google".equals(component)) {
268 if (hostComponents.length < 3 ||
269 (!"co".equals(component) && !"com".equals(component))) {
270 return false;
271 }
272 googleComponent = hostComponents.length - 3;
273 if (!"google".equals(hostComponents[googleComponent])) {
274 return false;
275 }
276 }
277
278 // Google corp network handling.
279 if (googleComponent > 0 && "corp".equals(
280 hostComponents[googleComponent - 1])) {
281 return false;
282 }
283
284 return true;
285 }
286 return false;
287 }
288
289}