BrowserActivity.java revision f4bb18a41259ce62209c768484547d33583f9c77
1/*
2 * Copyright (C) 2006 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 com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
30import android.content.ContentUris;
31import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
38import android.content.pm.PackageInfo;
39import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.DrawFilter;
50import android.graphics.Paint;
51import android.graphics.PaintFlagsDrawFilter;
52import android.graphics.Picture;
53import android.graphics.PixelFormat;
54import android.graphics.Rect;
55import android.graphics.drawable.Drawable;
56import android.hardware.SensorListener;
57import android.hardware.SensorManager;
58import android.net.ConnectivityManager;
59import android.net.Uri;
60import android.net.WebAddress;
61import android.net.http.EventHandler;
62import android.net.http.SslCertificate;
63import android.net.http.SslError;
64import android.os.AsyncTask;
65import android.os.Bundle;
66import android.os.Debug;
67import android.os.Environment;
68import android.os.Handler;
69import android.os.IBinder;
70import android.os.Message;
71import android.os.PowerManager;
72import android.os.Process;
73import android.os.RemoteException;
74import android.os.ServiceManager;
75import android.os.SystemClock;
76import android.provider.Browser;
77import android.provider.Contacts;
78import android.provider.Downloads;
79import android.provider.MediaStore;
80import android.provider.Contacts.Intents.Insert;
81import android.text.IClipboard;
82import android.text.TextUtils;
83import android.text.format.DateFormat;
84import android.text.util.Regex;
85import android.util.Log;
86import android.view.ContextMenu;
87import android.view.Gravity;
88import android.view.KeyEvent;
89import android.view.LayoutInflater;
90import android.view.Menu;
91import android.view.MenuInflater;
92import android.view.MenuItem;
93import android.view.MotionEvent;
94import android.view.View;
95import android.view.ViewGroup;
96import android.view.Window;
97import android.view.WindowManager;
98import android.view.ContextMenu.ContextMenuInfo;
99import android.view.MenuItem.OnMenuItemClickListener;
100import android.view.animation.AlphaAnimation;
101import android.view.animation.Animation;
102import android.view.animation.AnimationSet;
103import android.view.animation.DecelerateInterpolator;
104import android.view.animation.ScaleAnimation;
105import android.view.animation.TranslateAnimation;
106import android.webkit.CookieManager;
107import android.webkit.CookieSyncManager;
108import android.webkit.DownloadListener;
109import android.webkit.GeolocationPermissions;
110import android.webkit.HttpAuthHandler;
111import android.webkit.PluginManager;
112import android.webkit.SslErrorHandler;
113import android.webkit.URLUtil;
114import android.webkit.WebChromeClient;
115import android.webkit.WebChromeClient.CustomViewCallback;
116import android.webkit.WebHistoryItem;
117import android.webkit.WebIconDatabase;
118import android.webkit.WebStorage;
119import android.webkit.WebView;
120import android.webkit.WebViewClient;
121import android.widget.EditText;
122import android.widget.FrameLayout;
123import android.widget.LinearLayout;
124import android.widget.TextView;
125import android.widget.Toast;
126
127import java.io.BufferedOutputStream;
128import java.io.ByteArrayOutputStream;
129import java.io.File;
130import java.io.FileInputStream;
131import java.io.FileOutputStream;
132import java.io.IOException;
133import java.io.InputStream;
134import java.net.MalformedURLException;
135import java.net.URI;
136import java.net.URISyntaxException;
137import java.net.URL;
138import java.net.URLEncoder;
139import java.text.ParseException;
140import java.util.Date;
141import java.util.Enumeration;
142import java.util.HashMap;
143import java.util.LinkedList;
144import java.util.Vector;
145import java.util.regex.Matcher;
146import java.util.regex.Pattern;
147import java.util.zip.ZipEntry;
148import java.util.zip.ZipFile;
149
150public class BrowserActivity extends Activity
151    implements KeyTracker.OnKeyTracker,
152        View.OnCreateContextMenuListener,
153        DownloadListener {
154
155    /* Define some aliases to make these debugging flags easier to refer to.
156     * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
157     */
158    private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
159    private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
160    private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
161
162    private IGoogleLoginService mGls = null;
163    private ServiceConnection mGlsConnection = null;
164
165    private SensorManager mSensorManager = null;
166
167    // These are single-character shortcuts for searching popular sources.
168    private static final int SHORTCUT_INVALID = 0;
169    private static final int SHORTCUT_GOOGLE_SEARCH = 1;
170    private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
171    private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
172    private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
173
174    /* Whitelisted webpages
175    private static HashSet<String> sWhiteList;
176
177    static {
178        sWhiteList = new HashSet<String>();
179        sWhiteList.add("cnn.com/");
180        sWhiteList.add("espn.go.com/");
181        sWhiteList.add("nytimes.com/");
182        sWhiteList.add("engadget.com/");
183        sWhiteList.add("yahoo.com/");
184        sWhiteList.add("msn.com/");
185        sWhiteList.add("amazon.com/");
186        sWhiteList.add("consumerist.com/");
187        sWhiteList.add("google.com/m/news");
188    }
189    */
190
191    private void setupHomePage() {
192        final Runnable getAccount = new Runnable() {
193            public void run() {
194                // Lower priority
195                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
196                // get the default home page
197                String homepage = mSettings.getHomePage();
198
199                try {
200                    if (mGls == null) return;
201
202                    if (!homepage.startsWith("http://www.google.")) return;
203                    if (homepage.indexOf('?') == -1) return;
204
205                    String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
206                    String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
207
208                    // three cases:
209                    //
210                    //   hostedUser == googleUser
211                    //      The device has only a google account
212                    //
213                    //   hostedUser != googleUser
214                    //      The device has a hosted account and a google account
215                    //
216                    //   hostedUser != null, googleUser == null
217                    //      The device has only a hosted account (so far)
218
219                    // developers might have no accounts at all
220                    if (hostedUser == null) return;
221
222                    if (googleUser == null || !hostedUser.equals(googleUser)) {
223                        String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
224                        homepage = homepage.replace("?", "/a/" + domain + "?");
225                    }
226                } catch (RemoteException ignore) {
227                    // Login service died; carry on
228                } catch (RuntimeException ignore) {
229                    // Login service died; carry on
230                } finally {
231                    finish(homepage);
232                }
233            }
234
235            private void finish(final String homepage) {
236                mHandler.post(new Runnable() {
237                    public void run() {
238                        mSettings.setHomePage(BrowserActivity.this, homepage);
239                        resumeAfterCredentials();
240
241                        // as this is running in a separate thread,
242                        // BrowserActivity's onDestroy() may have been called,
243                        // which also calls unbindService().
244                        if (mGlsConnection != null) {
245                            // we no longer need to keep GLS open
246                            unbindService(mGlsConnection);
247                            mGlsConnection = null;
248                        }
249                    } });
250            } };
251
252        final boolean[] done = { false };
253
254        // Open a connection to the Google Login Service.  The first
255        // time the connection is established, set up the homepage depending on
256        // the account in a background thread.
257        mGlsConnection = new ServiceConnection() {
258            public void onServiceConnected(ComponentName className, IBinder service) {
259                mGls = IGoogleLoginService.Stub.asInterface(service);
260                if (done[0] == false) {
261                    done[0] = true;
262                    Thread account = new Thread(getAccount);
263                    account.setName("GLSAccount");
264                    account.start();
265                }
266            }
267            public void onServiceDisconnected(ComponentName className) {
268                mGls = null;
269            }
270        };
271
272        bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
273                    mGlsConnection, Context.BIND_AUTO_CREATE);
274    }
275
276    private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
277        @Override
278        public Void doInBackground(File... files) {
279            if (files != null) {
280                for (File f : files) {
281                    if (!f.delete()) {
282                      Log.e(LOGTAG, f.getPath() + " was not deleted");
283                    }
284                }
285            }
286            return null;
287        }
288    }
289
290    /**
291     * This layout holds everything you see below the status bar, including the
292     * error console, the custom view container, and the webviews.
293     */
294    private FrameLayout mBrowserFrameLayout;
295
296    @Override public void onCreate(Bundle icicle) {
297        if (LOGV_ENABLED) {
298            Log.v(LOGTAG, this + " onStart");
299        }
300        super.onCreate(icicle);
301        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
302        // test the browser in OpenGL
303        // requestWindowFeature(Window.FEATURE_OPENGL);
304
305        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
306
307        mResolver = getContentResolver();
308
309        //
310        // start MASF proxy service
311        //
312        //Intent proxyServiceIntent = new Intent();
313        //proxyServiceIntent.setComponent
314        //    (new ComponentName(
315        //        "com.android.masfproxyservice",
316        //        "com.android.masfproxyservice.MasfProxyService"));
317        //startService(proxyServiceIntent, null);
318
319        mSecLockIcon = Resources.getSystem().getDrawable(
320                android.R.drawable.ic_secure);
321        mMixLockIcon = Resources.getSystem().getDrawable(
322                android.R.drawable.ic_partial_secure);
323
324        FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
325                .findViewById(com.android.internal.R.id.content);
326        mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
327                .inflate(R.layout.custom_screen, null);
328        mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
329                R.id.main_content);
330        mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
331                .findViewById(R.id.error_console);
332        mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
333                .findViewById(R.id.fullscreen_custom_content);
334        frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
335        mTitleBar = new TitleBarSet(this);
336
337        // Create the tab control and our initial tab
338        mTabControl = new TabControl(this);
339
340        // Open the icon database and retain all the bookmark urls for favicons
341        retainIconsOnStartup();
342
343        // Keep a settings instance handy.
344        mSettings = BrowserSettings.getInstance();
345        mSettings.setTabControl(mTabControl);
346        mSettings.loadFromDb(this);
347
348        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
349        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
350
351        /* enables registration for changes in network status from
352           http stack */
353        mNetworkStateChangedFilter = new IntentFilter();
354        mNetworkStateChangedFilter.addAction(
355                ConnectivityManager.CONNECTIVITY_ACTION);
356        mNetworkStateIntentReceiver = new BroadcastReceiver() {
357                @Override
358                public void onReceive(Context context, Intent intent) {
359                    if (intent.getAction().equals(
360                            ConnectivityManager.CONNECTIVITY_ACTION)) {
361                        boolean down = intent.getBooleanExtra(
362                                ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
363                        onNetworkToggle(!down);
364                    }
365                }
366            };
367
368        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
369        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
370        filter.addDataScheme("package");
371        mPackageInstallationReceiver = new BroadcastReceiver() {
372            @Override
373            public void onReceive(Context context, Intent intent) {
374                final String action = intent.getAction();
375                final String packageName = intent.getData()
376                        .getSchemeSpecificPart();
377                final boolean replacing = intent.getBooleanExtra(
378                        Intent.EXTRA_REPLACING, false);
379                if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
380                    // if it is replacing, refreshPlugins() when adding
381                    return;
382                }
383                PackageManager pm = BrowserActivity.this.getPackageManager();
384                PackageInfo pkgInfo = null;
385                try {
386                    pkgInfo = pm.getPackageInfo(packageName,
387                            PackageManager.GET_PERMISSIONS);
388                } catch (PackageManager.NameNotFoundException e) {
389                    return;
390                }
391                if (pkgInfo != null) {
392                    String permissions[] = pkgInfo.requestedPermissions;
393                    if (permissions == null) {
394                        return;
395                    }
396                    boolean permissionOk = false;
397                    for (String permit : permissions) {
398                        if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
399                            permissionOk = true;
400                            break;
401                        }
402                    }
403                    if (permissionOk) {
404                        PluginManager.getInstance(BrowserActivity.this)
405                                .refreshPlugins(
406                                        Intent.ACTION_PACKAGE_ADDED
407                                                .equals(action));
408                    }
409                }
410            }
411        };
412        registerReceiver(mPackageInstallationReceiver, filter);
413
414        // If this was a web search request, pass it on to the default web search provider.
415        if (handleWebSearchIntent(getIntent())) {
416            moveTaskToBack(true);
417            return;
418        }
419
420        if (!mTabControl.restoreState(icicle)) {
421            // clear up the thumbnail directory if we can't restore the state as
422            // none of the files in the directory are referenced any more.
423            new ClearThumbnails().execute(
424                    mTabControl.getThumbnailDir().listFiles());
425            // there is no quit on Android. But if we can't restore the state,
426            // we can treat it as a new Browser, remove the old session cookies.
427            CookieManager.getInstance().removeSessionCookie();
428            final Intent intent = getIntent();
429            final Bundle extra = intent.getExtras();
430            // Create an initial tab.
431            // If the intent is ACTION_VIEW and data is not null, the Browser is
432            // invoked to view the content by another application. In this case,
433            // the tab will be close when exit.
434            UrlData urlData = getUrlDataFromIntent(intent);
435
436            final TabControl.Tab t = mTabControl.createNewTab(
437                    Intent.ACTION_VIEW.equals(intent.getAction()) &&
438                    intent.getData() != null,
439                    intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
440            mTabControl.setCurrentTab(t);
441            attachTabToContentView(t);
442            WebView webView = t.getWebView();
443            if (extra != null) {
444                int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
445                if (scale > 0 && scale <= 1000) {
446                    webView.setInitialScale(scale);
447                }
448            }
449            // If we are not restoring from an icicle, then there is a high
450            // likely hood this is the first run. So, check to see if the
451            // homepage needs to be configured and copy any plugins from our
452            // asset directory to the data partition.
453            if ((extra == null || !extra.getBoolean("testing"))
454                    && !mSettings.isLoginInitialized()) {
455                setupHomePage();
456            }
457
458            if (urlData.isEmpty()) {
459                if (mSettings.isLoginInitialized()) {
460                    webView.loadUrl(mSettings.getHomePage());
461                } else {
462                    waitForCredentials();
463                }
464            } else {
465                if (extra != null) {
466                    urlData.setPostData(extra
467                            .getByteArray(Browser.EXTRA_POST_DATA));
468                }
469                urlData.loadIn(webView);
470            }
471        } else {
472            // TabControl.restoreState() will create a new tab even if
473            // restoring the state fails.
474            attachTabToContentView(mTabControl.getCurrentTab());
475        }
476
477        mTitleBar.init(this);
478        // Create title bars for all of the tabs that have been created
479        for (int i = 0; i < mTabControl.getTabCount(); i ++) {
480            WebView view = mTabControl.getTab(i).getWebView();
481            mTitleBar.addTab(view, false);
482        }
483
484        mTitleBar.setCurrentTab(mTabControl.getCurrentIndex());
485
486        // Read JavaScript flags if it exists.
487        String jsFlags = mSettings.getJsFlags();
488        if (jsFlags.trim().length() != 0) {
489            mTabControl.getCurrentWebView().setJsFlags(jsFlags);
490        }
491    }
492
493    @Override
494    protected void onNewIntent(Intent intent) {
495        TabControl.Tab current = mTabControl.getCurrentTab();
496        // When a tab is closed on exit, the current tab index is set to -1.
497        // Reset before proceed as Browser requires the current tab to be set.
498        if (current == null) {
499            // Try to reset the tab in case the index was incorrect.
500            current = mTabControl.getTab(0);
501            if (current == null) {
502                // No tabs at all so just ignore this intent.
503                return;
504            }
505            mTabControl.setCurrentTab(current);
506            mTitleBar.setCurrentTab(mTabControl.getTabIndex(current));
507            attachTabToContentView(current);
508            resetTitleAndIcon(current.getWebView());
509        }
510        final String action = intent.getAction();
511        final int flags = intent.getFlags();
512        if (Intent.ACTION_MAIN.equals(action) ||
513                (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
514            // just resume the browser
515            return;
516        }
517        if (Intent.ACTION_VIEW.equals(action)
518                || Intent.ACTION_SEARCH.equals(action)
519                || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
520                || Intent.ACTION_WEB_SEARCH.equals(action)) {
521            // If this was a search request (e.g. search query directly typed into the address bar),
522            // pass it on to the default web search provider.
523            if (handleWebSearchIntent(intent)) {
524                return;
525            }
526
527            UrlData urlData = getUrlDataFromIntent(intent);
528            if (urlData.isEmpty()) {
529                urlData = new UrlData(mSettings.getHomePage());
530            }
531            urlData.setPostData(intent
532                    .getByteArrayExtra(Browser.EXTRA_POST_DATA));
533
534            final String appId = intent
535                    .getStringExtra(Browser.EXTRA_APPLICATION_ID);
536            if (Intent.ACTION_VIEW.equals(action)
537                    && !getPackageName().equals(appId)
538                    && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
539                TabControl.Tab appTab = mTabControl.getTabFromId(appId);
540                if (appTab != null) {
541                    Log.i(LOGTAG, "Reusing tab for " + appId);
542                    // Dismiss the subwindow if applicable.
543                    dismissSubWindow(appTab);
544                    // Since we might kill the WebView, remove it from the
545                    // content view first.
546                    removeTabFromContentView(appTab);
547                    // Recreate the main WebView after destroying the old one.
548                    // If the WebView has the same original url and is on that
549                    // page, it can be reused.
550                    boolean needsLoad =
551                            mTabControl.recreateWebView(appTab, urlData.mUrl);
552
553                    if (current != appTab) {
554                        switchToTab(mTabControl.getTabIndex(appTab));
555                        if (needsLoad) {
556                            urlData.loadIn(appTab.getWebView());
557                        }
558                    } else {
559                        // If the tab was the current tab, we have to attach
560                        // it to the view system again.
561                        attachTabToContentView(appTab);
562                        if (needsLoad) {
563                            urlData.loadIn(appTab.getWebView());
564                        }
565                    }
566                    return;
567                } else {
568                    // No matching application tab, try to find a regular tab
569                    // with a matching url.
570                    appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
571                    if (appTab != null) {
572                        if (current != appTab) {
573                            switchToTab(mTabControl.getTabIndex(appTab));
574                        }
575                        // Otherwise, we are already viewing the correct tab.
576                    } else {
577                        // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
578                        // will be opened in a new tab unless we have reached
579                        // MAX_TABS. Then the url will be opened in the current
580                        // tab. If a new tab is created, it will have "true" for
581                        // exit on close.
582                        openTabAndShow(urlData, true, appId);
583                    }
584                }
585            } else {
586                if ("about:debug".equals(urlData.mUrl)) {
587                    mSettings.toggleDebugSettings();
588                    return;
589                }
590                // Get rid of the subwindow if it exists
591                dismissSubWindow(current);
592                urlData.loadIn(current.getWebView());
593            }
594        }
595    }
596
597    private int parseUrlShortcut(String url) {
598        if (url == null) return SHORTCUT_INVALID;
599
600        // FIXME: quick search, need to be customized by setting
601        if (url.length() > 2 && url.charAt(1) == ' ') {
602            switch (url.charAt(0)) {
603            case 'g': return SHORTCUT_GOOGLE_SEARCH;
604            case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
605            case 'd': return SHORTCUT_DICTIONARY_SEARCH;
606            case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
607            }
608        }
609        return SHORTCUT_INVALID;
610    }
611
612    /**
613     * Launches the default web search activity with the query parameters if the given intent's data
614     * are identified as plain search terms and not URLs/shortcuts.
615     * @return true if the intent was handled and web search activity was launched, false if not.
616     */
617    private boolean handleWebSearchIntent(Intent intent) {
618        if (intent == null) return false;
619
620        String url = null;
621        final String action = intent.getAction();
622        if (Intent.ACTION_VIEW.equals(action)) {
623            url = intent.getData().toString();
624        } else if (Intent.ACTION_SEARCH.equals(action)
625                || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
626                || Intent.ACTION_WEB_SEARCH.equals(action)) {
627            url = intent.getStringExtra(SearchManager.QUERY);
628        }
629        return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
630    }
631
632    /**
633     * Launches the default web search activity with the query parameters if the given url string
634     * was identified as plain search terms and not URL/shortcut.
635     * @return true if the request was handled and web search activity was launched, false if not.
636     */
637    private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
638        if (inUrl == null) return false;
639
640        // In general, we shouldn't modify URL from Intent.
641        // But currently, we get the user-typed URL from search box as well.
642        String url = fixUrl(inUrl).trim();
643
644        // URLs and site specific search shortcuts are handled by the regular flow of control, so
645        // return early.
646        if (Regex.WEB_URL_PATTERN.matcher(url).matches()
647                || ACCEPTED_URI_SCHEMA.matcher(url).matches()
648                || parseUrlShortcut(url) != SHORTCUT_INVALID) {
649            return false;
650        }
651
652        Browser.updateVisitedHistory(mResolver, url, false);
653        Browser.addSearchUrl(mResolver, url);
654
655        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
656        intent.addCategory(Intent.CATEGORY_DEFAULT);
657        intent.putExtra(SearchManager.QUERY, url);
658        if (appData != null) {
659            intent.putExtra(SearchManager.APP_DATA, appData);
660        }
661        intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
662        startActivity(intent);
663
664        return true;
665    }
666
667    private UrlData getUrlDataFromIntent(Intent intent) {
668        String url = null;
669        if (intent != null) {
670            final String action = intent.getAction();
671            if (Intent.ACTION_VIEW.equals(action)) {
672                url = smartUrlFilter(intent.getData());
673                if (url != null && url.startsWith("content:")) {
674                    /* Append mimetype so webview knows how to display */
675                    String mimeType = intent.resolveType(getContentResolver());
676                    if (mimeType != null) {
677                        url += "?" + mimeType;
678                    }
679                }
680                if ("inline:".equals(url)) {
681                    return new InlinedUrlData(
682                            intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
683                            intent.getType(),
684                            intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
685                            intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
686                }
687            } else if (Intent.ACTION_SEARCH.equals(action)
688                    || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
689                    || Intent.ACTION_WEB_SEARCH.equals(action)) {
690                url = intent.getStringExtra(SearchManager.QUERY);
691                if (url != null) {
692                    mLastEnteredUrl = url;
693                    // Don't add Urls, just search terms.
694                    // Urls will get added when the page is loaded.
695                    if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
696                        Browser.updateVisitedHistory(mResolver, url, false);
697                    }
698                    // In general, we shouldn't modify URL from Intent.
699                    // But currently, we get the user-typed URL from search box as well.
700                    url = fixUrl(url);
701                    url = smartUrlFilter(url);
702                    String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
703                    if (url.contains(searchSource)) {
704                        String source = null;
705                        final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
706                        if (appData != null) {
707                            source = appData.getString(SearchManager.SOURCE);
708                        }
709                        if (TextUtils.isEmpty(source)) {
710                            source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
711                        }
712                        url = url.replace(searchSource, "&source=android-"+source+"&");
713                    }
714                }
715            }
716        }
717        return new UrlData(url);
718    }
719
720    /* package */ static String fixUrl(String inUrl) {
721        // FIXME: Converting the url to lower case
722        // duplicates functionality in smartUrlFilter().
723        // However, changing all current callers of fixUrl to
724        // call smartUrlFilter in addition may have unwanted
725        // consequences, and is deferred for now.
726        int colon = inUrl.indexOf(':');
727        boolean allLower = true;
728        for (int index = 0; index < colon; index++) {
729            char ch = inUrl.charAt(index);
730            if (!Character.isLetter(ch)) {
731                break;
732            }
733            allLower &= Character.isLowerCase(ch);
734            if (index == colon - 1 && !allLower) {
735                inUrl = inUrl.substring(0, colon).toLowerCase()
736                        + inUrl.substring(colon);
737            }
738        }
739        if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
740            return inUrl;
741        if (inUrl.startsWith("http:") ||
742                inUrl.startsWith("https:")) {
743            if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
744                inUrl = inUrl.replaceFirst("/", "//");
745            } else inUrl = inUrl.replaceFirst(":", "://");
746        }
747        return inUrl;
748    }
749
750    /**
751     * Looking for the pattern like this
752     *
753     *          *
754     *         * *
755     *      ***   *     *******
756     *             *   *
757     *              * *
758     *               *
759     */
760    private final SensorListener mSensorListener = new SensorListener() {
761        private long mLastGestureTime;
762        private float[] mPrev = new float[3];
763        private float[] mPrevDiff = new float[3];
764        private float[] mDiff = new float[3];
765        private float[] mRevertDiff = new float[3];
766
767        public void onSensorChanged(int sensor, float[] values) {
768            boolean show = false;
769            float[] diff = new float[3];
770
771            for (int i = 0; i < 3; i++) {
772                diff[i] = values[i] - mPrev[i];
773                if (Math.abs(diff[i]) > 1) {
774                    show = true;
775                }
776                if ((diff[i] > 1.0 && mDiff[i] < 0.2)
777                        || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
778                    // start track when there is a big move, or revert
779                    mRevertDiff[i] = mDiff[i];
780                    mDiff[i] = 0;
781                } else if (diff[i] > -0.2 && diff[i] < 0.2) {
782                    // reset when it is flat
783                    mDiff[i] = mRevertDiff[i]  = 0;
784                }
785                mDiff[i] += diff[i];
786                mPrevDiff[i] = diff[i];
787                mPrev[i] = values[i];
788            }
789
790            if (false) {
791                // only shows if we think the delta is big enough, in an attempt
792                // to detect "serious" moves left/right or up/down
793                Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
794                        + values[0] + ", " + values[1] + ", " + values[2] + ")"
795                        + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
796                        + ")");
797                Log.d("BrowserSensorHack", "      mDiff(" + mDiff[0] + " "
798                        + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
799                        + mRevertDiff[0] + " " + mRevertDiff[1] + " "
800                        + mRevertDiff[2] + ")");
801            }
802
803            long now = android.os.SystemClock.uptimeMillis();
804            if (now - mLastGestureTime > 1000) {
805                mLastGestureTime = 0;
806
807                float y = mDiff[1];
808                float z = mDiff[2];
809                float ay = Math.abs(y);
810                float az = Math.abs(z);
811                float ry = mRevertDiff[1];
812                float rz = mRevertDiff[2];
813                float ary = Math.abs(ry);
814                float arz = Math.abs(rz);
815                boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
816                boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
817
818                if ((gestY || gestZ) && !(gestY && gestZ)) {
819                    WebView view = mTabControl.getCurrentWebView();
820
821                    if (view != null) {
822                        if (gestZ) {
823                            if (z < 0) {
824                                view.zoomOut();
825                            } else {
826                                view.zoomIn();
827                            }
828                        } else {
829                            view.flingScroll(0, Math.round(y * 100));
830                        }
831                    }
832                    mLastGestureTime = now;
833                }
834            }
835        }
836
837        public void onAccuracyChanged(int sensor, int accuracy) {
838            // TODO Auto-generated method stub
839
840        }
841    };
842
843    @Override protected void onResume() {
844        super.onResume();
845        if (LOGV_ENABLED) {
846            Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
847        }
848
849        if (!mActivityInPause) {
850            Log.e(LOGTAG, "BrowserActivity is already resumed.");
851            return;
852        }
853
854        mTabControl.resumeCurrentTab();
855        mActivityInPause = false;
856        resumeWebViewTimers();
857
858        if (mWakeLock.isHeld()) {
859            mHandler.removeMessages(RELEASE_WAKELOCK);
860            mWakeLock.release();
861        }
862
863        if (mCredsDlg != null) {
864            if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
865             // In case credential request never comes back
866                mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
867            }
868        }
869
870        registerReceiver(mNetworkStateIntentReceiver,
871                         mNetworkStateChangedFilter);
872        WebView.enablePlatformNotifications();
873
874        if (mSettings.doFlick()) {
875            if (mSensorManager == null) {
876                mSensorManager = (SensorManager) getSystemService(
877                        Context.SENSOR_SERVICE);
878            }
879            mSensorManager.registerListener(mSensorListener,
880                    SensorManager.SENSOR_ACCELEROMETER,
881                    SensorManager.SENSOR_DELAY_FASTEST);
882        } else {
883            mSensorManager = null;
884        }
885    }
886
887    /**
888     * Since the actual title bar is embedded in the WebView, and removing it
889     * would change its appearance, create a temporary title bar to go at
890     * the top of the screen while the menu is open.
891     */
892    private TitleBar mFakeTitleBar;
893
894    /**
895     * Keeps track of whether the options menu is open.  This is important in
896     * determining whether to show or hide the title bar overlay.
897     */
898    private boolean mOptionsMenuOpen;
899
900    /**
901     * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
902     * of whether the configuration has changed.  The first onMenuOpened call
903     * after a configuration change is simply a reopening of the same menu
904     * (i.e. mIconView did not change).
905     */
906    private boolean mConfigChanged;
907
908    /**
909     * Whether or not the options menu is in its smaller, icon menu form.  When
910     * true, we want the title bar overlay to be up.  When false, we do not.
911     * Only meaningful if mOptionsMenuOpen is true.
912     */
913    private boolean mIconView;
914
915    @Override
916    public boolean onMenuOpened(int featureId, Menu menu) {
917        if (Window.FEATURE_OPTIONS_PANEL == featureId) {
918            if (mOptionsMenuOpen) {
919                if (mConfigChanged) {
920                    // We do not need to make any changes to the state of the
921                    // title bar, since the only thing that happened was a
922                    // change in orientation
923                    mConfigChanged = false;
924                } else {
925                    if (mIconView) {
926                        // Switching the menu to expanded view, so hide the
927                        // title bar.
928                        hideFakeTitleBar();
929                        mIconView = false;
930                    } else {
931                        // Switching the menu back to icon view, so show the
932                        // title bar once again.
933                        showFakeTitleBar();
934                        mIconView = true;
935                    }
936                }
937            } else {
938                // The options menu is closed, so open it, and show the title
939                showFakeTitleBar();
940                mOptionsMenuOpen = true;
941                mConfigChanged = false;
942                mIconView = true;
943            }
944        }
945        return true;
946    }
947
948    private void showFakeTitleBar() {
949        if (mFakeTitleBar == null) {
950            final WebView webView = getTopWindow();
951            mFakeTitleBar = new TitleBar(this, webView);
952            mFakeTitleBar.setTitleAndUrl(null, webView.getUrl());
953            mFakeTitleBar.setProgress(webView.getProgress());
954            mFakeTitleBar.setFavicon(webView.getFavicon());
955            updateLockIconToLatest();
956            final View title = mFakeTitleBar.findViewById(R.id.title);
957            mFakeTitleBar.setOnTouchListener(new View.OnTouchListener() {
958                    public boolean onTouch(View v, MotionEvent event) {
959                        if (event.getAction() == MotionEvent.ACTION_UP) {
960                            if (event.getX() > title.getRight()) {
961                                if (webView != null
962                                        && webView.getProgress() < 100) {
963                                    if (webView != null) {
964                                        webView.stopLoading();
965                                    }
966                                } else {
967                                    bookmarksOrHistoryPicker(false);
968                                }
969                            } else {
970                                onSearchRequested();
971                            }
972                            closeOptionsMenu();
973                        }
974                        return true;
975                    }
976            });
977
978            WindowManager manager
979                    = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
980
981            // Add the title bar to the window manager so it can receive touches
982            // while the menu is up
983            WindowManager.LayoutParams params
984                    = new WindowManager.LayoutParams(
985                    ViewGroup.LayoutParams.FILL_PARENT,
986                    ViewGroup.LayoutParams.WRAP_CONTENT,
987                    WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
988                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
989                    PixelFormat.OPAQUE);
990            params.gravity = Gravity.TOP;
991            WebView mainView = mTabControl.getCurrentWebView();
992            params.windowAnimations = mainView == null
993                    || mainView.getScrollY() != 0
994                    ? com.android.internal.R.style.Animation_DropDownDown : 0;
995            // XXX : Without providing an offset, the fake title bar will be
996            // placed underneath the status bar.  Use the global visible rect
997            // of mBrowserFrameLayout to determine the bottom of the status bar
998            Rect rectangle = new Rect();
999            mBrowserFrameLayout.getGlobalVisibleRect(rectangle);
1000            params.y = rectangle.top;
1001            manager.addView(mFakeTitleBar, params);
1002        }
1003    }
1004
1005    @Override
1006    public void onOptionsMenuClosed(Menu menu) {
1007        mOptionsMenuOpen = false;
1008        if (!mInLoad) {
1009            hideFakeTitleBar();
1010        } else if (!mIconView) {
1011            // The page is currently loading, and we are in expanded mode, so
1012            // we were not showing the menu.  Show it once again.  It will be
1013            // removed when the page finishes.
1014            showFakeTitleBar();
1015        }
1016    }
1017    private void hideFakeTitleBar() {
1018        if (mFakeTitleBar == null) return;
1019        WindowManager manager
1020                    = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
1021        manager.removeView(mFakeTitleBar);
1022        mFakeTitleBar = null;
1023    }
1024
1025    /**
1026     *  onSaveInstanceState(Bundle map)
1027     *  onSaveInstanceState is called right before onStop(). The map contains
1028     *  the saved state.
1029     */
1030    @Override protected void onSaveInstanceState(Bundle outState) {
1031        if (LOGV_ENABLED) {
1032            Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
1033        }
1034        // the default implementation requires each view to have an id. As the
1035        // browser handles the state itself and it doesn't use id for the views,
1036        // don't call the default implementation. Otherwise it will trigger the
1037        // warning like this, "couldn't save which view has focus because the
1038        // focused view XXX has no id".
1039
1040        // Save all the tabs
1041        mTabControl.saveState(outState);
1042    }
1043
1044    @Override protected void onPause() {
1045        super.onPause();
1046
1047        if (mActivityInPause) {
1048            Log.e(LOGTAG, "BrowserActivity is already paused.");
1049            return;
1050        }
1051
1052        mTabControl.pauseCurrentTab();
1053        mActivityInPause = true;
1054        if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
1055            mWakeLock.acquire();
1056            mHandler.sendMessageDelayed(mHandler
1057                    .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
1058        }
1059
1060        // Clear the credentials toast if it is up
1061        if (mCredsDlg != null && mCredsDlg.isShowing()) {
1062            mCredsDlg.dismiss();
1063        }
1064        mCredsDlg = null;
1065
1066        // FIXME: This removes the active tabs page and resets the menu to
1067        // MAIN_MENU.  A better solution might be to do this work in onNewIntent
1068        // but then we would need to save it in onSaveInstanceState and restore
1069        // it in onCreate/onRestoreInstanceState
1070        if (mActiveTabsPage != null) {
1071            removeActiveTabPage(true);
1072        }
1073
1074        cancelStopToast();
1075
1076        // unregister network state listener
1077        unregisterReceiver(mNetworkStateIntentReceiver);
1078        WebView.disablePlatformNotifications();
1079
1080        if (mSensorManager != null) {
1081            mSensorManager.unregisterListener(mSensorListener);
1082        }
1083    }
1084
1085    @Override protected void onDestroy() {
1086        if (LOGV_ENABLED) {
1087            Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
1088        }
1089        super.onDestroy();
1090        // Remove the current tab and sub window
1091        TabControl.Tab t = mTabControl.getCurrentTab();
1092        if (t != null) {
1093            dismissSubWindow(t);
1094            removeTabFromContentView(t);
1095        }
1096        // Destroy all the tabs
1097        mTabControl.destroy();
1098        WebIconDatabase.getInstance().close();
1099        if (mGlsConnection != null) {
1100            unbindService(mGlsConnection);
1101            mGlsConnection = null;
1102        }
1103
1104        //
1105        // stop MASF proxy service
1106        //
1107        //Intent proxyServiceIntent = new Intent();
1108        //proxyServiceIntent.setComponent
1109        //   (new ComponentName(
1110        //        "com.android.masfproxyservice",
1111        //        "com.android.masfproxyservice.MasfProxyService"));
1112        //stopService(proxyServiceIntent);
1113
1114        unregisterReceiver(mPackageInstallationReceiver);
1115    }
1116
1117    @Override
1118    public void onConfigurationChanged(Configuration newConfig) {
1119        mConfigChanged = true;
1120        super.onConfigurationChanged(newConfig);
1121
1122        if (mPageInfoDialog != null) {
1123            mPageInfoDialog.dismiss();
1124            showPageInfo(
1125                mPageInfoView,
1126                mPageInfoFromShowSSLCertificateOnError.booleanValue());
1127        }
1128        if (mSSLCertificateDialog != null) {
1129            mSSLCertificateDialog.dismiss();
1130            showSSLCertificate(
1131                mSSLCertificateView);
1132        }
1133        if (mSSLCertificateOnErrorDialog != null) {
1134            mSSLCertificateOnErrorDialog.dismiss();
1135            showSSLCertificateOnError(
1136                mSSLCertificateOnErrorView,
1137                mSSLCertificateOnErrorHandler,
1138                mSSLCertificateOnErrorError);
1139        }
1140        if (mHttpAuthenticationDialog != null) {
1141            String title = ((TextView) mHttpAuthenticationDialog
1142                    .findViewById(com.android.internal.R.id.alertTitle)).getText()
1143                    .toString();
1144            String name = ((TextView) mHttpAuthenticationDialog
1145                    .findViewById(R.id.username_edit)).getText().toString();
1146            String password = ((TextView) mHttpAuthenticationDialog
1147                    .findViewById(R.id.password_edit)).getText().toString();
1148            int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1149                    .getId();
1150            mHttpAuthenticationDialog.dismiss();
1151            showHttpAuthentication(mHttpAuthHandler, null, null, title,
1152                    name, password, focusId);
1153        }
1154        if (mFindDialog != null && mFindDialog.isShowing()) {
1155            mFindDialog.onConfigurationChanged(newConfig);
1156        }
1157    }
1158
1159    @Override public void onLowMemory() {
1160        super.onLowMemory();
1161        mTabControl.freeMemory();
1162    }
1163
1164    private boolean resumeWebViewTimers() {
1165        if ((!mActivityInPause && !mPageStarted) ||
1166                (mActivityInPause && mPageStarted)) {
1167            CookieSyncManager.getInstance().startSync();
1168            WebView w = mTabControl.getCurrentWebView();
1169            if (w != null) {
1170                w.resumeTimers();
1171            }
1172            return true;
1173        } else {
1174            return false;
1175        }
1176    }
1177
1178    private boolean pauseWebViewTimers() {
1179        if (mActivityInPause && !mPageStarted) {
1180            CookieSyncManager.getInstance().stopSync();
1181            WebView w = mTabControl.getCurrentWebView();
1182            if (w != null) {
1183                w.pauseTimers();
1184            }
1185            return true;
1186        } else {
1187            return false;
1188        }
1189    }
1190
1191    // FIXME: Do we want to call this when loading google for the first time?
1192    /*
1193     * This function is called when we are launching for the first time. We
1194     * are waiting for the login credentials before loading Google home
1195     * pages. This way the user will be logged in straight away.
1196     */
1197    private void waitForCredentials() {
1198        // Show a toast
1199        mCredsDlg = new ProgressDialog(this);
1200        mCredsDlg.setIndeterminate(true);
1201        mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1202        // If the user cancels the operation, then cancel the Google
1203        // Credentials request.
1204        mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1205        mCredsDlg.show();
1206
1207        // We set a timeout for the retrieval of credentials in onResume()
1208        // as that is when we have freed up some CPU time to get
1209        // the login credentials.
1210    }
1211
1212    /*
1213     * If we have received the credentials or we have timed out and we are
1214     * showing the credentials dialog, then it is time to move on.
1215     */
1216    private void resumeAfterCredentials() {
1217        if (mCredsDlg == null) {
1218            return;
1219        }
1220
1221        // Clear the toast
1222        if (mCredsDlg.isShowing()) {
1223            mCredsDlg.dismiss();
1224        }
1225        mCredsDlg = null;
1226
1227        // Clear any pending timeout
1228        mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1229
1230        // Load the page
1231        WebView w = mTabControl.getCurrentWebView();
1232        if (w != null) {
1233            w.loadUrl(mSettings.getHomePage());
1234        }
1235
1236        // Update the settings, need to do this last as it can take a moment
1237        // to persist the settings. In the mean time we could be loading
1238        // content.
1239        mSettings.setLoginInitialized(this);
1240    }
1241
1242    // Open the icon database and retain all the icons for visited sites.
1243    private void retainIconsOnStartup() {
1244        final WebIconDatabase db = WebIconDatabase.getInstance();
1245        db.open(getDir("icons", 0).getPath());
1246        try {
1247            Cursor c = Browser.getAllBookmarks(mResolver);
1248            if (!c.moveToFirst()) {
1249                c.deactivate();
1250                return;
1251            }
1252            int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1253            do {
1254                String url = c.getString(urlIndex);
1255                db.retainIconForPageUrl(url);
1256            } while (c.moveToNext());
1257            c.deactivate();
1258        } catch (IllegalStateException e) {
1259            Log.e(LOGTAG, "retainIconsOnStartup", e);
1260        }
1261    }
1262
1263    // Helper method for getting the top window.
1264    WebView getTopWindow() {
1265        return mTabControl.getCurrentTopWebView();
1266    }
1267
1268    @Override
1269    public boolean onCreateOptionsMenu(Menu menu) {
1270        super.onCreateOptionsMenu(menu);
1271
1272        MenuInflater inflater = getMenuInflater();
1273        inflater.inflate(R.menu.browser, menu);
1274        mMenu = menu;
1275        updateInLoadMenuItems();
1276        return true;
1277    }
1278
1279    /**
1280     * As the menu can be open when loading state changes
1281     * we must manually update the state of the stop/reload menu
1282     * item
1283     */
1284    private void updateInLoadMenuItems() {
1285        if (mMenu == null) {
1286            return;
1287        }
1288        MenuItem src = mInLoad ?
1289                mMenu.findItem(R.id.stop_menu_id):
1290                    mMenu.findItem(R.id.reload_menu_id);
1291        MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1292        dest.setIcon(src.getIcon());
1293        dest.setTitle(src.getTitle());
1294    }
1295
1296    @Override
1297    public boolean onContextItemSelected(MenuItem item) {
1298        // chording is not an issue with context menus, but we use the same
1299        // options selector, so set mCanChord to true so we can access them.
1300        mCanChord = true;
1301        int id = item.getItemId();
1302        final WebView webView = getTopWindow();
1303        if (null == webView) {
1304            return false;
1305        }
1306        final HashMap hrefMap = new HashMap();
1307        hrefMap.put("webview", webView);
1308        final Message msg = mHandler.obtainMessage(
1309                FOCUS_NODE_HREF, id, 0, hrefMap);
1310        switch (id) {
1311            // -- Browser context menu
1312            case R.id.open_context_menu_id:
1313            case R.id.open_newtab_context_menu_id:
1314            case R.id.bookmark_context_menu_id:
1315            case R.id.save_link_context_menu_id:
1316            case R.id.share_link_context_menu_id:
1317            case R.id.copy_link_context_menu_id:
1318                webView.requestFocusNodeHref(msg);
1319                break;
1320
1321            default:
1322                // For other context menus
1323                return onOptionsItemSelected(item);
1324        }
1325        mCanChord = false;
1326        return true;
1327    }
1328
1329    private Bundle createGoogleSearchSourceBundle(String source) {
1330        Bundle bundle = new Bundle();
1331        bundle.putString(SearchManager.SOURCE, source);
1332        return bundle;
1333    }
1334
1335    /**
1336     * Overriding this to insert a local information bundle
1337     */
1338    @Override
1339    public boolean onSearchRequested() {
1340        String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
1341        startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
1342                createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
1343        return true;
1344    }
1345
1346    @Override
1347    public void startSearch(String initialQuery, boolean selectInitialQuery,
1348            Bundle appSearchData, boolean globalSearch) {
1349        if (appSearchData == null) {
1350            appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1351        }
1352        super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1353    }
1354
1355    /**
1356     * Switch tabs.  Called by the TitleBarSet when sliding the title bar
1357     * results in changing tabs.
1358     * @param index Index of the tab to change to, as defined by
1359     *              mTabControl.getTabIndex(Tab t).
1360     * @return boolean True if we successfully switched to a different tab.  If
1361     *                 the indexth tab is null, or if that tab is the same as
1362     *                 the current one, return false.
1363     */
1364    /* package */ boolean switchToTab(int index) {
1365        TabControl.Tab tab = mTabControl.getTab(index);
1366        TabControl.Tab currentTab = mTabControl.getCurrentTab();
1367        if (tab == null || tab == currentTab) {
1368            return false;
1369        }
1370        if (currentTab != null) {
1371            // currentTab may be null if it was just removed.  In that case,
1372            // we do not need to remove it
1373            removeTabFromContentView(currentTab);
1374        }
1375        removeTabFromContentView(tab);
1376        mTabControl.setCurrentTab(tab);
1377        attachTabToContentView(tab);
1378        mTitleBar.setCurrentTab(index);
1379        return true;
1380    }
1381
1382    /* package */ TabControl.Tab openTabToHomePage() {
1383        return openTabAndShow(mSettings.getHomePage(), false, null);
1384    }
1385
1386    /* package */ void closeCurrentWindow() {
1387        final TabControl.Tab current = mTabControl.getCurrentTab();
1388        if (mTabControl.getTabCount() == 1) {
1389            // This is the last tab.  Open a new one, with the home
1390            // page and close the current one.
1391            TabControl.Tab newTab = openTabToHomePage();
1392            closeTab(current);
1393            return;
1394        }
1395        final TabControl.Tab parent = current.getParentTab();
1396        int indexToShow = -1;
1397        if (parent != null) {
1398            indexToShow = mTabControl.getTabIndex(parent);
1399        } else {
1400            final int currentIndex = mTabControl.getCurrentIndex();
1401            // Try to move to the tab to the right
1402            indexToShow = currentIndex + 1;
1403            if (indexToShow > mTabControl.getTabCount() - 1) {
1404                // Try to move to the tab to the left
1405                indexToShow = currentIndex - 1;
1406            }
1407        }
1408        if (switchToTab(indexToShow)) {
1409            // Close window
1410            closeTab(current);
1411        }
1412    }
1413
1414    private ActiveTabsPage mActiveTabsPage;
1415
1416    /**
1417     * Remove the active tabs page.
1418     * @param needToAttach If true, the active tabs page did not attach a tab
1419     *                     to the content view, so we need to do that here.
1420     */
1421    /* package */ void removeActiveTabPage(boolean needToAttach) {
1422        mContentView.removeView(mActiveTabsPage);
1423        mActiveTabsPage = null;
1424        mMenuState = R.id.MAIN_MENU;
1425        if (needToAttach) {
1426            attachTabToContentView(mTabControl.getCurrentTab());
1427        }
1428        getTopWindow().requestFocus();
1429    }
1430
1431    @Override
1432    public boolean onOptionsItemSelected(MenuItem item) {
1433        if (!mCanChord) {
1434            // The user has already fired a shortcut with this hold down of the
1435            // menu key.
1436            return false;
1437        }
1438        if (null == getTopWindow()) {
1439            return false;
1440        }
1441        if (mMenuIsDown) {
1442            // The shortcut action consumes the MENU. Even if it is still down,
1443            // it won't trigger the next shortcut action. In the case of the
1444            // shortcut action triggering a new activity, like Bookmarks, we
1445            // won't get onKeyUp for MENU. So it is important to reset it here.
1446            mMenuIsDown = false;
1447        }
1448        switch (item.getItemId()) {
1449            // -- Main menu
1450            case R.id.new_tab_menu_id:
1451                openTabToHomePage();
1452                break;
1453
1454            case R.id.goto_menu_id:
1455                bookmarksOrHistoryPicker(false);
1456                break;
1457
1458            case R.id.active_tabs_menu_id:
1459                mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
1460                removeTabFromContentView(mTabControl.getCurrentTab());
1461                mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
1462                mActiveTabsPage.requestFocus();
1463                mMenuState = EMPTY_MENU;
1464                break;
1465
1466            case R.id.add_bookmark_menu_id:
1467                Intent i = new Intent(BrowserActivity.this,
1468                        AddBookmarkPage.class);
1469                WebView w = getTopWindow();
1470                i.putExtra("url", w.getUrl());
1471                i.putExtra("title", w.getTitle());
1472                startActivity(i);
1473                break;
1474
1475            case R.id.stop_reload_menu_id:
1476                if (mInLoad) {
1477                    stopLoading();
1478                } else {
1479                    getTopWindow().reload();
1480                }
1481                break;
1482
1483            case R.id.back_menu_id:
1484                getTopWindow().goBack();
1485                break;
1486
1487            case R.id.forward_menu_id:
1488                getTopWindow().goForward();
1489                break;
1490
1491            case R.id.close_menu_id:
1492                // Close the subwindow if it exists.
1493                if (mTabControl.getCurrentSubWindow() != null) {
1494                    dismissSubWindow(mTabControl.getCurrentTab());
1495                    break;
1496                }
1497                closeCurrentWindow();
1498                break;
1499
1500            case R.id.homepage_menu_id:
1501                TabControl.Tab current = mTabControl.getCurrentTab();
1502                if (current != null) {
1503                    dismissSubWindow(current);
1504                    current.getWebView().loadUrl(mSettings.getHomePage());
1505                }
1506                break;
1507
1508            case R.id.preferences_menu_id:
1509                Intent intent = new Intent(this,
1510                        BrowserPreferencesPage.class);
1511                startActivityForResult(intent, PREFERENCES_PAGE);
1512                break;
1513
1514            case R.id.find_menu_id:
1515                if (null == mFindDialog) {
1516                    mFindDialog = new FindDialog(this);
1517                }
1518                mFindDialog.setWebView(getTopWindow());
1519                mFindDialog.show();
1520                mMenuState = EMPTY_MENU;
1521                break;
1522
1523            case R.id.select_text_id:
1524                getTopWindow().emulateShiftHeld();
1525                break;
1526            case R.id.page_info_menu_id:
1527                showPageInfo(mTabControl.getCurrentTab(), false);
1528                break;
1529
1530            case R.id.classic_history_menu_id:
1531                bookmarksOrHistoryPicker(true);
1532                break;
1533
1534            case R.id.share_page_menu_id:
1535                Browser.sendString(this, getTopWindow().getUrl());
1536                break;
1537
1538            case R.id.dump_nav_menu_id:
1539                getTopWindow().debugDump();
1540                break;
1541
1542            case R.id.zoom_in_menu_id:
1543                getTopWindow().zoomIn();
1544                break;
1545
1546            case R.id.zoom_out_menu_id:
1547                getTopWindow().zoomOut();
1548                break;
1549
1550            case R.id.view_downloads_menu_id:
1551                viewDownloads(null);
1552                break;
1553
1554            case R.id.window_one_menu_id:
1555            case R.id.window_two_menu_id:
1556            case R.id.window_three_menu_id:
1557            case R.id.window_four_menu_id:
1558            case R.id.window_five_menu_id:
1559            case R.id.window_six_menu_id:
1560            case R.id.window_seven_menu_id:
1561            case R.id.window_eight_menu_id:
1562                {
1563                    int menuid = item.getItemId();
1564                    for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1565                        if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1566                            TabControl.Tab desiredTab = mTabControl.getTab(id);
1567                            if (desiredTab != null &&
1568                                    desiredTab != mTabControl.getCurrentTab()) {
1569                                switchToTab(id);
1570                            }
1571                            break;
1572                        }
1573                    }
1574                }
1575                break;
1576
1577            default:
1578                if (!super.onOptionsItemSelected(item)) {
1579                    return false;
1580                }
1581                // Otherwise fall through.
1582        }
1583        mCanChord = false;
1584        return true;
1585    }
1586
1587    public void closeFind() {
1588        mMenuState = R.id.MAIN_MENU;
1589    }
1590
1591    @Override public boolean onPrepareOptionsMenu(Menu menu)
1592    {
1593        // This happens when the user begins to hold down the menu key, so
1594        // allow them to chord to get a shortcut.
1595        mCanChord = true;
1596        // Note: setVisible will decide whether an item is visible; while
1597        // setEnabled() will decide whether an item is enabled, which also means
1598        // whether the matching shortcut key will function.
1599        super.onPrepareOptionsMenu(menu);
1600        switch (mMenuState) {
1601            case EMPTY_MENU:
1602                if (mCurrentMenuState != mMenuState) {
1603                    menu.setGroupVisible(R.id.MAIN_MENU, false);
1604                    menu.setGroupEnabled(R.id.MAIN_MENU, false);
1605                    menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1606                }
1607                break;
1608            default:
1609                if (mCurrentMenuState != mMenuState) {
1610                    menu.setGroupVisible(R.id.MAIN_MENU, true);
1611                    menu.setGroupEnabled(R.id.MAIN_MENU, true);
1612                    menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1613                }
1614                final WebView w = getTopWindow();
1615                boolean canGoBack = false;
1616                boolean canGoForward = false;
1617                boolean isHome = false;
1618                if (w != null) {
1619                    canGoBack = w.canGoBack();
1620                    canGoForward = w.canGoForward();
1621                    isHome = mSettings.getHomePage().equals(w.getUrl());
1622                }
1623                final MenuItem back = menu.findItem(R.id.back_menu_id);
1624                back.setEnabled(canGoBack);
1625
1626                final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1627                home.setEnabled(!isHome);
1628
1629                menu.findItem(R.id.forward_menu_id)
1630                        .setEnabled(canGoForward);
1631
1632                menu.findItem(R.id.new_tab_menu_id).setEnabled(
1633                        mTabControl.getTabCount() < TabControl.MAX_TABS);
1634
1635                // decide whether to show the share link option
1636                PackageManager pm = getPackageManager();
1637                Intent send = new Intent(Intent.ACTION_SEND);
1638                send.setType("text/plain");
1639                ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1640                menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1641
1642                boolean isNavDump = mSettings.isNavDump();
1643                final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1644                nav.setVisible(isNavDump);
1645                nav.setEnabled(isNavDump);
1646                break;
1647        }
1648        mCurrentMenuState = mMenuState;
1649        return true;
1650    }
1651
1652    @Override
1653    public void onCreateContextMenu(ContextMenu menu, View v,
1654            ContextMenuInfo menuInfo) {
1655        WebView webview = (WebView) v;
1656        WebView.HitTestResult result = webview.getHitTestResult();
1657        if (result == null) {
1658            return;
1659        }
1660
1661        int type = result.getType();
1662        if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1663            Log.w(LOGTAG,
1664                    "We should not show context menu when nothing is touched");
1665            return;
1666        }
1667        if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1668            // let TextView handles context menu
1669            return;
1670        }
1671
1672        // Note, http://b/issue?id=1106666 is requesting that
1673        // an inflated menu can be used again. This is not available
1674        // yet, so inflate each time (yuk!)
1675        MenuInflater inflater = getMenuInflater();
1676        inflater.inflate(R.menu.browsercontext, menu);
1677
1678        // Show the correct menu group
1679        String extra = result.getExtra();
1680        menu.setGroupVisible(R.id.PHONE_MENU,
1681                type == WebView.HitTestResult.PHONE_TYPE);
1682        menu.setGroupVisible(R.id.EMAIL_MENU,
1683                type == WebView.HitTestResult.EMAIL_TYPE);
1684        menu.setGroupVisible(R.id.GEO_MENU,
1685                type == WebView.HitTestResult.GEO_TYPE);
1686        menu.setGroupVisible(R.id.IMAGE_MENU,
1687                type == WebView.HitTestResult.IMAGE_TYPE
1688                || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1689        menu.setGroupVisible(R.id.ANCHOR_MENU,
1690                type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1691                || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1692
1693        // Setup custom handling depending on the type
1694        switch (type) {
1695            case WebView.HitTestResult.PHONE_TYPE:
1696                menu.setHeaderTitle(Uri.decode(extra));
1697                menu.findItem(R.id.dial_context_menu_id).setIntent(
1698                        new Intent(Intent.ACTION_VIEW, Uri
1699                                .parse(WebView.SCHEME_TEL + extra)));
1700                Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1701                addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1702                addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1703                menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1704                        addIntent);
1705                menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1706                        new Copy(extra));
1707                break;
1708
1709            case WebView.HitTestResult.EMAIL_TYPE:
1710                menu.setHeaderTitle(extra);
1711                menu.findItem(R.id.email_context_menu_id).setIntent(
1712                        new Intent(Intent.ACTION_VIEW, Uri
1713                                .parse(WebView.SCHEME_MAILTO + extra)));
1714                menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1715                        new Copy(extra));
1716                break;
1717
1718            case WebView.HitTestResult.GEO_TYPE:
1719                menu.setHeaderTitle(extra);
1720                menu.findItem(R.id.map_context_menu_id).setIntent(
1721                        new Intent(Intent.ACTION_VIEW, Uri
1722                                .parse(WebView.SCHEME_GEO
1723                                        + URLEncoder.encode(extra))));
1724                menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1725                        new Copy(extra));
1726                break;
1727
1728            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1729            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1730                TextView titleView = (TextView) LayoutInflater.from(this)
1731                        .inflate(android.R.layout.browser_link_context_header,
1732                        null);
1733                titleView.setText(extra);
1734                menu.setHeaderView(titleView);
1735                // decide whether to show the open link in new tab option
1736                menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1737                        mTabControl.getTabCount() < TabControl.MAX_TABS);
1738                PackageManager pm = getPackageManager();
1739                Intent send = new Intent(Intent.ACTION_SEND);
1740                send.setType("text/plain");
1741                ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1742                menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1743                if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1744                    break;
1745                }
1746                // otherwise fall through to handle image part
1747            case WebView.HitTestResult.IMAGE_TYPE:
1748                if (type == WebView.HitTestResult.IMAGE_TYPE) {
1749                    menu.setHeaderTitle(extra);
1750                }
1751                menu.findItem(R.id.view_image_context_menu_id).setIntent(
1752                        new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1753                menu.findItem(R.id.download_context_menu_id).
1754                        setOnMenuItemClickListener(new Download(extra));
1755                break;
1756
1757            default:
1758                Log.w(LOGTAG, "We should not get here.");
1759                break;
1760        }
1761    }
1762
1763    // Attach the given tab to the content view.
1764    private void attachTabToContentView(TabControl.Tab t) {
1765        // Attach the container that contains the main WebView and any other UI
1766        // associated with the tab.
1767        mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
1768
1769        if (mShouldShowErrorConsole) {
1770            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1771            if (errorConsole.numberOfErrors() == 0) {
1772                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1773            } else {
1774                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1775            }
1776
1777            mErrorConsoleContainer.addView(errorConsole,
1778                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1779                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
1780        }
1781
1782        WebView view = t.getWebView();
1783        view.setEmbeddedTitleBar(mTitleBar);
1784        // Attach the sub window if necessary
1785        attachSubWindow(t);
1786        // Request focus on the top window.
1787        t.getTopWindow().requestFocus();
1788    }
1789
1790    // Attach a sub window to the main WebView of the given tab.
1791    private void attachSubWindow(TabControl.Tab t) {
1792        // If a sub window exists, attach it to the content view.
1793        final WebView subView = t.getSubWebView();
1794        if (subView != null) {
1795            final View container = t.getSubWebViewContainer();
1796            mContentView.addView(container, COVER_SCREEN_PARAMS);
1797            subView.requestFocus();
1798        }
1799    }
1800
1801    // Remove the given tab from the content view.
1802    private void removeTabFromContentView(TabControl.Tab t) {
1803        // Remove the container that contains the main WebView.
1804        mContentView.removeView(t.getContainer());
1805
1806        if (mTabControl.getCurrentErrorConsole(false) != null) {
1807            mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1808        }
1809
1810        WebView view = t.getWebView();
1811        view.setEmbeddedTitleBar(null);
1812
1813        // Remove the sub window if it exists.
1814        if (t.getSubWebView() != null) {
1815            mContentView.removeView(t.getSubWebViewContainer());
1816        }
1817    }
1818
1819    // Remove the sub window if it exists. Also called by TabControl when the
1820    // user clicks the 'X' to dismiss a sub window.
1821    /* package */ void dismissSubWindow(TabControl.Tab t) {
1822        final WebView mainView = t.getWebView();
1823        if (t.getSubWebView() != null) {
1824            // Remove the container view and request focus on the main WebView.
1825            mContentView.removeView(t.getSubWebViewContainer());
1826            mainView.requestFocus();
1827            // Tell the TabControl to dismiss the subwindow. This will destroy
1828            // the WebView.
1829            mTabControl.dismissSubWindow(t);
1830        }
1831    }
1832
1833    // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
1834    // that accepts url as string.
1835    private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1836            String appId) {
1837        return openTabAndShow(new UrlData(url), closeOnExit, appId);
1838    }
1839
1840    // This method does a ton of stuff. It will attempt to create a new tab
1841    // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
1842    // url isn't null, it will load the given url.
1843    /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
1844            boolean closeOnExit, String appId) {
1845        final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1846        final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1847        if (newTab) {
1848            final TabControl.Tab tab = mTabControl.createNewTab(
1849                    closeOnExit, appId, urlData.mUrl);
1850            WebView webview = tab.getWebView();
1851            mTitleBar.addTab(webview, true);
1852            // If the last tab was removed from the active tabs page, currentTab
1853            // will be null.
1854            if (currentTab != null) {
1855                removeTabFromContentView(currentTab);
1856            }
1857            attachTabToContentView(tab);
1858            // We must set the new tab as the current tab to reflect the old
1859            // animation behavior.
1860            mTabControl.setCurrentTab(tab);
1861            if (!urlData.isEmpty()) {
1862                urlData.loadIn(webview);
1863            }
1864            return tab;
1865        } else {
1866            // Get rid of the subwindow if it exists
1867            dismissSubWindow(currentTab);
1868            if (!urlData.isEmpty()) {
1869                // Load the given url.
1870                urlData.loadIn(currentTab.getWebView());
1871            }
1872        }
1873        return currentTab;
1874    }
1875
1876    private TabControl.Tab openTab(String url) {
1877        if (mSettings.openInBackground()) {
1878            TabControl.Tab t = mTabControl.createNewTab();
1879            if (t != null) {
1880                WebView view = t.getWebView();
1881                mTitleBar.addTab(view, false);
1882                view.loadUrl(url);
1883            }
1884            return t;
1885        } else {
1886            return openTabAndShow(url, false, null);
1887        }
1888    }
1889
1890    private class Copy implements OnMenuItemClickListener {
1891        private CharSequence mText;
1892
1893        public boolean onMenuItemClick(MenuItem item) {
1894            copy(mText);
1895            return true;
1896        }
1897
1898        public Copy(CharSequence toCopy) {
1899            mText = toCopy;
1900        }
1901    }
1902
1903    private class Download implements OnMenuItemClickListener {
1904        private String mText;
1905
1906        public boolean onMenuItemClick(MenuItem item) {
1907            onDownloadStartNoStream(mText, null, null, null, -1);
1908            return true;
1909        }
1910
1911        public Download(String toDownload) {
1912            mText = toDownload;
1913        }
1914    }
1915
1916    private void copy(CharSequence text) {
1917        try {
1918            IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1919            if (clip != null) {
1920                clip.setClipboardText(text);
1921            }
1922        } catch (android.os.RemoteException e) {
1923            Log.e(LOGTAG, "Copy failed", e);
1924        }
1925    }
1926
1927    /**
1928     * Resets the browser title-view to whatever it must be (for example, if we
1929     * load a page from history).
1930     */
1931    private void resetTitle() {
1932        resetLockIcon();
1933        resetTitleIconAndProgress();
1934    }
1935
1936    /**
1937     * Resets the browser title-view to whatever it must be
1938     * (for example, if we had a loading error)
1939     * When we have a new page, we call resetTitle, when we
1940     * have to reset the titlebar to whatever it used to be
1941     * (for example, if the user chose to stop loading), we
1942     * call resetTitleAndRevertLockIcon.
1943     */
1944    /* package */ void resetTitleAndRevertLockIcon() {
1945        revertLockIcon();
1946        resetTitleIconAndProgress();
1947    }
1948
1949    /**
1950     * Reset the title, favicon, and progress.
1951     */
1952    private void resetTitleIconAndProgress() {
1953        WebView current = mTabControl.getCurrentWebView();
1954        if (current == null) {
1955            return;
1956        }
1957        resetTitleAndIcon(current);
1958        int progress = current.getProgress();
1959        mWebChromeClient.onProgressChanged(current, progress);
1960    }
1961
1962    // Reset the title and the icon based on the given item.
1963    private void resetTitleAndIcon(WebView view) {
1964        WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1965        if (item != null) {
1966            setUrlTitle(item.getUrl(), item.getTitle(), view);
1967            setFavicon(item.getFavicon());
1968        } else {
1969            setUrlTitle(null, null, view);
1970            setFavicon(null);
1971        }
1972    }
1973
1974    /**
1975     * Sets a title composed of the URL and the title string.
1976     * @param url The URL of the site being loaded.
1977     * @param title The title of the site being loaded.
1978     */
1979    private void setUrlTitle(String url, String title, WebView view) {
1980        mUrl = url;
1981        mTitle = title;
1982
1983        mTitleBar.setTitleAndUrl(title, url, view);
1984        if (mFakeTitleBar != null) {
1985            mFakeTitleBar.setTitleAndUrl(title, url);
1986        }
1987    }
1988
1989    /**
1990     * @param url The URL to build a title version of the URL from.
1991     * @return The title version of the URL or null if fails.
1992     * The title version of the URL can be either the URL hostname,
1993     * or the hostname with an "https://" prefix (for secure URLs),
1994     * or an empty string if, for example, the URL in question is a
1995     * file:// URL with no hostname.
1996     */
1997    /* package */ static String buildTitleUrl(String url) {
1998        String titleUrl = null;
1999
2000        if (url != null) {
2001            try {
2002                // parse the url string
2003                URL urlObj = new URL(url);
2004                if (urlObj != null) {
2005                    titleUrl = "";
2006
2007                    String protocol = urlObj.getProtocol();
2008                    String host = urlObj.getHost();
2009
2010                    if (host != null && 0 < host.length()) {
2011                        titleUrl = host;
2012                        if (protocol != null) {
2013                            // if a secure site, add an "https://" prefix!
2014                            if (protocol.equalsIgnoreCase("https")) {
2015                                titleUrl = protocol + "://" + host;
2016                            }
2017                        }
2018                    }
2019                }
2020            } catch (MalformedURLException e) {}
2021        }
2022
2023        return titleUrl;
2024    }
2025
2026    // Set the favicon in the title bar.
2027    private void setFavicon(Bitmap icon) {
2028        mTitleBar.setFavicon(icon, getTopWindow());
2029        if (mFakeTitleBar != null) {
2030            mFakeTitleBar.setFavicon(icon);
2031        }
2032    }
2033
2034    /**
2035     * Saves the current lock-icon state before resetting
2036     * the lock icon. If we have an error, we may need to
2037     * roll back to the previous state.
2038     */
2039    private void saveLockIcon() {
2040        mPrevLockType = mLockIconType;
2041    }
2042
2043    /**
2044     * Reverts the lock-icon state to the last saved state,
2045     * for example, if we had an error, and need to cancel
2046     * the load.
2047     */
2048    private void revertLockIcon() {
2049        mLockIconType = mPrevLockType;
2050
2051        if (LOGV_ENABLED) {
2052            Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
2053                  " revert lock icon to " + mLockIconType);
2054        }
2055
2056        updateLockIconToLatest();
2057    }
2058
2059    /**
2060     * Close the tab, remove its associated title bar, and adjust mTabControl's
2061     * current tab to a valid value.
2062     */
2063    /* package */ void closeTab(TabControl.Tab t) {
2064        int currentIndex = mTabControl.getCurrentIndex();
2065        int removeIndex = mTabControl.getTabIndex(t);
2066        mTitleBar.removeTab(removeIndex);
2067        mTabControl.removeTab(t);
2068        if (currentIndex >= removeIndex && currentIndex != 0) {
2069            currentIndex--;
2070        }
2071        mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
2072    }
2073
2074    private void goBackOnePageOrQuit() {
2075        TabControl.Tab current = mTabControl.getCurrentTab();
2076        if (current == null) {
2077            /*
2078             * Instead of finishing the activity, simply push this to the back
2079             * of the stack and let ActivityManager to choose the foreground
2080             * activity. As BrowserActivity is singleTask, it will be always the
2081             * root of the task. So we can use either true or false for
2082             * moveTaskToBack().
2083             */
2084            moveTaskToBack(true);
2085        }
2086        WebView w = current.getWebView();
2087        if (w.canGoBack()) {
2088            w.goBack();
2089        } else {
2090            // Check to see if we are closing a window that was created by
2091            // another window. If so, we switch back to that window.
2092            TabControl.Tab parent = current.getParentTab();
2093            if (parent != null) {
2094                switchToTab(mTabControl.getTabIndex(parent));
2095                // Now we close the other tab
2096                closeTab(current);
2097            } else {
2098                if (current.closeOnExit()) {
2099                    // force mPageStarted to be false as we are going to either
2100                    // finish the activity or remove the tab. This will ensure
2101                    // pauseWebView() taking action.
2102                    mPageStarted = false;
2103                    if (mTabControl.getTabCount() == 1) {
2104                        finish();
2105                        return;
2106                    }
2107                    // call pauseWebViewTimers() now, we won't be able to call
2108                    // it in onPause() as the WebView won't be valid.
2109                    // Temporarily change mActivityInPause to be true as
2110                    // pauseWebViewTimers() will do nothing if mActivityInPause
2111                    // is false.
2112                    boolean savedState = mActivityInPause;
2113                    if (savedState) {
2114                        Log.e(LOGTAG, "BrowserActivity is already paused "
2115                                + "while handing goBackOnePageOrQuit.");
2116                    }
2117                    mActivityInPause = true;
2118                    pauseWebViewTimers();
2119                    mActivityInPause = savedState;
2120                    removeTabFromContentView(current);
2121                    mTitleBar.removeTab(mTabControl.getTabIndex(current));
2122                    mTabControl.removeTab(current);
2123                }
2124                /*
2125                 * Instead of finishing the activity, simply push this to the back
2126                 * of the stack and let ActivityManager to choose the foreground
2127                 * activity. As BrowserActivity is singleTask, it will be always the
2128                 * root of the task. So we can use either true or false for
2129                 * moveTaskToBack().
2130                 */
2131                moveTaskToBack(true);
2132            }
2133        }
2134    }
2135
2136    public KeyTracker.State onKeyTracker(int keyCode,
2137                                         KeyEvent event,
2138                                         KeyTracker.Stage stage,
2139                                         int duration) {
2140        // if onKeyTracker() is called after activity onStop()
2141        // because of accumulated key events,
2142        // we should ignore it as browser is not active any more.
2143        WebView topWindow = getTopWindow();
2144        if (topWindow == null && mCustomView == null)
2145            return KeyTracker.State.NOT_TRACKING;
2146
2147        if (keyCode == KeyEvent.KEYCODE_BACK) {
2148            // Check if a custom view is currently showing and, if it is, hide it.
2149            if (mCustomView != null) {
2150                mWebChromeClient.onHideCustomView();
2151                return KeyTracker.State.DONE_TRACKING;
2152            }
2153            if (stage == KeyTracker.Stage.LONG_REPEAT) {
2154                bookmarksOrHistoryPicker(true);
2155                return KeyTracker.State.DONE_TRACKING;
2156            } else if (stage == KeyTracker.Stage.UP) {
2157                // FIXME: Currently, we do not have a notion of the
2158                // history picker for the subwindow, but maybe we
2159                // should?
2160                WebView subwindow = mTabControl.getCurrentSubWindow();
2161                if (subwindow != null) {
2162                    if (subwindow.canGoBack()) {
2163                        subwindow.goBack();
2164                    } else {
2165                        dismissSubWindow(mTabControl.getCurrentTab());
2166                    }
2167                } else {
2168                    goBackOnePageOrQuit();
2169                }
2170                return KeyTracker.State.DONE_TRACKING;
2171            }
2172            return KeyTracker.State.KEEP_TRACKING;
2173        }
2174        return KeyTracker.State.NOT_TRACKING;
2175    }
2176
2177    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2178        if (keyCode == KeyEvent.KEYCODE_MENU) {
2179            mMenuIsDown = true;
2180        } else if (mMenuIsDown) {
2181            // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2182            // still down, we don't want to trigger the search. Pretend to
2183            // consume the key and do nothing.
2184            return true;
2185        }
2186        boolean handled =  mKeyTracker.doKeyDown(keyCode, event);
2187        if (!handled) {
2188            switch (keyCode) {
2189                case KeyEvent.KEYCODE_SPACE:
2190                    if (event.isShiftPressed()) {
2191                        getTopWindow().pageUp(false);
2192                    } else {
2193                        getTopWindow().pageDown(false);
2194                    }
2195                    handled = true;
2196                    break;
2197
2198                default:
2199                    break;
2200            }
2201        }
2202        return handled || super.onKeyDown(keyCode, event);
2203    }
2204
2205    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2206        if (keyCode == KeyEvent.KEYCODE_MENU) {
2207            mMenuIsDown = false;
2208        }
2209        return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2210    }
2211
2212    private void stopLoading() {
2213        resetTitleAndRevertLockIcon();
2214        WebView w = getTopWindow();
2215        w.stopLoading();
2216        mWebViewClient.onPageFinished(w, w.getUrl());
2217
2218        cancelStopToast();
2219        mStopToast = Toast
2220                .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2221        mStopToast.show();
2222    }
2223
2224    private void cancelStopToast() {
2225        if (mStopToast != null) {
2226            mStopToast.cancel();
2227            mStopToast = null;
2228        }
2229    }
2230
2231    // called by a non-UI thread to post the message
2232    public void postMessage(int what, int arg1, int arg2, Object obj) {
2233        mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2234    }
2235
2236    // public message ids
2237    public final static int LOAD_URL                = 1001;
2238    public final static int STOP_LOAD               = 1002;
2239
2240    // Message Ids
2241    private static final int FOCUS_NODE_HREF         = 102;
2242    private static final int CANCEL_CREDS_REQUEST    = 103;
2243    private static final int RELEASE_WAKELOCK        = 107;
2244
2245    // Private handler for handling javascript and saving passwords
2246    private Handler mHandler = new Handler() {
2247
2248        public void handleMessage(Message msg) {
2249            switch (msg.what) {
2250                case FOCUS_NODE_HREF:
2251                    String url = (String) msg.getData().get("url");
2252                    if (url == null || url.length() == 0) {
2253                        break;
2254                    }
2255                    HashMap focusNodeMap = (HashMap) msg.obj;
2256                    WebView view = (WebView) focusNodeMap.get("webview");
2257                    // Only apply the action if the top window did not change.
2258                    if (getTopWindow() != view) {
2259                        break;
2260                    }
2261                    switch (msg.arg1) {
2262                        case R.id.open_context_menu_id:
2263                        case R.id.view_image_context_menu_id:
2264                            loadURL(getTopWindow(), url);
2265                            break;
2266                        case R.id.open_newtab_context_menu_id:
2267                            final TabControl.Tab parent = mTabControl
2268                                    .getCurrentTab();
2269                            final TabControl.Tab newTab = openTab(url);
2270                            if (newTab != parent) {
2271                                parent.addChildTab(newTab);
2272                            }
2273                            break;
2274                        case R.id.bookmark_context_menu_id:
2275                            Intent intent = new Intent(BrowserActivity.this,
2276                                    AddBookmarkPage.class);
2277                            intent.putExtra("url", url);
2278                            startActivity(intent);
2279                            break;
2280                        case R.id.share_link_context_menu_id:
2281                            Browser.sendString(BrowserActivity.this, url);
2282                            break;
2283                        case R.id.copy_link_context_menu_id:
2284                            copy(url);
2285                            break;
2286                        case R.id.save_link_context_menu_id:
2287                        case R.id.download_context_menu_id:
2288                            onDownloadStartNoStream(url, null, null, null, -1);
2289                            break;
2290                    }
2291                    break;
2292
2293                case LOAD_URL:
2294                    loadURL(getTopWindow(), (String) msg.obj);
2295                    break;
2296
2297                case STOP_LOAD:
2298                    stopLoading();
2299                    break;
2300
2301                case CANCEL_CREDS_REQUEST:
2302                    resumeAfterCredentials();
2303                    break;
2304
2305                case RELEASE_WAKELOCK:
2306                    if (mWakeLock.isHeld()) {
2307                        mWakeLock.release();
2308                    }
2309                    break;
2310            }
2311        }
2312    };
2313
2314    private void updateScreenshot(WebView view) {
2315        // If this is a bookmarked site, add a screenshot to the database.
2316        // FIXME: When should we update?  Every time?
2317        // FIXME: Would like to make sure there is actually something to
2318        // draw, but the API for that (WebViewCore.pictureReady()) is not
2319        // currently accessible here.
2320        ContentResolver cr = getContentResolver();
2321        final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
2322                cr, view.getOriginalUrl(), view.getUrl(), false);
2323        if (c != null) {
2324            boolean succeed = c.moveToFirst();
2325            ContentValues values = null;
2326            while (succeed) {
2327                if (values == null) {
2328                    final ByteArrayOutputStream os
2329                            = new ByteArrayOutputStream();
2330                    Picture thumbnail = view.capturePicture();
2331                    // Keep width and height in sync with BrowserBookmarksPage
2332                    // and bookmark_thumb
2333                    Bitmap bm = Bitmap.createBitmap(100, 80,
2334                            Bitmap.Config.ARGB_4444);
2335                    Canvas canvas = new Canvas(bm);
2336                    // May need to tweak these values to determine what is the
2337                    // best scale factor
2338                    canvas.scale(.5f, .5f);
2339                    thumbnail.draw(canvas);
2340                    bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2341                    values = new ContentValues();
2342                    values.put(Browser.BookmarkColumns.THUMBNAIL,
2343                            os.toByteArray());
2344                }
2345                cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2346                        c.getInt(0)), values, null, null);
2347                succeed = c.moveToNext();
2348            }
2349            c.close();
2350        }
2351    }
2352
2353    // -------------------------------------------------------------------------
2354    // WebViewClient implementation.
2355    //-------------------------------------------------------------------------
2356
2357    // Use in overrideUrlLoading
2358    /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2359    /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2360    /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2361    /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2362
2363    /* package */ WebViewClient getWebViewClient() {
2364        return mWebViewClient;
2365    }
2366
2367    private void updateIcon(WebView view, Bitmap icon) {
2368        if (icon != null) {
2369            BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
2370                    view, icon);
2371        }
2372        setFavicon(icon);
2373    }
2374
2375    private final WebViewClient mWebViewClient = new WebViewClient() {
2376        @Override
2377        public void onPageStarted(WebView view, String url, Bitmap favicon) {
2378            resetLockIcon(url);
2379            setUrlTitle(url, null, view);
2380
2381            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2382            if (errorConsole != null) {
2383                errorConsole.clearErrorMessages();
2384                if (mShouldShowErrorConsole) {
2385                    errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2386                }
2387            }
2388
2389            // Call updateIcon instead of setFavicon so the bookmark
2390            // database can be updated.
2391            updateIcon(view, favicon);
2392
2393            if (mSettings.isTracing()) {
2394                String host;
2395                try {
2396                    WebAddress uri = new WebAddress(url);
2397                    host = uri.mHost;
2398                } catch (android.net.ParseException ex) {
2399                    host = "browser";
2400                }
2401                host = host.replace('.', '_');
2402                host += ".trace";
2403                mInTrace = true;
2404                Debug.startMethodTracing(host, 20 * 1024 * 1024);
2405            }
2406
2407            // Performance probe
2408            if (false) {
2409                mStart = SystemClock.uptimeMillis();
2410                mProcessStart = Process.getElapsedCpuTime();
2411                long[] sysCpu = new long[7];
2412                if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2413                        sysCpu, null)) {
2414                    mUserStart = sysCpu[0] + sysCpu[1];
2415                    mSystemStart = sysCpu[2];
2416                    mIdleStart = sysCpu[3];
2417                    mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2418                }
2419                mUiStart = SystemClock.currentThreadTimeMillis();
2420            }
2421
2422            if (!mPageStarted) {
2423                mPageStarted = true;
2424                // if onResume() has been called, resumeWebViewTimers() does
2425                // nothing.
2426                resumeWebViewTimers();
2427            }
2428
2429            // reset sync timer to avoid sync starts during loading a page
2430            CookieSyncManager.getInstance().resetSync();
2431
2432            mInLoad = true;
2433            WebView currentWebView = mTabControl.getCurrentWebView();
2434            if (currentWebView == null || currentWebView.getScrollY() != 0) {
2435                // This page has begun to load, so show the title bar
2436                showFakeTitleBar();
2437            }
2438            updateInLoadMenuItems();
2439            if (!mIsNetworkUp) {
2440                if ( mAlertDialog == null) {
2441                    mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2442                        .setTitle(R.string.loadSuspendedTitle)
2443                        .setMessage(R.string.loadSuspended)
2444                        .setPositiveButton(R.string.ok, null)
2445                        .show();
2446                }
2447                if (view != null) {
2448                    view.setNetworkAvailable(false);
2449                }
2450            }
2451        }
2452
2453        @Override
2454        public void onPageFinished(WebView view, String url) {
2455            // Reset the title and icon in case we stopped a provisional
2456            // load.
2457            resetTitleAndIcon(view);
2458
2459            // Update the lock icon image only once we are done loading
2460            updateLockIconToLatest();
2461            updateScreenshot(view);
2462
2463            // Performance probe
2464            if (false) {
2465                long[] sysCpu = new long[7];
2466                if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2467                        sysCpu, null)) {
2468                    String uiInfo = "UI thread used "
2469                            + (SystemClock.currentThreadTimeMillis() - mUiStart)
2470                            + " ms";
2471                    if (LOGD_ENABLED) {
2472                        Log.d(LOGTAG, uiInfo);
2473                    }
2474                    //The string that gets written to the log
2475                    String performanceString = "It took total "
2476                            + (SystemClock.uptimeMillis() - mStart)
2477                            + " ms clock time to load the page."
2478                            + "\nbrowser process used "
2479                            + (Process.getElapsedCpuTime() - mProcessStart)
2480                            + " ms, user processes used "
2481                            + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2482                            + " ms, kernel used "
2483                            + (sysCpu[2] - mSystemStart) * 10
2484                            + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2485                            + " ms and irq took "
2486                            + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2487                            * 10 + " ms, " + uiInfo;
2488                    if (LOGD_ENABLED) {
2489                        Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2490                    }
2491                    if (url != null) {
2492                        // strip the url to maintain consistency
2493                        String newUrl = new String(url);
2494                        if (newUrl.startsWith("http://www.")) {
2495                            newUrl = newUrl.substring(11);
2496                        } else if (newUrl.startsWith("http://")) {
2497                            newUrl = newUrl.substring(7);
2498                        } else if (newUrl.startsWith("https://www.")) {
2499                            newUrl = newUrl.substring(12);
2500                        } else if (newUrl.startsWith("https://")) {
2501                            newUrl = newUrl.substring(8);
2502                        }
2503                        if (LOGD_ENABLED) {
2504                            Log.d(LOGTAG, newUrl + " loaded");
2505                        }
2506                        /*
2507                        if (sWhiteList.contains(newUrl)) {
2508                            // The string that gets pushed to the statistcs
2509                            // service
2510                            performanceString = performanceString
2511                                    + "\nWebpage: "
2512                                    + newUrl
2513                                    + "\nCarrier: "
2514                                    + android.os.SystemProperties
2515                                            .get("gsm.sim.operator.alpha");
2516                            if (mWebView != null
2517                                    && mWebView.getContext() != null
2518                                    && mWebView.getContext().getSystemService(
2519                                    Context.CONNECTIVITY_SERVICE) != null) {
2520                                ConnectivityManager cManager =
2521                                        (ConnectivityManager) mWebView
2522                                        .getContext().getSystemService(
2523                                        Context.CONNECTIVITY_SERVICE);
2524                                NetworkInfo nInfo = cManager
2525                                        .getActiveNetworkInfo();
2526                                if (nInfo != null) {
2527                                    performanceString = performanceString
2528                                            + "\nNetwork Type: "
2529                                            + nInfo.getType().toString();
2530                                }
2531                            }
2532                            Checkin.logEvent(mResolver,
2533                                    Checkin.Events.Tag.WEBPAGE_LOAD,
2534                                    performanceString);
2535                            Log.w(LOGTAG, "pushed to the statistics service");
2536                        }
2537                        */
2538                    }
2539                }
2540             }
2541
2542            if (mInTrace) {
2543                mInTrace = false;
2544                Debug.stopMethodTracing();
2545            }
2546
2547            if (mPageStarted) {
2548                mPageStarted = false;
2549                // pauseWebViewTimers() will do nothing and return false if
2550                // onPause() is not called yet.
2551                if (pauseWebViewTimers()) {
2552                    if (mWakeLock.isHeld()) {
2553                        mHandler.removeMessages(RELEASE_WAKELOCK);
2554                        mWakeLock.release();
2555                    }
2556                }
2557            }
2558        }
2559
2560        // return true if want to hijack the url to let another app to handle it
2561        @Override
2562        public boolean shouldOverrideUrlLoading(WebView view, String url) {
2563            if (url.startsWith(SCHEME_WTAI)) {
2564                // wtai://wp/mc;number
2565                // number=string(phone-number)
2566                if (url.startsWith(SCHEME_WTAI_MC)) {
2567                    Intent intent = new Intent(Intent.ACTION_VIEW,
2568                            Uri.parse(WebView.SCHEME_TEL +
2569                            url.substring(SCHEME_WTAI_MC.length())));
2570                    startActivity(intent);
2571                    return true;
2572                }
2573                // wtai://wp/sd;dtmf
2574                // dtmf=string(dialstring)
2575                if (url.startsWith(SCHEME_WTAI_SD)) {
2576                    // TODO
2577                    // only send when there is active voice connection
2578                    return false;
2579                }
2580                // wtai://wp/ap;number;name
2581                // number=string(phone-number)
2582                // name=string
2583                if (url.startsWith(SCHEME_WTAI_AP)) {
2584                    // TODO
2585                    return false;
2586                }
2587            }
2588
2589            // The "about:" schemes are internal to the browser; don't
2590            // want these to be dispatched to other apps.
2591            if (url.startsWith("about:")) {
2592                return false;
2593            }
2594
2595            Intent intent;
2596
2597            // perform generic parsing of the URI to turn it into an Intent.
2598            try {
2599                intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2600            } catch (URISyntaxException ex) {
2601                Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
2602                return false;
2603            }
2604
2605            // check whether the intent can be resolved. If not, we will see
2606            // whether we can download it from the Market.
2607            if (getPackageManager().resolveActivity(intent, 0) == null) {
2608                String packagename = intent.getPackage();
2609                if (packagename != null) {
2610                    intent = new Intent(Intent.ACTION_VIEW, Uri
2611                            .parse("market://search?q=pname:" + packagename));
2612                    intent.addCategory(Intent.CATEGORY_BROWSABLE);
2613                    startActivity(intent);
2614                    return true;
2615                } else {
2616                    return false;
2617                }
2618            }
2619
2620            // sanitize the Intent, ensuring web pages can not bypass browser
2621            // security (only access to BROWSABLE activities).
2622            intent.addCategory(Intent.CATEGORY_BROWSABLE);
2623            intent.setComponent(null);
2624            try {
2625                if (startActivityIfNeeded(intent, -1)) {
2626                    return true;
2627                }
2628            } catch (ActivityNotFoundException ex) {
2629                // ignore the error. If no application can handle the URL,
2630                // eg about:blank, assume the browser can handle it.
2631            }
2632
2633            if (mMenuIsDown) {
2634                openTab(url);
2635                closeOptionsMenu();
2636                return true;
2637            }
2638
2639            return false;
2640        }
2641
2642        /**
2643         * Updates the lock icon. This method is called when we discover another
2644         * resource to be loaded for this page (for example, javascript). While
2645         * we update the icon type, we do not update the lock icon itself until
2646         * we are done loading, it is slightly more secure this way.
2647         */
2648        @Override
2649        public void onLoadResource(WebView view, String url) {
2650            if (url != null && url.length() > 0) {
2651                // It is only if the page claims to be secure
2652                // that we may have to update the lock:
2653                if (mLockIconType == LOCK_ICON_SECURE) {
2654                    // If NOT a 'safe' url, change the lock to mixed content!
2655                    if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2656                        mLockIconType = LOCK_ICON_MIXED;
2657                        if (LOGV_ENABLED) {
2658                            Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2659                                  " updated lock icon to " + mLockIconType + " due to " + url);
2660                        }
2661                    }
2662                }
2663            }
2664        }
2665
2666        /**
2667         * Show the dialog, asking the user if they would like to continue after
2668         * an excessive number of HTTP redirects.
2669         */
2670        @Override
2671        public void onTooManyRedirects(WebView view, final Message cancelMsg,
2672                final Message continueMsg) {
2673            new AlertDialog.Builder(BrowserActivity.this)
2674                .setTitle(R.string.browserFrameRedirect)
2675                .setMessage(R.string.browserFrame307Post)
2676                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2677                    public void onClick(DialogInterface dialog, int which) {
2678                        continueMsg.sendToTarget();
2679                    }})
2680                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2681                    public void onClick(DialogInterface dialog, int which) {
2682                        cancelMsg.sendToTarget();
2683                    }})
2684                .setOnCancelListener(new OnCancelListener() {
2685                    public void onCancel(DialogInterface dialog) {
2686                        cancelMsg.sendToTarget();
2687                    }})
2688                .show();
2689        }
2690
2691        // Container class for the next error dialog that needs to be
2692        // displayed.
2693        class ErrorDialog {
2694            public final int mTitle;
2695            public final String mDescription;
2696            public final int mError;
2697            ErrorDialog(int title, String desc, int error) {
2698                mTitle = title;
2699                mDescription = desc;
2700                mError = error;
2701            }
2702        };
2703
2704        private void processNextError() {
2705            if (mQueuedErrors == null) {
2706                return;
2707            }
2708            // The first one is currently displayed so just remove it.
2709            mQueuedErrors.removeFirst();
2710            if (mQueuedErrors.size() == 0) {
2711                mQueuedErrors = null;
2712                return;
2713            }
2714            showError(mQueuedErrors.getFirst());
2715        }
2716
2717        private DialogInterface.OnDismissListener mDialogListener =
2718                new DialogInterface.OnDismissListener() {
2719                    public void onDismiss(DialogInterface d) {
2720                        processNextError();
2721                    }
2722                };
2723        private LinkedList<ErrorDialog> mQueuedErrors;
2724
2725        private void queueError(int err, String desc) {
2726            if (mQueuedErrors == null) {
2727                mQueuedErrors = new LinkedList<ErrorDialog>();
2728            }
2729            for (ErrorDialog d : mQueuedErrors) {
2730                if (d.mError == err) {
2731                    // Already saw a similar error, ignore the new one.
2732                    return;
2733                }
2734            }
2735            ErrorDialog errDialog = new ErrorDialog(
2736                    err == WebViewClient.ERROR_FILE_NOT_FOUND ?
2737                    R.string.browserFrameFileErrorLabel :
2738                    R.string.browserFrameNetworkErrorLabel,
2739                    desc, err);
2740            mQueuedErrors.addLast(errDialog);
2741
2742            // Show the dialog now if the queue was empty.
2743            if (mQueuedErrors.size() == 1) {
2744                showError(errDialog);
2745            }
2746        }
2747
2748        private void showError(ErrorDialog errDialog) {
2749            AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2750                    .setTitle(errDialog.mTitle)
2751                    .setMessage(errDialog.mDescription)
2752                    .setPositiveButton(R.string.ok, null)
2753                    .create();
2754            d.setOnDismissListener(mDialogListener);
2755            d.show();
2756        }
2757
2758        /**
2759         * Show a dialog informing the user of the network error reported by
2760         * WebCore.
2761         */
2762        @Override
2763        public void onReceivedError(WebView view, int errorCode,
2764                String description, String failingUrl) {
2765            if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
2766                    errorCode != WebViewClient.ERROR_CONNECT &&
2767                    errorCode != WebViewClient.ERROR_BAD_URL &&
2768                    errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
2769                    errorCode != WebViewClient.ERROR_FILE) {
2770                queueError(errorCode, description);
2771            }
2772            Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2773                    + " " + description);
2774
2775            // We need to reset the title after an error.
2776            resetTitleAndRevertLockIcon();
2777        }
2778
2779        /**
2780         * Check with the user if it is ok to resend POST data as the page they
2781         * are trying to navigate to is the result of a POST.
2782         */
2783        @Override
2784        public void onFormResubmission(WebView view, final Message dontResend,
2785                                       final Message resend) {
2786            new AlertDialog.Builder(BrowserActivity.this)
2787                .setTitle(R.string.browserFrameFormResubmitLabel)
2788                .setMessage(R.string.browserFrameFormResubmitMessage)
2789                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2790                    public void onClick(DialogInterface dialog, int which) {
2791                        resend.sendToTarget();
2792                    }})
2793                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2794                    public void onClick(DialogInterface dialog, int which) {
2795                        dontResend.sendToTarget();
2796                    }})
2797                .setOnCancelListener(new OnCancelListener() {
2798                    public void onCancel(DialogInterface dialog) {
2799                        dontResend.sendToTarget();
2800                    }})
2801                .show();
2802        }
2803
2804        /**
2805         * Insert the url into the visited history database.
2806         * @param url The url to be inserted.
2807         * @param isReload True if this url is being reloaded.
2808         * FIXME: Not sure what to do when reloading the page.
2809         */
2810        @Override
2811        public void doUpdateVisitedHistory(WebView view, String url,
2812                boolean isReload) {
2813            if (url.regionMatches(true, 0, "about:", 0, 6)) {
2814                return;
2815            }
2816            // remove "client" before updating it to the history so that it wont
2817            // show up in the auto-complete list.
2818            int index = url.indexOf("client=ms-");
2819            if (index > 0 && url.contains(".google.")) {
2820                int end = url.indexOf('&', index);
2821                if (end > 0) {
2822                    url = url.substring(0, index-1).concat(url.substring(end));
2823                } else {
2824                    url = url.substring(0, index-1);
2825                }
2826            }
2827            Browser.updateVisitedHistory(mResolver, url, true);
2828            WebIconDatabase.getInstance().retainIconForPageUrl(url);
2829        }
2830
2831        /**
2832         * Displays SSL error(s) dialog to the user.
2833         */
2834        @Override
2835        public void onReceivedSslError(
2836            final WebView view, final SslErrorHandler handler, final SslError error) {
2837
2838            if (mSettings.showSecurityWarnings()) {
2839                final LayoutInflater factory =
2840                    LayoutInflater.from(BrowserActivity.this);
2841                final View warningsView =
2842                    factory.inflate(R.layout.ssl_warnings, null);
2843                final LinearLayout placeholder =
2844                    (LinearLayout)warningsView.findViewById(R.id.placeholder);
2845
2846                if (error.hasError(SslError.SSL_UNTRUSTED)) {
2847                    LinearLayout ll = (LinearLayout)factory
2848                        .inflate(R.layout.ssl_warning, null);
2849                    ((TextView)ll.findViewById(R.id.warning))
2850                        .setText(R.string.ssl_untrusted);
2851                    placeholder.addView(ll);
2852                }
2853
2854                if (error.hasError(SslError.SSL_IDMISMATCH)) {
2855                    LinearLayout ll = (LinearLayout)factory
2856                        .inflate(R.layout.ssl_warning, null);
2857                    ((TextView)ll.findViewById(R.id.warning))
2858                        .setText(R.string.ssl_mismatch);
2859                    placeholder.addView(ll);
2860                }
2861
2862                if (error.hasError(SslError.SSL_EXPIRED)) {
2863                    LinearLayout ll = (LinearLayout)factory
2864                        .inflate(R.layout.ssl_warning, null);
2865                    ((TextView)ll.findViewById(R.id.warning))
2866                        .setText(R.string.ssl_expired);
2867                    placeholder.addView(ll);
2868                }
2869
2870                if (error.hasError(SslError.SSL_NOTYETVALID)) {
2871                    LinearLayout ll = (LinearLayout)factory
2872                        .inflate(R.layout.ssl_warning, null);
2873                    ((TextView)ll.findViewById(R.id.warning))
2874                        .setText(R.string.ssl_not_yet_valid);
2875                    placeholder.addView(ll);
2876                }
2877
2878                new AlertDialog.Builder(BrowserActivity.this)
2879                    .setTitle(R.string.security_warning)
2880                    .setIcon(android.R.drawable.ic_dialog_alert)
2881                    .setView(warningsView)
2882                    .setPositiveButton(R.string.ssl_continue,
2883                            new DialogInterface.OnClickListener() {
2884                                public void onClick(DialogInterface dialog, int whichButton) {
2885                                    handler.proceed();
2886                                }
2887                            })
2888                    .setNeutralButton(R.string.view_certificate,
2889                            new DialogInterface.OnClickListener() {
2890                                public void onClick(DialogInterface dialog, int whichButton) {
2891                                    showSSLCertificateOnError(view, handler, error);
2892                                }
2893                            })
2894                    .setNegativeButton(R.string.cancel,
2895                            new DialogInterface.OnClickListener() {
2896                                public void onClick(DialogInterface dialog, int whichButton) {
2897                                    handler.cancel();
2898                                    BrowserActivity.this.resetTitleAndRevertLockIcon();
2899                                }
2900                            })
2901                    .setOnCancelListener(
2902                            new DialogInterface.OnCancelListener() {
2903                                public void onCancel(DialogInterface dialog) {
2904                                    handler.cancel();
2905                                    BrowserActivity.this.resetTitleAndRevertLockIcon();
2906                                }
2907                            })
2908                    .show();
2909            } else {
2910                handler.proceed();
2911            }
2912        }
2913
2914        /**
2915         * Handles an HTTP authentication request.
2916         *
2917         * @param handler The authentication handler
2918         * @param host The host
2919         * @param realm The realm
2920         */
2921        @Override
2922        public void onReceivedHttpAuthRequest(WebView view,
2923                final HttpAuthHandler handler, final String host, final String realm) {
2924            String username = null;
2925            String password = null;
2926
2927            boolean reuseHttpAuthUsernamePassword =
2928                handler.useHttpAuthUsernamePassword();
2929
2930            if (reuseHttpAuthUsernamePassword &&
2931                    (mTabControl.getCurrentWebView() != null)) {
2932                String[] credentials =
2933                        mTabControl.getCurrentWebView()
2934                                .getHttpAuthUsernamePassword(host, realm);
2935                if (credentials != null && credentials.length == 2) {
2936                    username = credentials[0];
2937                    password = credentials[1];
2938                }
2939            }
2940
2941            if (username != null && password != null) {
2942                handler.proceed(username, password);
2943            } else {
2944                showHttpAuthentication(handler, host, realm, null, null, null, 0);
2945            }
2946        }
2947
2948        @Override
2949        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2950            if (mMenuIsDown) {
2951                // only check shortcut key when MENU is held
2952                return getWindow().isShortcutKey(event.getKeyCode(), event);
2953            } else {
2954                return false;
2955            }
2956        }
2957
2958        @Override
2959        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2960            if (view != mTabControl.getCurrentTopWebView()) {
2961                return;
2962            }
2963            if (event.isDown()) {
2964                BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2965            } else {
2966                BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2967            }
2968        }
2969    };
2970
2971    //--------------------------------------------------------------------------
2972    // WebChromeClient implementation
2973    //--------------------------------------------------------------------------
2974
2975    /* package */ WebChromeClient getWebChromeClient() {
2976        return mWebChromeClient;
2977    }
2978
2979    private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2980        // Helper method to create a new tab or sub window.
2981        private void createWindow(final boolean dialog, final Message msg) {
2982            if (dialog) {
2983                mTabControl.createSubWindow();
2984                final TabControl.Tab t = mTabControl.getCurrentTab();
2985                attachSubWindow(t);
2986                WebView.WebViewTransport transport =
2987                        (WebView.WebViewTransport) msg.obj;
2988                transport.setWebView(t.getSubWebView());
2989                msg.sendToTarget();
2990            } else {
2991                final TabControl.Tab parent = mTabControl.getCurrentTab();
2992                final TabControl.Tab newTab
2993                        = openTabAndShow(EMPTY_URL_DATA, false, null);
2994                if (newTab != parent) {
2995                    parent.addChildTab(newTab);
2996                }
2997                WebView.WebViewTransport transport =
2998                        (WebView.WebViewTransport) msg.obj;
2999                transport.setWebView(mTabControl.getCurrentWebView());
3000                msg.sendToTarget();
3001            }
3002        }
3003
3004        @Override
3005        public boolean onCreateWindow(WebView view, final boolean dialog,
3006                final boolean userGesture, final Message resultMsg) {
3007            // Short-circuit if we can't create any more tabs or sub windows.
3008            if (dialog && mTabControl.getCurrentSubWindow() != null) {
3009                new AlertDialog.Builder(BrowserActivity.this)
3010                        .setTitle(R.string.too_many_subwindows_dialog_title)
3011                        .setIcon(android.R.drawable.ic_dialog_alert)
3012                        .setMessage(R.string.too_many_subwindows_dialog_message)
3013                        .setPositiveButton(R.string.ok, null)
3014                        .show();
3015                return false;
3016            } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
3017                new AlertDialog.Builder(BrowserActivity.this)
3018                        .setTitle(R.string.too_many_windows_dialog_title)
3019                        .setIcon(android.R.drawable.ic_dialog_alert)
3020                        .setMessage(R.string.too_many_windows_dialog_message)
3021                        .setPositiveButton(R.string.ok, null)
3022                        .show();
3023                return false;
3024            }
3025
3026            // Short-circuit if this was a user gesture.
3027            if (userGesture) {
3028                createWindow(dialog, resultMsg);
3029                return true;
3030            }
3031
3032            // Allow the popup and create the appropriate window.
3033            final AlertDialog.OnClickListener allowListener =
3034                    new AlertDialog.OnClickListener() {
3035                        public void onClick(DialogInterface d,
3036                                int which) {
3037                            createWindow(dialog, resultMsg);
3038                        }
3039                    };
3040
3041            // Block the popup by returning a null WebView.
3042            final AlertDialog.OnClickListener blockListener =
3043                    new AlertDialog.OnClickListener() {
3044                        public void onClick(DialogInterface d, int which) {
3045                            resultMsg.sendToTarget();
3046                        }
3047                    };
3048
3049            // Build a confirmation dialog to display to the user.
3050            final AlertDialog d =
3051                    new AlertDialog.Builder(BrowserActivity.this)
3052                    .setTitle(R.string.attention)
3053                    .setIcon(android.R.drawable.ic_dialog_alert)
3054                    .setMessage(R.string.popup_window_attempt)
3055                    .setPositiveButton(R.string.allow, allowListener)
3056                    .setNegativeButton(R.string.block, blockListener)
3057                    .setCancelable(false)
3058                    .create();
3059
3060            // Show the confirmation dialog.
3061            d.show();
3062            return true;
3063        }
3064
3065        @Override
3066        public void onCloseWindow(WebView window) {
3067            final TabControl.Tab current = mTabControl.getCurrentTab();
3068            final TabControl.Tab parent = current.getParentTab();
3069            if (parent != null) {
3070                // JavaScript can only close popup window.
3071                switchToTab(mTabControl.getTabIndex(parent));
3072                // Now we need to close the window
3073                closeTab(current);
3074            }
3075        }
3076
3077        @Override
3078        public void onProgressChanged(WebView view, int newProgress) {
3079            mTitleBar.setProgress(newProgress, view);
3080            if (mFakeTitleBar != null) {
3081                mFakeTitleBar.setProgress(newProgress);
3082            }
3083
3084            if (newProgress == 100) {
3085                // onProgressChanged() is called for sub-frame too while
3086                // onPageFinished() is only called for the main frame. sync
3087                // cookie and cache promptly here.
3088                CookieSyncManager.getInstance().sync();
3089                if (mInLoad) {
3090                    mInLoad = false;
3091                    updateInLoadMenuItems();
3092                    // If the options menu is open, leave the title bar
3093                    if (!mOptionsMenuOpen || !mIconView) {
3094                        hideFakeTitleBar();
3095                    }
3096                }
3097            } else if (!mInLoad) {
3098                // onPageFinished may have already been called but a subframe
3099                // is still loading and updating the progress. Reset mInLoad
3100                // and update the menu items.
3101                mInLoad = true;
3102                updateInLoadMenuItems();
3103                WebView currentWebView = mTabControl.getCurrentWebView();
3104                if ((currentWebView == null || currentWebView.getScrollY() != 0)
3105                        && (!mOptionsMenuOpen || mIconView)) {
3106                    // This page has begun to load, so show the title bar
3107                    showFakeTitleBar();
3108                }
3109            }
3110        }
3111
3112        @Override
3113        public void onReceivedTitle(WebView view, String title) {
3114            String url = view.getUrl();
3115
3116            // here, if url is null, we want to reset the title
3117            setUrlTitle(url, title, view);
3118
3119            if (url == null ||
3120                url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3121                return;
3122            }
3123            // See if we can find the current url in our history database and
3124            // add the new title to it.
3125            if (url.startsWith("http://www.")) {
3126                url = url.substring(11);
3127            } else if (url.startsWith("http://")) {
3128                url = url.substring(4);
3129            }
3130            try {
3131                url = "%" + url;
3132                String [] selArgs = new String[] { url };
3133
3134                String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3135                        + Browser.BookmarkColumns.BOOKMARK + " = 0";
3136                Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3137                    Browser.HISTORY_PROJECTION, where, selArgs, null);
3138                if (c.moveToFirst()) {
3139                    // Current implementation of database only has one entry per
3140                    // url.
3141                    ContentValues map = new ContentValues();
3142                    map.put(Browser.BookmarkColumns.TITLE, title);
3143                    mResolver.update(Browser.BOOKMARKS_URI, map,
3144                            "_id = " + c.getInt(0), null);
3145                }
3146                c.close();
3147            } catch (IllegalStateException e) {
3148                Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3149            } catch (SQLiteException ex) {
3150                Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3151            }
3152        }
3153
3154        @Override
3155        public void onReceivedIcon(WebView view, Bitmap icon) {
3156            updateIcon(view, icon);
3157        }
3158
3159        @Override
3160        public void onReceivedTouchIconUrl(WebView view, String url) {
3161            final ContentResolver cr = getContentResolver();
3162            final Cursor c =
3163                    BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
3164                            view.getOriginalUrl(), view.getUrl(), true);
3165            if (c != null) {
3166                if (c.getCount() > 0) {
3167                    new DownloadTouchIcon(cr, c, view).execute(url);
3168                } else {
3169                    c.close();
3170                }
3171            }
3172        }
3173
3174        @Override
3175        public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
3176            if (mCustomView != null)
3177                return;
3178
3179            // Add the custom view to its container.
3180            mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3181            mCustomView = view;
3182            mCustomViewCallback = callback;
3183            // Save the menu state and set it to empty while the custom
3184            // view is showing.
3185            mOldMenuState = mMenuState;
3186            mMenuState = EMPTY_MENU;
3187            // Hide the content view.
3188            mContentView.setVisibility(View.GONE);
3189            // Finally show the custom view container.
3190            mCustomViewContainer.setVisibility(View.VISIBLE);
3191            mCustomViewContainer.bringToFront();
3192        }
3193
3194        @Override
3195        public void onHideCustomView() {
3196            if (mCustomView == null)
3197                return;
3198
3199            // Hide the custom view.
3200            mCustomView.setVisibility(View.GONE);
3201            // Remove the custom view from its container.
3202            mCustomViewContainer.removeView(mCustomView);
3203            mCustomView = null;
3204            // Reset the old menu state.
3205            mMenuState = mOldMenuState;
3206            mOldMenuState = EMPTY_MENU;
3207            mCustomViewContainer.setVisibility(View.GONE);
3208            mCustomViewCallback.onCustomViewHidden();
3209            // Show the content view.
3210            mContentView.setVisibility(View.VISIBLE);
3211        }
3212
3213        /**
3214         * The origin has exceeded its database quota.
3215         * @param url the URL that exceeded the quota
3216         * @param databaseIdentifier the identifier of the database on
3217         *     which the transaction that caused the quota overflow was run
3218         * @param currentQuota the current quota for the origin.
3219         * @param estimatedSize the estimated size of the database.
3220         * @param totalUsedQuota is the sum of all origins' quota.
3221         * @param quotaUpdater The callback to run when a decision to allow or
3222         *     deny quota has been made. Don't forget to call this!
3223         */
3224        @Override
3225        public void onExceededDatabaseQuota(String url,
3226            String databaseIdentifier, long currentQuota, long estimatedSize,
3227            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3228            mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3229                    url, databaseIdentifier, currentQuota, estimatedSize,
3230                    totalUsedQuota, quotaUpdater);
3231        }
3232
3233        /**
3234         * The Application Cache has exceeded its max size.
3235         * @param spaceNeeded is the amount of disk space that would be needed
3236         * in order for the last appcache operation to succeed.
3237         * @param totalUsedQuota is the sum of all origins' quota.
3238         * @param quotaUpdater A callback to inform the WebCore thread that a new
3239         * app cache size is available. This callback must always be executed at
3240         * some point to ensure that the sleeping WebCore thread is woken up.
3241         */
3242        @Override
3243        public void onReachedMaxAppCacheSize(long spaceNeeded,
3244                long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3245            mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3246                    spaceNeeded, totalUsedQuota, quotaUpdater);
3247        }
3248
3249        /**
3250         * Instructs the browser to show a prompt to ask the user to set the
3251         * Geolocation permission state for the specified origin.
3252         * @param origin The origin for which Geolocation permissions are
3253         *     requested.
3254         * @param callback The callback to call once the user has set the
3255         *     Geolocation permission state.
3256         */
3257        @Override
3258        public void onGeolocationPermissionsShowPrompt(String origin,
3259                GeolocationPermissions.Callback callback) {
3260            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3261                    origin, callback);
3262        }
3263
3264        /**
3265         * Instructs the browser to hide the Geolocation permissions prompt.
3266         */
3267        @Override
3268        public void onGeolocationPermissionsHidePrompt() {
3269            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3270        }
3271
3272        /* Adds a JavaScript error message to the system log.
3273         * @param message The error message to report.
3274         * @param lineNumber The line number of the error.
3275         * @param sourceID The name of the source file that caused the error.
3276         */
3277        @Override
3278        public void addMessageToConsole(String message, int lineNumber, String sourceID) {
3279            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3280            errorConsole.addErrorMessage(message, sourceID, lineNumber);
3281                if (mShouldShowErrorConsole &&
3282                        errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3283                    errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3284                }
3285            Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
3286        }
3287    };
3288
3289    /**
3290     * Notify the host application a download should be done, or that
3291     * the data should be streamed if a streaming viewer is available.
3292     * @param url The full url to the content that should be downloaded
3293     * @param contentDisposition Content-disposition http header, if
3294     *                           present.
3295     * @param mimetype The mimetype of the content reported by the server
3296     * @param contentLength The file size reported by the server
3297     */
3298    public void onDownloadStart(String url, String userAgent,
3299            String contentDisposition, String mimetype, long contentLength) {
3300        // if we're dealing wih A/V content that's not explicitly marked
3301        //     for download, check if it's streamable.
3302        if (contentDisposition == null
3303                || !contentDisposition.regionMatches(
3304                        true, 0, "attachment", 0, 10)) {
3305            // query the package manager to see if there's a registered handler
3306            //     that matches.
3307            Intent intent = new Intent(Intent.ACTION_VIEW);
3308            intent.setDataAndType(Uri.parse(url), mimetype);
3309            ResolveInfo info = getPackageManager().resolveActivity(intent,
3310                    PackageManager.MATCH_DEFAULT_ONLY);
3311            if (info != null) {
3312                ComponentName myName = getComponentName();
3313                // If we resolved to ourselves, we don't want to attempt to
3314                // load the url only to try and download it again.
3315                if (!myName.getPackageName().equals(
3316                        info.activityInfo.packageName)
3317                        || !myName.getClassName().equals(
3318                                info.activityInfo.name)) {
3319                    // someone (other than us) knows how to handle this mime
3320                    // type with this scheme, don't download.
3321                    try {
3322                        startActivity(intent);
3323                        return;
3324                    } catch (ActivityNotFoundException ex) {
3325                        if (LOGD_ENABLED) {
3326                            Log.d(LOGTAG, "activity not found for " + mimetype
3327                                    + " over " + Uri.parse(url).getScheme(),
3328                                    ex);
3329                        }
3330                        // Best behavior is to fall back to a download in this
3331                        // case
3332                    }
3333                }
3334            }
3335        }
3336        onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3337    }
3338
3339    /**
3340     * Notify the host application a download should be done, even if there
3341     * is a streaming viewer available for thise type.
3342     * @param url The full url to the content that should be downloaded
3343     * @param contentDisposition Content-disposition http header, if
3344     *                           present.
3345     * @param mimetype The mimetype of the content reported by the server
3346     * @param contentLength The file size reported by the server
3347     */
3348    /*package */ void onDownloadStartNoStream(String url, String userAgent,
3349            String contentDisposition, String mimetype, long contentLength) {
3350
3351        String filename = URLUtil.guessFileName(url,
3352                contentDisposition, mimetype);
3353
3354        // Check to see if we have an SDCard
3355        String status = Environment.getExternalStorageState();
3356        if (!status.equals(Environment.MEDIA_MOUNTED)) {
3357            int title;
3358            String msg;
3359
3360            // Check to see if the SDCard is busy, same as the music app
3361            if (status.equals(Environment.MEDIA_SHARED)) {
3362                msg = getString(R.string.download_sdcard_busy_dlg_msg);
3363                title = R.string.download_sdcard_busy_dlg_title;
3364            } else {
3365                msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3366                title = R.string.download_no_sdcard_dlg_title;
3367            }
3368
3369            new AlertDialog.Builder(this)
3370                .setTitle(title)
3371                .setIcon(android.R.drawable.ic_dialog_alert)
3372                .setMessage(msg)
3373                .setPositiveButton(R.string.ok, null)
3374                .show();
3375            return;
3376        }
3377
3378        // java.net.URI is a lot stricter than KURL so we have to undo
3379        // KURL's percent-encoding and redo the encoding using java.net.URI.
3380        URI uri = null;
3381        try {
3382            // Undo the percent-encoding that KURL may have done.
3383            String newUrl = new String(URLUtil.decode(url.getBytes()));
3384            // Parse the url into pieces
3385            WebAddress w = new WebAddress(newUrl);
3386            String frag = null;
3387            String query = null;
3388            String path = w.mPath;
3389            // Break the path into path, query, and fragment
3390            if (path.length() > 0) {
3391                // Strip the fragment
3392                int idx = path.lastIndexOf('#');
3393                if (idx != -1) {
3394                    frag = path.substring(idx + 1);
3395                    path = path.substring(0, idx);
3396                }
3397                idx = path.lastIndexOf('?');
3398                if (idx != -1) {
3399                    query = path.substring(idx + 1);
3400                    path = path.substring(0, idx);
3401                }
3402            }
3403            uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3404                    query, frag);
3405        } catch (Exception e) {
3406            Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3407            return;
3408        }
3409
3410        // XXX: Have to use the old url since the cookies were stored using the
3411        // old percent-encoded url.
3412        String cookies = CookieManager.getInstance().getCookie(url);
3413
3414        ContentValues values = new ContentValues();
3415        values.put(Downloads.COLUMN_URI, uri.toString());
3416        values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3417        values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3418        values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
3419                getPackageName());
3420        values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
3421                BrowserDownloadPage.class.getCanonicalName());
3422        values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3423        values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3424        values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3425        values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
3426        if (contentLength > 0) {
3427            values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
3428        }
3429        if (mimetype == null) {
3430            // We must have long pressed on a link or image to download it. We
3431            // are not sure of the mimetype in this case, so do a head request
3432            new FetchUrlMimeType(this).execute(values);
3433        } else {
3434            final Uri contentUri =
3435                    getContentResolver().insert(Downloads.CONTENT_URI, values);
3436            viewDownloads(contentUri);
3437        }
3438
3439    }
3440
3441    /**
3442     * Resets the lock icon. This method is called when we start a new load and
3443     * know the url to be loaded.
3444     */
3445    private void resetLockIcon(String url) {
3446        // Save the lock-icon state (we revert to it if the load gets cancelled)
3447        saveLockIcon();
3448
3449        mLockIconType = LOCK_ICON_UNSECURE;
3450        if (URLUtil.isHttpsUrl(url)) {
3451            mLockIconType = LOCK_ICON_SECURE;
3452            if (LOGV_ENABLED) {
3453                Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3454                      " reset lock icon to " + mLockIconType);
3455            }
3456        }
3457
3458        updateLockIconImage(LOCK_ICON_UNSECURE);
3459    }
3460
3461    /**
3462     * Resets the lock icon.  This method is called when the icon needs to be
3463     * reset but we do not know whether we are loading a secure or not secure
3464     * page.
3465     */
3466    private void resetLockIcon() {
3467        // Save the lock-icon state (we revert to it if the load gets cancelled)
3468        saveLockIcon();
3469
3470        mLockIconType = LOCK_ICON_UNSECURE;
3471
3472        if (LOGV_ENABLED) {
3473          Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3474                " reset lock icon to " + mLockIconType);
3475        }
3476
3477        updateLockIconImage(LOCK_ICON_UNSECURE);
3478    }
3479
3480    /**
3481     * Update the lock icon to correspond to our latest state.
3482     */
3483    /* package */ void updateLockIconToLatest() {
3484        updateLockIconImage(mLockIconType);
3485    }
3486
3487    /**
3488     * Updates the lock-icon image in the title-bar.
3489     */
3490    private void updateLockIconImage(int lockIconType) {
3491        Drawable d = null;
3492        if (lockIconType == LOCK_ICON_SECURE) {
3493            d = mSecLockIcon;
3494        } else if (lockIconType == LOCK_ICON_MIXED) {
3495            d = mMixLockIcon;
3496        }
3497        mTitleBar.setLock(d, getTopWindow());
3498        if (mFakeTitleBar != null) {
3499            mFakeTitleBar.setLock(d);
3500        }
3501    }
3502
3503    /**
3504     * Displays a page-info dialog.
3505     * @param tab The tab to show info about
3506     * @param fromShowSSLCertificateOnError The flag that indicates whether
3507     * this dialog was opened from the SSL-certificate-on-error dialog or
3508     * not. This is important, since we need to know whether to return to
3509     * the parent dialog or simply dismiss.
3510     */
3511    private void showPageInfo(final TabControl.Tab tab,
3512                              final boolean fromShowSSLCertificateOnError) {
3513        final LayoutInflater factory = LayoutInflater
3514                .from(this);
3515
3516        final View pageInfoView = factory.inflate(R.layout.page_info, null);
3517
3518        final WebView view = tab.getWebView();
3519
3520        String url = null;
3521        String title = null;
3522
3523        if (view == null) {
3524            url = tab.getUrl();
3525            title = tab.getTitle();
3526        } else if (view == mTabControl.getCurrentWebView()) {
3527             // Use the cached title and url if this is the current WebView
3528            url = mUrl;
3529            title = mTitle;
3530        } else {
3531            url = view.getUrl();
3532            title = view.getTitle();
3533        }
3534
3535        if (url == null) {
3536            url = "";
3537        }
3538        if (title == null) {
3539            title = "";
3540        }
3541
3542        ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3543        ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3544
3545        mPageInfoView = tab;
3546        mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3547
3548        AlertDialog.Builder alertDialogBuilder =
3549            new AlertDialog.Builder(this)
3550            .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3551            .setView(pageInfoView)
3552            .setPositiveButton(
3553                R.string.ok,
3554                new DialogInterface.OnClickListener() {
3555                    public void onClick(DialogInterface dialog,
3556                                        int whichButton) {
3557                        mPageInfoDialog = null;
3558                        mPageInfoView = null;
3559                        mPageInfoFromShowSSLCertificateOnError = null;
3560
3561                        // if we came here from the SSL error dialog
3562                        if (fromShowSSLCertificateOnError) {
3563                            // go back to the SSL error dialog
3564                            showSSLCertificateOnError(
3565                                mSSLCertificateOnErrorView,
3566                                mSSLCertificateOnErrorHandler,
3567                                mSSLCertificateOnErrorError);
3568                        }
3569                    }
3570                })
3571            .setOnCancelListener(
3572                new DialogInterface.OnCancelListener() {
3573                    public void onCancel(DialogInterface dialog) {
3574                        mPageInfoDialog = null;
3575                        mPageInfoView = null;
3576                        mPageInfoFromShowSSLCertificateOnError = null;
3577
3578                        // if we came here from the SSL error dialog
3579                        if (fromShowSSLCertificateOnError) {
3580                            // go back to the SSL error dialog
3581                            showSSLCertificateOnError(
3582                                mSSLCertificateOnErrorView,
3583                                mSSLCertificateOnErrorHandler,
3584                                mSSLCertificateOnErrorError);
3585                        }
3586                    }
3587                });
3588
3589        // if we have a main top-level page SSL certificate set or a certificate
3590        // error
3591        if (fromShowSSLCertificateOnError ||
3592                (view != null && view.getCertificate() != null)) {
3593            // add a 'View Certificate' button
3594            alertDialogBuilder.setNeutralButton(
3595                R.string.view_certificate,
3596                new DialogInterface.OnClickListener() {
3597                    public void onClick(DialogInterface dialog,
3598                                        int whichButton) {
3599                        mPageInfoDialog = null;
3600                        mPageInfoView = null;
3601                        mPageInfoFromShowSSLCertificateOnError = null;
3602
3603                        // if we came here from the SSL error dialog
3604                        if (fromShowSSLCertificateOnError) {
3605                            // go back to the SSL error dialog
3606                            showSSLCertificateOnError(
3607                                mSSLCertificateOnErrorView,
3608                                mSSLCertificateOnErrorHandler,
3609                                mSSLCertificateOnErrorError);
3610                        } else {
3611                            // otherwise, display the top-most certificate from
3612                            // the chain
3613                            if (view.getCertificate() != null) {
3614                                showSSLCertificate(tab);
3615                            }
3616                        }
3617                    }
3618                });
3619        }
3620
3621        mPageInfoDialog = alertDialogBuilder.show();
3622    }
3623
3624       /**
3625     * Displays the main top-level page SSL certificate dialog
3626     * (accessible from the Page-Info dialog).
3627     * @param tab The tab to show certificate for.
3628     */
3629    private void showSSLCertificate(final TabControl.Tab tab) {
3630        final View certificateView =
3631                inflateCertificateView(tab.getWebView().getCertificate());
3632        if (certificateView == null) {
3633            return;
3634        }
3635
3636        LayoutInflater factory = LayoutInflater.from(this);
3637
3638        final LinearLayout placeholder =
3639                (LinearLayout)certificateView.findViewById(R.id.placeholder);
3640
3641        LinearLayout ll = (LinearLayout) factory.inflate(
3642            R.layout.ssl_success, placeholder);
3643        ((TextView)ll.findViewById(R.id.success))
3644            .setText(R.string.ssl_certificate_is_valid);
3645
3646        mSSLCertificateView = tab;
3647        mSSLCertificateDialog =
3648            new AlertDialog.Builder(this)
3649                .setTitle(R.string.ssl_certificate).setIcon(
3650                    R.drawable.ic_dialog_browser_certificate_secure)
3651                .setView(certificateView)
3652                .setPositiveButton(R.string.ok,
3653                        new DialogInterface.OnClickListener() {
3654                            public void onClick(DialogInterface dialog,
3655                                    int whichButton) {
3656                                mSSLCertificateDialog = null;
3657                                mSSLCertificateView = null;
3658
3659                                showPageInfo(tab, false);
3660                            }
3661                        })
3662                .setOnCancelListener(
3663                        new DialogInterface.OnCancelListener() {
3664                            public void onCancel(DialogInterface dialog) {
3665                                mSSLCertificateDialog = null;
3666                                mSSLCertificateView = null;
3667
3668                                showPageInfo(tab, false);
3669                            }
3670                        })
3671                .show();
3672    }
3673
3674    /**
3675     * Displays the SSL error certificate dialog.
3676     * @param view The target web-view.
3677     * @param handler The SSL error handler responsible for cancelling the
3678     * connection that resulted in an SSL error or proceeding per user request.
3679     * @param error The SSL error object.
3680     */
3681    private void showSSLCertificateOnError(
3682        final WebView view, final SslErrorHandler handler, final SslError error) {
3683
3684        final View certificateView =
3685            inflateCertificateView(error.getCertificate());
3686        if (certificateView == null) {
3687            return;
3688        }
3689
3690        LayoutInflater factory = LayoutInflater.from(this);
3691
3692        final LinearLayout placeholder =
3693                (LinearLayout)certificateView.findViewById(R.id.placeholder);
3694
3695        if (error.hasError(SslError.SSL_UNTRUSTED)) {
3696            LinearLayout ll = (LinearLayout)factory
3697                .inflate(R.layout.ssl_warning, placeholder);
3698            ((TextView)ll.findViewById(R.id.warning))
3699                .setText(R.string.ssl_untrusted);
3700        }
3701
3702        if (error.hasError(SslError.SSL_IDMISMATCH)) {
3703            LinearLayout ll = (LinearLayout)factory
3704                .inflate(R.layout.ssl_warning, placeholder);
3705            ((TextView)ll.findViewById(R.id.warning))
3706                .setText(R.string.ssl_mismatch);
3707        }
3708
3709        if (error.hasError(SslError.SSL_EXPIRED)) {
3710            LinearLayout ll = (LinearLayout)factory
3711                .inflate(R.layout.ssl_warning, placeholder);
3712            ((TextView)ll.findViewById(R.id.warning))
3713                .setText(R.string.ssl_expired);
3714        }
3715
3716        if (error.hasError(SslError.SSL_NOTYETVALID)) {
3717            LinearLayout ll = (LinearLayout)factory
3718                .inflate(R.layout.ssl_warning, placeholder);
3719            ((TextView)ll.findViewById(R.id.warning))
3720                .setText(R.string.ssl_not_yet_valid);
3721        }
3722
3723        mSSLCertificateOnErrorHandler = handler;
3724        mSSLCertificateOnErrorView = view;
3725        mSSLCertificateOnErrorError = error;
3726        mSSLCertificateOnErrorDialog =
3727            new AlertDialog.Builder(this)
3728                .setTitle(R.string.ssl_certificate).setIcon(
3729                    R.drawable.ic_dialog_browser_certificate_partially_secure)
3730                .setView(certificateView)
3731                .setPositiveButton(R.string.ok,
3732                        new DialogInterface.OnClickListener() {
3733                            public void onClick(DialogInterface dialog,
3734                                    int whichButton) {
3735                                mSSLCertificateOnErrorDialog = null;
3736                                mSSLCertificateOnErrorView = null;
3737                                mSSLCertificateOnErrorHandler = null;
3738                                mSSLCertificateOnErrorError = null;
3739
3740                                mWebViewClient.onReceivedSslError(
3741                                    view, handler, error);
3742                            }
3743                        })
3744                 .setNeutralButton(R.string.page_info_view,
3745                        new DialogInterface.OnClickListener() {
3746                            public void onClick(DialogInterface dialog,
3747                                    int whichButton) {
3748                                mSSLCertificateOnErrorDialog = null;
3749
3750                                // do not clear the dialog state: we will
3751                                // need to show the dialog again once the
3752                                // user is done exploring the page-info details
3753
3754                                showPageInfo(mTabControl.getTabFromView(view),
3755                                        true);
3756                            }
3757                        })
3758                .setOnCancelListener(
3759                        new DialogInterface.OnCancelListener() {
3760                            public void onCancel(DialogInterface dialog) {
3761                                mSSLCertificateOnErrorDialog = null;
3762                                mSSLCertificateOnErrorView = null;
3763                                mSSLCertificateOnErrorHandler = null;
3764                                mSSLCertificateOnErrorError = null;
3765
3766                                mWebViewClient.onReceivedSslError(
3767                                    view, handler, error);
3768                            }
3769                        })
3770                .show();
3771    }
3772
3773    /**
3774     * Inflates the SSL certificate view (helper method).
3775     * @param certificate The SSL certificate.
3776     * @return The resultant certificate view with issued-to, issued-by,
3777     * issued-on, expires-on, and possibly other fields set.
3778     * If the input certificate is null, returns null.
3779     */
3780    private View inflateCertificateView(SslCertificate certificate) {
3781        if (certificate == null) {
3782            return null;
3783        }
3784
3785        LayoutInflater factory = LayoutInflater.from(this);
3786
3787        View certificateView = factory.inflate(
3788            R.layout.ssl_certificate, null);
3789
3790        // issued to:
3791        SslCertificate.DName issuedTo = certificate.getIssuedTo();
3792        if (issuedTo != null) {
3793            ((TextView) certificateView.findViewById(R.id.to_common))
3794                .setText(issuedTo.getCName());
3795            ((TextView) certificateView.findViewById(R.id.to_org))
3796                .setText(issuedTo.getOName());
3797            ((TextView) certificateView.findViewById(R.id.to_org_unit))
3798                .setText(issuedTo.getUName());
3799        }
3800
3801        // issued by:
3802        SslCertificate.DName issuedBy = certificate.getIssuedBy();
3803        if (issuedBy != null) {
3804            ((TextView) certificateView.findViewById(R.id.by_common))
3805                .setText(issuedBy.getCName());
3806            ((TextView) certificateView.findViewById(R.id.by_org))
3807                .setText(issuedBy.getOName());
3808            ((TextView) certificateView.findViewById(R.id.by_org_unit))
3809                .setText(issuedBy.getUName());
3810        }
3811
3812        // issued on:
3813        String issuedOn = reformatCertificateDate(
3814            certificate.getValidNotBefore());
3815        ((TextView) certificateView.findViewById(R.id.issued_on))
3816            .setText(issuedOn);
3817
3818        // expires on:
3819        String expiresOn = reformatCertificateDate(
3820            certificate.getValidNotAfter());
3821        ((TextView) certificateView.findViewById(R.id.expires_on))
3822            .setText(expiresOn);
3823
3824        return certificateView;
3825    }
3826
3827    /**
3828     * Re-formats the certificate date (Date.toString()) string to
3829     * a properly localized date string.
3830     * @return Properly localized version of the certificate date string and
3831     * the original certificate date string if fails to localize.
3832     * If the original string is null, returns an empty string "".
3833     */
3834    private String reformatCertificateDate(String certificateDate) {
3835      String reformattedDate = null;
3836
3837      if (certificateDate != null) {
3838          Date date = null;
3839          try {
3840              date = java.text.DateFormat.getInstance().parse(certificateDate);
3841          } catch (ParseException e) {
3842              date = null;
3843          }
3844
3845          if (date != null) {
3846              reformattedDate =
3847                  DateFormat.getDateFormat(this).format(date);
3848          }
3849      }
3850
3851      return reformattedDate != null ? reformattedDate :
3852          (certificateDate != null ? certificateDate : "");
3853    }
3854
3855    /**
3856     * Displays an http-authentication dialog.
3857     */
3858    private void showHttpAuthentication(final HttpAuthHandler handler,
3859            final String host, final String realm, final String title,
3860            final String name, final String password, int focusId) {
3861        LayoutInflater factory = LayoutInflater.from(this);
3862        final View v = factory
3863                .inflate(R.layout.http_authentication, null);
3864        if (name != null) {
3865            ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3866        }
3867        if (password != null) {
3868            ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3869        }
3870
3871        String titleText = title;
3872        if (titleText == null) {
3873            titleText = getText(R.string.sign_in_to).toString().replace(
3874                    "%s1", host).replace("%s2", realm);
3875        }
3876
3877        mHttpAuthHandler = handler;
3878        AlertDialog dialog = new AlertDialog.Builder(this)
3879                .setTitle(titleText)
3880                .setIcon(android.R.drawable.ic_dialog_alert)
3881                .setView(v)
3882                .setPositiveButton(R.string.action,
3883                        new DialogInterface.OnClickListener() {
3884                             public void onClick(DialogInterface dialog,
3885                                     int whichButton) {
3886                                String nm = ((EditText) v
3887                                        .findViewById(R.id.username_edit))
3888                                        .getText().toString();
3889                                String pw = ((EditText) v
3890                                        .findViewById(R.id.password_edit))
3891                                        .getText().toString();
3892                                BrowserActivity.this.setHttpAuthUsernamePassword
3893                                        (host, realm, nm, pw);
3894                                handler.proceed(nm, pw);
3895                                mHttpAuthenticationDialog = null;
3896                                mHttpAuthHandler = null;
3897                            }})
3898                .setNegativeButton(R.string.cancel,
3899                        new DialogInterface.OnClickListener() {
3900                            public void onClick(DialogInterface dialog,
3901                                    int whichButton) {
3902                                handler.cancel();
3903                                BrowserActivity.this.resetTitleAndRevertLockIcon();
3904                                mHttpAuthenticationDialog = null;
3905                                mHttpAuthHandler = null;
3906                            }})
3907                .setOnCancelListener(new DialogInterface.OnCancelListener() {
3908                        public void onCancel(DialogInterface dialog) {
3909                            handler.cancel();
3910                            BrowserActivity.this.resetTitleAndRevertLockIcon();
3911                            mHttpAuthenticationDialog = null;
3912                            mHttpAuthHandler = null;
3913                        }})
3914                .create();
3915        // Make the IME appear when the dialog is displayed if applicable.
3916        dialog.getWindow().setSoftInputMode(
3917                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3918        dialog.show();
3919        if (focusId != 0) {
3920            dialog.findViewById(focusId).requestFocus();
3921        } else {
3922            v.findViewById(R.id.username_edit).requestFocus();
3923        }
3924        mHttpAuthenticationDialog = dialog;
3925    }
3926
3927    public int getProgress() {
3928        WebView w = mTabControl.getCurrentWebView();
3929        if (w != null) {
3930            return w.getProgress();
3931        } else {
3932            return 100;
3933        }
3934    }
3935
3936    /**
3937     * Set HTTP authentication password.
3938     *
3939     * @param host The host for the password
3940     * @param realm The realm for the password
3941     * @param username The username for the password. If it is null, it means
3942     *            password can't be saved.
3943     * @param password The password
3944     */
3945    public void setHttpAuthUsernamePassword(String host, String realm,
3946                                            String username,
3947                                            String password) {
3948        WebView w = mTabControl.getCurrentWebView();
3949        if (w != null) {
3950            w.setHttpAuthUsernamePassword(host, realm, username, password);
3951        }
3952    }
3953
3954    /**
3955     * connectivity manager says net has come or gone... inform the user
3956     * @param up true if net has come up, false if net has gone down
3957     */
3958    public void onNetworkToggle(boolean up) {
3959        if (up == mIsNetworkUp) {
3960            return;
3961        } else if (up) {
3962            mIsNetworkUp = true;
3963            if (mAlertDialog != null) {
3964                mAlertDialog.cancel();
3965                mAlertDialog = null;
3966            }
3967        } else {
3968            mIsNetworkUp = false;
3969            if (mInLoad && mAlertDialog == null) {
3970                mAlertDialog = new AlertDialog.Builder(this)
3971                        .setTitle(R.string.loadSuspendedTitle)
3972                        .setMessage(R.string.loadSuspended)
3973                        .setPositiveButton(R.string.ok, null)
3974                        .show();
3975            }
3976        }
3977        WebView w = mTabControl.getCurrentWebView();
3978        if (w != null) {
3979            w.setNetworkAvailable(up);
3980        }
3981    }
3982
3983    @Override
3984    protected void onActivityResult(int requestCode, int resultCode,
3985                                    Intent intent) {
3986        switch (requestCode) {
3987            case COMBO_PAGE:
3988                if (resultCode == RESULT_OK && intent != null) {
3989                    String data = intent.getAction();
3990                    Bundle extras = intent.getExtras();
3991                    if (extras != null && extras.getBoolean("new_window", false)) {
3992                        final TabControl.Tab newTab = openTab(data);
3993                        if (mSettings.openInBackground() &&
3994                                newTab != null) {
3995                            mTabControl.populatePickerData(newTab);
3996                            mTabControl.setCurrentTab(newTab);
3997                            int newIndex = mTabControl.getCurrentIndex();
3998                            mTitleBar.setCurrentTab(newIndex);
3999                        }
4000                    } else {
4001                        final TabControl.Tab currentTab =
4002                                mTabControl.getCurrentTab();
4003                        dismissSubWindow(currentTab);
4004                        if (data != null && data.length() != 0) {
4005                            getTopWindow().loadUrl(data);
4006                        }
4007                    }
4008                }
4009                break;
4010            default:
4011                break;
4012        }
4013        getTopWindow().requestFocus();
4014    }
4015
4016    /*
4017     * This method is called as a result of the user selecting the options
4018     * menu to see the download window, or when a download changes state. It
4019     * shows the download window ontop of the current window.
4020     */
4021    /* package */ void viewDownloads(Uri downloadRecord) {
4022        Intent intent = new Intent(this,
4023                BrowserDownloadPage.class);
4024        intent.setData(downloadRecord);
4025        startActivityForResult(intent, this.DOWNLOAD_PAGE);
4026
4027    }
4028
4029    /**
4030     * Open the Go page.
4031     * @param startWithHistory If true, open starting on the history tab.
4032     *                         Otherwise, start with the bookmarks tab.
4033     */
4034    /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
4035        WebView current = mTabControl.getCurrentWebView();
4036        if (current == null) {
4037            return;
4038        }
4039        Intent intent = new Intent(this,
4040                CombinedBookmarkHistoryActivity.class);
4041        String title = current.getTitle();
4042        String url = current.getUrl();
4043        // Just in case the user opens bookmarks before a page finishes loading
4044        // so the current history item, and therefore the page, is null.
4045        if (null == url) {
4046            url = mLastEnteredUrl;
4047            // This can happen.
4048            if (null == url) {
4049                url = mSettings.getHomePage();
4050            }
4051        }
4052        // In case the web page has not yet received its associated title.
4053        if (title == null) {
4054            title = url;
4055        }
4056        intent.putExtra("title", title);
4057        intent.putExtra("url", url);
4058        // Disable opening in a new window if we have maxed out the windows
4059        intent.putExtra("disable_new_window", mTabControl.getTabCount()
4060                >= TabControl.MAX_TABS);
4061        intent.putExtra("touch_icon_url", current.getTouchIconUrl());
4062        if (startWithHistory) {
4063            intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
4064                    CombinedBookmarkHistoryActivity.HISTORY_TAB);
4065        }
4066        startActivityForResult(intent, COMBO_PAGE);
4067    }
4068
4069    // Called when loading from context menu or LOAD_URL message
4070    private void loadURL(WebView view, String url) {
4071        // In case the user enters nothing.
4072        if (url != null && url.length() != 0 && view != null) {
4073            url = smartUrlFilter(url);
4074            if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
4075                view.loadUrl(url);
4076            }
4077        }
4078    }
4079
4080    private String smartUrlFilter(Uri inUri) {
4081        if (inUri != null) {
4082            return smartUrlFilter(inUri.toString());
4083        }
4084        return null;
4085    }
4086
4087
4088    // get window count
4089
4090    int getWindowCount(){
4091      if(mTabControl != null){
4092        return mTabControl.getTabCount();
4093      }
4094      return 0;
4095    }
4096
4097    protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
4098            "(?i)" + // switch on case insensitive matching
4099            "(" +    // begin group for schema
4100            "(?:http|https|file):\\/\\/" +
4101            "|(?:inline|data|about|content|javascript):" +
4102            ")" +
4103            "(.*)" );
4104
4105    /**
4106     * Attempts to determine whether user input is a URL or search
4107     * terms.  Anything with a space is passed to search.
4108     *
4109     * Converts to lowercase any mistakenly uppercased schema (i.e.,
4110     * "Http://" converts to "http://"
4111     *
4112     * @return Original or modified URL
4113     *
4114     */
4115    String smartUrlFilter(String url) {
4116
4117        String inUrl = url.trim();
4118        boolean hasSpace = inUrl.indexOf(' ') != -1;
4119
4120        Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4121        if (matcher.matches()) {
4122            // force scheme to lowercase
4123            String scheme = matcher.group(1);
4124            String lcScheme = scheme.toLowerCase();
4125            if (!lcScheme.equals(scheme)) {
4126                inUrl = lcScheme + matcher.group(2);
4127            }
4128            if (hasSpace) {
4129                inUrl = inUrl.replace(" ", "%20");
4130            }
4131            return inUrl;
4132        }
4133        if (hasSpace) {
4134            // FIXME: Is this the correct place to add to searches?
4135            // what if someone else calls this function?
4136            int shortcut = parseUrlShortcut(inUrl);
4137            if (shortcut != SHORTCUT_INVALID) {
4138                Browser.addSearchUrl(mResolver, inUrl);
4139                String query = inUrl.substring(2);
4140                switch (shortcut) {
4141                case SHORTCUT_GOOGLE_SEARCH:
4142                    return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
4143                case SHORTCUT_WIKIPEDIA_SEARCH:
4144                    return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4145                case SHORTCUT_DICTIONARY_SEARCH:
4146                    return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4147                case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
4148                    // FIXME: we need location in this case
4149                    return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
4150                }
4151            }
4152        } else {
4153            if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4154                return URLUtil.guessUrl(inUrl);
4155            }
4156        }
4157
4158        Browser.addSearchUrl(mResolver, inUrl);
4159        return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
4160    }
4161
4162    /* package */ void setShouldShowErrorConsole(boolean flag) {
4163        if (flag == mShouldShowErrorConsole) {
4164            // Nothing to do.
4165            return;
4166        }
4167
4168        mShouldShowErrorConsole = flag;
4169
4170        ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4171
4172        if (flag) {
4173            // Setting the show state of the console will cause it's the layout to be inflated.
4174            if (errorConsole.numberOfErrors() > 0) {
4175                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4176            } else {
4177                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4178            }
4179
4180            // Now we can add it to the main view.
4181            mErrorConsoleContainer.addView(errorConsole,
4182                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4183                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
4184        } else {
4185            mErrorConsoleContainer.removeView(errorConsole);
4186        }
4187
4188    }
4189
4190    private final static int LOCK_ICON_UNSECURE = 0;
4191    private final static int LOCK_ICON_SECURE   = 1;
4192    private final static int LOCK_ICON_MIXED    = 2;
4193
4194    private int mLockIconType = LOCK_ICON_UNSECURE;
4195    private int mPrevLockType = LOCK_ICON_UNSECURE;
4196
4197    private BrowserSettings mSettings;
4198    private TabControl      mTabControl;
4199    private ContentResolver mResolver;
4200    private FrameLayout     mContentView;
4201    private View            mCustomView;
4202    private FrameLayout     mCustomViewContainer;
4203    private WebChromeClient.CustomViewCallback mCustomViewCallback;
4204
4205    // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4206    // view, we should rewrite this.
4207    private int mCurrentMenuState = 0;
4208    private int mMenuState = R.id.MAIN_MENU;
4209    private int mOldMenuState = EMPTY_MENU;
4210    private static final int EMPTY_MENU = -1;
4211    private Menu mMenu;
4212
4213    private FindDialog mFindDialog;
4214    // Used to prevent chording to result in firing two shortcuts immediately
4215    // one after another.  Fixes bug 1211714.
4216    boolean mCanChord;
4217
4218    private boolean mInLoad;
4219    private boolean mIsNetworkUp;
4220
4221    private boolean mPageStarted;
4222    private boolean mActivityInPause = true;
4223
4224    private boolean mMenuIsDown;
4225
4226    private final KeyTracker mKeyTracker = new KeyTracker(this);
4227
4228    // As trackball doesn't send repeat down, we have to track it ourselves
4229    private boolean mTrackTrackball;
4230
4231    private static boolean mInTrace;
4232
4233    // Performance probe
4234    private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4235            Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4236            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4237            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4238            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4239            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4240            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4241            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4242            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG  // 7: softirq time
4243    };
4244
4245    private long mStart;
4246    private long mProcessStart;
4247    private long mUserStart;
4248    private long mSystemStart;
4249    private long mIdleStart;
4250    private long mIrqStart;
4251
4252    private long mUiStart;
4253
4254    private Drawable    mMixLockIcon;
4255    private Drawable    mSecLockIcon;
4256
4257    /* hold a ref so we can auto-cancel if necessary */
4258    private AlertDialog mAlertDialog;
4259
4260    // Wait for credentials before loading google.com
4261    private ProgressDialog mCredsDlg;
4262
4263    // The up-to-date URL and title (these can be different from those stored
4264    // in WebView, since it takes some time for the information in WebView to
4265    // get updated)
4266    private String mUrl;
4267    private String mTitle;
4268
4269    // As PageInfo has different style for landscape / portrait, we have
4270    // to re-open it when configuration changed
4271    private AlertDialog mPageInfoDialog;
4272    private TabControl.Tab mPageInfoView;
4273    // If the Page-Info dialog is launched from the SSL-certificate-on-error
4274    // dialog, we should not just dismiss it, but should get back to the
4275    // SSL-certificate-on-error dialog. This flag is used to store this state
4276    private Boolean mPageInfoFromShowSSLCertificateOnError;
4277
4278    // as SSLCertificateOnError has different style for landscape / portrait,
4279    // we have to re-open it when configuration changed
4280    private AlertDialog mSSLCertificateOnErrorDialog;
4281    private WebView mSSLCertificateOnErrorView;
4282    private SslErrorHandler mSSLCertificateOnErrorHandler;
4283    private SslError mSSLCertificateOnErrorError;
4284
4285    // as SSLCertificate has different style for landscape / portrait, we
4286    // have to re-open it when configuration changed
4287    private AlertDialog mSSLCertificateDialog;
4288    private TabControl.Tab mSSLCertificateView;
4289
4290    // as HttpAuthentication has different style for landscape / portrait, we
4291    // have to re-open it when configuration changed
4292    private AlertDialog mHttpAuthenticationDialog;
4293    private HttpAuthHandler mHttpAuthHandler;
4294
4295    /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4296                                            new FrameLayout.LayoutParams(
4297                                            ViewGroup.LayoutParams.FILL_PARENT,
4298                                            ViewGroup.LayoutParams.FILL_PARENT);
4299    /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4300                                            new FrameLayout.LayoutParams(
4301                                            ViewGroup.LayoutParams.FILL_PARENT,
4302                                            ViewGroup.LayoutParams.FILL_PARENT,
4303                                            Gravity.CENTER);
4304    // Google search
4305    final static String QuickSearch_G = "http://www.google.com/m?q=%s";
4306    // Wikipedia search
4307    final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4308    // Dictionary search
4309    final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4310    // Google Mobile Local search
4311    final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4312
4313    final static String QUERY_PLACE_HOLDER = "%s";
4314
4315    // "source" parameter for Google search through search key
4316    final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4317    // "source" parameter for Google search through goto menu
4318    final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4319    // "source" parameter for Google search through simplily type
4320    final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4321    // "source" parameter for Google search suggested by the browser
4322    final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4323    // "source" parameter for Google search from unknown source
4324    final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4325
4326    private final static String LOGTAG = "browser";
4327
4328    private String mLastEnteredUrl;
4329
4330    private PowerManager.WakeLock mWakeLock;
4331    private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4332
4333    private Toast mStopToast;
4334
4335    private TitleBarSet mTitleBar;
4336
4337    private LinearLayout mErrorConsoleContainer = null;
4338    private boolean mShouldShowErrorConsole = false;
4339
4340    // As the ids are dynamically created, we can't guarantee that they will
4341    // be in sequence, so this static array maps ids to a window number.
4342    final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4343    { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4344      R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4345      R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4346
4347    // monitor platform changes
4348    private IntentFilter mNetworkStateChangedFilter;
4349    private BroadcastReceiver mNetworkStateIntentReceiver;
4350
4351    private BroadcastReceiver mPackageInstallationReceiver;
4352
4353    // activity requestCode
4354    final static int COMBO_PAGE                 = 1;
4355    final static int DOWNLOAD_PAGE              = 2;
4356    final static int PREFERENCES_PAGE           = 3;
4357
4358    /**
4359     * A UrlData class to abstract how the content will be set to WebView.
4360     * This base class uses loadUrl to show the content.
4361     */
4362    private static class UrlData {
4363        String mUrl;
4364        byte[] mPostData;
4365
4366        UrlData(String url) {
4367            this.mUrl = url;
4368        }
4369
4370        void setPostData(byte[] postData) {
4371            mPostData = postData;
4372        }
4373
4374        boolean isEmpty() {
4375            return mUrl == null || mUrl.length() == 0;
4376        }
4377
4378        public void loadIn(WebView webView) {
4379            if (mPostData != null) {
4380                webView.postUrl(mUrl, mPostData);
4381            } else {
4382                webView.loadUrl(mUrl);
4383            }
4384        }
4385    };
4386
4387    /**
4388     * A subclass of UrlData class that can display inlined content using
4389     * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4390     */
4391    private static class InlinedUrlData extends UrlData {
4392        InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4393            super(failUrl);
4394            mInlined = inlined;
4395            mMimeType = mimeType;
4396            mEncoding = encoding;
4397        }
4398        String mMimeType;
4399        String mInlined;
4400        String mEncoding;
4401        @Override
4402        boolean isEmpty() {
4403            return mInlined == null || mInlined.length() == 0 || super.isEmpty();
4404        }
4405
4406        @Override
4407        public void loadIn(WebView webView) {
4408            webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4409        }
4410    }
4411
4412    /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
4413}
4414