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