blob: f1d1c4c60bb3dacf93ad8cb0a46d6511690a110a [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 Brennerca9898e2011-01-21 13:34:02 -0800122 boolean startActivityForUrl(String url) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800123 Intent intent;
124 // perform generic parsing of the URI to turn it into an Intent.
125 try {
126 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
127 } catch (URISyntaxException ex) {
128 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
129 return false;
130 }
131
132 // check whether the intent can be resolved. If not, we will see
133 // whether we can download it from the Market.
134 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
135 String packagename = intent.getPackage();
136 if (packagename != null) {
137 intent = new Intent(Intent.ACTION_VIEW, Uri
138 .parse("market://search?q=pname:" + packagename));
139 intent.addCategory(Intent.CATEGORY_BROWSABLE);
140 mActivity.startActivity(intent);
141 // before leaving BrowserActivity, close the empty child tab.
142 // If a new tab is created through JavaScript open to load this
143 // url, we would like to close it as we will load this url in a
144 // different Activity.
145 mController.closeEmptyChildTab();
146 return true;
147 } else {
148 return false;
149 }
150 }
151
152 // sanitize the Intent, ensuring web pages can not bypass browser
153 // security (only access to BROWSABLE activities).
154 intent.addCategory(Intent.CATEGORY_BROWSABLE);
155 intent.setComponent(null);
156 try {
157 if (mActivity.startActivityIfNeeded(intent, -1)) {
158 // before leaving BrowserActivity, close the empty child tab.
159 // If a new tab is created through JavaScript open to load this
160 // url, we would like to close it as we will load this url in a
161 // different Activity.
162 mController.closeEmptyChildTab();
163 return true;
164 }
165 } catch (ActivityNotFoundException ex) {
166 // ignore the error. If no application can handle the URL,
167 // eg about:blank, assume the browser can handle it.
168 }
169
170 return false;
171 }
172
Russell Brenner14e1ae22011-01-12 14:54:23 -0800173 // In case a physical keyboard is attached, handle clicks with the menu key
174 // depressed by opening in a new tab
Russell Brennerca9898e2011-01-21 13:34:02 -0800175 boolean handleMenuClick(Tab tab, String url) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800176 if (mController.isMenuDown()) {
177 mController.openTab(tab, url, false);
178 mActivity.closeOptionsMenu();
179 return true;
180 }
181
182 return false;
183 }
184
Russell Brennerca9898e2011-01-21 13:34:02 -0800185 // TODO: Move this class into Tab, where it can be properly stopped upon
186 // closure of the tab
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 Brennerca9898e2011-01-21 13:34:02 -0800218 // Make sure the Tab was not closed while handling the task
219 if (mController.getTabControl().getTabIndex(mTab) != -1) {
220 // If the Activity Manager is not invoked, load the URL directly
221 if (!startActivityForUrl(result)) {
222 if (!handleMenuClick(mTab, result)) {
223 mController.loadUrl(mWebView, result);
224 }
Russell Brenner14e1ae22011-01-12 14:54:23 -0800225 }
226 }
Ben Murdocha941f6e2010-12-07 16:09:16 +0000227 }
228 }
229
Michael Kolb8233fac2010-10-26 16:08:53 -0700230 // Determine whether the RLZ provider is present on the system.
231 private boolean rlzProviderPresent() {
232 if (mIsProviderPresent == null) {
233 PackageManager pm = mActivity.getPackageManager();
234 mIsProviderPresent = pm.resolveContentProvider(
235 BrowserSettings.RLZ_PROVIDER, 0) != null;
236 }
237 return mIsProviderPresent;
238 }
239
240 // Retrieve the RLZ access point string and cache the URI used to
241 // retrieve RLZ values.
242 private Uri getRlzUri() {
243 if (mRlzUri == null) {
244 String ap = mActivity.getResources()
245 .getString(R.string.rlz_access_point);
246 mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap);
247 }
248 return mRlzUri;
249 }
250
251 // Determine if this URI appears to be for a Google search
252 // and does not have an RLZ parameter.
253 // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
254 private static boolean needsRlzString(Uri uri) {
255 String scheme = uri.getScheme();
256 if (("http".equals(scheme) || "https".equals(scheme)) &&
257 (uri.getQueryParameter("q") != null) &&
258 (uri.getQueryParameter("rlz") == null)) {
259 String host = uri.getHost();
260 if (host == null) {
261 return false;
262 }
263 String[] hostComponents = host.split("\\.");
264
265 if (hostComponents.length < 2) {
266 return false;
267 }
268 int googleComponent = hostComponents.length - 2;
269 String component = hostComponents[googleComponent];
270 if (!"google".equals(component)) {
271 if (hostComponents.length < 3 ||
272 (!"co".equals(component) && !"com".equals(component))) {
273 return false;
274 }
275 googleComponent = hostComponents.length - 3;
276 if (!"google".equals(hostComponents[googleComponent])) {
277 return false;
278 }
279 }
280
281 // Google corp network handling.
282 if (googleComponent > 0 && "corp".equals(
283 hostComponents[googleComponent - 1])) {
284 return false;
285 }
286
287 return true;
288 }
289 return false;
290 }
291
292}