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