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