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