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 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 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 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 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 372 PruneThumbnails(Context context, List<Long> preserveIds) { 373 mContext = context.getApplicationContext(); 374 mIds = preserveIds; 375 } 376 377 @Override 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 401 public WebViewFactory getWebViewFactory() { 402 return mFactory; 403 } 404 405 @Override 406 public void onSetWebView(Tab tab, WebView view) { 407 mUi.onSetWebView(tab, view); 408 } 409 410 @Override 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 421 public Context getContext() { 422 return mActivity; 423 } 424 425 @Override 426 public Activity getActivity() { 427 return mActivity; 428 } 429 430 void setUi(UI ui) { 431 mUi = ui; 432 } 433 434 @Override 435 public BrowserSettings getSettings() { 436 return mSettings; 437 } 438 439 IntentHandler getIntentHandler() { 440 return mIntentHandler; 441 } 442 443 @Override 444 public UI getUi() { 445 return mUi; 446 } 447 448 int getMaxTabs() { 449 return mActivity.getResources().getInteger(R.integer.max_tabs); 450 } 451 452 @Override 453 public TabControl getTabControl() { 454 return mTabControl; 455 } 456 457 @Override 458 public List<Tab> getTabs() { 459 return mTabControl.getTabs(); 460 } 461 462 // Open the icon database. 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 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 555 public Tab getCurrentTab() { 556 return mTabControl.getCurrentTab(); 557 } 558 559 @Override 560 public void shareCurrentPage() { 561 shareCurrentPage(mTabControl.getCurrentTab()); 562 } 563 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 */ 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 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 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 623 public void handleNewIntent(Intent intent) { 624 if (!mUi.isWebShowing()) { 625 mUi.showWeb(false); 626 } 627 mIntentHandler.onNewIntent(intent); 628 } 629 630 @Override 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 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 */ 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 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 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 */ 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 */ 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 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 774 protected boolean isActivityPaused() { 775 return mActivityPaused; 776 } 777 778 @Override 779 public void onLowMemory() { 780 mTabControl.freeMemory(); 781 } 782 783 @Override 784 public boolean shouldShowErrorConsole() { 785 return mShouldShowErrorConsole; 786 } 787 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 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 813 boolean didUserStopLoading() { 814 return mLoadStopped; 815 } 816 817 // WebViewController 818 819 @Override 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 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 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 923 public void onUpdatedSecurityState(Tab tab) { 924 mUi.onTabDataChanged(tab); 925 } 926 927 @Override 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 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 948 public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) { 949 return mUrlHandler.shouldOverrideUrlLoading(tab, view, url); 950 } 951 952 @Override 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 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 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 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 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 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 1055 public Bitmap getDefaultVideoPoster() { 1056 return mUi.getDefaultVideoPoster(); 1057 } 1058 1059 @Override 1060 public View getVideoLoadingProgressView() { 1061 return mUi.getVideoLoadingProgressView(); 1062 } 1063 1064 @Override 1065 public void showSslCertificateOnError(WebView view, SslErrorHandler handler, 1066 SslError error) { 1067 mPageDialogsHandler.showSSLCertificateOnError(view, handler, error); 1068 } 1069 1070 @Override 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 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 */ 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 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 1108 protected void pageUp() { 1109 getCurrentTopWebView().pageUp(false); 1110 } 1111 1112 protected void pageDown() { 1113 getCurrentTopWebView().pageDown(false); 1114 } 1115 1116 // callback from phone title bar 1117 @Override 1118 public void editUrl() { 1119 if (mOptionsMenuOpen) mActivity.closeOptionsMenu(); 1120 mUi.editUrl(false, true); 1121 } 1122 1123 @Override 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 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 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 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 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 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 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 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 */ 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 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 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 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 1729 private SaveSnapshotTask(Tab tab) { 1730 mTab = tab; 1731 } 1732 1733 @Override 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 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 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 1780 public void onCancel(DialogInterface dialog) { 1781 cancel(true); 1782 } 1783 } 1784 1785 @Override 1786 public void toggleUserAgent() { 1787 WebView web = getCurrentWebView(); 1788 mSettings.toggleDesktopUseragent(web); 1789 web.loadUrl(web.getOriginalUrl()); 1790 } 1791 1792 @Override 1793 public void findOnPage() { 1794 getCurrentTopWebView().showFindDialog(null, true); 1795 } 1796 1797 @Override 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 1806 public void bookmarkCurrentPage() { 1807 Intent bookmarkIntent = createBookmarkCurrentPageIntent(false); 1808 if (bookmarkIntent != null) { 1809 mActivity.startActivity(bookmarkIntent); 1810 } 1811 } 1812 1813 private void goLive() { 1814 Tab t = getCurrentTab(); 1815 t.loadUrl(t.getUrl(), null); 1816 } 1817 1818 @Override 1819 public void showPageInfo() { 1820 mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(), false, null); 1821 } 1822 1823 @Override 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 */ 1860 public void openContextMenu(View view) { 1861 mActivity.openContextMenu(view); 1862 } 1863 1864 /** 1865 * programmatically open the options menu 1866 */ 1867 public void openOptionsMenu() { 1868 mActivity.openOptionsMenu(); 1869 } 1870 1871 @Override 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 1901 public void onOptionsMenuClosed(Menu menu) { 1902 mOptionsMenuOpen = false; 1903 mUi.onOptionsMenuClosed(isInLoad()); 1904 } 1905 1906 @Override 1907 public void onContextMenuClosed(Menu menu) { 1908 mUi.onContextMenuClosed(menu, isInLoad()); 1909 } 1910 1911 // Helper method for getting the top window. 1912 @Override 1913 public WebView getCurrentTopWebView() { 1914 return mTabControl.getCurrentTopWebView(); 1915 } 1916 1917 @Override 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 */ 1927 void viewDownloads() { 1928 Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); 1929 mActivity.startActivity(intent); 1930 } 1931 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 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 1952 public boolean isInCustomActionMode() { 1953 return mActionMode != null; 1954 } 1955 1956 /* 1957 * End the current ActionMode. 1958 */ 1959 @Override 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 1971 public void onActionModeFinished(ActionMode mode) { 1972 if (!isInCustomActionMode()) return; 1973 mUi.onActionModeFinished(isInLoad()); 1974 mActionMode = null; 1975 } 1976 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 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 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 */ 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 */ 2050 static int getDesiredThumbnailHeight(Context context) { 2051 return context.getResources().getDimensionPixelOffset( 2052 R.dimen.bookmarkThumbnailHeight); 2053 } 2054 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 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 2165 public boolean onMenuItemClick(MenuItem item) { 2166 copy(mText); 2167 return true; 2168 } 2169 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 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 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 */ 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 */ 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 2257 public boolean onMenuItemClick(MenuItem item) { 2258 if (mWebView != null) { 2259 return mWebView.selectText(); 2260 } 2261 return false; 2262 } 2263 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 2281 protected void addTab(Tab tab) { 2282 mUi.addTab(tab); 2283 } 2284 2285 protected void removeTab(Tab tab) { 2286 mUi.removeTab(tab); 2287 mTabControl.removeTab(tab); 2288 mCrashRecoveryHandler.backupState(); 2289 } 2290 2291 @Override 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 2301 protected void closeEmptyTab() { 2302 Tab current = mTabControl.getCurrentTab(); 2303 if (current != null 2304 && current.getWebView().copyBackForwardList().getSize() == 0) { 2305 closeCurrentTab(); 2306 } 2307 } 2308 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 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 2344 public void removeSubWindow(Tab t) { 2345 if (t.getSubWebView() != null) { 2346 mUi.removeSubWindow(t.getSubViewContainer()); 2347 } 2348 } 2349 2350 @Override 2351 public void attachSubWindow(Tab tab) { 2352 if (tab.getSubWebView() != null) { 2353 mUi.attachSubWindow(tab.getSubViewContainer()); 2354 getCurrentTopWebView().requestFocus(); 2355 } 2356 } 2357 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 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 2400 public Tab openTabToHomePage() { 2401 return openTab(mSettings.getHomePage(), false, true, false); 2402 } 2403 2404 @Override 2405 public Tab openIncognitoTab() { 2406 return openTab(INCOGNITO_URI, true, true, false); 2407 } 2408 2409 @Override 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 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 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 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 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 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 2492 public void closeCurrentTab() { 2493 closeCurrentTab(false); 2494 } 2495 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 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 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 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 2570 public void loadUrl(Tab tab, String url) { 2571 loadUrl(tab, url, null); 2572 } 2573 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 */ 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 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 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 */ 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 */ 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 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 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 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 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 2833 public boolean isMenuDown() { 2834 return mMenuIsDown; 2835 } 2836 2837 @Override 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 2850 public boolean onSearchRequested() { 2851 mUi.editUrl(false, true); 2852 return true; 2853 } 2854 2855 @Override 2856 public boolean shouldCaptureThumbnails() { 2857 return mUi.shouldCaptureThumbnails(); 2858 } 2859 2860 @Override 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 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 2878 public void setBlockEvents(boolean block) { 2879 mBlockEvents = block; 2880 } 2881 2882 @Override 2883 public boolean dispatchKeyEvent(KeyEvent event) { 2884 return mBlockEvents; 2885 } 2886 2887 @Override 2888 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 2889 return mBlockEvents; 2890 } 2891 2892 @Override 2893 public boolean dispatchTouchEvent(MotionEvent ev) { 2894 return mBlockEvents; 2895 } 2896 2897 @Override 2898 public boolean dispatchTrackballEvent(MotionEvent ev) { 2899 return mBlockEvents; 2900 } 2901 2902 @Override 2903 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 2904 return mBlockEvents; 2905 } 2906 2907 } 2908