BrowserActivity.java revision c928c30a51eee84b40cb7045842c288c1cfafff0
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
30import android.content.ContentUris;
31import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
38import android.content.pm.PackageInfo;
39import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.DrawFilter;
50import android.graphics.Paint;
51import android.graphics.PaintFlagsDrawFilter;
52import android.graphics.Picture;
53import android.graphics.PixelFormat;
54import android.graphics.Rect;
55import android.graphics.drawable.Drawable;
56import android.hardware.SensorListener;
57import android.hardware.SensorManager;
58import android.net.ConnectivityManager;
59import android.net.NetworkInfo;
60import android.net.Uri;
61import android.net.WebAddress;
62import android.net.http.EventHandler;
63import android.net.http.SslCertificate;
64import android.net.http.SslError;
65import android.os.AsyncTask;
66import android.os.Bundle;
67import android.os.Debug;
68import android.os.Environment;
69import android.os.Handler;
70import android.os.IBinder;
71import android.os.Message;
72import android.os.PowerManager;
73import android.os.Process;
74import android.os.RemoteException;
75import android.os.ServiceManager;
76import android.os.SystemClock;
77import android.provider.Browser;
78import android.provider.Contacts;
79import android.provider.Downloads;
80import android.provider.MediaStore;
81import android.provider.Contacts.Intents.Insert;
82import android.text.IClipboard;
83import android.text.TextUtils;
84import android.text.format.DateFormat;
85import android.text.util.Regex;
86import android.util.Log;
87import android.view.ContextMenu;
88import android.view.Gravity;
89import android.view.KeyEvent;
90import android.view.LayoutInflater;
91import android.view.Menu;
92import android.view.MenuInflater;
93import android.view.MenuItem;
94import android.view.View;
95import android.view.ViewGroup;
96import android.view.Window;
97import android.view.WindowManager;
98import android.view.ContextMenu.ContextMenuInfo;
99import android.view.MenuItem.OnMenuItemClickListener;
100import android.view.animation.AlphaAnimation;
101import android.view.animation.Animation;
102import android.view.animation.AnimationSet;
103import android.view.animation.DecelerateInterpolator;
104import android.view.animation.ScaleAnimation;
105import android.view.animation.TranslateAnimation;
106import android.webkit.CookieManager;
107import android.webkit.CookieSyncManager;
108import android.webkit.DownloadListener;
109import android.webkit.GeolocationPermissions;
110import android.webkit.HttpAuthHandler;
111import android.webkit.PluginManager;
112import android.webkit.SslErrorHandler;
113import android.webkit.URLUtil;
114import android.webkit.WebChromeClient;
115import android.webkit.WebChromeClient.CustomViewCallback;
116import android.webkit.WebHistoryItem;
117import android.webkit.WebIconDatabase;
118import android.webkit.WebStorage;
119import android.webkit.WebView;
120import android.webkit.WebViewClient;
121import android.widget.EditText;
122import android.widget.FrameLayout;
123import android.widget.LinearLayout;
124import android.widget.TextView;
125import android.widget.Toast;
126
127import java.io.BufferedOutputStream;
128import java.io.ByteArrayOutputStream;
129import java.io.File;
130import java.io.FileInputStream;
131import java.io.FileOutputStream;
132import java.io.IOException;
133import java.io.InputStream;
134import java.net.MalformedURLException;
135import java.net.URI;
136import java.net.URISyntaxException;
137import java.net.URL;
138import java.net.URLEncoder;
139import java.text.ParseException;
140import java.util.Date;
141import java.util.Enumeration;
142import java.util.HashMap;
143import java.util.LinkedList;
144import java.util.Vector;
145import java.util.regex.Matcher;
146import java.util.regex.Pattern;
147import java.util.zip.ZipEntry;
148import java.util.zip.ZipFile;
149
150public class BrowserActivity extends Activity
151    implements KeyTracker.OnKeyTracker,
152        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    public KeyTracker.State onKeyTracker(int keyCode,
2133                                         KeyEvent event,
2134                                         KeyTracker.Stage stage,
2135                                         int duration) {
2136        // if onKeyTracker() is called after activity onStop()
2137        // because of accumulated key events,
2138        // we should ignore it as browser is not active any more.
2139        WebView topWindow = getTopWindow();
2140        if (topWindow == null && mCustomView == null)
2141            return KeyTracker.State.NOT_TRACKING;
2142
2143        if (keyCode == KeyEvent.KEYCODE_BACK) {
2144            // Check if a custom view is currently showing and, if it is, hide it.
2145            if (mCustomView != null) {
2146                mWebChromeClient.onHideCustomView();
2147                return KeyTracker.State.DONE_TRACKING;
2148            }
2149            if (stage == KeyTracker.Stage.LONG_REPEAT) {
2150                bookmarksOrHistoryPicker(true);
2151                return KeyTracker.State.DONE_TRACKING;
2152            } else if (stage == KeyTracker.Stage.UP) {
2153                // FIXME: Currently, we do not have a notion of the
2154                // history picker for the subwindow, but maybe we
2155                // should?
2156                WebView subwindow = mTabControl.getCurrentSubWindow();
2157                if (subwindow != null) {
2158                    if (subwindow.canGoBack()) {
2159                        subwindow.goBack();
2160                    } else {
2161                        dismissSubWindow(mTabControl.getCurrentTab());
2162                    }
2163                } else {
2164                    goBackOnePageOrQuit();
2165                }
2166                return KeyTracker.State.DONE_TRACKING;
2167            }
2168            return KeyTracker.State.KEEP_TRACKING;
2169        }
2170        return KeyTracker.State.NOT_TRACKING;
2171    }
2172
2173    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2174        if (keyCode == KeyEvent.KEYCODE_MENU) {
2175            mMenuIsDown = true;
2176        } else if (mMenuIsDown) {
2177            // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2178            // still down, we don't want to trigger the search. Pretend to
2179            // consume the key and do nothing.
2180            return true;
2181        }
2182        boolean handled =  mKeyTracker.doKeyDown(keyCode, event);
2183        if (!handled) {
2184            switch (keyCode) {
2185                case KeyEvent.KEYCODE_SPACE:
2186                    if (event.isShiftPressed()) {
2187                        getTopWindow().pageUp(false);
2188                    } else {
2189                        getTopWindow().pageDown(false);
2190                    }
2191                    handled = true;
2192                    break;
2193
2194                default:
2195                    break;
2196            }
2197        }
2198        return handled || super.onKeyDown(keyCode, event);
2199    }
2200
2201    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2202        if (keyCode == KeyEvent.KEYCODE_MENU) {
2203            mMenuIsDown = false;
2204        }
2205        return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2206    }
2207
2208    /* package */ void stopLoading() {
2209        resetTitleAndRevertLockIcon();
2210        WebView w = getTopWindow();
2211        w.stopLoading();
2212        mWebViewClient.onPageFinished(w, w.getUrl());
2213
2214        cancelStopToast();
2215        mStopToast = Toast
2216                .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2217        mStopToast.show();
2218    }
2219
2220    private void cancelStopToast() {
2221        if (mStopToast != null) {
2222            mStopToast.cancel();
2223            mStopToast = null;
2224        }
2225    }
2226
2227    // called by a non-UI thread to post the message
2228    public void postMessage(int what, int arg1, int arg2, Object obj) {
2229        mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2230    }
2231
2232    // public message ids
2233    public final static int LOAD_URL                = 1001;
2234    public final static int STOP_LOAD               = 1002;
2235
2236    // Message Ids
2237    private static final int FOCUS_NODE_HREF         = 102;
2238    private static final int CANCEL_CREDS_REQUEST    = 103;
2239    private static final int RELEASE_WAKELOCK        = 107;
2240
2241    // Private handler for handling javascript and saving passwords
2242    private Handler mHandler = new Handler() {
2243
2244        public void handleMessage(Message msg) {
2245            switch (msg.what) {
2246                case FOCUS_NODE_HREF:
2247                    String url = (String) msg.getData().get("url");
2248                    if (url == null || url.length() == 0) {
2249                        break;
2250                    }
2251                    HashMap focusNodeMap = (HashMap) msg.obj;
2252                    WebView view = (WebView) focusNodeMap.get("webview");
2253                    // Only apply the action if the top window did not change.
2254                    if (getTopWindow() != view) {
2255                        break;
2256                    }
2257                    switch (msg.arg1) {
2258                        case R.id.open_context_menu_id:
2259                        case R.id.view_image_context_menu_id:
2260                            loadURL(getTopWindow(), url);
2261                            break;
2262                        case R.id.open_newtab_context_menu_id:
2263                            final TabControl.Tab parent = mTabControl
2264                                    .getCurrentTab();
2265                            final TabControl.Tab newTab = openTab(url);
2266                            if (newTab != parent) {
2267                                parent.addChildTab(newTab);
2268                            }
2269                            break;
2270                        case R.id.bookmark_context_menu_id:
2271                            Intent intent = new Intent(BrowserActivity.this,
2272                                    AddBookmarkPage.class);
2273                            intent.putExtra("url", url);
2274                            startActivity(intent);
2275                            break;
2276                        case R.id.share_link_context_menu_id:
2277                            Browser.sendString(BrowserActivity.this, url);
2278                            break;
2279                        case R.id.copy_link_context_menu_id:
2280                            copy(url);
2281                            break;
2282                        case R.id.save_link_context_menu_id:
2283                        case R.id.download_context_menu_id:
2284                            onDownloadStartNoStream(url, null, null, null, -1);
2285                            break;
2286                    }
2287                    break;
2288
2289                case LOAD_URL:
2290                    loadURL(getTopWindow(), (String) msg.obj);
2291                    break;
2292
2293                case STOP_LOAD:
2294                    stopLoading();
2295                    break;
2296
2297                case CANCEL_CREDS_REQUEST:
2298                    resumeAfterCredentials();
2299                    break;
2300
2301                case RELEASE_WAKELOCK:
2302                    if (mWakeLock.isHeld()) {
2303                        mWakeLock.release();
2304                    }
2305                    break;
2306            }
2307        }
2308    };
2309
2310    private void updateScreenshot(WebView view) {
2311        // If this is a bookmarked site, add a screenshot to the database.
2312        // FIXME: When should we update?  Every time?
2313        // FIXME: Would like to make sure there is actually something to
2314        // draw, but the API for that (WebViewCore.pictureReady()) is not
2315        // currently accessible here.
2316        ContentResolver cr = getContentResolver();
2317        final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
2318                cr, view.getOriginalUrl(), view.getUrl(), false);
2319        if (c != null) {
2320            boolean succeed = c.moveToFirst();
2321            ContentValues values = null;
2322            while (succeed) {
2323                if (values == null) {
2324                    final ByteArrayOutputStream os
2325                            = new ByteArrayOutputStream();
2326                    Picture thumbnail = view.capturePicture();
2327                    // Keep width and height in sync with BrowserBookmarksPage
2328                    // and bookmark_thumb
2329                    Bitmap bm = Bitmap.createBitmap(100, 80,
2330                            Bitmap.Config.ARGB_4444);
2331                    Canvas canvas = new Canvas(bm);
2332                    // May need to tweak these values to determine what is the
2333                    // best scale factor
2334                    canvas.scale(.5f, .5f);
2335                    thumbnail.draw(canvas);
2336                    bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2337                    values = new ContentValues();
2338                    values.put(Browser.BookmarkColumns.THUMBNAIL,
2339                            os.toByteArray());
2340                }
2341                cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2342                        c.getInt(0)), values, null, null);
2343                succeed = c.moveToNext();
2344            }
2345            c.close();
2346        }
2347    }
2348
2349    // -------------------------------------------------------------------------
2350    // WebViewClient implementation.
2351    //-------------------------------------------------------------------------
2352
2353    // Use in overrideUrlLoading
2354    /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2355    /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2356    /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2357    /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2358
2359    /* package */ WebViewClient getWebViewClient() {
2360        return mWebViewClient;
2361    }
2362
2363    private void updateIcon(WebView view, Bitmap icon) {
2364        if (icon != null) {
2365            BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
2366                    view, icon);
2367        }
2368        setFavicon(icon);
2369    }
2370
2371    private final WebViewClient mWebViewClient = new WebViewClient() {
2372        @Override
2373        public void onPageStarted(WebView view, String url, Bitmap favicon) {
2374            resetLockIcon(url);
2375            setUrlTitle(url, null);
2376
2377            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2378            if (errorConsole != null) {
2379                errorConsole.clearErrorMessages();
2380                if (mShouldShowErrorConsole) {
2381                    errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2382                }
2383            }
2384
2385            // Call updateIcon instead of setFavicon so the bookmark
2386            // database can be updated.
2387            updateIcon(view, favicon);
2388
2389            if (mSettings.isTracing()) {
2390                String host;
2391                try {
2392                    WebAddress uri = new WebAddress(url);
2393                    host = uri.mHost;
2394                } catch (android.net.ParseException ex) {
2395                    host = "browser";
2396                }
2397                host = host.replace('.', '_');
2398                host += ".trace";
2399                mInTrace = true;
2400                Debug.startMethodTracing(host, 20 * 1024 * 1024);
2401            }
2402
2403            // Performance probe
2404            if (false) {
2405                mStart = SystemClock.uptimeMillis();
2406                mProcessStart = Process.getElapsedCpuTime();
2407                long[] sysCpu = new long[7];
2408                if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2409                        sysCpu, null)) {
2410                    mUserStart = sysCpu[0] + sysCpu[1];
2411                    mSystemStart = sysCpu[2];
2412                    mIdleStart = sysCpu[3];
2413                    mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2414                }
2415                mUiStart = SystemClock.currentThreadTimeMillis();
2416            }
2417
2418            if (!mPageStarted) {
2419                mPageStarted = true;
2420                // if onResume() has been called, resumeWebViewTimers() does
2421                // nothing.
2422                resumeWebViewTimers();
2423            }
2424
2425            // reset sync timer to avoid sync starts during loading a page
2426            CookieSyncManager.getInstance().resetSync();
2427
2428            mInLoad = true;
2429            WebView currentWebView = mTabControl.getCurrentWebView();
2430            if (currentWebView == null || currentWebView.getScrollY() != 0) {
2431                // This page has begun to load, so show the title bar
2432                showFakeTitleBar();
2433            }
2434            updateInLoadMenuItems();
2435            if (!mIsNetworkUp) {
2436                createAndShowNetworkDialog();
2437                if (view != null) {
2438                    view.setNetworkAvailable(false);
2439                }
2440            }
2441        }
2442
2443        @Override
2444        public void onPageFinished(WebView view, String url) {
2445            // Reset the title and icon in case we stopped a provisional
2446            // load.
2447            resetTitleAndIcon(view);
2448
2449            // Update the lock icon image only once we are done loading
2450            updateLockIconToLatest();
2451            updateScreenshot(view);
2452
2453            // Performance probe
2454            if (false) {
2455                long[] sysCpu = new long[7];
2456                if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2457                        sysCpu, null)) {
2458                    String uiInfo = "UI thread used "
2459                            + (SystemClock.currentThreadTimeMillis() - mUiStart)
2460                            + " ms";
2461                    if (LOGD_ENABLED) {
2462                        Log.d(LOGTAG, uiInfo);
2463                    }
2464                    //The string that gets written to the log
2465                    String performanceString = "It took total "
2466                            + (SystemClock.uptimeMillis() - mStart)
2467                            + " ms clock time to load the page."
2468                            + "\nbrowser process used "
2469                            + (Process.getElapsedCpuTime() - mProcessStart)
2470                            + " ms, user processes used "
2471                            + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2472                            + " ms, kernel used "
2473                            + (sysCpu[2] - mSystemStart) * 10
2474                            + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2475                            + " ms and irq took "
2476                            + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2477                            * 10 + " ms, " + uiInfo;
2478                    if (LOGD_ENABLED) {
2479                        Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2480                    }
2481                    if (url != null) {
2482                        // strip the url to maintain consistency
2483                        String newUrl = new String(url);
2484                        if (newUrl.startsWith("http://www.")) {
2485                            newUrl = newUrl.substring(11);
2486                        } else if (newUrl.startsWith("http://")) {
2487                            newUrl = newUrl.substring(7);
2488                        } else if (newUrl.startsWith("https://www.")) {
2489                            newUrl = newUrl.substring(12);
2490                        } else if (newUrl.startsWith("https://")) {
2491                            newUrl = newUrl.substring(8);
2492                        }
2493                        if (LOGD_ENABLED) {
2494                            Log.d(LOGTAG, newUrl + " loaded");
2495                        }
2496                        /*
2497                        if (sWhiteList.contains(newUrl)) {
2498                            // The string that gets pushed to the statistcs
2499                            // service
2500                            performanceString = performanceString
2501                                    + "\nWebpage: "
2502                                    + newUrl
2503                                    + "\nCarrier: "
2504                                    + android.os.SystemProperties
2505                                            .get("gsm.sim.operator.alpha");
2506                            if (mWebView != null
2507                                    && mWebView.getContext() != null
2508                                    && mWebView.getContext().getSystemService(
2509                                    Context.CONNECTIVITY_SERVICE) != null) {
2510                                ConnectivityManager cManager =
2511                                        (ConnectivityManager) mWebView
2512                                        .getContext().getSystemService(
2513                                        Context.CONNECTIVITY_SERVICE);
2514                                NetworkInfo nInfo = cManager
2515                                        .getActiveNetworkInfo();
2516                                if (nInfo != null) {
2517                                    performanceString = performanceString
2518                                            + "\nNetwork Type: "
2519                                            + nInfo.getType().toString();
2520                                }
2521                            }
2522                            Checkin.logEvent(mResolver,
2523                                    Checkin.Events.Tag.WEBPAGE_LOAD,
2524                                    performanceString);
2525                            Log.w(LOGTAG, "pushed to the statistics service");
2526                        }
2527                        */
2528                    }
2529                }
2530             }
2531
2532            if (mInTrace) {
2533                mInTrace = false;
2534                Debug.stopMethodTracing();
2535            }
2536
2537            if (mPageStarted) {
2538                mPageStarted = false;
2539                // pauseWebViewTimers() will do nothing and return false if
2540                // onPause() is not called yet.
2541                if (pauseWebViewTimers()) {
2542                    if (mWakeLock.isHeld()) {
2543                        mHandler.removeMessages(RELEASE_WAKELOCK);
2544                        mWakeLock.release();
2545                    }
2546                }
2547            }
2548        }
2549
2550        // return true if want to hijack the url to let another app to handle it
2551        @Override
2552        public boolean shouldOverrideUrlLoading(WebView view, String url) {
2553            if (url.startsWith(SCHEME_WTAI)) {
2554                // wtai://wp/mc;number
2555                // number=string(phone-number)
2556                if (url.startsWith(SCHEME_WTAI_MC)) {
2557                    Intent intent = new Intent(Intent.ACTION_VIEW,
2558                            Uri.parse(WebView.SCHEME_TEL +
2559                            url.substring(SCHEME_WTAI_MC.length())));
2560                    startActivity(intent);
2561                    return true;
2562                }
2563                // wtai://wp/sd;dtmf
2564                // dtmf=string(dialstring)
2565                if (url.startsWith(SCHEME_WTAI_SD)) {
2566                    // TODO
2567                    // only send when there is active voice connection
2568                    return false;
2569                }
2570                // wtai://wp/ap;number;name
2571                // number=string(phone-number)
2572                // name=string
2573                if (url.startsWith(SCHEME_WTAI_AP)) {
2574                    // TODO
2575                    return false;
2576                }
2577            }
2578
2579            // The "about:" schemes are internal to the browser; don't
2580            // want these to be dispatched to other apps.
2581            if (url.startsWith("about:")) {
2582                return false;
2583            }
2584
2585            Intent intent;
2586
2587            // perform generic parsing of the URI to turn it into an Intent.
2588            try {
2589                intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2590            } catch (URISyntaxException ex) {
2591                Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
2592                return false;
2593            }
2594
2595            // check whether the intent can be resolved. If not, we will see
2596            // whether we can download it from the Market.
2597            if (getPackageManager().resolveActivity(intent, 0) == null) {
2598                String packagename = intent.getPackage();
2599                if (packagename != null) {
2600                    intent = new Intent(Intent.ACTION_VIEW, Uri
2601                            .parse("market://search?q=pname:" + packagename));
2602                    intent.addCategory(Intent.CATEGORY_BROWSABLE);
2603                    startActivity(intent);
2604                    return true;
2605                } else {
2606                    return false;
2607                }
2608            }
2609
2610            // sanitize the Intent, ensuring web pages can not bypass browser
2611            // security (only access to BROWSABLE activities).
2612            intent.addCategory(Intent.CATEGORY_BROWSABLE);
2613            intent.setComponent(null);
2614            try {
2615                if (startActivityIfNeeded(intent, -1)) {
2616                    return true;
2617                }
2618            } catch (ActivityNotFoundException ex) {
2619                // ignore the error. If no application can handle the URL,
2620                // eg about:blank, assume the browser can handle it.
2621            }
2622
2623            if (mMenuIsDown) {
2624                openTab(url);
2625                closeOptionsMenu();
2626                return true;
2627            }
2628
2629            return false;
2630        }
2631
2632        /**
2633         * Updates the lock icon. This method is called when we discover another
2634         * resource to be loaded for this page (for example, javascript). While
2635         * we update the icon type, we do not update the lock icon itself until
2636         * we are done loading, it is slightly more secure this way.
2637         */
2638        @Override
2639        public void onLoadResource(WebView view, String url) {
2640            if (url != null && url.length() > 0) {
2641                // It is only if the page claims to be secure
2642                // that we may have to update the lock:
2643                if (mLockIconType == LOCK_ICON_SECURE) {
2644                    // If NOT a 'safe' url, change the lock to mixed content!
2645                    if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2646                        mLockIconType = LOCK_ICON_MIXED;
2647                        if (LOGV_ENABLED) {
2648                            Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2649                                  " updated lock icon to " + mLockIconType + " due to " + url);
2650                        }
2651                    }
2652                }
2653            }
2654        }
2655
2656        /**
2657         * Show the dialog, asking the user if they would like to continue after
2658         * an excessive number of HTTP redirects.
2659         */
2660        @Override
2661        public void onTooManyRedirects(WebView view, final Message cancelMsg,
2662                final Message continueMsg) {
2663            new AlertDialog.Builder(BrowserActivity.this)
2664                .setTitle(R.string.browserFrameRedirect)
2665                .setMessage(R.string.browserFrame307Post)
2666                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2667                    public void onClick(DialogInterface dialog, int which) {
2668                        continueMsg.sendToTarget();
2669                    }})
2670                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2671                    public void onClick(DialogInterface dialog, int which) {
2672                        cancelMsg.sendToTarget();
2673                    }})
2674                .setOnCancelListener(new OnCancelListener() {
2675                    public void onCancel(DialogInterface dialog) {
2676                        cancelMsg.sendToTarget();
2677                    }})
2678                .show();
2679        }
2680
2681        // Container class for the next error dialog that needs to be
2682        // displayed.
2683        class ErrorDialog {
2684            public final int mTitle;
2685            public final String mDescription;
2686            public final int mError;
2687            ErrorDialog(int title, String desc, int error) {
2688                mTitle = title;
2689                mDescription = desc;
2690                mError = error;
2691            }
2692        };
2693
2694        private void processNextError() {
2695            if (mQueuedErrors == null) {
2696                return;
2697            }
2698            // The first one is currently displayed so just remove it.
2699            mQueuedErrors.removeFirst();
2700            if (mQueuedErrors.size() == 0) {
2701                mQueuedErrors = null;
2702                return;
2703            }
2704            showError(mQueuedErrors.getFirst());
2705        }
2706
2707        private DialogInterface.OnDismissListener mDialogListener =
2708                new DialogInterface.OnDismissListener() {
2709                    public void onDismiss(DialogInterface d) {
2710                        processNextError();
2711                    }
2712                };
2713        private LinkedList<ErrorDialog> mQueuedErrors;
2714
2715        private void queueError(int err, String desc) {
2716            if (mQueuedErrors == null) {
2717                mQueuedErrors = new LinkedList<ErrorDialog>();
2718            }
2719            for (ErrorDialog d : mQueuedErrors) {
2720                if (d.mError == err) {
2721                    // Already saw a similar error, ignore the new one.
2722                    return;
2723                }
2724            }
2725            ErrorDialog errDialog = new ErrorDialog(
2726                    err == WebViewClient.ERROR_FILE_NOT_FOUND ?
2727                    R.string.browserFrameFileErrorLabel :
2728                    R.string.browserFrameNetworkErrorLabel,
2729                    desc, err);
2730            mQueuedErrors.addLast(errDialog);
2731
2732            // Show the dialog now if the queue was empty.
2733            if (mQueuedErrors.size() == 1) {
2734                showError(errDialog);
2735            }
2736        }
2737
2738        private void showError(ErrorDialog errDialog) {
2739            AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2740                    .setTitle(errDialog.mTitle)
2741                    .setMessage(errDialog.mDescription)
2742                    .setPositiveButton(R.string.ok, null)
2743                    .create();
2744            d.setOnDismissListener(mDialogListener);
2745            d.show();
2746        }
2747
2748        /**
2749         * Show a dialog informing the user of the network error reported by
2750         * WebCore.
2751         */
2752        @Override
2753        public void onReceivedError(WebView view, int errorCode,
2754                String description, String failingUrl) {
2755            if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
2756                    errorCode != WebViewClient.ERROR_CONNECT &&
2757                    errorCode != WebViewClient.ERROR_BAD_URL &&
2758                    errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
2759                    errorCode != WebViewClient.ERROR_FILE) {
2760                queueError(errorCode, description);
2761            }
2762            Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2763                    + " " + description);
2764
2765            // We need to reset the title after an error.
2766            resetTitleAndRevertLockIcon();
2767        }
2768
2769        /**
2770         * Check with the user if it is ok to resend POST data as the page they
2771         * are trying to navigate to is the result of a POST.
2772         */
2773        @Override
2774        public void onFormResubmission(WebView view, final Message dontResend,
2775                                       final Message resend) {
2776            new AlertDialog.Builder(BrowserActivity.this)
2777                .setTitle(R.string.browserFrameFormResubmitLabel)
2778                .setMessage(R.string.browserFrameFormResubmitMessage)
2779                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2780                    public void onClick(DialogInterface dialog, int which) {
2781                        resend.sendToTarget();
2782                    }})
2783                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2784                    public void onClick(DialogInterface dialog, int which) {
2785                        dontResend.sendToTarget();
2786                    }})
2787                .setOnCancelListener(new OnCancelListener() {
2788                    public void onCancel(DialogInterface dialog) {
2789                        dontResend.sendToTarget();
2790                    }})
2791                .show();
2792        }
2793
2794        /**
2795         * Insert the url into the visited history database.
2796         * @param url The url to be inserted.
2797         * @param isReload True if this url is being reloaded.
2798         * FIXME: Not sure what to do when reloading the page.
2799         */
2800        @Override
2801        public void doUpdateVisitedHistory(WebView view, String url,
2802                boolean isReload) {
2803            if (url.regionMatches(true, 0, "about:", 0, 6)) {
2804                return;
2805            }
2806            // remove "client" before updating it to the history so that it wont
2807            // show up in the auto-complete list.
2808            int index = url.indexOf("client=ms-");
2809            if (index > 0 && url.contains(".google.")) {
2810                int end = url.indexOf('&', index);
2811                if (end > 0) {
2812                    url = url.substring(0, index-1).concat(url.substring(end));
2813                } else {
2814                    url = url.substring(0, index-1);
2815                }
2816            }
2817            Browser.updateVisitedHistory(mResolver, url, true);
2818            WebIconDatabase.getInstance().retainIconForPageUrl(url);
2819        }
2820
2821        /**
2822         * Displays SSL error(s) dialog to the user.
2823         */
2824        @Override
2825        public void onReceivedSslError(
2826            final WebView view, final SslErrorHandler handler, final SslError error) {
2827
2828            if (mSettings.showSecurityWarnings()) {
2829                final LayoutInflater factory =
2830                    LayoutInflater.from(BrowserActivity.this);
2831                final View warningsView =
2832                    factory.inflate(R.layout.ssl_warnings, null);
2833                final LinearLayout placeholder =
2834                    (LinearLayout)warningsView.findViewById(R.id.placeholder);
2835
2836                if (error.hasError(SslError.SSL_UNTRUSTED)) {
2837                    LinearLayout ll = (LinearLayout)factory
2838                        .inflate(R.layout.ssl_warning, null);
2839                    ((TextView)ll.findViewById(R.id.warning))
2840                        .setText(R.string.ssl_untrusted);
2841                    placeholder.addView(ll);
2842                }
2843
2844                if (error.hasError(SslError.SSL_IDMISMATCH)) {
2845                    LinearLayout ll = (LinearLayout)factory
2846                        .inflate(R.layout.ssl_warning, null);
2847                    ((TextView)ll.findViewById(R.id.warning))
2848                        .setText(R.string.ssl_mismatch);
2849                    placeholder.addView(ll);
2850                }
2851
2852                if (error.hasError(SslError.SSL_EXPIRED)) {
2853                    LinearLayout ll = (LinearLayout)factory
2854                        .inflate(R.layout.ssl_warning, null);
2855                    ((TextView)ll.findViewById(R.id.warning))
2856                        .setText(R.string.ssl_expired);
2857                    placeholder.addView(ll);
2858                }
2859
2860                if (error.hasError(SslError.SSL_NOTYETVALID)) {
2861                    LinearLayout ll = (LinearLayout)factory
2862                        .inflate(R.layout.ssl_warning, null);
2863                    ((TextView)ll.findViewById(R.id.warning))
2864                        .setText(R.string.ssl_not_yet_valid);
2865                    placeholder.addView(ll);
2866                }
2867
2868                new AlertDialog.Builder(BrowserActivity.this)
2869                    .setTitle(R.string.security_warning)
2870                    .setIcon(android.R.drawable.ic_dialog_alert)
2871                    .setView(warningsView)
2872                    .setPositiveButton(R.string.ssl_continue,
2873                            new DialogInterface.OnClickListener() {
2874                                public void onClick(DialogInterface dialog, int whichButton) {
2875                                    handler.proceed();
2876                                }
2877                            })
2878                    .setNeutralButton(R.string.view_certificate,
2879                            new DialogInterface.OnClickListener() {
2880                                public void onClick(DialogInterface dialog, int whichButton) {
2881                                    showSSLCertificateOnError(view, handler, error);
2882                                }
2883                            })
2884                    .setNegativeButton(R.string.cancel,
2885                            new DialogInterface.OnClickListener() {
2886                                public void onClick(DialogInterface dialog, int whichButton) {
2887                                    handler.cancel();
2888                                    BrowserActivity.this.resetTitleAndRevertLockIcon();
2889                                }
2890                            })
2891                    .setOnCancelListener(
2892                            new DialogInterface.OnCancelListener() {
2893                                public void onCancel(DialogInterface dialog) {
2894                                    handler.cancel();
2895                                    BrowserActivity.this.resetTitleAndRevertLockIcon();
2896                                }
2897                            })
2898                    .show();
2899            } else {
2900                handler.proceed();
2901            }
2902        }
2903
2904        /**
2905         * Handles an HTTP authentication request.
2906         *
2907         * @param handler The authentication handler
2908         * @param host The host
2909         * @param realm The realm
2910         */
2911        @Override
2912        public void onReceivedHttpAuthRequest(WebView view,
2913                final HttpAuthHandler handler, final String host, final String realm) {
2914            String username = null;
2915            String password = null;
2916
2917            boolean reuseHttpAuthUsernamePassword =
2918                handler.useHttpAuthUsernamePassword();
2919
2920            if (reuseHttpAuthUsernamePassword &&
2921                    (mTabControl.getCurrentWebView() != null)) {
2922                String[] credentials =
2923                        mTabControl.getCurrentWebView()
2924                                .getHttpAuthUsernamePassword(host, realm);
2925                if (credentials != null && credentials.length == 2) {
2926                    username = credentials[0];
2927                    password = credentials[1];
2928                }
2929            }
2930
2931            if (username != null && password != null) {
2932                handler.proceed(username, password);
2933            } else {
2934                showHttpAuthentication(handler, host, realm, null, null, null, 0);
2935            }
2936        }
2937
2938        @Override
2939        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2940            if (mMenuIsDown) {
2941                // only check shortcut key when MENU is held
2942                return getWindow().isShortcutKey(event.getKeyCode(), event);
2943            } else {
2944                return false;
2945            }
2946        }
2947
2948        @Override
2949        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2950            if (view != mTabControl.getCurrentTopWebView()) {
2951                return;
2952            }
2953            if (event.isDown()) {
2954                BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2955            } else {
2956                BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2957            }
2958        }
2959    };
2960
2961    //--------------------------------------------------------------------------
2962    // WebChromeClient implementation
2963    //--------------------------------------------------------------------------
2964
2965    /* package */ WebChromeClient getWebChromeClient() {
2966        return mWebChromeClient;
2967    }
2968
2969    private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2970        // Helper method to create a new tab or sub window.
2971        private void createWindow(final boolean dialog, final Message msg) {
2972            if (dialog) {
2973                mTabControl.createSubWindow();
2974                final TabControl.Tab t = mTabControl.getCurrentTab();
2975                attachSubWindow(t);
2976                WebView.WebViewTransport transport =
2977                        (WebView.WebViewTransport) msg.obj;
2978                transport.setWebView(t.getSubWebView());
2979                msg.sendToTarget();
2980            } else {
2981                final TabControl.Tab parent = mTabControl.getCurrentTab();
2982                final TabControl.Tab newTab
2983                        = openTabAndShow(EMPTY_URL_DATA, false, null);
2984                if (newTab != parent) {
2985                    parent.addChildTab(newTab);
2986                }
2987                WebView.WebViewTransport transport =
2988                        (WebView.WebViewTransport) msg.obj;
2989                transport.setWebView(mTabControl.getCurrentWebView());
2990                msg.sendToTarget();
2991            }
2992        }
2993
2994        @Override
2995        public boolean onCreateWindow(WebView view, final boolean dialog,
2996                final boolean userGesture, final Message resultMsg) {
2997            // Short-circuit if we can't create any more tabs or sub windows.
2998            if (dialog && mTabControl.getCurrentSubWindow() != null) {
2999                new AlertDialog.Builder(BrowserActivity.this)
3000                        .setTitle(R.string.too_many_subwindows_dialog_title)
3001                        .setIcon(android.R.drawable.ic_dialog_alert)
3002                        .setMessage(R.string.too_many_subwindows_dialog_message)
3003                        .setPositiveButton(R.string.ok, null)
3004                        .show();
3005                return false;
3006            } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
3007                new AlertDialog.Builder(BrowserActivity.this)
3008                        .setTitle(R.string.too_many_windows_dialog_title)
3009                        .setIcon(android.R.drawable.ic_dialog_alert)
3010                        .setMessage(R.string.too_many_windows_dialog_message)
3011                        .setPositiveButton(R.string.ok, null)
3012                        .show();
3013                return false;
3014            }
3015
3016            // Short-circuit if this was a user gesture.
3017            if (userGesture) {
3018                createWindow(dialog, resultMsg);
3019                return true;
3020            }
3021
3022            // Allow the popup and create the appropriate window.
3023            final AlertDialog.OnClickListener allowListener =
3024                    new AlertDialog.OnClickListener() {
3025                        public void onClick(DialogInterface d,
3026                                int which) {
3027                            createWindow(dialog, resultMsg);
3028                        }
3029                    };
3030
3031            // Block the popup by returning a null WebView.
3032            final AlertDialog.OnClickListener blockListener =
3033                    new AlertDialog.OnClickListener() {
3034                        public void onClick(DialogInterface d, int which) {
3035                            resultMsg.sendToTarget();
3036                        }
3037                    };
3038
3039            // Build a confirmation dialog to display to the user.
3040            final AlertDialog d =
3041                    new AlertDialog.Builder(BrowserActivity.this)
3042                    .setTitle(R.string.attention)
3043                    .setIcon(android.R.drawable.ic_dialog_alert)
3044                    .setMessage(R.string.popup_window_attempt)
3045                    .setPositiveButton(R.string.allow, allowListener)
3046                    .setNegativeButton(R.string.block, blockListener)
3047                    .setCancelable(false)
3048                    .create();
3049
3050            // Show the confirmation dialog.
3051            d.show();
3052            return true;
3053        }
3054
3055        @Override
3056        public void onCloseWindow(WebView window) {
3057            final TabControl.Tab current = mTabControl.getCurrentTab();
3058            final TabControl.Tab parent = current.getParentTab();
3059            if (parent != null) {
3060                // JavaScript can only close popup window.
3061                switchToTab(mTabControl.getTabIndex(parent));
3062                // Now we need to close the window
3063                closeTab(current);
3064            }
3065        }
3066
3067        @Override
3068        public void onProgressChanged(WebView view, int newProgress) {
3069            mTitleBar.setProgress(newProgress);
3070            if (mFakeTitleBar != null) {
3071                mFakeTitleBar.setProgress(newProgress);
3072            }
3073
3074            if (newProgress == 100) {
3075                // onProgressChanged() is called for sub-frame too while
3076                // onPageFinished() is only called for the main frame. sync
3077                // cookie and cache promptly here.
3078                CookieSyncManager.getInstance().sync();
3079                if (mInLoad) {
3080                    mInLoad = false;
3081                    updateInLoadMenuItems();
3082                    // If the options menu is open, leave the title bar
3083                    if (!mOptionsMenuOpen || !mIconView) {
3084                        hideFakeTitleBar();
3085                    }
3086                }
3087            } else if (!mInLoad) {
3088                // onPageFinished may have already been called but a subframe
3089                // is still loading and updating the progress. Reset mInLoad
3090                // and update the menu items.
3091                mInLoad = true;
3092                updateInLoadMenuItems();
3093                WebView currentWebView = mTabControl.getCurrentWebView();
3094                if ((currentWebView == null || currentWebView.getScrollY() != 0)
3095                        && (!mOptionsMenuOpen || mIconView)) {
3096                    // This page has begun to load, so show the title bar
3097                    showFakeTitleBar();
3098                }
3099            }
3100        }
3101
3102        @Override
3103        public void onReceivedTitle(WebView view, String title) {
3104            String url = view.getUrl();
3105
3106            // here, if url is null, we want to reset the title
3107            setUrlTitle(url, title);
3108
3109            if (url == null ||
3110                url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3111                return;
3112            }
3113            // See if we can find the current url in our history database and
3114            // add the new title to it.
3115            if (url.startsWith("http://www.")) {
3116                url = url.substring(11);
3117            } else if (url.startsWith("http://")) {
3118                url = url.substring(4);
3119            }
3120            try {
3121                url = "%" + url;
3122                String [] selArgs = new String[] { url };
3123
3124                String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3125                        + Browser.BookmarkColumns.BOOKMARK + " = 0";
3126                Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3127                    Browser.HISTORY_PROJECTION, where, selArgs, null);
3128                if (c.moveToFirst()) {
3129                    // Current implementation of database only has one entry per
3130                    // url.
3131                    ContentValues map = new ContentValues();
3132                    map.put(Browser.BookmarkColumns.TITLE, title);
3133                    mResolver.update(Browser.BOOKMARKS_URI, map,
3134                            "_id = " + c.getInt(0), null);
3135                }
3136                c.close();
3137            } catch (IllegalStateException e) {
3138                Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3139            } catch (SQLiteException ex) {
3140                Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3141            }
3142        }
3143
3144        @Override
3145        public void onReceivedIcon(WebView view, Bitmap icon) {
3146            updateIcon(view, icon);
3147        }
3148
3149        @Override
3150        public void onReceivedTouchIconUrl(WebView view, String url) {
3151            final ContentResolver cr = getContentResolver();
3152            final Cursor c =
3153                    BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
3154                            view.getOriginalUrl(), view.getUrl(), true);
3155            if (c != null) {
3156                if (c.getCount() > 0) {
3157                    new DownloadTouchIcon(cr, c, view).execute(url);
3158                } else {
3159                    c.close();
3160                }
3161            }
3162        }
3163
3164        @Override
3165        public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
3166            if (mCustomView != null)
3167                return;
3168
3169            // Add the custom view to its container.
3170            mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3171            mCustomView = view;
3172            mCustomViewCallback = callback;
3173            // Save the menu state and set it to empty while the custom
3174            // view is showing.
3175            mOldMenuState = mMenuState;
3176            mMenuState = EMPTY_MENU;
3177            // Hide the content view.
3178            mContentView.setVisibility(View.GONE);
3179            // Finally show the custom view container.
3180            mCustomViewContainer.setVisibility(View.VISIBLE);
3181            mCustomViewContainer.bringToFront();
3182        }
3183
3184        @Override
3185        public void onHideCustomView() {
3186            if (mCustomView == null)
3187                return;
3188
3189            // Hide the custom view.
3190            mCustomView.setVisibility(View.GONE);
3191            // Remove the custom view from its container.
3192            mCustomViewContainer.removeView(mCustomView);
3193            mCustomView = null;
3194            // Reset the old menu state.
3195            mMenuState = mOldMenuState;
3196            mOldMenuState = EMPTY_MENU;
3197            mCustomViewContainer.setVisibility(View.GONE);
3198            mCustomViewCallback.onCustomViewHidden();
3199            // Show the content view.
3200            mContentView.setVisibility(View.VISIBLE);
3201        }
3202
3203        /**
3204         * The origin has exceeded its database quota.
3205         * @param url the URL that exceeded the quota
3206         * @param databaseIdentifier the identifier of the database on
3207         *     which the transaction that caused the quota overflow was run
3208         * @param currentQuota the current quota for the origin.
3209         * @param estimatedSize the estimated size of the database.
3210         * @param totalUsedQuota is the sum of all origins' quota.
3211         * @param quotaUpdater The callback to run when a decision to allow or
3212         *     deny quota has been made. Don't forget to call this!
3213         */
3214        @Override
3215        public void onExceededDatabaseQuota(String url,
3216            String databaseIdentifier, long currentQuota, long estimatedSize,
3217            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3218            mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3219                    url, databaseIdentifier, currentQuota, estimatedSize,
3220                    totalUsedQuota, quotaUpdater);
3221        }
3222
3223        /**
3224         * The Application Cache has exceeded its max size.
3225         * @param spaceNeeded is the amount of disk space that would be needed
3226         * in order for the last appcache operation to succeed.
3227         * @param totalUsedQuota is the sum of all origins' quota.
3228         * @param quotaUpdater A callback to inform the WebCore thread that a new
3229         * app cache size is available. This callback must always be executed at
3230         * some point to ensure that the sleeping WebCore thread is woken up.
3231         */
3232        @Override
3233        public void onReachedMaxAppCacheSize(long spaceNeeded,
3234                long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3235            mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3236                    spaceNeeded, totalUsedQuota, quotaUpdater);
3237        }
3238
3239        /**
3240         * Instructs the browser to show a prompt to ask the user to set the
3241         * Geolocation permission state for the specified origin.
3242         * @param origin The origin for which Geolocation permissions are
3243         *     requested.
3244         * @param callback The callback to call once the user has set the
3245         *     Geolocation permission state.
3246         */
3247        @Override
3248        public void onGeolocationPermissionsShowPrompt(String origin,
3249                GeolocationPermissions.Callback callback) {
3250            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3251                    origin, callback);
3252        }
3253
3254        /**
3255         * Instructs the browser to hide the Geolocation permissions prompt.
3256         */
3257        @Override
3258        public void onGeolocationPermissionsHidePrompt() {
3259            mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3260        }
3261
3262        /* Adds a JavaScript error message to the system log.
3263         * @param message The error message to report.
3264         * @param lineNumber The line number of the error.
3265         * @param sourceID The name of the source file that caused the error.
3266         */
3267        @Override
3268        public void addMessageToConsole(String message, int lineNumber, String sourceID) {
3269            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3270            errorConsole.addErrorMessage(message, sourceID, lineNumber);
3271                if (mShouldShowErrorConsole &&
3272                        errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3273                    errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3274                }
3275            Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
3276        }
3277    };
3278
3279    /**
3280     * Notify the host application a download should be done, or that
3281     * the data should be streamed if a streaming viewer is available.
3282     * @param url The full url to the content that should be downloaded
3283     * @param contentDisposition Content-disposition http header, if
3284     *                           present.
3285     * @param mimetype The mimetype of the content reported by the server
3286     * @param contentLength The file size reported by the server
3287     */
3288    public void onDownloadStart(String url, String userAgent,
3289            String contentDisposition, String mimetype, long contentLength) {
3290        // if we're dealing wih A/V content that's not explicitly marked
3291        //     for download, check if it's streamable.
3292        if (contentDisposition == null
3293                || !contentDisposition.regionMatches(
3294                        true, 0, "attachment", 0, 10)) {
3295            // query the package manager to see if there's a registered handler
3296            //     that matches.
3297            Intent intent = new Intent(Intent.ACTION_VIEW);
3298            intent.setDataAndType(Uri.parse(url), mimetype);
3299            ResolveInfo info = getPackageManager().resolveActivity(intent,
3300                    PackageManager.MATCH_DEFAULT_ONLY);
3301            if (info != null) {
3302                ComponentName myName = getComponentName();
3303                // If we resolved to ourselves, we don't want to attempt to
3304                // load the url only to try and download it again.
3305                if (!myName.getPackageName().equals(
3306                        info.activityInfo.packageName)
3307                        || !myName.getClassName().equals(
3308                                info.activityInfo.name)) {
3309                    // someone (other than us) knows how to handle this mime
3310                    // type with this scheme, don't download.
3311                    try {
3312                        startActivity(intent);
3313                        return;
3314                    } catch (ActivityNotFoundException ex) {
3315                        if (LOGD_ENABLED) {
3316                            Log.d(LOGTAG, "activity not found for " + mimetype
3317                                    + " over " + Uri.parse(url).getScheme(),
3318                                    ex);
3319                        }
3320                        // Best behavior is to fall back to a download in this
3321                        // case
3322                    }
3323                }
3324            }
3325        }
3326        onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3327    }
3328
3329    /**
3330     * Notify the host application a download should be done, even if there
3331     * is a streaming viewer available for thise type.
3332     * @param url The full url to the content that should be downloaded
3333     * @param contentDisposition Content-disposition http header, if
3334     *                           present.
3335     * @param mimetype The mimetype of the content reported by the server
3336     * @param contentLength The file size reported by the server
3337     */
3338    /*package */ void onDownloadStartNoStream(String url, String userAgent,
3339            String contentDisposition, String mimetype, long contentLength) {
3340
3341        String filename = URLUtil.guessFileName(url,
3342                contentDisposition, mimetype);
3343
3344        // Check to see if we have an SDCard
3345        String status = Environment.getExternalStorageState();
3346        if (!status.equals(Environment.MEDIA_MOUNTED)) {
3347            int title;
3348            String msg;
3349
3350            // Check to see if the SDCard is busy, same as the music app
3351            if (status.equals(Environment.MEDIA_SHARED)) {
3352                msg = getString(R.string.download_sdcard_busy_dlg_msg);
3353                title = R.string.download_sdcard_busy_dlg_title;
3354            } else {
3355                msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3356                title = R.string.download_no_sdcard_dlg_title;
3357            }
3358
3359            new AlertDialog.Builder(this)
3360                .setTitle(title)
3361                .setIcon(android.R.drawable.ic_dialog_alert)
3362                .setMessage(msg)
3363                .setPositiveButton(R.string.ok, null)
3364                .show();
3365            return;
3366        }
3367
3368        // java.net.URI is a lot stricter than KURL so we have to undo
3369        // KURL's percent-encoding and redo the encoding using java.net.URI.
3370        URI uri = null;
3371        try {
3372            // Undo the percent-encoding that KURL may have done.
3373            String newUrl = new String(URLUtil.decode(url.getBytes()));
3374            // Parse the url into pieces
3375            WebAddress w = new WebAddress(newUrl);
3376            String frag = null;
3377            String query = null;
3378            String path = w.mPath;
3379            // Break the path into path, query, and fragment
3380            if (path.length() > 0) {
3381                // Strip the fragment
3382                int idx = path.lastIndexOf('#');
3383                if (idx != -1) {
3384                    frag = path.substring(idx + 1);
3385                    path = path.substring(0, idx);
3386                }
3387                idx = path.lastIndexOf('?');
3388                if (idx != -1) {
3389                    query = path.substring(idx + 1);
3390                    path = path.substring(0, idx);
3391                }
3392            }
3393            uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3394                    query, frag);
3395        } catch (Exception e) {
3396            Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3397            return;
3398        }
3399
3400        // XXX: Have to use the old url since the cookies were stored using the
3401        // old percent-encoded url.
3402        String cookies = CookieManager.getInstance().getCookie(url);
3403
3404        ContentValues values = new ContentValues();
3405        values.put(Downloads.COLUMN_URI, uri.toString());
3406        values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3407        values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3408        values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
3409                getPackageName());
3410        values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
3411                BrowserDownloadPage.class.getCanonicalName());
3412        values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3413        values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3414        values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3415        values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
3416        if (contentLength > 0) {
3417            values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
3418        }
3419        if (mimetype == null) {
3420            // We must have long pressed on a link or image to download it. We
3421            // are not sure of the mimetype in this case, so do a head request
3422            new FetchUrlMimeType(this).execute(values);
3423        } else {
3424            final Uri contentUri =
3425                    getContentResolver().insert(Downloads.CONTENT_URI, values);
3426            viewDownloads(contentUri);
3427        }
3428
3429    }
3430
3431    /**
3432     * Resets the lock icon. This method is called when we start a new load and
3433     * know the url to be loaded.
3434     */
3435    private void resetLockIcon(String url) {
3436        // Save the lock-icon state (we revert to it if the load gets cancelled)
3437        saveLockIcon();
3438
3439        mLockIconType = LOCK_ICON_UNSECURE;
3440        if (URLUtil.isHttpsUrl(url)) {
3441            mLockIconType = LOCK_ICON_SECURE;
3442            if (LOGV_ENABLED) {
3443                Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3444                      " reset lock icon to " + mLockIconType);
3445            }
3446        }
3447
3448        updateLockIconImage(LOCK_ICON_UNSECURE);
3449    }
3450
3451    /* package */ void setLockIconType(int type) {
3452        mLockIconType = type;
3453    }
3454
3455    /* package */ int getLockIconType() {
3456        return mLockIconType;
3457    }
3458
3459    /* package */ void setPrevLockType(int type) {
3460        mPrevLockType = type;
3461    }
3462
3463    /* package */ int getPrevLockType() {
3464        return mPrevLockType;
3465    }
3466
3467    /**
3468     * Update the lock icon to correspond to our latest state.
3469     */
3470    /* package */ void updateLockIconToLatest() {
3471        updateLockIconImage(mLockIconType);
3472    }
3473
3474    /**
3475     * Updates the lock-icon image in the title-bar.
3476     */
3477    private void updateLockIconImage(int lockIconType) {
3478        Drawable d = null;
3479        if (lockIconType == LOCK_ICON_SECURE) {
3480            d = mSecLockIcon;
3481        } else if (lockIconType == LOCK_ICON_MIXED) {
3482            d = mMixLockIcon;
3483        }
3484        mTitleBar.setLock(d);
3485        if (mFakeTitleBar != null) {
3486            mFakeTitleBar.setLock(d);
3487        }
3488    }
3489
3490    /**
3491     * Displays a page-info dialog.
3492     * @param tab The tab to show info about
3493     * @param fromShowSSLCertificateOnError The flag that indicates whether
3494     * this dialog was opened from the SSL-certificate-on-error dialog or
3495     * not. This is important, since we need to know whether to return to
3496     * the parent dialog or simply dismiss.
3497     */
3498    private void showPageInfo(final TabControl.Tab tab,
3499                              final boolean fromShowSSLCertificateOnError) {
3500        final LayoutInflater factory = LayoutInflater
3501                .from(this);
3502
3503        final View pageInfoView = factory.inflate(R.layout.page_info, null);
3504
3505        final WebView view = tab.getWebView();
3506
3507        String url = null;
3508        String title = null;
3509
3510        if (view == null) {
3511            url = tab.getUrl();
3512            title = tab.getTitle();
3513        } else if (view == mTabControl.getCurrentWebView()) {
3514             // Use the cached title and url if this is the current WebView
3515            url = mUrl;
3516            title = mTitle;
3517        } else {
3518            url = view.getUrl();
3519            title = view.getTitle();
3520        }
3521
3522        if (url == null) {
3523            url = "";
3524        }
3525        if (title == null) {
3526            title = "";
3527        }
3528
3529        ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3530        ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3531
3532        mPageInfoView = tab;
3533        mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3534
3535        AlertDialog.Builder alertDialogBuilder =
3536            new AlertDialog.Builder(this)
3537            .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3538            .setView(pageInfoView)
3539            .setPositiveButton(
3540                R.string.ok,
3541                new DialogInterface.OnClickListener() {
3542                    public void onClick(DialogInterface dialog,
3543                                        int whichButton) {
3544                        mPageInfoDialog = null;
3545                        mPageInfoView = null;
3546                        mPageInfoFromShowSSLCertificateOnError = null;
3547
3548                        // if we came here from the SSL error dialog
3549                        if (fromShowSSLCertificateOnError) {
3550                            // go back to the SSL error dialog
3551                            showSSLCertificateOnError(
3552                                mSSLCertificateOnErrorView,
3553                                mSSLCertificateOnErrorHandler,
3554                                mSSLCertificateOnErrorError);
3555                        }
3556                    }
3557                })
3558            .setOnCancelListener(
3559                new DialogInterface.OnCancelListener() {
3560                    public void onCancel(DialogInterface dialog) {
3561                        mPageInfoDialog = null;
3562                        mPageInfoView = null;
3563                        mPageInfoFromShowSSLCertificateOnError = null;
3564
3565                        // if we came here from the SSL error dialog
3566                        if (fromShowSSLCertificateOnError) {
3567                            // go back to the SSL error dialog
3568                            showSSLCertificateOnError(
3569                                mSSLCertificateOnErrorView,
3570                                mSSLCertificateOnErrorHandler,
3571                                mSSLCertificateOnErrorError);
3572                        }
3573                    }
3574                });
3575
3576        // if we have a main top-level page SSL certificate set or a certificate
3577        // error
3578        if (fromShowSSLCertificateOnError ||
3579                (view != null && view.getCertificate() != null)) {
3580            // add a 'View Certificate' button
3581            alertDialogBuilder.setNeutralButton(
3582                R.string.view_certificate,
3583                new DialogInterface.OnClickListener() {
3584                    public void onClick(DialogInterface dialog,
3585                                        int whichButton) {
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                        } else {
3598                            // otherwise, display the top-most certificate from
3599                            // the chain
3600                            if (view.getCertificate() != null) {
3601                                showSSLCertificate(tab);
3602                            }
3603                        }
3604                    }
3605                });
3606        }
3607
3608        mPageInfoDialog = alertDialogBuilder.show();
3609    }
3610
3611       /**
3612     * Displays the main top-level page SSL certificate dialog
3613     * (accessible from the Page-Info dialog).
3614     * @param tab The tab to show certificate for.
3615     */
3616    private void showSSLCertificate(final TabControl.Tab tab) {
3617        final View certificateView =
3618                inflateCertificateView(tab.getWebView().getCertificate());
3619        if (certificateView == null) {
3620            return;
3621        }
3622
3623        LayoutInflater factory = LayoutInflater.from(this);
3624
3625        final LinearLayout placeholder =
3626                (LinearLayout)certificateView.findViewById(R.id.placeholder);
3627
3628        LinearLayout ll = (LinearLayout) factory.inflate(
3629            R.layout.ssl_success, placeholder);
3630        ((TextView)ll.findViewById(R.id.success))
3631            .setText(R.string.ssl_certificate_is_valid);
3632
3633        mSSLCertificateView = tab;
3634        mSSLCertificateDialog =
3635            new AlertDialog.Builder(this)
3636                .setTitle(R.string.ssl_certificate).setIcon(
3637                    R.drawable.ic_dialog_browser_certificate_secure)
3638                .setView(certificateView)
3639                .setPositiveButton(R.string.ok,
3640                        new DialogInterface.OnClickListener() {
3641                            public void onClick(DialogInterface dialog,
3642                                    int whichButton) {
3643                                mSSLCertificateDialog = null;
3644                                mSSLCertificateView = null;
3645
3646                                showPageInfo(tab, false);
3647                            }
3648                        })
3649                .setOnCancelListener(
3650                        new DialogInterface.OnCancelListener() {
3651                            public void onCancel(DialogInterface dialog) {
3652                                mSSLCertificateDialog = null;
3653                                mSSLCertificateView = null;
3654
3655                                showPageInfo(tab, false);
3656                            }
3657                        })
3658                .show();
3659    }
3660
3661    /**
3662     * Displays the SSL error certificate dialog.
3663     * @param view The target web-view.
3664     * @param handler The SSL error handler responsible for cancelling the
3665     * connection that resulted in an SSL error or proceeding per user request.
3666     * @param error The SSL error object.
3667     */
3668    private void showSSLCertificateOnError(
3669        final WebView view, final SslErrorHandler handler, final SslError error) {
3670
3671        final View certificateView =
3672            inflateCertificateView(error.getCertificate());
3673        if (certificateView == null) {
3674            return;
3675        }
3676
3677        LayoutInflater factory = LayoutInflater.from(this);
3678
3679        final LinearLayout placeholder =
3680                (LinearLayout)certificateView.findViewById(R.id.placeholder);
3681
3682        if (error.hasError(SslError.SSL_UNTRUSTED)) {
3683            LinearLayout ll = (LinearLayout)factory
3684                .inflate(R.layout.ssl_warning, placeholder);
3685            ((TextView)ll.findViewById(R.id.warning))
3686                .setText(R.string.ssl_untrusted);
3687        }
3688
3689        if (error.hasError(SslError.SSL_IDMISMATCH)) {
3690            LinearLayout ll = (LinearLayout)factory
3691                .inflate(R.layout.ssl_warning, placeholder);
3692            ((TextView)ll.findViewById(R.id.warning))
3693                .setText(R.string.ssl_mismatch);
3694        }
3695
3696        if (error.hasError(SslError.SSL_EXPIRED)) {
3697            LinearLayout ll = (LinearLayout)factory
3698                .inflate(R.layout.ssl_warning, placeholder);
3699            ((TextView)ll.findViewById(R.id.warning))
3700                .setText(R.string.ssl_expired);
3701        }
3702
3703        if (error.hasError(SslError.SSL_NOTYETVALID)) {
3704            LinearLayout ll = (LinearLayout)factory
3705                .inflate(R.layout.ssl_warning, placeholder);
3706            ((TextView)ll.findViewById(R.id.warning))
3707                .setText(R.string.ssl_not_yet_valid);
3708        }
3709
3710        mSSLCertificateOnErrorHandler = handler;
3711        mSSLCertificateOnErrorView = view;
3712        mSSLCertificateOnErrorError = error;
3713        mSSLCertificateOnErrorDialog =
3714            new AlertDialog.Builder(this)
3715                .setTitle(R.string.ssl_certificate).setIcon(
3716                    R.drawable.ic_dialog_browser_certificate_partially_secure)
3717                .setView(certificateView)
3718                .setPositiveButton(R.string.ok,
3719                        new DialogInterface.OnClickListener() {
3720                            public void onClick(DialogInterface dialog,
3721                                    int whichButton) {
3722                                mSSLCertificateOnErrorDialog = null;
3723                                mSSLCertificateOnErrorView = null;
3724                                mSSLCertificateOnErrorHandler = null;
3725                                mSSLCertificateOnErrorError = null;
3726
3727                                mWebViewClient.onReceivedSslError(
3728                                    view, handler, error);
3729                            }
3730                        })
3731                 .setNeutralButton(R.string.page_info_view,
3732                        new DialogInterface.OnClickListener() {
3733                            public void onClick(DialogInterface dialog,
3734                                    int whichButton) {
3735                                mSSLCertificateOnErrorDialog = null;
3736
3737                                // do not clear the dialog state: we will
3738                                // need to show the dialog again once the
3739                                // user is done exploring the page-info details
3740
3741                                showPageInfo(mTabControl.getTabFromView(view),
3742                                        true);
3743                            }
3744                        })
3745                .setOnCancelListener(
3746                        new DialogInterface.OnCancelListener() {
3747                            public void onCancel(DialogInterface dialog) {
3748                                mSSLCertificateOnErrorDialog = null;
3749                                mSSLCertificateOnErrorView = null;
3750                                mSSLCertificateOnErrorHandler = null;
3751                                mSSLCertificateOnErrorError = null;
3752
3753                                mWebViewClient.onReceivedSslError(
3754                                    view, handler, error);
3755                            }
3756                        })
3757                .show();
3758    }
3759
3760    /**
3761     * Inflates the SSL certificate view (helper method).
3762     * @param certificate The SSL certificate.
3763     * @return The resultant certificate view with issued-to, issued-by,
3764     * issued-on, expires-on, and possibly other fields set.
3765     * If the input certificate is null, returns null.
3766     */
3767    private View inflateCertificateView(SslCertificate certificate) {
3768        if (certificate == null) {
3769            return null;
3770        }
3771
3772        LayoutInflater factory = LayoutInflater.from(this);
3773
3774        View certificateView = factory.inflate(
3775            R.layout.ssl_certificate, null);
3776
3777        // issued to:
3778        SslCertificate.DName issuedTo = certificate.getIssuedTo();
3779        if (issuedTo != null) {
3780            ((TextView) certificateView.findViewById(R.id.to_common))
3781                .setText(issuedTo.getCName());
3782            ((TextView) certificateView.findViewById(R.id.to_org))
3783                .setText(issuedTo.getOName());
3784            ((TextView) certificateView.findViewById(R.id.to_org_unit))
3785                .setText(issuedTo.getUName());
3786        }
3787
3788        // issued by:
3789        SslCertificate.DName issuedBy = certificate.getIssuedBy();
3790        if (issuedBy != null) {
3791            ((TextView) certificateView.findViewById(R.id.by_common))
3792                .setText(issuedBy.getCName());
3793            ((TextView) certificateView.findViewById(R.id.by_org))
3794                .setText(issuedBy.getOName());
3795            ((TextView) certificateView.findViewById(R.id.by_org_unit))
3796                .setText(issuedBy.getUName());
3797        }
3798
3799        // issued on:
3800        String issuedOn = reformatCertificateDate(
3801            certificate.getValidNotBefore());
3802        ((TextView) certificateView.findViewById(R.id.issued_on))
3803            .setText(issuedOn);
3804
3805        // expires on:
3806        String expiresOn = reformatCertificateDate(
3807            certificate.getValidNotAfter());
3808        ((TextView) certificateView.findViewById(R.id.expires_on))
3809            .setText(expiresOn);
3810
3811        return certificateView;
3812    }
3813
3814    /**
3815     * Re-formats the certificate date (Date.toString()) string to
3816     * a properly localized date string.
3817     * @return Properly localized version of the certificate date string and
3818     * the original certificate date string if fails to localize.
3819     * If the original string is null, returns an empty string "".
3820     */
3821    private String reformatCertificateDate(String certificateDate) {
3822      String reformattedDate = null;
3823
3824      if (certificateDate != null) {
3825          Date date = null;
3826          try {
3827              date = java.text.DateFormat.getInstance().parse(certificateDate);
3828          } catch (ParseException e) {
3829              date = null;
3830          }
3831
3832          if (date != null) {
3833              reformattedDate =
3834                  DateFormat.getDateFormat(this).format(date);
3835          }
3836      }
3837
3838      return reformattedDate != null ? reformattedDate :
3839          (certificateDate != null ? certificateDate : "");
3840    }
3841
3842    /**
3843     * Displays an http-authentication dialog.
3844     */
3845    private void showHttpAuthentication(final HttpAuthHandler handler,
3846            final String host, final String realm, final String title,
3847            final String name, final String password, int focusId) {
3848        LayoutInflater factory = LayoutInflater.from(this);
3849        final View v = factory
3850                .inflate(R.layout.http_authentication, null);
3851        if (name != null) {
3852            ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3853        }
3854        if (password != null) {
3855            ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3856        }
3857
3858        String titleText = title;
3859        if (titleText == null) {
3860            titleText = getText(R.string.sign_in_to).toString().replace(
3861                    "%s1", host).replace("%s2", realm);
3862        }
3863
3864        mHttpAuthHandler = handler;
3865        AlertDialog dialog = new AlertDialog.Builder(this)
3866                .setTitle(titleText)
3867                .setIcon(android.R.drawable.ic_dialog_alert)
3868                .setView(v)
3869                .setPositiveButton(R.string.action,
3870                        new DialogInterface.OnClickListener() {
3871                             public void onClick(DialogInterface dialog,
3872                                     int whichButton) {
3873                                String nm = ((EditText) v
3874                                        .findViewById(R.id.username_edit))
3875                                        .getText().toString();
3876                                String pw = ((EditText) v
3877                                        .findViewById(R.id.password_edit))
3878                                        .getText().toString();
3879                                BrowserActivity.this.setHttpAuthUsernamePassword
3880                                        (host, realm, nm, pw);
3881                                handler.proceed(nm, pw);
3882                                mHttpAuthenticationDialog = null;
3883                                mHttpAuthHandler = null;
3884                            }})
3885                .setNegativeButton(R.string.cancel,
3886                        new DialogInterface.OnClickListener() {
3887                            public void onClick(DialogInterface dialog,
3888                                    int whichButton) {
3889                                handler.cancel();
3890                                BrowserActivity.this.resetTitleAndRevertLockIcon();
3891                                mHttpAuthenticationDialog = null;
3892                                mHttpAuthHandler = null;
3893                            }})
3894                .setOnCancelListener(new DialogInterface.OnCancelListener() {
3895                        public void onCancel(DialogInterface dialog) {
3896                            handler.cancel();
3897                            BrowserActivity.this.resetTitleAndRevertLockIcon();
3898                            mHttpAuthenticationDialog = null;
3899                            mHttpAuthHandler = null;
3900                        }})
3901                .create();
3902        // Make the IME appear when the dialog is displayed if applicable.
3903        dialog.getWindow().setSoftInputMode(
3904                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3905        dialog.show();
3906        if (focusId != 0) {
3907            dialog.findViewById(focusId).requestFocus();
3908        } else {
3909            v.findViewById(R.id.username_edit).requestFocus();
3910        }
3911        mHttpAuthenticationDialog = dialog;
3912    }
3913
3914    public int getProgress() {
3915        WebView w = mTabControl.getCurrentWebView();
3916        if (w != null) {
3917            return w.getProgress();
3918        } else {
3919            return 100;
3920        }
3921    }
3922
3923    /**
3924     * Set HTTP authentication password.
3925     *
3926     * @param host The host for the password
3927     * @param realm The realm for the password
3928     * @param username The username for the password. If it is null, it means
3929     *            password can't be saved.
3930     * @param password The password
3931     */
3932    public void setHttpAuthUsernamePassword(String host, String realm,
3933                                            String username,
3934                                            String password) {
3935        WebView w = mTabControl.getCurrentWebView();
3936        if (w != null) {
3937            w.setHttpAuthUsernamePassword(host, realm, username, password);
3938        }
3939    }
3940
3941    /**
3942     * connectivity manager says net has come or gone... inform the user
3943     * @param up true if net has come up, false if net has gone down
3944     */
3945    public void onNetworkToggle(boolean up) {
3946        if (up == mIsNetworkUp) {
3947            return;
3948        } else if (up) {
3949            mIsNetworkUp = true;
3950            if (mAlertDialog != null) {
3951                mAlertDialog.cancel();
3952                mAlertDialog = null;
3953            }
3954        } else {
3955            mIsNetworkUp = false;
3956            if (mInLoad) {
3957                createAndShowNetworkDialog();
3958           }
3959        }
3960        WebView w = mTabControl.getCurrentWebView();
3961        if (w != null) {
3962            w.setNetworkAvailable(up);
3963        }
3964    }
3965
3966    // This method shows the network dialog alerting the user that the net is
3967    // down. It will only show the dialog if mAlertDialog is null.
3968    private void createAndShowNetworkDialog() {
3969        if (mAlertDialog == null) {
3970            mAlertDialog = new AlertDialog.Builder(this)
3971                    .setTitle(R.string.loadSuspendedTitle)
3972                    .setMessage(R.string.loadSuspended)
3973                    .setPositiveButton(R.string.ok, null)
3974                    .show();
3975        }
3976    }
3977
3978    @Override
3979    protected void onActivityResult(int requestCode, int resultCode,
3980                                    Intent intent) {
3981        switch (requestCode) {
3982            case COMBO_PAGE:
3983                if (resultCode == RESULT_OK && intent != null) {
3984                    String data = intent.getAction();
3985                    Bundle extras = intent.getExtras();
3986                    if (extras != null && extras.getBoolean("new_window", false)) {
3987                        openTab(data);
3988                    } else {
3989                        final TabControl.Tab currentTab =
3990                                mTabControl.getCurrentTab();
3991                        dismissSubWindow(currentTab);
3992                        if (data != null && data.length() != 0) {
3993                            getTopWindow().loadUrl(data);
3994                        }
3995                    }
3996                }
3997                break;
3998            default:
3999                break;
4000        }
4001        getTopWindow().requestFocus();
4002    }
4003
4004    /*
4005     * This method is called as a result of the user selecting the options
4006     * menu to see the download window, or when a download changes state. It
4007     * shows the download window ontop of the current window.
4008     */
4009    /* package */ void viewDownloads(Uri downloadRecord) {
4010        Intent intent = new Intent(this,
4011                BrowserDownloadPage.class);
4012        intent.setData(downloadRecord);
4013        startActivityForResult(intent, this.DOWNLOAD_PAGE);
4014
4015    }
4016
4017    /**
4018     * Open the Go page.
4019     * @param startWithHistory If true, open starting on the history tab.
4020     *                         Otherwise, start with the bookmarks tab.
4021     */
4022    /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
4023        WebView current = mTabControl.getCurrentWebView();
4024        if (current == null) {
4025            return;
4026        }
4027        Intent intent = new Intent(this,
4028                CombinedBookmarkHistoryActivity.class);
4029        String title = current.getTitle();
4030        String url = current.getUrl();
4031        // Just in case the user opens bookmarks before a page finishes loading
4032        // so the current history item, and therefore the page, is null.
4033        if (null == url) {
4034            url = mLastEnteredUrl;
4035            // This can happen.
4036            if (null == url) {
4037                url = mSettings.getHomePage();
4038            }
4039        }
4040        // In case the web page has not yet received its associated title.
4041        if (title == null) {
4042            title = url;
4043        }
4044        intent.putExtra("title", title);
4045        intent.putExtra("url", url);
4046        // Disable opening in a new window if we have maxed out the windows
4047        intent.putExtra("disable_new_window", mTabControl.getTabCount()
4048                >= TabControl.MAX_TABS);
4049        intent.putExtra("touch_icon_url", current.getTouchIconUrl());
4050        if (startWithHistory) {
4051            intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
4052                    CombinedBookmarkHistoryActivity.HISTORY_TAB);
4053        }
4054        startActivityForResult(intent, COMBO_PAGE);
4055    }
4056
4057    // Called when loading from context menu or LOAD_URL message
4058    private void loadURL(WebView view, String url) {
4059        // In case the user enters nothing.
4060        if (url != null && url.length() != 0 && view != null) {
4061            url = smartUrlFilter(url);
4062            if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
4063                view.loadUrl(url);
4064            }
4065        }
4066    }
4067
4068    private String smartUrlFilter(Uri inUri) {
4069        if (inUri != null) {
4070            return smartUrlFilter(inUri.toString());
4071        }
4072        return null;
4073    }
4074
4075
4076    // get window count
4077
4078    int getWindowCount(){
4079      if(mTabControl != null){
4080        return mTabControl.getTabCount();
4081      }
4082      return 0;
4083    }
4084
4085    protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
4086            "(?i)" + // switch on case insensitive matching
4087            "(" +    // begin group for schema
4088            "(?:http|https|file):\\/\\/" +
4089            "|(?:inline|data|about|content|javascript):" +
4090            ")" +
4091            "(.*)" );
4092
4093    /**
4094     * Attempts to determine whether user input is a URL or search
4095     * terms.  Anything with a space is passed to search.
4096     *
4097     * Converts to lowercase any mistakenly uppercased schema (i.e.,
4098     * "Http://" converts to "http://"
4099     *
4100     * @return Original or modified URL
4101     *
4102     */
4103    String smartUrlFilter(String url) {
4104
4105        String inUrl = url.trim();
4106        boolean hasSpace = inUrl.indexOf(' ') != -1;
4107
4108        Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4109        if (matcher.matches()) {
4110            // force scheme to lowercase
4111            String scheme = matcher.group(1);
4112            String lcScheme = scheme.toLowerCase();
4113            if (!lcScheme.equals(scheme)) {
4114                inUrl = lcScheme + matcher.group(2);
4115            }
4116            if (hasSpace) {
4117                inUrl = inUrl.replace(" ", "%20");
4118            }
4119            return inUrl;
4120        }
4121        if (hasSpace) {
4122            // FIXME: Is this the correct place to add to searches?
4123            // what if someone else calls this function?
4124            int shortcut = parseUrlShortcut(inUrl);
4125            if (shortcut != SHORTCUT_INVALID) {
4126                Browser.addSearchUrl(mResolver, inUrl);
4127                String query = inUrl.substring(2);
4128                switch (shortcut) {
4129                case SHORTCUT_GOOGLE_SEARCH:
4130                    return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
4131                case SHORTCUT_WIKIPEDIA_SEARCH:
4132                    return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4133                case SHORTCUT_DICTIONARY_SEARCH:
4134                    return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4135                case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
4136                    // FIXME: we need location in this case
4137                    return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
4138                }
4139            }
4140        } else {
4141            if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4142                return URLUtil.guessUrl(inUrl);
4143            }
4144        }
4145
4146        Browser.addSearchUrl(mResolver, inUrl);
4147        return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
4148    }
4149
4150    /* package */ void setShouldShowErrorConsole(boolean flag) {
4151        if (flag == mShouldShowErrorConsole) {
4152            // Nothing to do.
4153            return;
4154        }
4155
4156        mShouldShowErrorConsole = flag;
4157
4158        ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4159
4160        if (flag) {
4161            // Setting the show state of the console will cause it's the layout to be inflated.
4162            if (errorConsole.numberOfErrors() > 0) {
4163                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4164            } else {
4165                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4166            }
4167
4168            // Now we can add it to the main view.
4169            mErrorConsoleContainer.addView(errorConsole,
4170                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4171                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
4172        } else {
4173            mErrorConsoleContainer.removeView(errorConsole);
4174        }
4175
4176    }
4177
4178    final static int LOCK_ICON_UNSECURE = 0;
4179    final static int LOCK_ICON_SECURE   = 1;
4180    final static int LOCK_ICON_MIXED    = 2;
4181
4182    private int mLockIconType = LOCK_ICON_UNSECURE;
4183    private int mPrevLockType = LOCK_ICON_UNSECURE;
4184
4185    private BrowserSettings mSettings;
4186    private TabControl      mTabControl;
4187    private ContentResolver mResolver;
4188    private FrameLayout     mContentView;
4189    private View            mCustomView;
4190    private FrameLayout     mCustomViewContainer;
4191    private WebChromeClient.CustomViewCallback mCustomViewCallback;
4192
4193    // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4194    // view, we should rewrite this.
4195    private int mCurrentMenuState = 0;
4196    private int mMenuState = R.id.MAIN_MENU;
4197    private int mOldMenuState = EMPTY_MENU;
4198    private static final int EMPTY_MENU = -1;
4199    private Menu mMenu;
4200
4201    private FindDialog mFindDialog;
4202    // Used to prevent chording to result in firing two shortcuts immediately
4203    // one after another.  Fixes bug 1211714.
4204    boolean mCanChord;
4205
4206    private boolean mInLoad;
4207    private boolean mIsNetworkUp;
4208
4209    private boolean mPageStarted;
4210    private boolean mActivityInPause = true;
4211
4212    private boolean mMenuIsDown;
4213
4214    private final KeyTracker mKeyTracker = new KeyTracker(this);
4215
4216    // As trackball doesn't send repeat down, we have to track it ourselves
4217    private boolean mTrackTrackball;
4218
4219    private static boolean mInTrace;
4220
4221    // Performance probe
4222    private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4223            Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4224            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4225            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4226            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4227            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4228            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4229            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4230            Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG  // 7: softirq time
4231    };
4232
4233    private long mStart;
4234    private long mProcessStart;
4235    private long mUserStart;
4236    private long mSystemStart;
4237    private long mIdleStart;
4238    private long mIrqStart;
4239
4240    private long mUiStart;
4241
4242    private Drawable    mMixLockIcon;
4243    private Drawable    mSecLockIcon;
4244
4245    /* hold a ref so we can auto-cancel if necessary */
4246    private AlertDialog mAlertDialog;
4247
4248    // Wait for credentials before loading google.com
4249    private ProgressDialog mCredsDlg;
4250
4251    // The up-to-date URL and title (these can be different from those stored
4252    // in WebView, since it takes some time for the information in WebView to
4253    // get updated)
4254    private String mUrl;
4255    private String mTitle;
4256
4257    // As PageInfo has different style for landscape / portrait, we have
4258    // to re-open it when configuration changed
4259    private AlertDialog mPageInfoDialog;
4260    private TabControl.Tab mPageInfoView;
4261    // If the Page-Info dialog is launched from the SSL-certificate-on-error
4262    // dialog, we should not just dismiss it, but should get back to the
4263    // SSL-certificate-on-error dialog. This flag is used to store this state
4264    private Boolean mPageInfoFromShowSSLCertificateOnError;
4265
4266    // as SSLCertificateOnError has different style for landscape / portrait,
4267    // we have to re-open it when configuration changed
4268    private AlertDialog mSSLCertificateOnErrorDialog;
4269    private WebView mSSLCertificateOnErrorView;
4270    private SslErrorHandler mSSLCertificateOnErrorHandler;
4271    private SslError mSSLCertificateOnErrorError;
4272
4273    // as SSLCertificate has different style for landscape / portrait, we
4274    // have to re-open it when configuration changed
4275    private AlertDialog mSSLCertificateDialog;
4276    private TabControl.Tab mSSLCertificateView;
4277
4278    // as HttpAuthentication has different style for landscape / portrait, we
4279    // have to re-open it when configuration changed
4280    private AlertDialog mHttpAuthenticationDialog;
4281    private HttpAuthHandler mHttpAuthHandler;
4282
4283    /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4284                                            new FrameLayout.LayoutParams(
4285                                            ViewGroup.LayoutParams.FILL_PARENT,
4286                                            ViewGroup.LayoutParams.FILL_PARENT);
4287    /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4288                                            new FrameLayout.LayoutParams(
4289                                            ViewGroup.LayoutParams.FILL_PARENT,
4290                                            ViewGroup.LayoutParams.FILL_PARENT,
4291                                            Gravity.CENTER);
4292    // Google search
4293    final static String QuickSearch_G = "http://www.google.com/m?q=%s";
4294    // Wikipedia search
4295    final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4296    // Dictionary search
4297    final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4298    // Google Mobile Local search
4299    final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4300
4301    final static String QUERY_PLACE_HOLDER = "%s";
4302
4303    // "source" parameter for Google search through search key
4304    final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4305    // "source" parameter for Google search through goto menu
4306    final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4307    // "source" parameter for Google search through simplily type
4308    final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4309    // "source" parameter for Google search suggested by the browser
4310    final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4311    // "source" parameter for Google search from unknown source
4312    final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4313
4314    private final static String LOGTAG = "browser";
4315
4316    private String mLastEnteredUrl;
4317
4318    private PowerManager.WakeLock mWakeLock;
4319    private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4320
4321    private Toast mStopToast;
4322
4323    private TitleBar mTitleBar;
4324
4325    private LinearLayout mErrorConsoleContainer = null;
4326    private boolean mShouldShowErrorConsole = false;
4327
4328    // As the ids are dynamically created, we can't guarantee that they will
4329    // be in sequence, so this static array maps ids to a window number.
4330    final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4331    { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4332      R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4333      R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4334
4335    // monitor platform changes
4336    private IntentFilter mNetworkStateChangedFilter;
4337    private BroadcastReceiver mNetworkStateIntentReceiver;
4338
4339    private BroadcastReceiver mPackageInstallationReceiver;
4340
4341    // activity requestCode
4342    final static int COMBO_PAGE                 = 1;
4343    final static int DOWNLOAD_PAGE              = 2;
4344    final static int PREFERENCES_PAGE           = 3;
4345
4346    /**
4347     * A UrlData class to abstract how the content will be set to WebView.
4348     * This base class uses loadUrl to show the content.
4349     */
4350    private static class UrlData {
4351        String mUrl;
4352        byte[] mPostData;
4353
4354        UrlData(String url) {
4355            this.mUrl = url;
4356        }
4357
4358        void setPostData(byte[] postData) {
4359            mPostData = postData;
4360        }
4361
4362        boolean isEmpty() {
4363            return mUrl == null || mUrl.length() == 0;
4364        }
4365
4366        public void loadIn(WebView webView) {
4367            if (mPostData != null) {
4368                webView.postUrl(mUrl, mPostData);
4369            } else {
4370                webView.loadUrl(mUrl);
4371            }
4372        }
4373    };
4374
4375    /**
4376     * A subclass of UrlData class that can display inlined content using
4377     * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4378     */
4379    private static class InlinedUrlData extends UrlData {
4380        InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4381            super(failUrl);
4382            mInlined = inlined;
4383            mMimeType = mimeType;
4384            mEncoding = encoding;
4385        }
4386        String mMimeType;
4387        String mInlined;
4388        String mEncoding;
4389        @Override
4390        boolean isEmpty() {
4391            return mInlined == null || mInlined.length() == 0 || super.isEmpty();
4392        }
4393
4394        @Override
4395        public void loadIn(WebView webView) {
4396            webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4397        }
4398    }
4399
4400    /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
4401}
4402