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