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