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