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