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