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