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