Controller.java revision 3527dd1ff849ec4798eab6289593aa404dcae40b
1/*
2 * Copyright (C) 2010 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.android.browser.IntentHandler.UrlData;
20import com.android.browser.search.SearchEngine;
21import com.android.common.Search;
22
23import android.app.Activity;
24import android.app.DownloadManager;
25import android.app.SearchManager;
26import android.content.ClipboardManager;
27import android.content.ContentProvider;
28import android.content.ContentProviderClient;
29import android.content.ContentResolver;
30import android.content.ContentValues;
31import android.content.Context;
32import android.content.Intent;
33import android.content.pm.PackageManager;
34import android.content.pm.ResolveInfo;
35import android.content.res.Configuration;
36import android.database.ContentObserver;
37import android.database.Cursor;
38import android.database.sqlite.SQLiteDatabase;
39import android.graphics.Bitmap;
40import android.graphics.Canvas;
41import android.graphics.Picture;
42import android.net.Uri;
43import android.net.http.SslError;
44import android.os.AsyncTask;
45import android.os.Bundle;
46import android.os.Environment;
47import android.os.Handler;
48import android.os.Message;
49import android.os.PowerManager;
50import android.os.PowerManager.WakeLock;
51import android.preference.PreferenceActivity;
52import android.provider.Browser;
53import android.provider.BrowserContract;
54import android.provider.BrowserContract.Images;
55import android.provider.ContactsContract;
56import android.provider.ContactsContract.Intents.Insert;
57import android.speech.RecognizerIntent;
58import android.speech.RecognizerResultsIntent;
59import android.text.TextUtils;
60import android.util.Log;
61import android.util.Patterns;
62import android.view.ActionMode;
63import android.view.ContextMenu;
64import android.view.ContextMenu.ContextMenuInfo;
65import android.view.Gravity;
66import android.view.KeyEvent;
67import android.view.Menu;
68import android.view.MenuInflater;
69import android.view.MenuItem;
70import android.view.MenuItem.OnMenuItemClickListener;
71import android.view.View;
72import android.webkit.CookieManager;
73import android.webkit.CookieSyncManager;
74import android.webkit.HttpAuthHandler;
75import android.webkit.SslErrorHandler;
76import android.webkit.ValueCallback;
77import android.webkit.WebChromeClient;
78import android.webkit.WebIconDatabase;
79import android.webkit.WebSettings;
80import android.webkit.WebView;
81import android.widget.Toast;
82
83import java.io.ByteArrayOutputStream;
84import java.io.File;
85import java.net.URLEncoder;
86import java.util.Calendar;
87import java.util.HashMap;
88import java.util.List;
89
90/**
91 * Controller for browser
92 */
93public class Controller
94        implements WebViewController, UiController {
95
96    private static final String LOGTAG = "Controller";
97    private static final String SEND_APP_ID_EXTRA =
98        "android.speech.extras.SEND_APPLICATION_ID_EXTRA";
99
100
101    // public message ids
102    public final static int LOAD_URL = 1001;
103    public final static int STOP_LOAD = 1002;
104
105    // Message Ids
106    private static final int FOCUS_NODE_HREF = 102;
107    private static final int RELEASE_WAKELOCK = 107;
108
109    static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
110
111    private static final int OPEN_BOOKMARKS = 201;
112
113    private static final int EMPTY_MENU = -1;
114
115    // activity requestCode
116    final static int PREFERENCES_PAGE = 3;
117    final static int FILE_SELECTED = 4;
118    final static int AUTOFILL_SETUP = 5;
119
120    private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
121
122    // As the ids are dynamically created, we can't guarantee that they will
123    // be in sequence, so this static array maps ids to a window number.
124    final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
125    { R.id.window_one_menu_id, R.id.window_two_menu_id,
126      R.id.window_three_menu_id, R.id.window_four_menu_id,
127      R.id.window_five_menu_id, R.id.window_six_menu_id,
128      R.id.window_seven_menu_id, R.id.window_eight_menu_id };
129
130    // "source" parameter for Google search through search key
131    final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
132    // "source" parameter for Google search through simplily type
133    final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
134
135    private Activity mActivity;
136    private UI mUi;
137    private TabControl mTabControl;
138    private BrowserSettings mSettings;
139    private WebViewFactory mFactory;
140    private OptionsMenuHandler mOptionsMenuHandler = null;
141
142    private WakeLock mWakeLock;
143
144    private UrlHandler mUrlHandler;
145    private UploadHandler mUploadHandler;
146    private IntentHandler mIntentHandler;
147    private PageDialogsHandler mPageDialogsHandler;
148    private NetworkStateHandler mNetworkHandler;
149
150    private Message mAutoFillSetupMessage;
151
152    private boolean mShouldShowErrorConsole;
153
154    private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
155
156    // FIXME, temp address onPrepareMenu performance problem.
157    // When we move everything out of view, we should rewrite this.
158    private int mCurrentMenuState = 0;
159    private int mMenuState = R.id.MAIN_MENU;
160    private int mOldMenuState = EMPTY_MENU;
161    private Menu mCachedMenu;
162
163    // Used to prevent chording to result in firing two shortcuts immediately
164    // one after another.  Fixes bug 1211714.
165    boolean mCanChord;
166    private boolean mMenuIsDown;
167
168    // For select and find, we keep track of the ActionMode so that
169    // finish() can be called as desired.
170    private ActionMode mActionMode;
171
172    /**
173     * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
174     * of whether the configuration has changed.  The first onMenuOpened call
175     * after a configuration change is simply a reopening of the same menu
176     * (i.e. mIconView did not change).
177     */
178    private boolean mConfigChanged;
179
180    /**
181     * Keeps track of whether the options menu is open. This is important in
182     * determining whether to show or hide the title bar overlay
183     */
184    private boolean mOptionsMenuOpen;
185
186    /**
187     * Whether or not the options menu is in its bigger, popup menu form. When
188     * true, we want the title bar overlay to be gone. When false, we do not.
189     * Only meaningful if mOptionsMenuOpen is true.
190     */
191    private boolean mExtendedMenuOpen;
192
193    private boolean mInLoad;
194
195    private boolean mActivityPaused = true;
196    private boolean mLoadStopped;
197
198    private Handler mHandler;
199    // Checks to see when the bookmarks database has changed, and updates the
200    // Tabs' notion of whether they represent bookmarked sites.
201    private ContentObserver mBookmarksObserver;
202    private DataController mDataController;
203
204    private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
205        @Override
206        public Void doInBackground(File... files) {
207            if (files != null) {
208                for (File f : files) {
209                    if (!f.delete()) {
210                        Log.e(LOGTAG, f.getPath() + " was not deleted");
211                    }
212                }
213            }
214            return null;
215        }
216    }
217
218    public Controller(Activity browser) {
219        mActivity = browser;
220        mSettings = BrowserSettings.getInstance();
221        mDataController = DataController.getInstance(mActivity);
222        mTabControl = new TabControl(this);
223        mSettings.setController(this);
224
225        mUrlHandler = new UrlHandler(this);
226        mIntentHandler = new IntentHandler(mActivity, this);
227        mPageDialogsHandler = new PageDialogsHandler(mActivity, this);
228
229        PowerManager pm = (PowerManager) mActivity
230                .getSystemService(Context.POWER_SERVICE);
231        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
232
233        startHandler();
234        mBookmarksObserver = new ContentObserver(mHandler) {
235            @Override
236            public void onChange(boolean selfChange) {
237                int size = mTabControl.getTabCount();
238                for (int i = 0; i < size; i++) {
239                    mTabControl.getTab(i).updateBookmarkedStatus();
240                }
241            }
242
243        };
244        browser.getContentResolver().registerContentObserver(
245                BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
246
247        mNetworkHandler = new NetworkStateHandler(mActivity, this);
248        // Start watching the default geolocation permissions
249        mSystemAllowGeolocationOrigins =
250                new SystemAllowGeolocationOrigins(mActivity.getApplicationContext());
251        mSystemAllowGeolocationOrigins.start();
252
253        retainIconsOnStartup();
254    }
255
256    void start(final Bundle icicle, final Intent intent) {
257        // Unless the last browser usage was within 24 hours, destroy any
258        // remaining incognito tabs.
259
260        Calendar lastActiveDate = icicle != null ?
261                (Calendar) icicle.getSerializable("lastActiveDate") : null;
262        Calendar today = Calendar.getInstance();
263        Calendar yesterday = Calendar.getInstance();
264        yesterday.add(Calendar.DATE, -1);
265
266        final boolean restoreIncognitoTabs = !(lastActiveDate == null
267            || lastActiveDate.before(yesterday)
268            || lastActiveDate.after(today));
269
270        // Find out if we will restore any state and remember the tab.
271        final int currentTab =
272                mTabControl.canRestoreState(icicle, restoreIncognitoTabs);
273
274        if (currentTab == -1) {
275            // Not able to restore so we go ahead and clear session cookies.  We
276            // must do this before trying to login the user as we don't want to
277            // clear any session cookies set during login.
278            CookieManager.getInstance().removeSessionCookie();
279        }
280
281        GoogleAccountLogin.startLoginIfNeeded(mActivity, mSettings,
282                new Runnable() {
283                    @Override public void run() {
284                        start(icicle, intent, currentTab, restoreIncognitoTabs);
285                    }
286                });
287    }
288
289    private void start(Bundle icicle, Intent intent, int currentTab,
290            boolean restoreIncognitoTabs) {
291        if (currentTab == -1) {
292            final Bundle extra = intent.getExtras();
293            // Create an initial tab.
294            // If the intent is ACTION_VIEW and data is not null, the Browser is
295            // invoked to view the content by another application. In this case,
296            // the tab will be close when exit.
297            UrlData urlData = mIntentHandler.getUrlDataFromIntent(intent);
298
299            String action = intent.getAction();
300            final Tab t = mTabControl.createNewTab(
301                    (Intent.ACTION_VIEW.equals(action) &&
302                    intent.getData() != null)
303                    || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
304                    .equals(action),
305                    intent.getStringExtra(Browser.EXTRA_APPLICATION_ID),
306                    urlData.mUrl, false);
307            addTab(t);
308            setActiveTab(t);
309            WebView webView = t.getWebView();
310            if (extra != null) {
311                int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
312                if (scale > 0 && scale <= 1000) {
313                    webView.setInitialScale(scale);
314                }
315            }
316
317            if (urlData.isEmpty()) {
318                loadUrl(webView, mSettings.getHomePage());
319            } else {
320                loadUrlDataIn(t, urlData);
321            }
322        } else {
323            mTabControl.restoreState(icicle, currentTab, restoreIncognitoTabs,
324                    mUi.needsRestoreAllTabs());
325            mUi.updateTabs(mTabControl.getTabs());
326            // TabControl.restoreState() will create a new tab even if
327            // restoring the state fails.
328            setActiveTab(mTabControl.getCurrentTab());
329        }
330        // clear up the thumbnail directory, which is no longer used;
331        // ideally this should only be run once after an upgrade from
332        // a previous version of the browser
333        new ClearThumbnails().execute(mTabControl.getThumbnailDir()
334                .listFiles());
335        // Read JavaScript flags if it exists.
336        String jsFlags = getSettings().getJsFlags();
337        if (jsFlags.trim().length() != 0) {
338            getCurrentWebView().setJsFlags(jsFlags);
339        }
340        if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(intent.getAction())) {
341            bookmarksOrHistoryPicker(false);
342        }
343    }
344
345    void setWebViewFactory(WebViewFactory factory) {
346        mFactory = factory;
347    }
348
349    @Override
350    public WebViewFactory getWebViewFactory() {
351        return mFactory;
352    }
353
354    @Override
355    public void onSetWebView(Tab tab, WebView view) {
356        mUi.onSetWebView(tab, view);
357    }
358
359    @Override
360    public void createSubWindow(Tab tab) {
361        endActionMode();
362        WebView mainView = tab.getWebView();
363        WebView subView = mFactory.createWebView((mainView == null)
364                ? false
365                : mainView.isPrivateBrowsingEnabled());
366        mUi.createSubWindow(tab, subView);
367    }
368
369    @Override
370    public Activity getActivity() {
371        return mActivity;
372    }
373
374    void setUi(UI ui) {
375        mUi = ui;
376    }
377
378    BrowserSettings getSettings() {
379        return mSettings;
380    }
381
382    IntentHandler getIntentHandler() {
383        return mIntentHandler;
384    }
385
386    @Override
387    public UI getUi() {
388        return mUi;
389    }
390
391    int getMaxTabs() {
392        return mActivity.getResources().getInteger(R.integer.max_tabs);
393    }
394
395    @Override
396    public TabControl getTabControl() {
397        return mTabControl;
398    }
399
400    @Override
401    public List<Tab> getTabs() {
402        return mTabControl.getTabs();
403    }
404
405    // Open the icon database and retain all the icons for visited sites.
406    // This is done on a background thread so as not to stall startup.
407    private void retainIconsOnStartup() {
408        // WebIconDatabase needs to be retrieved on the UI thread so that if
409        // it has not been created successfully yet the Handler is started on the
410        // UI thread.
411        new RetainIconsOnStartupTask(WebIconDatabase.getInstance()).execute();
412    }
413
414    private class RetainIconsOnStartupTask extends AsyncTask<Void, Void, Void> {
415        private WebIconDatabase mDb;
416
417        public RetainIconsOnStartupTask(WebIconDatabase db) {
418            mDb = db;
419        }
420
421        @Override
422        protected Void doInBackground(Void... unused) {
423            mDb.open(mActivity.getDir("icons", 0).getPath());
424            Cursor c = null;
425            try {
426                c = Browser.getAllBookmarks(mActivity.getContentResolver());
427                if (c.moveToFirst()) {
428                    int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
429                    do {
430                        String url = c.getString(urlIndex);
431                        mDb.retainIconForPageUrl(url);
432                    } while (c.moveToNext());
433                }
434            } catch (IllegalStateException e) {
435                Log.e(LOGTAG, "retainIconsOnStartup", e);
436            } finally {
437                if (c != null) c.close();
438            }
439
440            return null;
441        }
442    }
443
444    private void startHandler() {
445        mHandler = new Handler() {
446
447            @Override
448            public void handleMessage(Message msg) {
449                switch (msg.what) {
450                    case OPEN_BOOKMARKS:
451                        bookmarksOrHistoryPicker(false);
452                        break;
453                    case FOCUS_NODE_HREF:
454                    {
455                        String url = (String) msg.getData().get("url");
456                        String title = (String) msg.getData().get("title");
457                        String src = (String) msg.getData().get("src");
458                        if (url == "") url = src; // use image if no anchor
459                        if (TextUtils.isEmpty(url)) {
460                            break;
461                        }
462                        HashMap focusNodeMap = (HashMap) msg.obj;
463                        WebView view = (WebView) focusNodeMap.get("webview");
464                        // Only apply the action if the top window did not change.
465                        if (getCurrentTopWebView() != view) {
466                            break;
467                        }
468                        switch (msg.arg1) {
469                            case R.id.open_context_menu_id:
470                                loadUrlFromContext(getCurrentTopWebView(), url);
471                                break;
472                            case R.id.view_image_context_menu_id:
473                                loadUrlFromContext(getCurrentTopWebView(), src);
474                                break;
475                            case R.id.open_newtab_context_menu_id:
476                                final Tab parent = mTabControl.getCurrentTab();
477                                final Tab newTab = openTab(parent, url, false);
478                                if (newTab != null && newTab != parent) {
479                                    parent.addChildTab(newTab);
480                                }
481                                break;
482                            case R.id.copy_link_context_menu_id:
483                                copy(url);
484                                break;
485                            case R.id.save_link_context_menu_id:
486                            case R.id.download_context_menu_id:
487                                DownloadHandler.onDownloadStartNoStream(
488                                        mActivity, url, null, null, null);
489                                break;
490                        }
491                        break;
492                    }
493
494                    case LOAD_URL:
495                        loadUrlFromContext(getCurrentTopWebView(), (String) msg.obj);
496                        break;
497
498                    case STOP_LOAD:
499                        stopLoading();
500                        break;
501
502                    case RELEASE_WAKELOCK:
503                        if (mWakeLock.isHeld()) {
504                            mWakeLock.release();
505                            // if we reach here, Browser should be still in the
506                            // background loading after WAKELOCK_TIMEOUT (5-min).
507                            // To avoid burning the battery, stop loading.
508                            mTabControl.stopAllLoading();
509                        }
510                        break;
511
512                    case UPDATE_BOOKMARK_THUMBNAIL:
513                        Tab tab = (Tab) msg.obj;
514                        if (tab != null) {
515                            updateScreenshot(tab);
516                        }
517                        break;
518                }
519            }
520        };
521
522    }
523
524    @Override
525    public void shareCurrentPage() {
526        shareCurrentPage(mTabControl.getCurrentTab());
527    }
528
529    private void shareCurrentPage(Tab tab) {
530        if (tab != null) {
531            sharePage(mActivity, tab.getTitle(),
532                    tab.getUrl(), tab.getFavicon(),
533                    createScreenshot(tab.getWebView(),
534                            getDesiredThumbnailWidth(mActivity),
535                            getDesiredThumbnailHeight(mActivity)));
536        }
537    }
538
539    /**
540     * Share a page, providing the title, url, favicon, and a screenshot.  Uses
541     * an {@link Intent} to launch the Activity chooser.
542     * @param c Context used to launch a new Activity.
543     * @param title Title of the page.  Stored in the Intent with
544     *          {@link Intent#EXTRA_SUBJECT}
545     * @param url URL of the page.  Stored in the Intent with
546     *          {@link Intent#EXTRA_TEXT}
547     * @param favicon Bitmap of the favicon for the page.  Stored in the Intent
548     *          with {@link Browser#EXTRA_SHARE_FAVICON}
549     * @param screenshot Bitmap of a screenshot of the page.  Stored in the
550     *          Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
551     */
552    static final void sharePage(Context c, String title, String url,
553            Bitmap favicon, Bitmap screenshot) {
554        Intent send = new Intent(Intent.ACTION_SEND);
555        send.setType("text/plain");
556        send.putExtra(Intent.EXTRA_TEXT, url);
557        send.putExtra(Intent.EXTRA_SUBJECT, title);
558        send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
559        send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
560        try {
561            c.startActivity(Intent.createChooser(send, c.getString(
562                    R.string.choosertitle_sharevia)));
563        } catch(android.content.ActivityNotFoundException ex) {
564            // if no app handles it, do nothing
565        }
566    }
567
568    private void copy(CharSequence text) {
569        ClipboardManager cm = (ClipboardManager) mActivity
570                .getSystemService(Context.CLIPBOARD_SERVICE);
571        cm.setText(text);
572    }
573
574    // lifecycle
575
576    protected void onConfgurationChanged(Configuration config) {
577        mConfigChanged = true;
578        if (mPageDialogsHandler != null) {
579            mPageDialogsHandler.onConfigurationChanged(config);
580        }
581        mUi.onConfigurationChanged(config);
582    }
583
584    @Override
585    public void handleNewIntent(Intent intent) {
586        mIntentHandler.onNewIntent(intent);
587    }
588
589    protected void onPause() {
590        if (mUi.isCustomViewShowing()) {
591            hideCustomView();
592        }
593        if (mActivityPaused) {
594            Log.e(LOGTAG, "BrowserActivity is already paused.");
595            return;
596        }
597        mActivityPaused = true;
598        Tab tab = mTabControl.getCurrentTab();
599        if (tab != null) {
600            tab.pause();
601            if (!pauseWebViewTimers(tab)) {
602                mWakeLock.acquire();
603                mHandler.sendMessageDelayed(mHandler
604                        .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
605            }
606        }
607        mUi.onPause();
608        mNetworkHandler.onPause();
609
610        WebView.disablePlatformNotifications();
611    }
612
613    void onSaveInstanceState(Bundle outState) {
614        // the default implementation requires each view to have an id. As the
615        // browser handles the state itself and it doesn't use id for the views,
616        // don't call the default implementation. Otherwise it will trigger the
617        // warning like this, "couldn't save which view has focus because the
618        // focused view XXX has no id".
619
620        // Save all the tabs
621        mTabControl.saveState(outState);
622        // Save time so that we know how old incognito tabs (if any) are.
623        outState.putSerializable("lastActiveDate", Calendar.getInstance());
624    }
625
626    void onResume() {
627        if (!mActivityPaused) {
628            Log.e(LOGTAG, "BrowserActivity is already resumed.");
629            return;
630        }
631        mActivityPaused = false;
632        Tab current = mTabControl.getCurrentTab();
633        if (current != null) {
634            current.resume();
635            resumeWebViewTimers(current);
636        }
637        if (mWakeLock.isHeld()) {
638            mHandler.removeMessages(RELEASE_WAKELOCK);
639            mWakeLock.release();
640        }
641        mUi.onResume();
642        mNetworkHandler.onResume();
643        WebView.enablePlatformNotifications();
644    }
645
646    /**
647     * resume all WebView timers using the WebView instance of the given tab
648     * @param tab guaranteed non-null
649     */
650    private void resumeWebViewTimers(Tab tab) {
651        boolean inLoad = tab.inPageLoad();
652        if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) {
653            CookieSyncManager.getInstance().startSync();
654            WebView w = tab.getWebView();
655            if (w != null) {
656                w.resumeTimers();
657            }
658        }
659    }
660
661    /**
662     * Pause all WebView timers using the WebView of the given tab
663     * @param tab
664     * @return true if the timers are paused or tab is null
665     */
666    private boolean pauseWebViewTimers(Tab tab) {
667        if (tab == null) {
668            return true;
669        } else if (!tab.inPageLoad()) {
670            CookieSyncManager.getInstance().stopSync();
671            WebView w = getCurrentWebView();
672            if (w != null) {
673                w.pauseTimers();
674            }
675            return true;
676        }
677        return false;
678    }
679
680    void onDestroy() {
681        if (mUploadHandler != null) {
682            mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
683            mUploadHandler = null;
684        }
685        if (mTabControl == null) return;
686        mUi.onDestroy();
687        // Remove the current tab and sub window
688        Tab t = mTabControl.getCurrentTab();
689        if (t != null) {
690            dismissSubWindow(t);
691            removeTab(t);
692        }
693        mActivity.getContentResolver().unregisterContentObserver(mBookmarksObserver);
694        // Destroy all the tabs
695        mTabControl.destroy();
696        WebIconDatabase.getInstance().close();
697        // Stop watching the default geolocation permissions
698        mSystemAllowGeolocationOrigins.stop();
699        mSystemAllowGeolocationOrigins = null;
700    }
701
702    protected boolean isActivityPaused() {
703        return mActivityPaused;
704    }
705
706    protected void onLowMemory() {
707        mTabControl.freeMemory();
708    }
709
710    @Override
711    public boolean shouldShowErrorConsole() {
712        return mShouldShowErrorConsole;
713    }
714
715    protected void setShouldShowErrorConsole(boolean show) {
716        if (show == mShouldShowErrorConsole) {
717            // Nothing to do.
718            return;
719        }
720        mShouldShowErrorConsole = show;
721        Tab t = mTabControl.getCurrentTab();
722        if (t == null) {
723            // There is no current tab so we cannot toggle the error console
724            return;
725        }
726        mUi.setShouldShowErrorConsole(t, show);
727    }
728
729    @Override
730    public void stopLoading() {
731        mLoadStopped = true;
732        Tab tab = mTabControl.getCurrentTab();
733        WebView w = getCurrentTopWebView();
734        w.stopLoading();
735        mUi.onPageStopped(tab);
736    }
737
738    boolean didUserStopLoading() {
739        return mLoadStopped;
740    }
741
742    // WebViewController
743
744    @Override
745    public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
746
747        // We've started to load a new page. If there was a pending message
748        // to save a screenshot then we will now take the new page and save
749        // an incorrect screenshot. Therefore, remove any pending thumbnail
750        // messages from the queue.
751        mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
752                tab);
753
754        // reset sync timer to avoid sync starts during loading a page
755        CookieSyncManager.getInstance().resetSync();
756
757        if (!mNetworkHandler.isNetworkUp()) {
758            view.setNetworkAvailable(false);
759        }
760
761        // when BrowserActivity just starts, onPageStarted may be called before
762        // onResume as it is triggered from onCreate. Call resumeWebViewTimers
763        // to start the timer. As we won't switch tabs while an activity is in
764        // pause state, we can ensure calling resume and pause in pair.
765        if (mActivityPaused) {
766            resumeWebViewTimers(tab);
767        }
768        mLoadStopped = false;
769        if (!mNetworkHandler.isNetworkUp()) {
770            mNetworkHandler.createAndShowNetworkDialog();
771        }
772        endActionMode();
773
774        mUi.onTabDataChanged(tab);
775
776        String url = tab.getUrl();
777        // update the bookmark database for favicon
778        maybeUpdateFavicon(tab, null, url, favicon);
779
780        Performance.tracePageStart(url);
781
782        // Performance probe
783        if (false) {
784            Performance.onPageStarted();
785        }
786
787    }
788
789    @Override
790    public void onPageFinished(Tab tab) {
791        mUi.onTabDataChanged(tab);
792        if (!tab.isPrivateBrowsingEnabled()
793                && !TextUtils.isEmpty(tab.getUrl())) {
794            if (tab.inForeground() && !didUserStopLoading()
795                    || !tab.inForeground()) {
796                // Only update the bookmark screenshot if the user did not
797                // cancel the load early.
798                mHandler.sendMessageDelayed(mHandler.obtainMessage(
799                        UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab),
800                        500);
801            }
802        }
803        // pause the WebView timer and release the wake lock if it is finished
804        // while BrowserActivity is in pause state.
805        if (mActivityPaused && pauseWebViewTimers(tab)) {
806            if (mWakeLock.isHeld()) {
807                mHandler.removeMessages(RELEASE_WAKELOCK);
808                mWakeLock.release();
809            }
810        }
811        // Performance probe
812        if (false) {
813            Performance.onPageFinished(tab.getUrl());
814         }
815
816        Performance.tracePageFinished();
817    }
818
819    @Override
820    public void onProgressChanged(Tab tab) {
821        int newProgress = tab.getLoadProgress();
822
823        if (newProgress == 100) {
824            CookieSyncManager.getInstance().sync();
825            // onProgressChanged() may continue to be called after the main
826            // frame has finished loading, as any remaining sub frames continue
827            // to load. We'll only get called once though with newProgress as
828            // 100 when everything is loaded. (onPageFinished is called once
829            // when the main frame completes loading regardless of the state of
830            // any sub frames so calls to onProgressChanges may continue after
831            // onPageFinished has executed)
832            if (mInLoad) {
833                mInLoad = false;
834                updateInLoadMenuItems(mCachedMenu);
835            }
836        } else {
837            if (!mInLoad) {
838                // onPageFinished may have already been called but a subframe is
839                // still loading and updating the progress. Reset mInLoad and
840                // update the menu items.
841                mInLoad = true;
842                updateInLoadMenuItems(mCachedMenu);
843            }
844        }
845        mUi.onProgressChanged(tab);
846    }
847
848    @Override
849    public void onUpdatedLockIcon(Tab tab) {
850        mUi.onTabDataChanged(tab);
851    }
852
853    @Override
854    public void onReceivedTitle(Tab tab, final String title) {
855        mUi.onTabDataChanged(tab);
856        final String pageUrl = tab.getUrl();
857        if (TextUtils.isEmpty(pageUrl) || pageUrl.length()
858                >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
859            return;
860        }
861        // Update the title in the history database if not in private browsing mode
862        if (!tab.isPrivateBrowsingEnabled()) {
863            mDataController.updateHistoryTitle(pageUrl, title);
864        }
865    }
866
867    @Override
868    public void onFavicon(Tab tab, WebView view, Bitmap icon) {
869        mUi.onTabDataChanged(tab);
870        maybeUpdateFavicon(tab, view.getOriginalUrl(), view.getUrl(), icon);
871    }
872
873    @Override
874    public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
875        return mUrlHandler.shouldOverrideUrlLoading(tab, view, url);
876    }
877
878    @Override
879    public boolean shouldOverrideKeyEvent(KeyEvent event) {
880        if (mMenuIsDown) {
881            // only check shortcut key when MENU is held
882            return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
883                    event);
884        } else {
885            return false;
886        }
887    }
888
889    @Override
890    public void onUnhandledKeyEvent(KeyEvent event) {
891        if (!isActivityPaused()) {
892            if (event.getAction() == KeyEvent.ACTION_DOWN) {
893                mActivity.onKeyDown(event.getKeyCode(), event);
894            } else {
895                mActivity.onKeyUp(event.getKeyCode(), event);
896            }
897        }
898    }
899
900    @Override
901    public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
902        // Don't save anything in private browsing mode
903        if (tab.isPrivateBrowsingEnabled()) return;
904        String url = tab.getUrl();
905
906        if (TextUtils.isEmpty(url)
907                || url.regionMatches(true, 0, "about:", 0, 6)) {
908            return;
909        }
910        mDataController.updateVisitedHistory(url);
911        WebIconDatabase.getInstance().retainIconForPageUrl(url);
912    }
913
914    @Override
915    public void getVisitedHistory(final ValueCallback<String[]> callback) {
916        AsyncTask<Void, Void, String[]> task =
917                new AsyncTask<Void, Void, String[]>() {
918            @Override
919            public String[] doInBackground(Void... unused) {
920                return Browser.getVisitedHistory(mActivity.getContentResolver());
921            }
922            @Override
923            public void onPostExecute(String[] result) {
924                callback.onReceiveValue(result);
925            }
926        };
927        task.execute();
928    }
929
930    @Override
931    public void onReceivedHttpAuthRequest(Tab tab, WebView view,
932            final HttpAuthHandler handler, final String host,
933            final String realm) {
934        String username = null;
935        String password = null;
936
937        boolean reuseHttpAuthUsernamePassword
938                = handler.useHttpAuthUsernamePassword();
939
940        if (reuseHttpAuthUsernamePassword && view != null) {
941            String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
942            if (credentials != null && credentials.length == 2) {
943                username = credentials[0];
944                password = credentials[1];
945            }
946        }
947
948        if (username != null && password != null) {
949            handler.proceed(username, password);
950        } else {
951            if (tab.inForeground()) {
952                mPageDialogsHandler.showHttpAuthentication(tab, handler, host, realm);
953            } else {
954                handler.cancel();
955            }
956        }
957    }
958
959    @Override
960    public void onDownloadStart(Tab tab, String url, String userAgent,
961            String contentDisposition, String mimetype, long contentLength) {
962        DownloadHandler.onDownloadStart(mActivity, url, userAgent,
963                contentDisposition, mimetype);
964        if (tab.getWebView().copyBackForwardList().getSize() == 0) {
965            // This Tab was opened for the sole purpose of downloading a
966            // file. Remove it.
967            if (tab == mTabControl.getCurrentTab()) {
968                // In this case, the Tab is still on top.
969                goBackOnePageOrQuit();
970            } else {
971                // In this case, it is not.
972                closeTab(tab);
973            }
974        }
975    }
976
977    @Override
978    public Bitmap getDefaultVideoPoster() {
979        return mUi.getDefaultVideoPoster();
980    }
981
982    @Override
983    public View getVideoLoadingProgressView() {
984        return mUi.getVideoLoadingProgressView();
985    }
986
987    @Override
988    public void showSslCertificateOnError(WebView view, SslErrorHandler handler,
989            SslError error) {
990        mPageDialogsHandler.showSSLCertificateOnError(view, handler, error);
991    }
992
993    // helper method
994
995    /*
996     * Update the favorites icon if the private browsing isn't enabled and the
997     * icon is valid.
998     */
999    private void maybeUpdateFavicon(Tab tab, final String originalUrl,
1000            final String url, Bitmap favicon) {
1001        if (favicon == null) {
1002            return;
1003        }
1004        if (!tab.isPrivateBrowsingEnabled()) {
1005            Bookmarks.updateFavicon(mActivity
1006                    .getContentResolver(), originalUrl, url, favicon);
1007        }
1008    }
1009
1010    @Override
1011    public void bookmarkedStatusHasChanged(Tab tab) {
1012        // TODO: Switch to using onTabDataChanged after b/3262950 is fixed
1013        mUi.bookmarkedStatusHasChanged(tab);
1014    }
1015
1016    // end WebViewController
1017
1018    protected void pageUp() {
1019        getCurrentTopWebView().pageUp(false);
1020    }
1021
1022    protected void pageDown() {
1023        getCurrentTopWebView().pageDown(false);
1024    }
1025
1026    // callback from phone title bar
1027    public void editUrl() {
1028        if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
1029        mUi.editUrl(false);
1030    }
1031
1032    public void startVoiceSearch() {
1033        Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
1034        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
1035                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
1036        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
1037                mActivity.getComponentName().flattenToString());
1038        intent.putExtra(SEND_APP_ID_EXTRA, false);
1039        intent.putExtra(RecognizerIntent.EXTRA_WEB_SEARCH_ONLY, true);
1040        mActivity.startActivity(intent);
1041    }
1042
1043    public void activateVoiceSearchMode(String title) {
1044        mUi.showVoiceTitleBar(title);
1045    }
1046
1047    public void revertVoiceSearchMode(Tab tab) {
1048        mUi.revertVoiceTitleBar(tab);
1049    }
1050
1051    public void showCustomView(Tab tab, View view,
1052            WebChromeClient.CustomViewCallback callback) {
1053        if (tab.inForeground()) {
1054            if (mUi.isCustomViewShowing()) {
1055                callback.onCustomViewHidden();
1056                return;
1057            }
1058            mUi.showCustomView(view, callback);
1059            // Save the menu state and set it to empty while the custom
1060            // view is showing.
1061            mOldMenuState = mMenuState;
1062            mMenuState = EMPTY_MENU;
1063            mActivity.invalidateOptionsMenu();
1064        }
1065    }
1066
1067    @Override
1068    public void hideCustomView() {
1069        if (mUi.isCustomViewShowing()) {
1070            mUi.onHideCustomView();
1071            // Reset the old menu state.
1072            mMenuState = mOldMenuState;
1073            mOldMenuState = EMPTY_MENU;
1074            mActivity.invalidateOptionsMenu();
1075        }
1076    }
1077
1078    protected void onActivityResult(int requestCode, int resultCode,
1079            Intent intent) {
1080        if (getCurrentTopWebView() == null) return;
1081        switch (requestCode) {
1082            case PREFERENCES_PAGE:
1083                if (resultCode == Activity.RESULT_OK && intent != null) {
1084                    String action = intent.getStringExtra(Intent.EXTRA_TEXT);
1085                    if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) {
1086                        mTabControl.removeParentChildRelationShips();
1087                    }
1088                }
1089                break;
1090            case FILE_SELECTED:
1091                // Choose a file from the file picker.
1092                if (null == mUploadHandler) break;
1093                mUploadHandler.onResult(resultCode, intent);
1094                mUploadHandler = null;
1095                break;
1096            case AUTOFILL_SETUP:
1097                // Determine whether a profile was actually set up or not
1098                // and if so, send the message back to the WebTextView to
1099                // fill the form with the new profile.
1100                if (getSettings().getAutoFillProfile() != null) {
1101                    mAutoFillSetupMessage.sendToTarget();
1102                    mAutoFillSetupMessage = null;
1103                }
1104                break;
1105            default:
1106                break;
1107        }
1108        getCurrentTopWebView().requestFocus();
1109    }
1110
1111    /**
1112     * Open the Go page.
1113     * @param startWithHistory If true, open starting on the history tab.
1114     *                         Otherwise, start with the bookmarks tab.
1115     */
1116    @Override
1117    public void bookmarksOrHistoryPicker(boolean startWithHistory) {
1118        if (mTabControl.getCurrentWebView() == null) {
1119            return;
1120        }
1121        // clear action mode
1122        if (isInCustomActionMode()) {
1123            endActionMode();
1124        }
1125        Bundle extras = new Bundle();
1126        // Disable opening in a new window if we have maxed out the windows
1127        extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
1128                !mTabControl.canCreateNewTab());
1129        mUi.showComboView(startWithHistory, extras);
1130    }
1131
1132    // combo view callbacks
1133
1134    /**
1135     * callback from ComboPage when clear history is requested
1136     */
1137    public void onRemoveParentChildRelationships() {
1138        mTabControl.removeParentChildRelationShips();
1139    }
1140
1141    /**
1142     * callback from ComboPage when bookmark/history selection
1143     */
1144    @Override
1145    public void onUrlSelected(String url, boolean newTab) {
1146        removeComboView();
1147        if (!TextUtils.isEmpty(url)) {
1148            if (newTab) {
1149                openTab(mTabControl.getCurrentTab(), url, false);
1150            } else {
1151                final Tab currentTab = mTabControl.getCurrentTab();
1152                dismissSubWindow(currentTab);
1153                loadUrl(getCurrentTopWebView(), url);
1154            }
1155        }
1156    }
1157
1158    /**
1159     * dismiss the ComboPage
1160     */
1161    @Override
1162    public void removeComboView() {
1163        mUi.hideComboView();
1164    }
1165
1166    // active tabs page handling
1167
1168    protected void showActiveTabsPage() {
1169        mMenuState = EMPTY_MENU;
1170        mUi.showActiveTabsPage();
1171    }
1172
1173    /**
1174     * Remove the active tabs page.
1175     * @param needToAttach If true, the active tabs page did not attach a tab
1176     *                     to the content view, so we need to do that here.
1177     */
1178    @Override
1179    public void removeActiveTabsPage(boolean needToAttach) {
1180        mMenuState = R.id.MAIN_MENU;
1181        mUi.removeActiveTabsPage();
1182        if (needToAttach) {
1183            setActiveTab(mTabControl.getCurrentTab());
1184        }
1185        getCurrentTopWebView().requestFocus();
1186    }
1187
1188    // key handling
1189    protected void onBackKey() {
1190        if (!mUi.onBackKey()) {
1191            WebView subwindow = mTabControl.getCurrentSubWindow();
1192            if (subwindow != null) {
1193                if (subwindow.canGoBack()) {
1194                    subwindow.goBack();
1195                } else {
1196                    dismissSubWindow(mTabControl.getCurrentTab());
1197                }
1198            } else {
1199                goBackOnePageOrQuit();
1200            }
1201        }
1202    }
1203
1204    // menu handling and state
1205    // TODO: maybe put into separate handler
1206
1207    protected boolean onCreateOptionsMenu(Menu menu) {
1208        if (mOptionsMenuHandler != null) {
1209            return mOptionsMenuHandler.onCreateOptionsMenu(menu);
1210        }
1211
1212        if (mMenuState == EMPTY_MENU) {
1213            return false;
1214        }
1215        MenuInflater inflater = mActivity.getMenuInflater();
1216        inflater.inflate(R.menu.browser, menu);
1217        updateInLoadMenuItems(menu);
1218        // hold on to the menu reference here; it is used by the page callbacks
1219        // to update the menu based on loading state
1220        mCachedMenu = menu;
1221        return true;
1222    }
1223
1224    protected void onCreateContextMenu(ContextMenu menu, View v,
1225            ContextMenuInfo menuInfo) {
1226        if (v instanceof TitleBarBase) {
1227            return;
1228        }
1229        if (!(v instanceof WebView)) {
1230            return;
1231        }
1232        final WebView webview = (WebView) v;
1233        WebView.HitTestResult result = webview.getHitTestResult();
1234        if (result == null) {
1235            return;
1236        }
1237
1238        int type = result.getType();
1239        if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1240            Log.w(LOGTAG,
1241                    "We should not show context menu when nothing is touched");
1242            return;
1243        }
1244        if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1245            // let TextView handles context menu
1246            return;
1247        }
1248
1249        // Note, http://b/issue?id=1106666 is requesting that
1250        // an inflated menu can be used again. This is not available
1251        // yet, so inflate each time (yuk!)
1252        MenuInflater inflater = mActivity.getMenuInflater();
1253        inflater.inflate(R.menu.browsercontext, menu);
1254
1255        // Show the correct menu group
1256        final String extra = result.getExtra();
1257        menu.setGroupVisible(R.id.PHONE_MENU,
1258                type == WebView.HitTestResult.PHONE_TYPE);
1259        menu.setGroupVisible(R.id.EMAIL_MENU,
1260                type == WebView.HitTestResult.EMAIL_TYPE);
1261        menu.setGroupVisible(R.id.GEO_MENU,
1262                type == WebView.HitTestResult.GEO_TYPE);
1263        menu.setGroupVisible(R.id.IMAGE_MENU,
1264                type == WebView.HitTestResult.IMAGE_TYPE
1265                || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1266        menu.setGroupVisible(R.id.ANCHOR_MENU,
1267                type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1268                || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1269        boolean hitText = type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1270                || type == WebView.HitTestResult.PHONE_TYPE
1271                || type == WebView.HitTestResult.EMAIL_TYPE
1272                || type == WebView.HitTestResult.GEO_TYPE;
1273        menu.setGroupVisible(R.id.SELECT_TEXT_MENU, hitText);
1274        if (hitText) {
1275            menu.findItem(R.id.select_text_menu_id)
1276                    .setOnMenuItemClickListener(new SelectText(webview));
1277        }
1278        // Setup custom handling depending on the type
1279        switch (type) {
1280            case WebView.HitTestResult.PHONE_TYPE:
1281                menu.setHeaderTitle(Uri.decode(extra));
1282                menu.findItem(R.id.dial_context_menu_id).setIntent(
1283                        new Intent(Intent.ACTION_VIEW, Uri
1284                                .parse(WebView.SCHEME_TEL + extra)));
1285                Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1286                addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1287                addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
1288                menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1289                        addIntent);
1290                menu.findItem(R.id.copy_phone_context_menu_id)
1291                        .setOnMenuItemClickListener(
1292                        new Copy(extra));
1293                break;
1294
1295            case WebView.HitTestResult.EMAIL_TYPE:
1296                menu.setHeaderTitle(extra);
1297                menu.findItem(R.id.email_context_menu_id).setIntent(
1298                        new Intent(Intent.ACTION_VIEW, Uri
1299                                .parse(WebView.SCHEME_MAILTO + extra)));
1300                menu.findItem(R.id.copy_mail_context_menu_id)
1301                        .setOnMenuItemClickListener(
1302                        new Copy(extra));
1303                break;
1304
1305            case WebView.HitTestResult.GEO_TYPE:
1306                menu.setHeaderTitle(extra);
1307                menu.findItem(R.id.map_context_menu_id).setIntent(
1308                        new Intent(Intent.ACTION_VIEW, Uri
1309                                .parse(WebView.SCHEME_GEO
1310                                        + URLEncoder.encode(extra))));
1311                menu.findItem(R.id.copy_geo_context_menu_id)
1312                        .setOnMenuItemClickListener(
1313                        new Copy(extra));
1314                break;
1315
1316            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1317            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1318                menu.setHeaderTitle(extra);
1319                // decide whether to show the open link in new tab option
1320                boolean showNewTab = mTabControl.canCreateNewTab();
1321                MenuItem newTabItem
1322                        = menu.findItem(R.id.open_newtab_context_menu_id);
1323                newTabItem.setTitle(
1324                        BrowserSettings.getInstance().openInBackground()
1325                        ? R.string.contextmenu_openlink_newwindow_background
1326                        : R.string.contextmenu_openlink_newwindow);
1327                newTabItem.setVisible(showNewTab);
1328                if (showNewTab) {
1329                    if (WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == type) {
1330                        newTabItem.setOnMenuItemClickListener(
1331                                new MenuItem.OnMenuItemClickListener() {
1332                                    @Override
1333                                    public boolean onMenuItemClick(MenuItem item) {
1334                                        final HashMap<String, WebView> hrefMap =
1335                                                new HashMap<String, WebView>();
1336                                        hrefMap.put("webview", webview);
1337                                        final Message msg = mHandler.obtainMessage(
1338                                                FOCUS_NODE_HREF,
1339                                                R.id.open_newtab_context_menu_id,
1340                                                0, hrefMap);
1341                                        webview.requestFocusNodeHref(msg);
1342                                        return true;
1343                                    }
1344                                });
1345                    } else {
1346                        newTabItem.setOnMenuItemClickListener(
1347                                new MenuItem.OnMenuItemClickListener() {
1348                                    @Override
1349                                    public boolean onMenuItemClick(MenuItem item) {
1350                                        final Tab parent = mTabControl.getCurrentTab();
1351                                        final Tab newTab = openTab(parent,
1352                                                extra, false);
1353                                        if (newTab != parent) {
1354                                            parent.addChildTab(newTab);
1355                                        }
1356                                        return true;
1357                                    }
1358                                });
1359                    }
1360                }
1361                if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1362                    break;
1363                }
1364                // otherwise fall through to handle image part
1365            case WebView.HitTestResult.IMAGE_TYPE:
1366                if (type == WebView.HitTestResult.IMAGE_TYPE) {
1367                    menu.setHeaderTitle(extra);
1368                }
1369                menu.findItem(R.id.view_image_context_menu_id).setIntent(
1370                        new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1371                menu.findItem(R.id.download_context_menu_id).
1372                        setOnMenuItemClickListener(new Download(mActivity, extra));
1373                menu.findItem(R.id.set_wallpaper_context_menu_id).
1374                        setOnMenuItemClickListener(new WallpaperHandler(mActivity,
1375                                extra));
1376                break;
1377
1378            default:
1379                Log.w(LOGTAG, "We should not get here.");
1380                break;
1381        }
1382        //update the ui
1383        mUi.onContextMenuCreated(menu);
1384    }
1385
1386    /**
1387     * As the menu can be open when loading state changes
1388     * we must manually update the state of the stop/reload menu
1389     * item
1390     */
1391    private void updateInLoadMenuItems(Menu menu) {
1392        if (menu == null) {
1393            return;
1394        }
1395        MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
1396        MenuItem src = mInLoad ?
1397                menu.findItem(R.id.stop_menu_id):
1398                menu.findItem(R.id.reload_menu_id);
1399        if (src != null) {
1400            dest.setIcon(src.getIcon());
1401            dest.setTitle(src.getTitle());
1402        }
1403    }
1404
1405    boolean onPrepareOptionsMenu(Menu menu) {
1406        if (mOptionsMenuHandler != null) {
1407            return mOptionsMenuHandler.onPrepareOptionsMenu(menu);
1408        }
1409        // This happens when the user begins to hold down the menu key, so
1410        // allow them to chord to get a shortcut.
1411        mCanChord = true;
1412        // Note: setVisible will decide whether an item is visible; while
1413        // setEnabled() will decide whether an item is enabled, which also means
1414        // whether the matching shortcut key will function.
1415        switch (mMenuState) {
1416            case EMPTY_MENU:
1417                if (mCurrentMenuState != mMenuState) {
1418                    menu.setGroupVisible(R.id.MAIN_MENU, false);
1419                    menu.setGroupEnabled(R.id.MAIN_MENU, false);
1420                    menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1421                }
1422                break;
1423            default:
1424                if (mCurrentMenuState != mMenuState) {
1425                    menu.setGroupVisible(R.id.MAIN_MENU, true);
1426                    menu.setGroupEnabled(R.id.MAIN_MENU, true);
1427                    menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1428                }
1429                final WebView w = getCurrentTopWebView();
1430                boolean canGoBack = false;
1431                boolean canGoForward = false;
1432                boolean isHome = false;
1433                if (w != null) {
1434                    canGoBack = w.canGoBack();
1435                    canGoForward = w.canGoForward();
1436                    isHome = mSettings.getHomePage().equals(w.getUrl());
1437                }
1438                final MenuItem back = menu.findItem(R.id.back_menu_id);
1439                back.setEnabled(canGoBack);
1440
1441                final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1442                home.setEnabled(!isHome);
1443
1444                final MenuItem forward = menu.findItem(R.id.forward_menu_id);
1445                forward.setEnabled(canGoForward);
1446
1447                // decide whether to show the share link option
1448                PackageManager pm = mActivity.getPackageManager();
1449                Intent send = new Intent(Intent.ACTION_SEND);
1450                send.setType("text/plain");
1451                ResolveInfo ri = pm.resolveActivity(send,
1452                        PackageManager.MATCH_DEFAULT_ONLY);
1453                menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1454
1455                boolean isNavDump = mSettings.isNavDump();
1456                final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1457                nav.setVisible(isNavDump);
1458                nav.setEnabled(isNavDump);
1459
1460                boolean showDebugSettings = mSettings.showDebugSettings();
1461                final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
1462                counter.setVisible(showDebugSettings);
1463                counter.setEnabled(showDebugSettings);
1464
1465                final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
1466                newtab.setEnabled(getTabControl().canCreateNewTab());
1467
1468                MenuItem archive = menu.findItem(R.id.save_webarchive_menu_id);
1469                String url = w != null ? w.getUrl() : null;
1470                archive.setVisible(url != null && !url.endsWith(".webarchivexml"));
1471                break;
1472        }
1473        mCurrentMenuState = mMenuState;
1474        return true;
1475    }
1476
1477    public boolean onOptionsItemSelected(MenuItem item) {
1478        if (mOptionsMenuHandler != null &&
1479                mOptionsMenuHandler.onOptionsItemSelected(item)) {
1480            return true;
1481        }
1482
1483        if (item.getGroupId() != R.id.CONTEXT_MENU) {
1484            // menu remains active, so ensure comboview is dismissed
1485            // if main menu option is selected
1486            removeComboView();
1487        }
1488        if (!mCanChord) {
1489            // The user has already fired a shortcut with this hold down of the
1490            // menu key.
1491            return false;
1492        }
1493        if (null == getCurrentTopWebView()) {
1494            return false;
1495        }
1496        if (mMenuIsDown) {
1497            // The shortcut action consumes the MENU. Even if it is still down,
1498            // it won't trigger the next shortcut action. In the case of the
1499            // shortcut action triggering a new activity, like Bookmarks, we
1500            // won't get onKeyUp for MENU. So it is important to reset it here.
1501            mMenuIsDown = false;
1502        }
1503        switch (item.getItemId()) {
1504            // -- Main menu
1505            case R.id.new_tab_menu_id:
1506                openTabToHomePage();
1507                break;
1508
1509            case R.id.incognito_menu_id:
1510                openIncognitoTab();
1511                break;
1512
1513            case R.id.goto_menu_id:
1514                editUrl();
1515                break;
1516
1517            case R.id.bookmarks_menu_id:
1518                bookmarksOrHistoryPicker(false);
1519                break;
1520
1521            case R.id.active_tabs_menu_id:
1522                showActiveTabsPage();
1523                break;
1524
1525            case R.id.add_bookmark_menu_id:
1526                bookmarkCurrentPage(AddBookmarkPage.DEFAULT_FOLDER_ID, false);
1527                break;
1528
1529            case R.id.stop_reload_menu_id:
1530                if (mInLoad) {
1531                    stopLoading();
1532                } else {
1533                    getCurrentTopWebView().reload();
1534                }
1535                break;
1536
1537            case R.id.back_menu_id:
1538                getCurrentTopWebView().goBack();
1539                break;
1540
1541            case R.id.forward_menu_id:
1542                getCurrentTopWebView().goForward();
1543                break;
1544
1545            case R.id.close_menu_id:
1546                // Close the subwindow if it exists.
1547                if (mTabControl.getCurrentSubWindow() != null) {
1548                    dismissSubWindow(mTabControl.getCurrentTab());
1549                    break;
1550                }
1551                closeCurrentTab();
1552                break;
1553
1554            case R.id.homepage_menu_id:
1555                Tab current = mTabControl.getCurrentTab();
1556                if (current != null) {
1557                    dismissSubWindow(current);
1558                    loadUrl(current.getWebView(), mSettings.getHomePage());
1559                }
1560                break;
1561
1562            case R.id.preferences_menu_id:
1563                Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
1564                intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
1565                        getCurrentTopWebView().getUrl());
1566                mActivity.startActivityForResult(intent, PREFERENCES_PAGE);
1567                break;
1568
1569            case R.id.find_menu_id:
1570                getCurrentTopWebView().showFindDialog(null, true);
1571                break;
1572
1573            case R.id.save_webarchive_menu_id:
1574                String state = Environment.getExternalStorageState();
1575                if (!Environment.MEDIA_MOUNTED.equals(state)) {
1576                    Log.e(LOGTAG, "External storage not mounted");
1577                    Toast.makeText(mActivity, R.string.webarchive_failed,
1578                            Toast.LENGTH_SHORT).show();
1579                    break;
1580                }
1581                final String directory = Environment.getExternalStoragePublicDirectory(
1582                        Environment.DIRECTORY_DOWNLOADS) + File.separator;
1583                File dir = new File(directory);
1584                if (!dir.exists() && !dir.mkdirs()) {
1585                    Log.e(LOGTAG, "Save as Web Archive: mkdirs for " + directory + " failed!");
1586                    Toast.makeText(mActivity, R.string.webarchive_failed,
1587                            Toast.LENGTH_SHORT).show();
1588                    break;
1589                }
1590                WebView topWebView = getCurrentTopWebView();
1591                final String title = topWebView.getTitle();
1592                topWebView.saveWebArchive(directory, true,
1593                        new ValueCallback<String>() {
1594                    @Override
1595                    public void onReceiveValue(final String value) {
1596                        if (value != null) {
1597                            File file = new File(value);
1598                            final long length = file.length();
1599                            if (file.exists() && length > 0) {
1600                                Toast.makeText(mActivity, R.string.webarchive_saved,
1601                                        Toast.LENGTH_SHORT).show();
1602                                final DownloadManager manager = (DownloadManager) mActivity
1603                                        .getSystemService(Context.DOWNLOAD_SERVICE);
1604                                new Thread("Add WebArchive to download manager") {
1605                                    @Override
1606                                    public void run() {
1607                                        manager.completedDownload(null == title ? value : title,
1608                                                value, true, "application/x-webarchive-xml",
1609                                                value, length, true);
1610                                    }
1611                                }.start();
1612                                return;
1613                            }
1614                        }
1615                        Toast.makeText(mActivity,
1616                                R.string.webarchive_failed, Toast.LENGTH_SHORT).show();
1617                    }
1618                });
1619                break;
1620
1621            case R.id.page_info_menu_id:
1622                mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(),
1623                        false);
1624                break;
1625
1626            case R.id.classic_history_menu_id:
1627                bookmarksOrHistoryPicker(true);
1628                break;
1629
1630            case R.id.title_bar_share_page_url:
1631            case R.id.share_page_menu_id:
1632                Tab currentTab = mTabControl.getCurrentTab();
1633                if (null == currentTab) {
1634                    mCanChord = false;
1635                    return false;
1636                }
1637                shareCurrentPage(currentTab);
1638                break;
1639
1640            case R.id.dump_nav_menu_id:
1641                getCurrentTopWebView().debugDump();
1642                break;
1643
1644            case R.id.dump_counters_menu_id:
1645                getCurrentTopWebView().dumpV8Counters();
1646                break;
1647
1648            case R.id.zoom_in_menu_id:
1649                getCurrentTopWebView().zoomIn();
1650                break;
1651
1652            case R.id.zoom_out_menu_id:
1653                getCurrentTopWebView().zoomOut();
1654                break;
1655
1656            case R.id.view_downloads_menu_id:
1657                viewDownloads();
1658                break;
1659
1660            case R.id.window_one_menu_id:
1661            case R.id.window_two_menu_id:
1662            case R.id.window_three_menu_id:
1663            case R.id.window_four_menu_id:
1664            case R.id.window_five_menu_id:
1665            case R.id.window_six_menu_id:
1666            case R.id.window_seven_menu_id:
1667            case R.id.window_eight_menu_id:
1668                {
1669                    int menuid = item.getItemId();
1670                    for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1671                        if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1672                            Tab desiredTab = mTabControl.getTab(id);
1673                            if (desiredTab != null &&
1674                                    desiredTab != mTabControl.getCurrentTab()) {
1675                                switchToTab(id);
1676                            }
1677                            break;
1678                        }
1679                    }
1680                }
1681                break;
1682
1683            default:
1684                return false;
1685        }
1686        mCanChord = false;
1687        return true;
1688    }
1689
1690    public boolean onContextItemSelected(MenuItem item) {
1691        // Let the History and Bookmark fragments handle menus they created.
1692        if (item.getGroupId() == R.id.CONTEXT_MENU) {
1693            return false;
1694        }
1695
1696        // chording is not an issue with context menus, but we use the same
1697        // options selector, so set mCanChord to true so we can access them.
1698        mCanChord = true;
1699        int id = item.getItemId();
1700        boolean result = true;
1701        switch (id) {
1702            // For the context menu from the title bar
1703            case R.id.title_bar_copy_page_url:
1704                Tab currentTab = mTabControl.getCurrentTab();
1705                if (null == currentTab) {
1706                    result = false;
1707                    break;
1708                }
1709                WebView mainView = currentTab.getWebView();
1710                if (null == mainView) {
1711                    result = false;
1712                    break;
1713                }
1714                copy(mainView.getUrl());
1715                break;
1716            // -- Browser context menu
1717            case R.id.open_context_menu_id:
1718            case R.id.save_link_context_menu_id:
1719            case R.id.copy_link_context_menu_id:
1720                final WebView webView = getCurrentTopWebView();
1721                if (null == webView) {
1722                    result = false;
1723                    break;
1724                }
1725                final HashMap<String, WebView> hrefMap =
1726                        new HashMap<String, WebView>();
1727                hrefMap.put("webview", webView);
1728                final Message msg = mHandler.obtainMessage(
1729                        FOCUS_NODE_HREF, id, 0, hrefMap);
1730                webView.requestFocusNodeHref(msg);
1731                break;
1732
1733            default:
1734                // For other context menus
1735                result = onOptionsItemSelected(item);
1736        }
1737        mCanChord = false;
1738        return result;
1739    }
1740
1741    /**
1742     * support programmatically opening the context menu
1743     */
1744    public void openContextMenu(View view) {
1745        mActivity.openContextMenu(view);
1746    }
1747
1748    /**
1749     * programmatically open the options menu
1750     */
1751    public void openOptionsMenu() {
1752        mActivity.openOptionsMenu();
1753    }
1754
1755    public boolean onMenuOpened(int featureId, Menu menu) {
1756        if (mOptionsMenuOpen) {
1757            if (mConfigChanged) {
1758                // We do not need to make any changes to the state of the
1759                // title bar, since the only thing that happened was a
1760                // change in orientation
1761                mConfigChanged = false;
1762            } else {
1763                if (!mExtendedMenuOpen) {
1764                    mExtendedMenuOpen = true;
1765                    mUi.onExtendedMenuOpened();
1766                } else {
1767                    // Switching the menu back to icon view, so show the
1768                    // title bar once again.
1769                    mExtendedMenuOpen = false;
1770                    mUi.onExtendedMenuClosed(mInLoad);
1771                    mUi.onOptionsMenuOpened();
1772                }
1773            }
1774        } else {
1775            // The options menu is closed, so open it, and show the title
1776            mOptionsMenuOpen = true;
1777            mConfigChanged = false;
1778            mExtendedMenuOpen = false;
1779            mUi.onOptionsMenuOpened();
1780        }
1781        return true;
1782    }
1783
1784    public void onOptionsMenuClosed(Menu menu) {
1785        mOptionsMenuOpen = false;
1786        mUi.onOptionsMenuClosed(mInLoad);
1787    }
1788
1789    public void onContextMenuClosed(Menu menu) {
1790        mUi.onContextMenuClosed(menu, mInLoad);
1791    }
1792
1793    // Helper method for getting the top window.
1794    @Override
1795    public WebView getCurrentTopWebView() {
1796        return mTabControl.getCurrentTopWebView();
1797    }
1798
1799    @Override
1800    public WebView getCurrentWebView() {
1801        return mTabControl.getCurrentWebView();
1802    }
1803
1804    /*
1805     * This method is called as a result of the user selecting the options
1806     * menu to see the download window. It shows the download window on top of
1807     * the current window.
1808     */
1809    void viewDownloads() {
1810        Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1811        mActivity.startActivity(intent);
1812    }
1813
1814    // action mode
1815
1816    void onActionModeStarted(ActionMode mode) {
1817        mUi.onActionModeStarted(mode);
1818        mActionMode = mode;
1819    }
1820
1821    /*
1822     * True if a custom ActionMode (i.e. find or select) is in use.
1823     */
1824    @Override
1825    public boolean isInCustomActionMode() {
1826        return mActionMode != null;
1827    }
1828
1829    /*
1830     * End the current ActionMode.
1831     */
1832    @Override
1833    public void endActionMode() {
1834        if (mActionMode != null) {
1835            mActionMode.finish();
1836        }
1837    }
1838
1839    /*
1840     * Called by find and select when they are finished.  Replace title bars
1841     * as necessary.
1842     */
1843    public void onActionModeFinished(ActionMode mode) {
1844        if (!isInCustomActionMode()) return;
1845        mUi.onActionModeFinished(mInLoad);
1846        mActionMode = null;
1847    }
1848
1849    boolean isInLoad() {
1850        return mInLoad;
1851    }
1852
1853    // bookmark handling
1854
1855    /**
1856     * add the current page as a bookmark to the given folder id
1857     * @param folderId use -1 for the default folder
1858     * @param canBeAnEdit If true, check to see whether the site is already
1859     *          bookmarked, and if it is, edit that bookmark.  If false, and
1860     *          the site is already bookmarked, do not attempt to edit the
1861     *          existing bookmark.
1862     */
1863    @Override
1864    public void bookmarkCurrentPage(long folderId, boolean canBeAnEdit) {
1865        Intent i = new Intent(mActivity,
1866                AddBookmarkPage.class);
1867        WebView w = getCurrentTopWebView();
1868        i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl());
1869        i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle());
1870        String touchIconUrl = w.getTouchIconUrl();
1871        if (touchIconUrl != null) {
1872            i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl);
1873            WebSettings settings = w.getSettings();
1874            if (settings != null) {
1875                i.putExtra(AddBookmarkPage.USER_AGENT,
1876                        settings.getUserAgentString());
1877            }
1878        }
1879        i.putExtra(BrowserContract.Bookmarks.THUMBNAIL,
1880                createScreenshot(w, getDesiredThumbnailWidth(mActivity),
1881                getDesiredThumbnailHeight(mActivity)));
1882        i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
1883        i.putExtra(BrowserContract.Bookmarks.PARENT,
1884                folderId);
1885        if (canBeAnEdit) {
1886            i.putExtra(AddBookmarkPage.CHECK_FOR_DUPE, true);
1887        }
1888        // Put the dialog at the upper right of the screen, covering the
1889        // star on the title bar.
1890        i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
1891        mActivity.startActivity(i);
1892    }
1893
1894    // file chooser
1895    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
1896        mUploadHandler = new UploadHandler(this);
1897        mUploadHandler.openFileChooser(uploadMsg, acceptType);
1898    }
1899
1900    // thumbnails
1901
1902    /**
1903     * Return the desired width for thumbnail screenshots, which are stored in
1904     * the database, and used on the bookmarks screen.
1905     * @param context Context for finding out the density of the screen.
1906     * @return desired width for thumbnail screenshot.
1907     */
1908    static int getDesiredThumbnailWidth(Context context) {
1909        return context.getResources().getDimensionPixelOffset(
1910                R.dimen.bookmarkThumbnailWidth);
1911    }
1912
1913    /**
1914     * Return the desired height for thumbnail screenshots, which are stored in
1915     * the database, and used on the bookmarks screen.
1916     * @param context Context for finding out the density of the screen.
1917     * @return desired height for thumbnail screenshot.
1918     */
1919    static int getDesiredThumbnailHeight(Context context) {
1920        return context.getResources().getDimensionPixelOffset(
1921                R.dimen.bookmarkThumbnailHeight);
1922    }
1923
1924    private static Bitmap createScreenshot(WebView view, int width, int height) {
1925        // We render to a bitmap 2x the desired size so that we can then
1926        // re-scale it with filtering since canvas.scale doesn't filter
1927        // This helps reduce aliasing at the cost of being slightly blurry
1928        final int filter_scale = 2;
1929        Picture thumbnail = view.capturePicture();
1930        if (thumbnail == null) {
1931            return null;
1932        }
1933        width *= filter_scale;
1934        height *= filter_scale;
1935        Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
1936        Canvas canvas = new Canvas(bm);
1937        // May need to tweak these values to determine what is the
1938        // best scale factor
1939        int thumbnailWidth = thumbnail.getWidth();
1940        int thumbnailHeight = thumbnail.getHeight();
1941        float scaleFactor = 1.0f;
1942        if (thumbnailWidth > 0) {
1943            scaleFactor = (float) width / (float)thumbnailWidth;
1944        } else {
1945            return null;
1946        }
1947
1948        if (view.getWidth() > view.getHeight() &&
1949                thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
1950            // If the device is in landscape and the page is shorter
1951            // than the height of the view, center the thumnail and crop the sides
1952            scaleFactor = (float) height / (float)thumbnailHeight;
1953            float wx = (thumbnailWidth * scaleFactor) - width;
1954            canvas.translate((int) -(wx / 2), 0);
1955        }
1956
1957        canvas.scale(scaleFactor, scaleFactor);
1958
1959        thumbnail.draw(canvas);
1960        Bitmap ret = Bitmap.createScaledBitmap(bm, width / filter_scale,
1961                height / filter_scale, true);
1962        bm.recycle();
1963        return ret;
1964    }
1965
1966    private void updateScreenshot(Tab tab) {
1967        // If this is a bookmarked site, add a screenshot to the database.
1968        // FIXME: Would like to make sure there is actually something to
1969        // draw, but the API for that (WebViewCore.pictureReady()) is not
1970        // currently accessible here.
1971
1972        WebView view = tab.getWebView();
1973        if (view == null) {
1974            // Tab was destroyed
1975            return;
1976        }
1977        final String url = tab.getUrl();
1978        final String originalUrl = view.getOriginalUrl();
1979
1980        if (TextUtils.isEmpty(url)) {
1981            return;
1982        }
1983
1984        // Only update thumbnails for web urls (http(s)://), not for
1985        // about:, javascript:, data:, etc...
1986        // Unless it is a bookmarked site, then always update
1987        if (!Patterns.WEB_URL.matcher(url).matches() && !tab.isBookmarkedSite()) {
1988            return;
1989        }
1990
1991        final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
1992                getDesiredThumbnailHeight(mActivity));
1993        if (bm == null) {
1994            return;
1995        }
1996
1997        final ContentResolver cr = mActivity.getContentResolver();
1998        new AsyncTask<Void, Void, Void>() {
1999            @Override
2000            protected Void doInBackground(Void... unused) {
2001                Cursor cursor = null;
2002                try {
2003                    // TODO: Clean this up
2004                    cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
2005                    if (cursor != null && cursor.moveToFirst()) {
2006                        final ByteArrayOutputStream os =
2007                                new ByteArrayOutputStream();
2008                        bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2009
2010                        ContentValues values = new ContentValues();
2011                        values.put(Images.THUMBNAIL, os.toByteArray());
2012
2013                        do {
2014                            values.put(Images.URL, cursor.getString(0));
2015                            cr.update(Images.CONTENT_URI, values, null, null);
2016                        } while (cursor.moveToNext());
2017                    }
2018                } catch (IllegalStateException e) {
2019                    // Ignore
2020                } finally {
2021                    if (cursor != null) cursor.close();
2022                }
2023                return null;
2024            }
2025        }.execute();
2026    }
2027
2028    private class Copy implements OnMenuItemClickListener {
2029        private CharSequence mText;
2030
2031        public boolean onMenuItemClick(MenuItem item) {
2032            copy(mText);
2033            return true;
2034        }
2035
2036        public Copy(CharSequence toCopy) {
2037            mText = toCopy;
2038        }
2039    }
2040
2041    private static class Download implements OnMenuItemClickListener {
2042        private Activity mActivity;
2043        private String mText;
2044
2045        public boolean onMenuItemClick(MenuItem item) {
2046            DownloadHandler.onDownloadStartNoStream(mActivity, mText, null,
2047                    null, null);
2048            return true;
2049        }
2050
2051        public Download(Activity activity, String toDownload) {
2052            mActivity = activity;
2053            mText = toDownload;
2054        }
2055    }
2056
2057    private static class SelectText implements OnMenuItemClickListener {
2058        private WebView mWebView;
2059
2060        public boolean onMenuItemClick(MenuItem item) {
2061            if (mWebView != null) {
2062                return mWebView.selectText();
2063            }
2064            return false;
2065        }
2066
2067        public SelectText(WebView webView) {
2068            mWebView = webView;
2069        }
2070
2071    }
2072
2073    /********************** TODO: UI stuff *****************************/
2074
2075    // these methods have been copied, they still need to be cleaned up
2076
2077    /****************** tabs ***************************************************/
2078
2079    // basic tab interactions:
2080
2081    // it is assumed that tabcontrol already knows about the tab
2082    protected void addTab(Tab tab) {
2083        mUi.addTab(tab);
2084    }
2085
2086    protected void removeTab(Tab tab) {
2087        mUi.removeTab(tab);
2088        mTabControl.removeTab(tab);
2089    }
2090
2091    protected void setActiveTab(Tab tab) {
2092        mTabControl.setCurrentTab(tab);
2093        // the tab is guaranteed to have a webview after setCurrentTab
2094        mUi.setActiveTab(tab);
2095    }
2096
2097    protected void closeEmptyChildTab() {
2098        Tab current = mTabControl.getCurrentTab();
2099        if (current != null
2100                && current.getWebView().copyBackForwardList().getSize() == 0) {
2101            Tab parent = current.getParentTab();
2102            if (parent != null) {
2103                switchToTab(mTabControl.getTabIndex(parent));
2104                closeTab(current);
2105            }
2106        }
2107    }
2108
2109    protected void reuseTab(Tab appTab, String appId, UrlData urlData) {
2110        // Dismiss the subwindow if applicable.
2111        dismissSubWindow(appTab);
2112        // Since we might kill the WebView, remove it from the
2113        // content view first.
2114        mUi.detachTab(appTab);
2115        // Recreate the main WebView after destroying the old one.
2116        mTabControl.recreateWebView(appTab);
2117        // TODO: analyze why the remove and add are necessary
2118        mUi.attachTab(appTab);
2119        if (mTabControl.getCurrentTab() != appTab) {
2120            switchToTab(mTabControl.getTabIndex(appTab));
2121            loadUrlDataIn(appTab, urlData);
2122        } else {
2123            // If the tab was the current tab, we have to attach
2124            // it to the view system again.
2125            setActiveTab(appTab);
2126            loadUrlDataIn(appTab, urlData);
2127        }
2128    }
2129
2130    // Remove the sub window if it exists. Also called by TabControl when the
2131    // user clicks the 'X' to dismiss a sub window.
2132    public void dismissSubWindow(Tab tab) {
2133        removeSubWindow(tab);
2134        // dismiss the subwindow. This will destroy the WebView.
2135        tab.dismissSubWindow();
2136        getCurrentTopWebView().requestFocus();
2137    }
2138
2139    @Override
2140    public void removeSubWindow(Tab t) {
2141        if (t.getSubWebView() != null) {
2142            mUi.removeSubWindow(t.getSubViewContainer());
2143        }
2144    }
2145
2146    @Override
2147    public void attachSubWindow(Tab tab) {
2148        if (tab.getSubWebView() != null) {
2149            mUi.attachSubWindow(tab.getSubViewContainer());
2150            getCurrentTopWebView().requestFocus();
2151        }
2152    }
2153
2154    @Override
2155    public Tab openTabToHomePage() {
2156        // check for max tabs
2157        if (mTabControl.canCreateNewTab()) {
2158            return openTabAndShow(null, new UrlData(mSettings.getHomePage()),
2159                    false, null);
2160        } else {
2161            mUi.showMaxTabsWarning();
2162            return null;
2163        }
2164    }
2165
2166    protected Tab openTab(Tab parent, String url, boolean forceForeground) {
2167        if (mSettings.openInBackground() && !forceForeground) {
2168            Tab tab = mTabControl.createNewTab(false, null, null,
2169                    (parent != null) && parent.isPrivateBrowsingEnabled());
2170            if (tab != null) {
2171                addTab(tab);
2172                WebView view = tab.getWebView();
2173                loadUrl(view, url);
2174            }
2175            return tab;
2176        } else {
2177            return openTabAndShow(parent, new UrlData(url), false, null);
2178        }
2179    }
2180
2181
2182    // This method does a ton of stuff. It will attempt to create a new tab
2183    // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
2184    // url isn't null, it will load the given url.
2185    public Tab openTabAndShow(Tab parent, final UrlData urlData,
2186            boolean closeOnExit, String appId) {
2187        final Tab currentTab = mTabControl.getCurrentTab();
2188        if (mTabControl.canCreateNewTab()) {
2189            final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
2190                    urlData.mUrl,
2191                    (parent != null) && parent.isPrivateBrowsingEnabled());
2192            WebView webview = tab.getWebView();
2193            // We must set the new tab as the current tab to reflect the old
2194            // animation behavior.
2195            addTab(tab);
2196            setActiveTab(tab);
2197
2198            // Callback to load the url data.
2199            final Runnable load = new Runnable() {
2200                @Override public void run() {
2201                    if (!urlData.isEmpty()) {
2202                        loadUrlDataIn(tab, urlData);
2203                    }
2204                }
2205            };
2206
2207            GoogleAccountLogin.startLoginIfNeeded(mActivity, mSettings, load);
2208            return tab;
2209        } else {
2210            // Get rid of the subwindow if it exists
2211            dismissSubWindow(currentTab);
2212            if (!urlData.isEmpty()) {
2213                // Load the given url.
2214                loadUrlDataIn(currentTab, urlData);
2215            }
2216            return currentTab;
2217        }
2218    }
2219
2220    @Override
2221    public Tab openIncognitoTab() {
2222        if (mTabControl.canCreateNewTab()) {
2223            Tab currentTab = mTabControl.getCurrentTab();
2224            Tab tab = mTabControl.createNewTab(false, null,
2225                    null, true);
2226            addTab(tab);
2227            setActiveTab(tab);
2228            loadUrlDataIn(tab, new UrlData("browser:incognito"));
2229            return tab;
2230        } else {
2231            mUi.showMaxTabsWarning();
2232            return null;
2233        }
2234    }
2235
2236    /**
2237     * @param index Index of the tab to change to, as defined by
2238     *              mTabControl.getTabIndex(Tab t).
2239     * @return boolean True if we successfully switched to a different tab.  If
2240     *                 the indexth tab is null, or if that tab is the same as
2241     *                 the current one, return false.
2242     */
2243    @Override
2244    public boolean switchToTab(int index) {
2245        // hide combo view if open
2246        removeComboView();
2247        Tab tab = mTabControl.getTab(index);
2248        Tab currentTab = mTabControl.getCurrentTab();
2249        if (tab == null || tab == currentTab) {
2250            return false;
2251        }
2252        setActiveTab(tab);
2253        return true;
2254    }
2255
2256    @Override
2257    public void closeCurrentTab() {
2258        // hide combo view if open
2259        removeComboView();
2260        final Tab current = mTabControl.getCurrentTab();
2261        if (mTabControl.getTabCount() == 1) {
2262            mActivity.finish();
2263            return;
2264        }
2265        final Tab parent = current.getParentTab();
2266        int indexToShow = -1;
2267        if (parent != null) {
2268            indexToShow = mTabControl.getTabIndex(parent);
2269        } else {
2270            final int currentIndex = mTabControl.getCurrentIndex();
2271            // Try to move to the tab to the right
2272            indexToShow = currentIndex + 1;
2273            if (indexToShow > mTabControl.getTabCount() - 1) {
2274                // Try to move to the tab to the left
2275                indexToShow = currentIndex - 1;
2276            }
2277        }
2278        if (switchToTab(indexToShow)) {
2279            // Close window
2280            closeTab(current);
2281        }
2282    }
2283
2284    /**
2285     * Close the tab, remove its associated title bar, and adjust mTabControl's
2286     * current tab to a valid value.
2287     */
2288    @Override
2289    public void closeTab(Tab tab) {
2290        // hide combo view if open
2291        removeComboView();
2292        int currentIndex = mTabControl.getCurrentIndex();
2293        int removeIndex = mTabControl.getTabIndex(tab);
2294        Tab newtab = mTabControl.getTab(currentIndex);
2295        setActiveTab(newtab);
2296        removeTab(tab);
2297    }
2298
2299    /**************** TODO: Url loading clean up *******************************/
2300
2301    // Called when loading from context menu or LOAD_URL message
2302    protected void loadUrlFromContext(WebView view, String url) {
2303        // In case the user enters nothing.
2304        if (url != null && url.length() != 0 && view != null) {
2305            url = UrlUtils.smartUrlFilter(url);
2306            if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
2307                loadUrl(view, url);
2308            }
2309        }
2310    }
2311
2312    /**
2313     * Load the URL into the given WebView and update the title bar
2314     * to reflect the new load.  Call this instead of WebView.loadUrl
2315     * directly.
2316     * @param view The WebView used to load url.
2317     * @param url The URL to load.
2318     */
2319    protected void loadUrl(WebView view, String url) {
2320        view.loadUrl(url);
2321    }
2322
2323    /**
2324     * Load UrlData into a Tab and update the title bar to reflect the new
2325     * load.  Call this instead of UrlData.loadIn directly.
2326     * @param t The Tab used to load.
2327     * @param data The UrlData being loaded.
2328     */
2329    protected void loadUrlDataIn(Tab t, UrlData data) {
2330        data.loadIn(t);
2331    }
2332
2333    @Override
2334    public void onUserCanceledSsl(Tab tab) {
2335        WebView web = tab.getWebView();
2336        // TODO: Figure out the "right" behavior
2337        if (web.canGoBack()) {
2338            web.goBack();
2339        } else {
2340            web.loadUrl(mSettings.getHomePage());
2341        }
2342    }
2343
2344    void goBackOnePageOrQuit() {
2345        Tab current = mTabControl.getCurrentTab();
2346        if (current == null) {
2347            /*
2348             * Instead of finishing the activity, simply push this to the back
2349             * of the stack and let ActivityManager to choose the foreground
2350             * activity. As BrowserActivity is singleTask, it will be always the
2351             * root of the task. So we can use either true or false for
2352             * moveTaskToBack().
2353             */
2354            mActivity.moveTaskToBack(true);
2355            return;
2356        }
2357        WebView w = current.getWebView();
2358        if (w.canGoBack()) {
2359            w.goBack();
2360        } else {
2361            // Check to see if we are closing a window that was created by
2362            // another window. If so, we switch back to that window.
2363            Tab parent = current.getParentTab();
2364            if (parent != null) {
2365                switchToTab(mTabControl.getTabIndex(parent));
2366                // Now we close the other tab
2367                closeTab(current);
2368            } else {
2369                if (current.closeOnExit()) {
2370                    // force the tab's inLoad() to be false as we are going to
2371                    // either finish the activity or remove the tab. This will
2372                    // ensure pauseWebViewTimers() taking action.
2373                    current.clearInPageLoad();
2374                    if (mTabControl.getTabCount() == 1) {
2375                        mActivity.finish();
2376                        return;
2377                    }
2378                    if (mActivityPaused) {
2379                        Log.e(LOGTAG, "BrowserActivity is already paused "
2380                                + "while handing goBackOnePageOrQuit.");
2381                    }
2382                    pauseWebViewTimers(current);
2383                    closeCurrentTab();
2384                }
2385                /*
2386                 * Instead of finishing the activity, simply push this to the back
2387                 * of the stack and let ActivityManager to choose the foreground
2388                 * activity. As BrowserActivity is singleTask, it will be always the
2389                 * root of the task. So we can use either true or false for
2390                 * moveTaskToBack().
2391                 */
2392                mActivity.moveTaskToBack(true);
2393            }
2394        }
2395    }
2396
2397    /**
2398     * Feed the previously stored results strings to the BrowserProvider so that
2399     * the SearchDialog will show them instead of the standard searches.
2400     * @param result String to show on the editable line of the SearchDialog.
2401     */
2402    @Override
2403    public void showVoiceSearchResults(String result) {
2404        ContentProviderClient client = mActivity.getContentResolver()
2405                .acquireContentProviderClient(Browser.BOOKMARKS_URI);
2406        ContentProvider prov = client.getLocalContentProvider();
2407        BrowserProvider bp = (BrowserProvider) prov;
2408        bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
2409        client.release();
2410
2411        Bundle bundle = createGoogleSearchSourceBundle(
2412                GOOGLE_SEARCH_SOURCE_SEARCHKEY);
2413        bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
2414        startSearch(result, false, bundle, false);
2415    }
2416
2417    @Override
2418    public void startSearch(String url) {
2419        startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
2420                null, false);
2421    }
2422
2423    private void startSearch(String initialQuery, boolean selectInitialQuery,
2424            Bundle appSearchData, boolean globalSearch) {
2425        if (appSearchData == null) {
2426            appSearchData = createGoogleSearchSourceBundle(
2427                    GOOGLE_SEARCH_SOURCE_TYPE);
2428        }
2429
2430        SearchEngine searchEngine = mSettings.getSearchEngine();
2431        if (searchEngine != null && !searchEngine.supportsVoiceSearch()) {
2432            appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true);
2433        }
2434        mActivity.startSearch(initialQuery, selectInitialQuery, appSearchData,
2435                globalSearch);
2436    }
2437
2438    private Bundle createGoogleSearchSourceBundle(String source) {
2439        Bundle bundle = new Bundle();
2440        bundle.putString(Search.SOURCE, source);
2441        return bundle;
2442    }
2443
2444    /**
2445     * handle key events in browser
2446     *
2447     * @param keyCode
2448     * @param event
2449     * @return true if handled, false to pass to super
2450     */
2451    boolean onKeyDown(int keyCode, KeyEvent event) {
2452        boolean noModifiers = event.hasNoModifiers();
2453
2454        // Even if MENU is already held down, we need to call to super to open
2455        // the IME on long press.
2456        if (!noModifiers
2457                && ((KeyEvent.KEYCODE_MENU == keyCode)
2458                        || (KeyEvent.KEYCODE_CTRL_LEFT == keyCode)
2459                        || (KeyEvent.KEYCODE_CTRL_RIGHT == keyCode))) {
2460            mMenuIsDown = true;
2461            return false;
2462        }
2463
2464        WebView webView = getCurrentTopWebView();
2465        if (webView == null) return false;
2466
2467        boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
2468        boolean shift = event.hasModifiers(KeyEvent.META_SHIFT_ON);
2469
2470        switch(keyCode) {
2471            case KeyEvent.KEYCODE_ESCAPE:
2472                if (!noModifiers) break;
2473                stopLoading();
2474                return true;
2475            case KeyEvent.KEYCODE_SPACE:
2476                // WebView/WebTextView handle the keys in the KeyDown. As
2477                // the Activity's shortcut keys are only handled when WebView
2478                // doesn't, have to do it in onKeyDown instead of onKeyUp.
2479                if (shift) {
2480                    pageUp();
2481                } else if (noModifiers) {
2482                    pageDown();
2483                }
2484                return true;
2485            case KeyEvent.KEYCODE_BACK:
2486                if (!noModifiers) break;
2487                if (event.getRepeatCount() == 0) {
2488                    event.startTracking();
2489                    return true;
2490                } else if (mUi.showsWeb()
2491                        && event.isLongPress()) {
2492                    bookmarksOrHistoryPicker(true);
2493                    return true;
2494                }
2495                break;
2496            case KeyEvent.KEYCODE_DPAD_LEFT:
2497                if (ctrl) {
2498                    webView.goBack();
2499                    return true;
2500                }
2501                break;
2502            case KeyEvent.KEYCODE_DPAD_RIGHT:
2503                if (ctrl) {
2504                    webView.goForward();
2505                    return true;
2506                }
2507                break;
2508            case KeyEvent.KEYCODE_A:
2509                if (ctrl) {
2510                    webView.selectAll();
2511                    return true;
2512                }
2513                break;
2514//          case KeyEvent.KEYCODE_B:    // menu
2515            case KeyEvent.KEYCODE_C:
2516                if (ctrl) {
2517                    webView.copySelection();
2518                    return true;
2519                }
2520                break;
2521//          case KeyEvent.KEYCODE_D:    // menu
2522//          case KeyEvent.KEYCODE_E:    // in Chrome: puts '?' in URL bar
2523//          case KeyEvent.KEYCODE_F:    // menu
2524//          case KeyEvent.KEYCODE_G:    // in Chrome: finds next match
2525//          case KeyEvent.KEYCODE_H:    // menu
2526//          case KeyEvent.KEYCODE_I:    // unused
2527//          case KeyEvent.KEYCODE_J:    // menu
2528//          case KeyEvent.KEYCODE_K:    // in Chrome: puts '?' in URL bar
2529//          case KeyEvent.KEYCODE_L:    // menu
2530//          case KeyEvent.KEYCODE_M:    // unused
2531//          case KeyEvent.KEYCODE_N:    // in Chrome: new window
2532//          case KeyEvent.KEYCODE_O:    // in Chrome: open file
2533//          case KeyEvent.KEYCODE_P:    // in Chrome: print page
2534//          case KeyEvent.KEYCODE_Q:    // unused
2535//          case KeyEvent.KEYCODE_R:
2536//          case KeyEvent.KEYCODE_S:    // in Chrome: saves page
2537            case KeyEvent.KEYCODE_T:
2538                // we can't use the ctrl/shift flags, they check for
2539                // exclusive use of a modifier
2540                if (event.isCtrlPressed()) {
2541                    if (event.isShiftPressed()) {
2542                        openIncognitoTab();
2543                    } else {
2544                        openTabToHomePage();
2545                    }
2546                    return true;
2547                }
2548                break;
2549//          case KeyEvent.KEYCODE_U:    // in Chrome: opens source of page
2550//          case KeyEvent.KEYCODE_V:    // text view intercepts to paste
2551//          case KeyEvent.KEYCODE_W:    // menu
2552//          case KeyEvent.KEYCODE_X:    // text view intercepts to cut
2553//          case KeyEvent.KEYCODE_Y:    // unused
2554//          case KeyEvent.KEYCODE_Z:    // unused
2555        }
2556        // it is a regular key and webview is not null
2557         return mUi.dispatchKey(keyCode, event);
2558    }
2559
2560    boolean onKeyUp(int keyCode, KeyEvent event) {
2561        if (!event.hasNoModifiers()) return false;
2562        switch(keyCode) {
2563            case KeyEvent.KEYCODE_MENU:
2564                mMenuIsDown = false;
2565                break;
2566            case KeyEvent.KEYCODE_BACK:
2567                if (event.isTracking() && !event.isCanceled()) {
2568                    onBackKey();
2569                    return true;
2570                }
2571                break;
2572        }
2573        return false;
2574    }
2575
2576    public boolean isMenuDown() {
2577        return mMenuIsDown;
2578    }
2579
2580    public void setupAutoFill(Message message) {
2581        // Open the settings activity at the AutoFill profile fragment so that
2582        // the user can create a new profile. When they return, we will dispatch
2583        // the message so that we can autofill the form using their new profile.
2584        Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
2585        intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
2586                AutoFillSettingsFragment.class.getName());
2587        mAutoFillSetupMessage = message;
2588        mActivity.startActivityForResult(intent, AUTOFILL_SETUP);
2589    }
2590
2591    @Override
2592    public void registerOptionsMenuHandler(OptionsMenuHandler handler) {
2593        mOptionsMenuHandler = handler;
2594    }
2595
2596    @Override
2597    public void unregisterOptionsMenuHandler(OptionsMenuHandler handler) {
2598        if (mOptionsMenuHandler == handler) {
2599            mOptionsMenuHandler = null;
2600        }
2601    }
2602
2603}
2604