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