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