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