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