UrlHandler.java revision 539e2eced0f35144d7841477e5cdc2d8c521e82a
1/*
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;
25import android.os.AsyncTask;
26import android.os.Bundle;
27import 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
54    boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
55        if (view.isPrivateBrowsingEnabled()) {
56            // Don't allow urls to leave the browser app when in
57            // private browsing mode
58            return false;
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)) {
102                // 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(tab, siteUri, view).execute();
107                return true;
108            }
109        }
110
111        if (startActivityForUrl(url)) {
112            return true;
113        }
114
115        if (handleMenuClick(tab, url)) {
116            return true;
117        }
118
119        return false;
120    }
121
122    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
174    // 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
187    private class RLZTask extends AsyncTask<Void, Void, String> {
188        private Tab mTab;
189        private Uri mSiteUri;
190        private WebView mWebView;
191
192        public RLZTask(Tab tab, Uri uri, WebView webView) {
193            mTab = tab;
194            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) {
218            // 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            }
224        }
225    }
226
227    // 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}
290