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