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