blob: 37c90a6579c186ed7d6b1adec03a9633951bf98c [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;
Michael Kolb8233fac2010-10-26 16:08:53 -070026import android.util.Log;
27import android.webkit.WebView;
28
29import java.net.URISyntaxException;
30
31/**
32 *
33 */
34public class UrlHandler {
35
36 // Use in overrideUrlLoading
37 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
38 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
39 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
40 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
41
42 Controller mController;
43 Activity mActivity;
44
45 private Boolean mIsProviderPresent = null;
46 private Uri mRlzUri = null;
47
48 public UrlHandler(Controller controller) {
49 mController = controller;
50 mActivity = mController.getActivity();
51 }
52
Michael Kolb18eb3772010-12-10 14:29:51 -080053 boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -070054 if (view.isPrivateBrowsingEnabled()) {
55 // Don't allow urls to leave the browser app when in
56 // private browsing mode
57 mController.loadUrl(view, url);
58 return true;
59 }
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.
106 new RLZTask(siteUri, view).execute();
Michael Kolb8233fac2010-10-26 16:08:53 -0700107 return true;
108 }
109 }
110
111 Intent intent;
112 // perform generic parsing of the URI to turn it into an Intent.
113 try {
114 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
115 } catch (URISyntaxException ex) {
116 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
117 return false;
118 }
119
120 // check whether the intent can be resolved. If not, we will see
121 // whether we can download it from the Market.
122 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
123 String packagename = intent.getPackage();
124 if (packagename != null) {
125 intent = new Intent(Intent.ACTION_VIEW, Uri
126 .parse("market://search?q=pname:" + packagename));
127 intent.addCategory(Intent.CATEGORY_BROWSABLE);
128 mActivity.startActivity(intent);
129 // before leaving BrowserActivity, close the empty child tab.
130 // If a new tab is created through JavaScript open to load this
131 // url, we would like to close it as we will load this url in a
132 // different Activity.
133 mController.closeEmptyChildTab();
134 return true;
135 } else {
136 return false;
137 }
138 }
139
140 // sanitize the Intent, ensuring web pages can not bypass browser
141 // security (only access to BROWSABLE activities).
142 intent.addCategory(Intent.CATEGORY_BROWSABLE);
143 intent.setComponent(null);
144 try {
145 if (mActivity.startActivityIfNeeded(intent, -1)) {
146 // before leaving BrowserActivity, close the empty child tab.
147 // If a new tab is created through JavaScript open to load this
148 // url, we would like to close it as we will load this url in a
149 // different Activity.
150 mController.closeEmptyChildTab();
151 return true;
152 }
153 } catch (ActivityNotFoundException ex) {
154 // ignore the error. If no application can handle the URL,
155 // eg about:blank, assume the browser can handle it.
156 }
157
158 if (mController.isMenuDown()) {
Michael Kolb18eb3772010-12-10 14:29:51 -0800159 mController.openTab(tab, url, false);
Michael Kolb8233fac2010-10-26 16:08:53 -0700160 mActivity.closeOptionsMenu();
161 return true;
162 }
163 return false;
164 }
165
Ben Murdocha941f6e2010-12-07 16:09:16 +0000166 private class RLZTask extends AsyncTask<Void, Void, String> {
167 private Uri mSiteUri;
168 private WebView mWebView;
169
170 public RLZTask(Uri uri, WebView webView) {
171 mSiteUri = uri;
172 mWebView = webView;
173 }
174
175 protected String doInBackground(Void... unused) {
176 String result = mSiteUri.toString();
177 Cursor cur = null;
178 try {
179 cur = mActivity.getContentResolver()
180 .query(getRlzUri(), null, null, null, null);
181 if (cur != null && cur.moveToFirst() && !cur.isNull(0)) {
182 result = mSiteUri.buildUpon()
183 .appendQueryParameter("rlz", cur.getString(0))
184 .build().toString();
185 }
186 } finally {
187 if (cur != null) {
188 cur.close();
189 }
190 }
191 return result;
192 }
193
194 protected void onPostExecute(String result) {
195 mController.loadUrl(mWebView, result);
196 }
197 }
198
Michael Kolb8233fac2010-10-26 16:08:53 -0700199 // Determine whether the RLZ provider is present on the system.
200 private boolean rlzProviderPresent() {
201 if (mIsProviderPresent == null) {
202 PackageManager pm = mActivity.getPackageManager();
203 mIsProviderPresent = pm.resolveContentProvider(
204 BrowserSettings.RLZ_PROVIDER, 0) != null;
205 }
206 return mIsProviderPresent;
207 }
208
209 // Retrieve the RLZ access point string and cache the URI used to
210 // retrieve RLZ values.
211 private Uri getRlzUri() {
212 if (mRlzUri == null) {
213 String ap = mActivity.getResources()
214 .getString(R.string.rlz_access_point);
215 mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap);
216 }
217 return mRlzUri;
218 }
219
220 // Determine if this URI appears to be for a Google search
221 // and does not have an RLZ parameter.
222 // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
223 private static boolean needsRlzString(Uri uri) {
224 String scheme = uri.getScheme();
225 if (("http".equals(scheme) || "https".equals(scheme)) &&
226 (uri.getQueryParameter("q") != null) &&
227 (uri.getQueryParameter("rlz") == null)) {
228 String host = uri.getHost();
229 if (host == null) {
230 return false;
231 }
232 String[] hostComponents = host.split("\\.");
233
234 if (hostComponents.length < 2) {
235 return false;
236 }
237 int googleComponent = hostComponents.length - 2;
238 String component = hostComponents[googleComponent];
239 if (!"google".equals(component)) {
240 if (hostComponents.length < 3 ||
241 (!"co".equals(component) && !"com".equals(component))) {
242 return false;
243 }
244 googleComponent = hostComponents.length - 3;
245 if (!"google".equals(hostComponents[googleComponent])) {
246 return false;
247 }
248 }
249
250 // Google corp network handling.
251 if (googleComponent > 0 && "corp".equals(
252 hostComponents[googleComponent - 1])) {
253 return false;
254 }
255
256 return true;
257 }
258 return false;
259 }
260
261}