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