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