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