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