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