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