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