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