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