blob: b5b69ba91787487314d5a13366d9ccf07d425a16 [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.net.Uri;
26import android.util.Log;
27import android.webkit.WebView;
28
29import java.net.URISyntaxException;
John Reck95a49ff2011-02-08 18:23:22 -080030import java.util.List;
John Reckdb3d43d2011-02-11 11:56:38 -080031import java.util.regex.Matcher;
Michael Kolb8233fac2010-10-26 16:08:53 -070032
33/**
34 *
35 */
36public class UrlHandler {
37
38 // Use in overrideUrlLoading
39 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
40 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
41 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
42 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
43
44 Controller mController;
45 Activity mActivity;
46
Michael Kolb8233fac2010-10-26 16:08:53 -070047 public UrlHandler(Controller controller) {
48 mController = controller;
49 mActivity = mController.getActivity();
50 }
51
Michael Kolb18eb3772010-12-10 14:29:51 -080052 boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -070053 if (view.isPrivateBrowsingEnabled()) {
54 // Don't allow urls to leave the browser app when in
55 // private browsing mode
Patrick Scottd05faa62010-12-16 09:15:34 -050056 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -070057 }
58
59 if (url.startsWith(SCHEME_WTAI)) {
60 // wtai://wp/mc;number
61 // number=string(phone-number)
62 if (url.startsWith(SCHEME_WTAI_MC)) {
63 Intent intent = new Intent(Intent.ACTION_VIEW,
64 Uri.parse(WebView.SCHEME_TEL +
65 url.substring(SCHEME_WTAI_MC.length())));
66 mActivity.startActivity(intent);
67 // before leaving BrowserActivity, close the empty child tab.
68 // If a new tab is created through JavaScript open to load this
69 // url, we would like to close it as we will load this url in a
70 // different Activity.
71 mController.closeEmptyChildTab();
72 return true;
73 }
74 // wtai://wp/sd;dtmf
75 // dtmf=string(dialstring)
76 if (url.startsWith(SCHEME_WTAI_SD)) {
77 // TODO: only send when there is active voice connection
78 return false;
79 }
80 // wtai://wp/ap;number;name
81 // number=string(phone-number)
82 // name=string
83 if (url.startsWith(SCHEME_WTAI_AP)) {
84 // TODO
85 return false;
86 }
87 }
88
89 // The "about:" schemes are internal to the browser; don't want these to
John Reck27535d12011-06-21 14:44:14 -070090 // be dispatched to other apps. Similarly, javascript: schemas are private
91 // to the page
92 if (url.startsWith("about:") || url.startsWith("javascript:")) {
Michael Kolb8233fac2010-10-26 16:08:53 -070093 return false;
94 }
95
Russell Brennerd4afde12011-01-07 11:09:36 -080096 if (startActivityForUrl(url)) {
97 return true;
Michael Kolb8233fac2010-10-26 16:08:53 -070098 }
99
Russell Brenner14e1ae22011-01-12 14:54:23 -0800100 if (handleMenuClick(tab, url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700101 return true;
102 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800103
Michael Kolb8233fac2010-10-26 16:08:53 -0700104 return false;
105 }
106
Russell Brennerca9898e2011-01-21 13:34:02 -0800107 boolean startActivityForUrl(String url) {
Russell Brennerd4afde12011-01-07 11:09:36 -0800108 Intent intent;
109 // perform generic parsing of the URI to turn it into an Intent.
110 try {
111 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
112 } catch (URISyntaxException ex) {
113 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
114 return false;
115 }
116
117 // check whether the intent can be resolved. If not, we will see
118 // whether we can download it from the Market.
119 if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) {
120 String packagename = intent.getPackage();
121 if (packagename != null) {
122 intent = new Intent(Intent.ACTION_VIEW, Uri
123 .parse("market://search?q=pname:" + packagename));
124 intent.addCategory(Intent.CATEGORY_BROWSABLE);
125 mActivity.startActivity(intent);
126 // before leaving BrowserActivity, close the empty child tab.
127 // If a new tab is created through JavaScript open to load this
128 // url, we would like to close it as we will load this url in a
129 // different Activity.
130 mController.closeEmptyChildTab();
131 return true;
132 } else {
133 return false;
134 }
135 }
136
137 // sanitize the Intent, ensuring web pages can not bypass browser
138 // security (only access to BROWSABLE activities).
139 intent.addCategory(Intent.CATEGORY_BROWSABLE);
140 intent.setComponent(null);
John Reckdb3d43d2011-02-11 11:56:38 -0800141 // Make sure webkit can handle it internally before checking for specialized
142 // handlers. If webkit can't handle it internally, we need to call
143 // startActivityIfNeeded
144 Matcher m = UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url);
145 if (m.matches() && !isSpecializedHandlerAvailable(intent)) {
John Reck95a49ff2011-02-08 18:23:22 -0800146 return false;
147 }
Russell Brennerd4afde12011-01-07 11:09:36 -0800148 try {
149 if (mActivity.startActivityIfNeeded(intent, -1)) {
150 // before leaving BrowserActivity, close the empty child tab.
151 // If a new tab is created through JavaScript open to load this
152 // url, we would like to close it as we will load this url in a
153 // different Activity.
154 mController.closeEmptyChildTab();
155 return true;
156 }
157 } catch (ActivityNotFoundException ex) {
158 // ignore the error. If no application can handle the URL,
159 // eg about:blank, assume the browser can handle it.
160 }
161
162 return false;
163 }
164
John Reck95a49ff2011-02-08 18:23:22 -0800165 /**
166 * Search for intent handlers that are specific to this URL
167 * aka, specialized apps like google maps or youtube
168 */
169 private boolean isSpecializedHandlerAvailable(Intent intent) {
170 PackageManager pm = mActivity.getPackageManager();
171 List<ResolveInfo> handlers = pm.queryIntentActivities(intent,
172 PackageManager.GET_RESOLVED_FILTER);
173 if (handlers == null || handlers.size() == 0) {
174 return false;
175 }
176 for (ResolveInfo resolveInfo : handlers) {
177 IntentFilter filter = resolveInfo.filter;
178 if (filter == null) {
179 // No intent filter matches this intent?
180 // Error on the side of staying in the browser, ignore
181 continue;
182 }
183 if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) {
184 // Generic handler, skip
185 continue;
186 }
187 return true;
188 }
189 return false;
190 }
191
Russell Brenner14e1ae22011-01-12 14:54:23 -0800192 // In case a physical keyboard is attached, handle clicks with the menu key
193 // depressed by opening in a new tab
Russell Brennerca9898e2011-01-21 13:34:02 -0800194 boolean handleMenuClick(Tab tab, String url) {
Russell Brenner14e1ae22011-01-12 14:54:23 -0800195 if (mController.isMenuDown()) {
196 mController.openTab(tab, url, false);
197 mActivity.closeOptionsMenu();
198 return true;
199 }
200
201 return false;
202 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700203}