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