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