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