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