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