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