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