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