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