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