BrowserActivity.java revision 30995e7cef0a0d5f9c7228d504076e0c158fa8b7
1/* 2 * Copyright (C) 2006 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 17package com.android.browser; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.ProgressDialog; 22import android.app.SearchManager; 23import android.content.ActivityNotFoundException; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.ContentProvider; 27import android.content.ContentProviderClient; 28import android.content.ContentResolver; 29import android.content.ContentUris; 30import android.content.ContentValues; 31import android.content.Context; 32import android.content.DialogInterface; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.content.pm.PackageInfo; 36import android.content.pm.PackageManager; 37import android.content.pm.ResolveInfo; 38import android.content.res.Configuration; 39import android.content.res.Resources; 40import android.database.Cursor; 41import android.database.DatabaseUtils; 42import android.graphics.Bitmap; 43import android.graphics.BitmapFactory; 44import android.graphics.Canvas; 45import android.graphics.Picture; 46import android.graphics.PixelFormat; 47import android.graphics.Rect; 48import android.graphics.drawable.Drawable; 49import android.net.ConnectivityManager; 50import android.net.NetworkInfo; 51import android.net.Uri; 52import android.net.WebAddress; 53import android.net.http.SslCertificate; 54import android.net.http.SslError; 55import android.os.AsyncTask; 56import android.os.Bundle; 57import android.os.Debug; 58import android.os.Environment; 59import android.os.Handler; 60import android.os.Message; 61import android.os.PowerManager; 62import android.os.Process; 63import android.os.ServiceManager; 64import android.os.SystemClock; 65import android.provider.Browser; 66import android.provider.ContactsContract; 67import android.provider.ContactsContract.Intents.Insert; 68import android.provider.Downloads; 69import android.provider.MediaStore; 70import android.speech.RecognizerResultsIntent; 71import android.text.IClipboard; 72import android.text.TextUtils; 73import android.text.format.DateFormat; 74import android.util.AttributeSet; 75import android.util.Log; 76import android.view.ContextMenu; 77import android.view.Gravity; 78import android.view.KeyEvent; 79import android.view.LayoutInflater; 80import android.view.Menu; 81import android.view.MenuInflater; 82import android.view.MenuItem; 83import android.view.View; 84import android.view.ViewGroup; 85import android.view.Window; 86import android.view.WindowManager; 87import android.view.ContextMenu.ContextMenuInfo; 88import android.view.MenuItem.OnMenuItemClickListener; 89import android.webkit.CookieManager; 90import android.webkit.CookieSyncManager; 91import android.webkit.DownloadListener; 92import android.webkit.HttpAuthHandler; 93import android.webkit.PluginManager; 94import android.webkit.SslErrorHandler; 95import android.webkit.URLUtil; 96import android.webkit.ValueCallback; 97import android.webkit.WebChromeClient; 98import android.webkit.WebHistoryItem; 99import android.webkit.WebIconDatabase; 100import android.webkit.WebView; 101import android.widget.EditText; 102import android.widget.FrameLayout; 103import android.widget.LinearLayout; 104import android.widget.TextView; 105import android.widget.Toast; 106import android.accounts.Account; 107import android.accounts.AccountManager; 108import android.accounts.AccountManagerFuture; 109import android.accounts.AuthenticatorException; 110import android.accounts.OperationCanceledException; 111import android.accounts.AccountManagerCallback; 112 113import com.android.common.Patterns; 114import com.android.common.Search; 115import com.android.common.speech.LoggingEvents; 116 117import com.google.android.gsf.GoogleLoginServiceConstants; 118 119import java.io.ByteArrayOutputStream; 120import java.io.File; 121import java.io.IOException; 122import java.io.InputStream; 123import java.net.MalformedURLException; 124import java.net.URI; 125import java.net.URISyntaxException; 126import java.net.URL; 127import java.net.URLEncoder; 128import java.text.ParseException; 129import java.util.Date; 130import java.util.HashMap; 131import java.util.HashSet; 132import java.util.Iterator; 133import java.util.List; 134import java.util.Map; 135import java.util.Set; 136import java.util.regex.Matcher; 137import java.util.regex.Pattern; 138 139public class BrowserActivity extends Activity 140 implements View.OnCreateContextMenuListener, DownloadListener, 141 AccountManagerCallback<Account[]> { 142 143 /* Define some aliases to make these debugging flags easier to refer to. 144 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG". 145 */ 146 private final static boolean DEBUG = com.android.browser.Browser.DEBUG; 147 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED; 148 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED; 149 150 // These are single-character shortcuts for searching popular sources. 151 private static final int SHORTCUT_INVALID = 0; 152 private static final int SHORTCUT_GOOGLE_SEARCH = 1; 153 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2; 154 private static final int SHORTCUT_DICTIONARY_SEARCH = 3; 155 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4; 156 157 private Account[] mAccountsGoogle; 158 private Account[] mAccountsPreferHosted; 159 160 private void startReadOfGoogleAccounts() { 161 mAccountsGoogle = null; 162 mAccountsPreferHosted = null; 163 164 AccountManager.get(this).getAccountsByTypeAndFeatures( 165 GoogleLoginServiceConstants.ACCOUNT_TYPE, 166 new String[]{GoogleLoginServiceConstants.FEATURE_LEGACY_HOSTED_OR_GOOGLE}, 167 this, null); 168 } 169 170 /** This implements AccountManagerCallback<Account[]> */ 171 public void run(AccountManagerFuture<Account[]> accountManagerFuture) { 172 try { 173 if (mAccountsGoogle == null) { 174 mAccountsGoogle = accountManagerFuture.getResult(); 175 176 AccountManager.get(this).getAccountsByTypeAndFeatures( 177 GoogleLoginServiceConstants.ACCOUNT_TYPE, 178 new String[]{GoogleLoginServiceConstants.FEATURE_LEGACY_GOOGLE}, 179 this, null); 180 } else { 181 mAccountsPreferHosted = accountManagerFuture.getResult(); 182 setupHomePage(); 183 } 184 } catch (OperationCanceledException e) { 185 setupHomePage(); 186 } catch (IOException e) { 187 setupHomePage(); 188 } catch (AuthenticatorException e) { 189 setupHomePage(); 190 } 191 } 192 193 private void setupHomePage() { 194 // get the default home page 195 String homepage = mSettings.getHomePage(); 196 197 if (mAccountsPreferHosted != null && mAccountsGoogle != null) { 198 // three cases: 199 // 200 // hostedUser == googleUser 201 // The device has only a google account 202 // 203 // hostedUser != googleUser 204 // The device has a hosted account and a google account 205 // 206 // hostedUser != null, googleUser == null 207 // The device has only a hosted account (so far) 208 String hostedUser = mAccountsPreferHosted.length == 0 209 ? null 210 : mAccountsPreferHosted[0].name; 211 String googleUser = mAccountsGoogle.length == 0 ? null : mAccountsGoogle[0].name; 212 213 // developers might have no accounts at all 214 if (hostedUser == null) return; 215 216 if (googleUser == null || !hostedUser.equals(googleUser)) { 217 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1); 218 homepage = homepage.replace("?", "/a/" + domain + "?"); 219 } 220 } 221 222 mSettings.setHomePage(BrowserActivity.this, homepage); 223 resumeAfterCredentials(); 224 } 225 226 private static class ClearThumbnails extends AsyncTask<File, Void, Void> { 227 @Override 228 public Void doInBackground(File... files) { 229 if (files != null) { 230 for (File f : files) { 231 if (!f.delete()) { 232 Log.e(LOGTAG, f.getPath() + " was not deleted"); 233 } 234 } 235 } 236 return null; 237 } 238 } 239 240 /** 241 * This layout holds everything you see below the status bar, including the 242 * error console, the custom view container, and the webviews. 243 */ 244 private FrameLayout mBrowserFrameLayout; 245 246 @Override 247 public void onCreate(Bundle icicle) { 248 if (LOGV_ENABLED) { 249 Log.v(LOGTAG, this + " onStart"); 250 } 251 super.onCreate(icicle); 252 // test the browser in OpenGL 253 // requestWindowFeature(Window.FEATURE_OPENGL); 254 255 // enable this to test the browser in 32bit 256 if (false) { 257 getWindow().setFormat(PixelFormat.RGBX_8888); 258 BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888); 259 } 260 261 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); 262 263 mResolver = getContentResolver(); 264 265 // If this was a web search request, pass it on to the default web 266 // search provider and finish this activity. 267 if (handleWebSearchIntent(getIntent())) { 268 finish(); 269 return; 270 } 271 272 mSecLockIcon = Resources.getSystem().getDrawable( 273 android.R.drawable.ic_secure); 274 mMixLockIcon = Resources.getSystem().getDrawable( 275 android.R.drawable.ic_partial_secure); 276 277 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView() 278 .findViewById(com.android.internal.R.id.content); 279 mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this) 280 .inflate(R.layout.custom_screen, null); 281 mContentView = (FrameLayout) mBrowserFrameLayout.findViewById( 282 R.id.main_content); 283 mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout 284 .findViewById(R.id.error_console); 285 mCustomViewContainer = (FrameLayout) mBrowserFrameLayout 286 .findViewById(R.id.fullscreen_custom_content); 287 frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); 288 mTitleBar = new TitleBar(this); 289 mFakeTitleBar = new TitleBar(this); 290 291 // Create the tab control and our initial tab 292 mTabControl = new TabControl(this); 293 294 // Open the icon database and retain all the bookmark urls for favicons 295 retainIconsOnStartup(); 296 297 // Keep a settings instance handy. 298 mSettings = BrowserSettings.getInstance(); 299 mSettings.setTabControl(mTabControl); 300 mSettings.loadFromDb(this); 301 302 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 303 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser"); 304 305 /* enables registration for changes in network status from 306 http stack */ 307 mNetworkStateChangedFilter = new IntentFilter(); 308 mNetworkStateChangedFilter.addAction( 309 ConnectivityManager.CONNECTIVITY_ACTION); 310 mNetworkStateIntentReceiver = new BroadcastReceiver() { 311 @Override 312 public void onReceive(Context context, Intent intent) { 313 if (intent.getAction().equals( 314 ConnectivityManager.CONNECTIVITY_ACTION)) { 315 boolean noConnectivity = intent.getBooleanExtra( 316 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 317 if (!noConnectivity) { 318 NetworkInfo info = intent.getParcelableExtra( 319 ConnectivityManager.EXTRA_NETWORK_INFO); 320 String typeName = info.getTypeName(); 321 String subtypeName = info.getSubtypeName(); 322 sendNetworkType(typeName.toLowerCase(), 323 (subtypeName != null ? subtypeName.toLowerCase() : "")); 324 } 325 onNetworkToggle(!noConnectivity); 326 } 327 } 328 }; 329 330 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 331 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 332 filter.addDataScheme("package"); 333 mPackageInstallationReceiver = new BroadcastReceiver() { 334 @Override 335 public void onReceive(Context context, Intent intent) { 336 final String action = intent.getAction(); 337 final String packageName = intent.getData() 338 .getSchemeSpecificPart(); 339 final boolean replacing = intent.getBooleanExtra( 340 Intent.EXTRA_REPLACING, false); 341 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) { 342 // if it is replacing, refreshPlugins() when adding 343 return; 344 } 345 346 if (sGoogleApps.contains(packageName)) { 347 BrowserActivity.this.packageChanged(packageName, 348 Intent.ACTION_PACKAGE_ADDED.equals(action)); 349 } 350 351 PackageManager pm = BrowserActivity.this.getPackageManager(); 352 PackageInfo pkgInfo = null; 353 try { 354 pkgInfo = pm.getPackageInfo(packageName, 355 PackageManager.GET_PERMISSIONS); 356 } catch (PackageManager.NameNotFoundException e) { 357 return; 358 } 359 if (pkgInfo != null) { 360 String permissions[] = pkgInfo.requestedPermissions; 361 if (permissions == null) { 362 return; 363 } 364 boolean permissionOk = false; 365 for (String permit : permissions) { 366 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) { 367 permissionOk = true; 368 break; 369 } 370 } 371 if (permissionOk) { 372 PluginManager.getInstance(BrowserActivity.this) 373 .refreshPlugins( 374 Intent.ACTION_PACKAGE_ADDED 375 .equals(action)); 376 } 377 } 378 } 379 }; 380 registerReceiver(mPackageInstallationReceiver, filter); 381 382 if (!mTabControl.restoreState(icicle)) { 383 // clear up the thumbnail directory if we can't restore the state as 384 // none of the files in the directory are referenced any more. 385 new ClearThumbnails().execute( 386 mTabControl.getThumbnailDir().listFiles()); 387 // there is no quit on Android. But if we can't restore the state, 388 // we can treat it as a new Browser, remove the old session cookies. 389 CookieManager.getInstance().removeSessionCookie(); 390 final Intent intent = getIntent(); 391 final Bundle extra = intent.getExtras(); 392 // Create an initial tab. 393 // If the intent is ACTION_VIEW and data is not null, the Browser is 394 // invoked to view the content by another application. In this case, 395 // the tab will be close when exit. 396 UrlData urlData = getUrlDataFromIntent(intent); 397 398 String action = intent.getAction(); 399 final Tab t = mTabControl.createNewTab( 400 (Intent.ACTION_VIEW.equals(action) && 401 intent.getData() != null) 402 || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS 403 .equals(action), 404 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl); 405 mTabControl.setCurrentTab(t); 406 attachTabToContentView(t); 407 WebView webView = t.getWebView(); 408 if (extra != null) { 409 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0); 410 if (scale > 0 && scale <= 1000) { 411 webView.setInitialScale(scale); 412 } 413 } 414 // If we are not restoring from an icicle, then there is a high 415 // likely hood this is the first run. So, check to see if the 416 // homepage needs to be configured and copy any plugins from our 417 // asset directory to the data partition. 418 if ((extra == null || !extra.getBoolean("testing")) 419 && !mSettings.isLoginInitialized()) { 420 startReadOfGoogleAccounts(); 421 } 422 423 if (urlData.isEmpty()) { 424 if (mSettings.isLoginInitialized()) { 425 webView.loadUrl(mSettings.getHomePage()); 426 } else { 427 waitForCredentials(); 428 } 429 } else { 430 urlData.loadIn(t); 431 } 432 } else { 433 // TabControl.restoreState() will create a new tab even if 434 // restoring the state fails. 435 attachTabToContentView(mTabControl.getCurrentTab()); 436 } 437 438 // Read JavaScript flags if it exists. 439 String jsFlags = mSettings.getJsFlags(); 440 if (jsFlags.trim().length() != 0) { 441 mTabControl.getCurrentWebView().setJsFlags(jsFlags); 442 } 443 // Work out which packages are installed on the system. 444 getInstalledPackages(); 445 } 446 447 /** 448 * Feed the previously stored results strings to the BrowserProvider so that 449 * the SearchDialog will show them instead of the standard searches. 450 * @param result String to show on the editable line of the SearchDialog. 451 */ 452 /* package */ void showVoiceSearchResults(String result) { 453 ContentProviderClient client = mResolver.acquireContentProviderClient( 454 Browser.BOOKMARKS_URI); 455 ContentProvider prov = client.getLocalContentProvider(); 456 BrowserProvider bp = (BrowserProvider) prov; 457 bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults()); 458 client.release(); 459 460 startSearch(result, false, 461 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), 462 false); 463 } 464 465 @Override 466 protected void onNewIntent(Intent intent) { 467 Tab current = mTabControl.getCurrentTab(); 468 // When a tab is closed on exit, the current tab index is set to -1. 469 // Reset before proceed as Browser requires the current tab to be set. 470 if (current == null) { 471 // Try to reset the tab in case the index was incorrect. 472 current = mTabControl.getTab(0); 473 if (current == null) { 474 // No tabs at all so just ignore this intent. 475 return; 476 } 477 mTabControl.setCurrentTab(current); 478 attachTabToContentView(current); 479 resetTitleAndIcon(current.getWebView()); 480 } 481 final String action = intent.getAction(); 482 final int flags = intent.getFlags(); 483 if (Intent.ACTION_MAIN.equals(action) || 484 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { 485 // just resume the browser 486 return; 487 } 488 boolean activateVoiceSearch = RecognizerResultsIntent 489 .ACTION_VOICE_SEARCH_RESULTS.equals(action); 490 if (Intent.ACTION_VIEW.equals(action) 491 || Intent.ACTION_SEARCH.equals(action) 492 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 493 || Intent.ACTION_WEB_SEARCH.equals(action) 494 || activateVoiceSearch) { 495 if (current.isInVoiceSearchMode()) { 496 String title = current.getVoiceDisplayTitle(); 497 if (title != null && title.equals(intent.getStringExtra( 498 SearchManager.QUERY))) { 499 // The user submitted the same search as the last voice 500 // search, so do nothing. 501 return; 502 } 503 if (Intent.ACTION_SEARCH.equals(action) 504 && current.voiceSearchSourceIsGoogle()) { 505 Intent logIntent = new Intent( 506 LoggingEvents.ACTION_LOG_EVENT); 507 logIntent.putExtra(LoggingEvents.EXTRA_EVENT, 508 LoggingEvents.VoiceSearch.QUERY_UPDATED); 509 logIntent.putExtra( 510 LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE, 511 intent.getDataString()); 512 sendBroadcast(logIntent); 513 // Note, onPageStarted will revert the voice title bar 514 // When http://b/issue?id=2379215 is fixed, we should update 515 // the title bar here. 516 } 517 } 518 // If this was a search request (e.g. search query directly typed into the address bar), 519 // pass it on to the default web search provider. 520 if (handleWebSearchIntent(intent)) { 521 return; 522 } 523 524 UrlData urlData = getUrlDataFromIntent(intent); 525 if (urlData.isEmpty()) { 526 urlData = new UrlData(mSettings.getHomePage()); 527 } 528 529 final String appId = intent 530 .getStringExtra(Browser.EXTRA_APPLICATION_ID); 531 if ((Intent.ACTION_VIEW.equals(action) || activateVoiceSearch) 532 && !getPackageName().equals(appId) 533 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { 534 Tab appTab = mTabControl.getTabFromId(appId); 535 if (appTab != null) { 536 Log.i(LOGTAG, "Reusing tab for " + appId); 537 // Dismiss the subwindow if applicable. 538 dismissSubWindow(appTab); 539 // Since we might kill the WebView, remove it from the 540 // content view first. 541 removeTabFromContentView(appTab); 542 // Recreate the main WebView after destroying the old one. 543 // If the WebView has the same original url and is on that 544 // page, it can be reused. 545 boolean needsLoad = 546 mTabControl.recreateWebView(appTab, urlData.mUrl); 547 548 if (current != appTab) { 549 switchToTab(mTabControl.getTabIndex(appTab)); 550 if (needsLoad) { 551 urlData.loadIn(appTab); 552 } 553 } else { 554 // If the tab was the current tab, we have to attach 555 // it to the view system again. 556 attachTabToContentView(appTab); 557 if (needsLoad) { 558 urlData.loadIn(appTab); 559 } 560 } 561 return; 562 } else { 563 // No matching application tab, try to find a regular tab 564 // with a matching url. 565 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl); 566 if (appTab != null) { 567 if (current != appTab) { 568 switchToTab(mTabControl.getTabIndex(appTab)); 569 } 570 // Otherwise, we are already viewing the correct tab. 571 } else { 572 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url 573 // will be opened in a new tab unless we have reached 574 // MAX_TABS. Then the url will be opened in the current 575 // tab. If a new tab is created, it will have "true" for 576 // exit on close. 577 openTabAndShow(urlData, true, appId); 578 } 579 } 580 } else { 581 if (!urlData.isEmpty() 582 && urlData.mUrl.startsWith("about:debug")) { 583 if ("about:debug.dom".equals(urlData.mUrl)) { 584 current.getWebView().dumpDomTree(false); 585 } else if ("about:debug.dom.file".equals(urlData.mUrl)) { 586 current.getWebView().dumpDomTree(true); 587 } else if ("about:debug.render".equals(urlData.mUrl)) { 588 current.getWebView().dumpRenderTree(false); 589 } else if ("about:debug.render.file".equals(urlData.mUrl)) { 590 current.getWebView().dumpRenderTree(true); 591 } else if ("about:debug.display".equals(urlData.mUrl)) { 592 current.getWebView().dumpDisplayTree(); 593 } else if (urlData.mUrl.startsWith("about:debug.drag")) { 594 int index = urlData.mUrl.codePointAt(16) - '0'; 595 if (index <= 0 || index > 9) { 596 current.getWebView().setDragTracker(null); 597 } else { 598 current.getWebView().setDragTracker(new MeshTracker(index)); 599 } 600 } else { 601 mSettings.toggleDebugSettings(); 602 } 603 return; 604 } 605 // Get rid of the subwindow if it exists 606 dismissSubWindow(current); 607 urlData.loadIn(current); 608 } 609 } 610 } 611 612 private int parseUrlShortcut(String url) { 613 if (url == null) return SHORTCUT_INVALID; 614 615 // FIXME: quick search, need to be customized by setting 616 if (url.length() > 2 && url.charAt(1) == ' ') { 617 switch (url.charAt(0)) { 618 case 'g': return SHORTCUT_GOOGLE_SEARCH; 619 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH; 620 case 'd': return SHORTCUT_DICTIONARY_SEARCH; 621 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH; 622 } 623 } 624 return SHORTCUT_INVALID; 625 } 626 627 /** 628 * Launches the default web search activity with the query parameters if the given intent's data 629 * are identified as plain search terms and not URLs/shortcuts. 630 * @return true if the intent was handled and web search activity was launched, false if not. 631 */ 632 private boolean handleWebSearchIntent(Intent intent) { 633 if (intent == null) return false; 634 635 String url = null; 636 final String action = intent.getAction(); 637 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals( 638 action)) { 639 return false; 640 } 641 if (Intent.ACTION_VIEW.equals(action)) { 642 Uri data = intent.getData(); 643 if (data != null) url = data.toString(); 644 } else if (Intent.ACTION_SEARCH.equals(action) 645 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 646 || Intent.ACTION_WEB_SEARCH.equals(action)) { 647 url = intent.getStringExtra(SearchManager.QUERY); 648 } 649 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA), 650 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); 651 } 652 653 /** 654 * Launches the default web search activity with the query parameters if the given url string 655 * was identified as plain search terms and not URL/shortcut. 656 * @return true if the request was handled and web search activity was launched, false if not. 657 */ 658 private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) { 659 if (inUrl == null) return false; 660 661 // In general, we shouldn't modify URL from Intent. 662 // But currently, we get the user-typed URL from search box as well. 663 String url = fixUrl(inUrl).trim(); 664 665 // URLs and site specific search shortcuts are handled by the regular flow of control, so 666 // return early. 667 if (Patterns.WEB_URL.matcher(url).matches() 668 || ACCEPTED_URI_SCHEMA.matcher(url).matches() 669 || parseUrlShortcut(url) != SHORTCUT_INVALID) { 670 return false; 671 } 672 673 Browser.updateVisitedHistory(mResolver, url, false); 674 Browser.addSearchUrl(mResolver, url); 675 676 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); 677 intent.addCategory(Intent.CATEGORY_DEFAULT); 678 intent.putExtra(SearchManager.QUERY, url); 679 if (appData != null) { 680 intent.putExtra(SearchManager.APP_DATA, appData); 681 } 682 if (extraData != null) { 683 intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); 684 } 685 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName()); 686 startActivity(intent); 687 688 return true; 689 } 690 691 private UrlData getUrlDataFromIntent(Intent intent) { 692 String url = ""; 693 Map<String, String> headers = null; 694 if (intent != null) { 695 final String action = intent.getAction(); 696 if (Intent.ACTION_VIEW.equals(action)) { 697 url = smartUrlFilter(intent.getData()); 698 if (url != null && url.startsWith("content:")) { 699 /* Append mimetype so webview knows how to display */ 700 String mimeType = intent.resolveType(getContentResolver()); 701 if (mimeType != null) { 702 url += "?" + mimeType; 703 } 704 } 705 if (url != null && url.startsWith("http")) { 706 final Bundle pairs = intent 707 .getBundleExtra(Browser.EXTRA_HEADERS); 708 if (pairs != null && !pairs.isEmpty()) { 709 Iterator<String> iter = pairs.keySet().iterator(); 710 headers = new HashMap<String, String>(); 711 while (iter.hasNext()) { 712 String key = iter.next(); 713 headers.put(key, pairs.getString(key)); 714 } 715 } 716 } 717 } else if (Intent.ACTION_SEARCH.equals(action) 718 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 719 || Intent.ACTION_WEB_SEARCH.equals(action)) { 720 url = intent.getStringExtra(SearchManager.QUERY); 721 if (url != null) { 722 mLastEnteredUrl = url; 723 Browser.updateVisitedHistory(mResolver, url, false); 724 // In general, we shouldn't modify URL from Intent. 725 // But currently, we get the user-typed URL from search box as well. 726 url = fixUrl(url); 727 url = smartUrlFilter(url); 728 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&"; 729 if (url.contains(searchSource)) { 730 String source = null; 731 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); 732 if (appData != null) { 733 source = appData.getString(Search.SOURCE); 734 } 735 if (TextUtils.isEmpty(source)) { 736 source = GOOGLE_SEARCH_SOURCE_UNKNOWN; 737 } 738 url = url.replace(searchSource, "&source=android-"+source+"&"); 739 } 740 } 741 } 742 } 743 return new UrlData(url, headers, intent); 744 } 745 /* package */ void showVoiceTitleBar(String title) { 746 mTitleBar.setInVoiceMode(true); 747 mFakeTitleBar.setInVoiceMode(true); 748 749 mTitleBar.setDisplayTitle(title); 750 mFakeTitleBar.setDisplayTitle(title); 751 } 752 /* package */ void revertVoiceTitleBar() { 753 mTitleBar.setInVoiceMode(false); 754 mFakeTitleBar.setInVoiceMode(false); 755 756 mTitleBar.setDisplayTitle(mTitle); 757 mFakeTitleBar.setDisplayTitle(mTitle); 758 } 759 /* package */ static String fixUrl(String inUrl) { 760 // FIXME: Converting the url to lower case 761 // duplicates functionality in smartUrlFilter(). 762 // However, changing all current callers of fixUrl to 763 // call smartUrlFilter in addition may have unwanted 764 // consequences, and is deferred for now. 765 int colon = inUrl.indexOf(':'); 766 boolean allLower = true; 767 for (int index = 0; index < colon; index++) { 768 char ch = inUrl.charAt(index); 769 if (!Character.isLetter(ch)) { 770 break; 771 } 772 allLower &= Character.isLowerCase(ch); 773 if (index == colon - 1 && !allLower) { 774 inUrl = inUrl.substring(0, colon).toLowerCase() 775 + inUrl.substring(colon); 776 } 777 } 778 if (inUrl.startsWith("http://") || inUrl.startsWith("https://")) 779 return inUrl; 780 if (inUrl.startsWith("http:") || 781 inUrl.startsWith("https:")) { 782 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) { 783 inUrl = inUrl.replaceFirst("/", "//"); 784 } else inUrl = inUrl.replaceFirst(":", "://"); 785 } 786 return inUrl; 787 } 788 789 @Override 790 protected void onResume() { 791 super.onResume(); 792 if (LOGV_ENABLED) { 793 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this); 794 } 795 796 if (!mActivityInPause) { 797 Log.e(LOGTAG, "BrowserActivity is already resumed."); 798 return; 799 } 800 801 mTabControl.resumeCurrentTab(); 802 mActivityInPause = false; 803 resumeWebViewTimers(); 804 805 if (mWakeLock.isHeld()) { 806 mHandler.removeMessages(RELEASE_WAKELOCK); 807 mWakeLock.release(); 808 } 809 810 if (mCredsDlg != null) { 811 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) { 812 // In case credential request never comes back 813 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000); 814 } 815 } 816 817 registerReceiver(mNetworkStateIntentReceiver, 818 mNetworkStateChangedFilter); 819 WebView.enablePlatformNotifications(); 820 } 821 822 /** 823 * Since the actual title bar is embedded in the WebView, and removing it 824 * would change its appearance, use a different TitleBar to show overlayed 825 * at the top of the screen, when the menu is open or the page is loading. 826 */ 827 private TitleBar mFakeTitleBar; 828 829 /** 830 * Holder for the fake title bar. It will have a foreground shadow, as well 831 * as a white background, so the fake title bar looks like the real one. 832 */ 833 private ViewGroup mFakeTitleBarHolder; 834 835 /** 836 * Layout parameters for the fake title bar within mFakeTitleBarHolder 837 */ 838 private FrameLayout.LayoutParams mFakeTitleBarParams 839 = new FrameLayout.LayoutParams( 840 ViewGroup.LayoutParams.MATCH_PARENT, 841 ViewGroup.LayoutParams.WRAP_CONTENT); 842 /** 843 * Keeps track of whether the options menu is open. This is important in 844 * determining whether to show or hide the title bar overlay. 845 */ 846 private boolean mOptionsMenuOpen; 847 848 /** 849 * Only meaningful when mOptionsMenuOpen is true. This variable keeps track 850 * of whether the configuration has changed. The first onMenuOpened call 851 * after a configuration change is simply a reopening of the same menu 852 * (i.e. mIconView did not change). 853 */ 854 private boolean mConfigChanged; 855 856 /** 857 * Whether or not the options menu is in its smaller, icon menu form. When 858 * true, we want the title bar overlay to be up. When false, we do not. 859 * Only meaningful if mOptionsMenuOpen is true. 860 */ 861 private boolean mIconView; 862 863 @Override 864 public boolean onMenuOpened(int featureId, Menu menu) { 865 if (Window.FEATURE_OPTIONS_PANEL == featureId) { 866 if (mOptionsMenuOpen) { 867 if (mConfigChanged) { 868 // We do not need to make any changes to the state of the 869 // title bar, since the only thing that happened was a 870 // change in orientation 871 mConfigChanged = false; 872 } else { 873 if (mIconView) { 874 // Switching the menu to expanded view, so hide the 875 // title bar. 876 hideFakeTitleBar(); 877 mIconView = false; 878 } else { 879 // Switching the menu back to icon view, so show the 880 // title bar once again. 881 showFakeTitleBar(); 882 mIconView = true; 883 } 884 } 885 } else { 886 // The options menu is closed, so open it, and show the title 887 showFakeTitleBar(); 888 mOptionsMenuOpen = true; 889 mConfigChanged = false; 890 mIconView = true; 891 } 892 } 893 return true; 894 } 895 896 /** 897 * Special class used exclusively for the shadow drawn underneath the fake 898 * title bar. The shadow does not need to be drawn if the WebView 899 * underneath is scrolled to the top, because it will draw directly on top 900 * of the embedded shadow. 901 */ 902 private static class Shadow extends View { 903 private WebView mWebView; 904 905 public Shadow(Context context, AttributeSet attrs) { 906 super(context, attrs); 907 } 908 909 public void setWebView(WebView view) { 910 mWebView = view; 911 } 912 913 @Override 914 public void draw(Canvas canvas) { 915 // In general onDraw is the method to override, but we care about 916 // whether or not the background gets drawn, which happens in draw() 917 if (mWebView == null || mWebView.getScrollY() > getHeight()) { 918 super.draw(canvas); 919 } 920 // Need to invalidate so that if the scroll position changes, we 921 // still draw as appropriate. 922 invalidate(); 923 } 924 } 925 926 private void showFakeTitleBar() { 927 final View decor = getWindow().peekDecorView(); 928 if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null 929 && !mActivityInPause && decor != null 930 && decor.getWindowToken() != null) { 931 Rect visRect = new Rect(); 932 if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) { 933 if (LOGD_ENABLED) { 934 Log.d(LOGTAG, "showFakeTitleBar visRect failed"); 935 } 936 return; 937 } 938 939 WindowManager manager 940 = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 941 942 // Add the title bar to the window manager so it can receive touches 943 // while the menu is up 944 WindowManager.LayoutParams params 945 = new WindowManager.LayoutParams( 946 ViewGroup.LayoutParams.MATCH_PARENT, 947 ViewGroup.LayoutParams.WRAP_CONTENT, 948 WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 949 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 950 PixelFormat.TRANSLUCENT); 951 params.gravity = Gravity.TOP; 952 WebView mainView = mTabControl.getCurrentWebView(); 953 boolean atTop = mainView != null && mainView.getScrollY() == 0; 954 params.windowAnimations = atTop ? 0 : R.style.TitleBar; 955 // XXX : Without providing an offset, the fake title bar will be 956 // placed underneath the status bar. Use the global visible rect 957 // of mBrowserFrameLayout to determine the bottom of the status bar 958 params.y = visRect.top; 959 // Add a holder for the title bar. It also holds a shadow to show 960 // below the title bar. 961 if (mFakeTitleBarHolder == null) { 962 mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this) 963 .inflate(R.layout.title_bar_bg, null); 964 } 965 Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById( 966 R.id.shadow); 967 shadow.setWebView(mainView); 968 mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams); 969 manager.addView(mFakeTitleBarHolder, params); 970 } 971 } 972 973 @Override 974 public void onOptionsMenuClosed(Menu menu) { 975 mOptionsMenuOpen = false; 976 if (!mInLoad) { 977 hideFakeTitleBar(); 978 } else if (!mIconView) { 979 // The page is currently loading, and we are in expanded mode, so 980 // we were not showing the menu. Show it once again. It will be 981 // removed when the page finishes. 982 showFakeTitleBar(); 983 } 984 } 985 986 private void hideFakeTitleBar() { 987 if (mFakeTitleBar.getParent() == null) return; 988 WindowManager.LayoutParams params = (WindowManager.LayoutParams) 989 mFakeTitleBarHolder.getLayoutParams(); 990 WebView mainView = mTabControl.getCurrentWebView(); 991 // Although we decided whether or not to animate based on the current 992 // scroll position, the scroll position may have changed since the 993 // fake title bar was displayed. Make sure it has the appropriate 994 // animation/lack thereof before removing. 995 params.windowAnimations = mainView != null && mainView.getScrollY() == 0 996 ? 0 : R.style.TitleBar; 997 WindowManager manager 998 = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 999 manager.updateViewLayout(mFakeTitleBarHolder, params); 1000 mFakeTitleBarHolder.removeView(mFakeTitleBar); 1001 manager.removeView(mFakeTitleBarHolder); 1002 } 1003 1004 /** 1005 * Special method for the fake title bar to call when displaying its context 1006 * menu, since it is in its own Window, and its parent does not show a 1007 * context menu. 1008 */ 1009 /* package */ void showTitleBarContextMenu() { 1010 if (null == mTitleBar.getParent()) { 1011 return; 1012 } 1013 openContextMenu(mTitleBar); 1014 } 1015 1016 @Override 1017 public void onContextMenuClosed(Menu menu) { 1018 super.onContextMenuClosed(menu); 1019 if (mInLoad) { 1020 showFakeTitleBar(); 1021 } 1022 } 1023 1024 /** 1025 * onSaveInstanceState(Bundle map) 1026 * onSaveInstanceState is called right before onStop(). The map contains 1027 * the saved state. 1028 */ 1029 @Override 1030 protected void onSaveInstanceState(Bundle outState) { 1031 if (LOGV_ENABLED) { 1032 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this); 1033 } 1034 // the default implementation requires each view to have an id. As the 1035 // browser handles the state itself and it doesn't use id for the views, 1036 // don't call the default implementation. Otherwise it will trigger the 1037 // warning like this, "couldn't save which view has focus because the 1038 // focused view XXX has no id". 1039 1040 // Save all the tabs 1041 mTabControl.saveState(outState); 1042 } 1043 1044 @Override 1045 protected void onPause() { 1046 super.onPause(); 1047 1048 if (mActivityInPause) { 1049 Log.e(LOGTAG, "BrowserActivity is already paused."); 1050 return; 1051 } 1052 1053 mTabControl.pauseCurrentTab(); 1054 mActivityInPause = true; 1055 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) { 1056 mWakeLock.acquire(); 1057 mHandler.sendMessageDelayed(mHandler 1058 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT); 1059 } 1060 1061 // Clear the credentials toast if it is up 1062 if (mCredsDlg != null && mCredsDlg.isShowing()) { 1063 mCredsDlg.dismiss(); 1064 } 1065 mCredsDlg = null; 1066 1067 // FIXME: This removes the active tabs page and resets the menu to 1068 // MAIN_MENU. A better solution might be to do this work in onNewIntent 1069 // but then we would need to save it in onSaveInstanceState and restore 1070 // it in onCreate/onRestoreInstanceState 1071 if (mActiveTabsPage != null) { 1072 removeActiveTabPage(true); 1073 } 1074 1075 cancelStopToast(); 1076 1077 // unregister network state listener 1078 unregisterReceiver(mNetworkStateIntentReceiver); 1079 WebView.disablePlatformNotifications(); 1080 } 1081 1082 @Override 1083 protected void onDestroy() { 1084 if (LOGV_ENABLED) { 1085 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this); 1086 } 1087 super.onDestroy(); 1088 1089 if (mUploadMessage != null) { 1090 mUploadMessage.onReceiveValue(null); 1091 mUploadMessage = null; 1092 } 1093 1094 if (mTabControl == null) return; 1095 1096 // Remove the fake title bar if it is there 1097 hideFakeTitleBar(); 1098 1099 // Remove the current tab and sub window 1100 Tab t = mTabControl.getCurrentTab(); 1101 if (t != null) { 1102 dismissSubWindow(t); 1103 removeTabFromContentView(t); 1104 } 1105 // Destroy all the tabs 1106 mTabControl.destroy(); 1107 WebIconDatabase.getInstance().close(); 1108 1109 unregisterReceiver(mPackageInstallationReceiver); 1110 } 1111 1112 @Override 1113 public void onConfigurationChanged(Configuration newConfig) { 1114 mConfigChanged = true; 1115 super.onConfigurationChanged(newConfig); 1116 1117 if (mPageInfoDialog != null) { 1118 mPageInfoDialog.dismiss(); 1119 showPageInfo( 1120 mPageInfoView, 1121 mPageInfoFromShowSSLCertificateOnError); 1122 } 1123 if (mSSLCertificateDialog != null) { 1124 mSSLCertificateDialog.dismiss(); 1125 showSSLCertificate( 1126 mSSLCertificateView); 1127 } 1128 if (mSSLCertificateOnErrorDialog != null) { 1129 mSSLCertificateOnErrorDialog.dismiss(); 1130 showSSLCertificateOnError( 1131 mSSLCertificateOnErrorView, 1132 mSSLCertificateOnErrorHandler, 1133 mSSLCertificateOnErrorError); 1134 } 1135 if (mHttpAuthenticationDialog != null) { 1136 String title = ((TextView) mHttpAuthenticationDialog 1137 .findViewById(com.android.internal.R.id.alertTitle)).getText() 1138 .toString(); 1139 String name = ((TextView) mHttpAuthenticationDialog 1140 .findViewById(R.id.username_edit)).getText().toString(); 1141 String password = ((TextView) mHttpAuthenticationDialog 1142 .findViewById(R.id.password_edit)).getText().toString(); 1143 int focusId = mHttpAuthenticationDialog.getCurrentFocus() 1144 .getId(); 1145 mHttpAuthenticationDialog.dismiss(); 1146 showHttpAuthentication(mHttpAuthHandler, null, null, title, 1147 name, password, focusId); 1148 } 1149 } 1150 1151 @Override 1152 public void onLowMemory() { 1153 super.onLowMemory(); 1154 mTabControl.freeMemory(); 1155 } 1156 1157 private boolean resumeWebViewTimers() { 1158 Tab tab = mTabControl.getCurrentTab(); 1159 boolean inLoad = tab.inLoad(); 1160 if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) { 1161 CookieSyncManager.getInstance().startSync(); 1162 WebView w = tab.getWebView(); 1163 if (w != null) { 1164 w.resumeTimers(); 1165 } 1166 return true; 1167 } else { 1168 return false; 1169 } 1170 } 1171 1172 private boolean pauseWebViewTimers() { 1173 Tab tab = mTabControl.getCurrentTab(); 1174 boolean inLoad = tab.inLoad(); 1175 if (mActivityInPause && !inLoad) { 1176 CookieSyncManager.getInstance().stopSync(); 1177 WebView w = mTabControl.getCurrentWebView(); 1178 if (w != null) { 1179 w.pauseTimers(); 1180 } 1181 return true; 1182 } else { 1183 return false; 1184 } 1185 } 1186 1187 // FIXME: Do we want to call this when loading google for the first time? 1188 /* 1189 * This function is called when we are launching for the first time. We 1190 * are waiting for the login credentials before loading Google home 1191 * pages. This way the user will be logged in straight away. 1192 */ 1193 private void waitForCredentials() { 1194 // Show a toast 1195 mCredsDlg = new ProgressDialog(this); 1196 mCredsDlg.setIndeterminate(true); 1197 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg)); 1198 // If the user cancels the operation, then cancel the Google 1199 // Credentials request. 1200 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST)); 1201 mCredsDlg.show(); 1202 1203 // We set a timeout for the retrieval of credentials in onResume() 1204 // as that is when we have freed up some CPU time to get 1205 // the login credentials. 1206 } 1207 1208 /* 1209 * If we have received the credentials or we have timed out and we are 1210 * showing the credentials dialog, then it is time to move on. 1211 */ 1212 private void resumeAfterCredentials() { 1213 if (mCredsDlg == null) { 1214 return; 1215 } 1216 1217 // Clear the toast 1218 if (mCredsDlg.isShowing()) { 1219 mCredsDlg.dismiss(); 1220 } 1221 mCredsDlg = null; 1222 1223 // Clear any pending timeout 1224 mHandler.removeMessages(CANCEL_CREDS_REQUEST); 1225 1226 // Load the page 1227 WebView w = mTabControl.getCurrentWebView(); 1228 if (w != null) { 1229 w.loadUrl(mSettings.getHomePage()); 1230 } 1231 1232 // Update the settings, need to do this last as it can take a moment 1233 // to persist the settings. In the mean time we could be loading 1234 // content. 1235 mSettings.setLoginInitialized(this); 1236 } 1237 1238 // Open the icon database and retain all the icons for visited sites. 1239 private void retainIconsOnStartup() { 1240 final WebIconDatabase db = WebIconDatabase.getInstance(); 1241 db.open(getDir("icons", 0).getPath()); 1242 try { 1243 Cursor c = Browser.getAllBookmarks(mResolver); 1244 if (!c.moveToFirst()) { 1245 c.deactivate(); 1246 return; 1247 } 1248 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); 1249 do { 1250 String url = c.getString(urlIndex); 1251 db.retainIconForPageUrl(url); 1252 } while (c.moveToNext()); 1253 c.deactivate(); 1254 } catch (IllegalStateException e) { 1255 Log.e(LOGTAG, "retainIconsOnStartup", e); 1256 } 1257 } 1258 1259 // Helper method for getting the top window. 1260 WebView getTopWindow() { 1261 return mTabControl.getCurrentTopWebView(); 1262 } 1263 1264 TabControl getTabControl() { 1265 return mTabControl; 1266 } 1267 1268 @Override 1269 public boolean onCreateOptionsMenu(Menu menu) { 1270 super.onCreateOptionsMenu(menu); 1271 1272 MenuInflater inflater = getMenuInflater(); 1273 inflater.inflate(R.menu.browser, menu); 1274 mMenu = menu; 1275 updateInLoadMenuItems(); 1276 return true; 1277 } 1278 1279 /** 1280 * As the menu can be open when loading state changes 1281 * we must manually update the state of the stop/reload menu 1282 * item 1283 */ 1284 private void updateInLoadMenuItems() { 1285 if (mMenu == null) { 1286 return; 1287 } 1288 MenuItem src = mInLoad ? 1289 mMenu.findItem(R.id.stop_menu_id): 1290 mMenu.findItem(R.id.reload_menu_id); 1291 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); 1292 dest.setIcon(src.getIcon()); 1293 dest.setTitle(src.getTitle()); 1294 } 1295 1296 @Override 1297 public boolean onContextItemSelected(MenuItem item) { 1298 // chording is not an issue with context menus, but we use the same 1299 // options selector, so set mCanChord to true so we can access them. 1300 mCanChord = true; 1301 int id = item.getItemId(); 1302 boolean result = true; 1303 switch (id) { 1304 // For the context menu from the title bar 1305 case R.id.title_bar_copy_page_url: 1306 Tab currentTab = mTabControl.getCurrentTab(); 1307 if (null == currentTab) { 1308 result = false; 1309 break; 1310 } 1311 WebView mainView = currentTab.getWebView(); 1312 if (null == mainView) { 1313 result = false; 1314 break; 1315 } 1316 copy(mainView.getUrl()); 1317 break; 1318 // -- Browser context menu 1319 case R.id.open_context_menu_id: 1320 case R.id.open_newtab_context_menu_id: 1321 case R.id.bookmark_context_menu_id: 1322 case R.id.save_link_context_menu_id: 1323 case R.id.share_link_context_menu_id: 1324 case R.id.copy_link_context_menu_id: 1325 final WebView webView = getTopWindow(); 1326 if (null == webView) { 1327 result = false; 1328 break; 1329 } 1330 final HashMap hrefMap = new HashMap(); 1331 hrefMap.put("webview", webView); 1332 final Message msg = mHandler.obtainMessage( 1333 FOCUS_NODE_HREF, id, 0, hrefMap); 1334 webView.requestFocusNodeHref(msg); 1335 break; 1336 1337 default: 1338 // For other context menus 1339 result = onOptionsItemSelected(item); 1340 } 1341 mCanChord = false; 1342 return result; 1343 } 1344 1345 private Bundle createGoogleSearchSourceBundle(String source) { 1346 Bundle bundle = new Bundle(); 1347 bundle.putString(Search.SOURCE, source); 1348 return bundle; 1349 } 1350 1351 /** 1352 * Overriding this to insert a local information bundle 1353 */ 1354 @Override 1355 public boolean onSearchRequested() { 1356 if (mOptionsMenuOpen) closeOptionsMenu(); 1357 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl(); 1358 startSearch(mSettings.getHomePage().equals(url) ? null : url, true, 1359 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false); 1360 return true; 1361 } 1362 1363 @Override 1364 public void startSearch(String initialQuery, boolean selectInitialQuery, 1365 Bundle appSearchData, boolean globalSearch) { 1366 if (appSearchData == null) { 1367 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE); 1368 } 1369 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 1370 } 1371 1372 /** 1373 * Switch tabs. Called by the TitleBarSet when sliding the title bar 1374 * results in changing tabs. 1375 * @param index Index of the tab to change to, as defined by 1376 * mTabControl.getTabIndex(Tab t). 1377 * @return boolean True if we successfully switched to a different tab. If 1378 * the indexth tab is null, or if that tab is the same as 1379 * the current one, return false. 1380 */ 1381 /* package */ boolean switchToTab(int index) { 1382 Tab tab = mTabControl.getTab(index); 1383 Tab currentTab = mTabControl.getCurrentTab(); 1384 if (tab == null || tab == currentTab) { 1385 return false; 1386 } 1387 if (currentTab != null) { 1388 // currentTab may be null if it was just removed. In that case, 1389 // we do not need to remove it 1390 removeTabFromContentView(currentTab); 1391 } 1392 mTabControl.setCurrentTab(tab); 1393 attachTabToContentView(tab); 1394 resetTitleIconAndProgress(); 1395 updateLockIconToLatest(); 1396 return true; 1397 } 1398 1399 /* package */ Tab openTabToHomePage() { 1400 return openTabAndShow(mSettings.getHomePage(), false, null); 1401 } 1402 1403 /* package */ void closeCurrentWindow() { 1404 final Tab current = mTabControl.getCurrentTab(); 1405 if (mTabControl.getTabCount() == 1) { 1406 // This is the last tab. Open a new one, with the home 1407 // page and close the current one. 1408 openTabToHomePage(); 1409 closeTab(current); 1410 return; 1411 } 1412 final Tab parent = current.getParentTab(); 1413 int indexToShow = -1; 1414 if (parent != null) { 1415 indexToShow = mTabControl.getTabIndex(parent); 1416 } else { 1417 final int currentIndex = mTabControl.getCurrentIndex(); 1418 // Try to move to the tab to the right 1419 indexToShow = currentIndex + 1; 1420 if (indexToShow > mTabControl.getTabCount() - 1) { 1421 // Try to move to the tab to the left 1422 indexToShow = currentIndex - 1; 1423 } 1424 } 1425 if (switchToTab(indexToShow)) { 1426 // Close window 1427 closeTab(current); 1428 } 1429 } 1430 1431 private ActiveTabsPage mActiveTabsPage; 1432 1433 /** 1434 * Remove the active tabs page. 1435 * @param needToAttach If true, the active tabs page did not attach a tab 1436 * to the content view, so we need to do that here. 1437 */ 1438 /* package */ void removeActiveTabPage(boolean needToAttach) { 1439 mContentView.removeView(mActiveTabsPage); 1440 mActiveTabsPage = null; 1441 mMenuState = R.id.MAIN_MENU; 1442 if (needToAttach) { 1443 attachTabToContentView(mTabControl.getCurrentTab()); 1444 } 1445 getTopWindow().requestFocus(); 1446 } 1447 1448 @Override 1449 public boolean onOptionsItemSelected(MenuItem item) { 1450 if (!mCanChord) { 1451 // The user has already fired a shortcut with this hold down of the 1452 // menu key. 1453 return false; 1454 } 1455 if (null == getTopWindow()) { 1456 return false; 1457 } 1458 if (mMenuIsDown) { 1459 // The shortcut action consumes the MENU. Even if it is still down, 1460 // it won't trigger the next shortcut action. In the case of the 1461 // shortcut action triggering a new activity, like Bookmarks, we 1462 // won't get onKeyUp for MENU. So it is important to reset it here. 1463 mMenuIsDown = false; 1464 } 1465 switch (item.getItemId()) { 1466 // -- Main menu 1467 case R.id.new_tab_menu_id: 1468 openTabToHomePage(); 1469 break; 1470 1471 case R.id.goto_menu_id: 1472 onSearchRequested(); 1473 break; 1474 1475 case R.id.bookmarks_menu_id: 1476 bookmarksOrHistoryPicker(false); 1477 break; 1478 1479 case R.id.active_tabs_menu_id: 1480 mActiveTabsPage = new ActiveTabsPage(this, mTabControl); 1481 removeTabFromContentView(mTabControl.getCurrentTab()); 1482 hideFakeTitleBar(); 1483 mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); 1484 mActiveTabsPage.requestFocus(); 1485 mMenuState = EMPTY_MENU; 1486 break; 1487 1488 case R.id.add_bookmark_menu_id: 1489 Intent i = new Intent(BrowserActivity.this, 1490 AddBookmarkPage.class); 1491 WebView w = getTopWindow(); 1492 i.putExtra("url", w.getUrl()); 1493 i.putExtra("title", w.getTitle()); 1494 i.putExtra("touch_icon_url", w.getTouchIconUrl()); 1495 i.putExtra("thumbnail", createScreenshot(w)); 1496 startActivity(i); 1497 break; 1498 1499 case R.id.stop_reload_menu_id: 1500 if (mInLoad) { 1501 stopLoading(); 1502 } else { 1503 getTopWindow().reload(); 1504 } 1505 break; 1506 1507 case R.id.back_menu_id: 1508 getTopWindow().goBack(); 1509 break; 1510 1511 case R.id.forward_menu_id: 1512 getTopWindow().goForward(); 1513 break; 1514 1515 case R.id.close_menu_id: 1516 // Close the subwindow if it exists. 1517 if (mTabControl.getCurrentSubWindow() != null) { 1518 dismissSubWindow(mTabControl.getCurrentTab()); 1519 break; 1520 } 1521 closeCurrentWindow(); 1522 break; 1523 1524 case R.id.homepage_menu_id: 1525 Tab current = mTabControl.getCurrentTab(); 1526 if (current != null) { 1527 dismissSubWindow(current); 1528 current.getWebView().loadUrl(mSettings.getHomePage()); 1529 } 1530 break; 1531 1532 case R.id.preferences_menu_id: 1533 Intent intent = new Intent(this, 1534 BrowserPreferencesPage.class); 1535 intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE, 1536 getTopWindow().getUrl()); 1537 startActivityForResult(intent, PREFERENCES_PAGE); 1538 break; 1539 1540 case R.id.find_menu_id: 1541 if (null == mFindDialog) { 1542 mFindDialog = new FindDialog(this); 1543 } 1544 mFindDialog.setWebView(getTopWindow()); 1545 mFindDialog.show(); 1546 mMenuState = EMPTY_MENU; 1547 break; 1548 1549 case R.id.select_text_id: 1550 getTopWindow().emulateShiftHeld(); 1551 break; 1552 case R.id.page_info_menu_id: 1553 showPageInfo(mTabControl.getCurrentTab(), false); 1554 break; 1555 1556 case R.id.classic_history_menu_id: 1557 bookmarksOrHistoryPicker(true); 1558 break; 1559 1560 case R.id.title_bar_share_page_url: 1561 case R.id.share_page_menu_id: 1562 Tab currentTab = mTabControl.getCurrentTab(); 1563 if (null == currentTab) { 1564 mCanChord = false; 1565 return false; 1566 } 1567 currentTab.populatePickerData(); 1568 sharePage(this, currentTab.getTitle(), 1569 currentTab.getUrl(), currentTab.getFavicon(), 1570 createScreenshot(currentTab.getWebView())); 1571 break; 1572 1573 case R.id.dump_nav_menu_id: 1574 getTopWindow().debugDump(); 1575 break; 1576 1577 case R.id.dump_counters_menu_id: 1578 getTopWindow().dumpV8Counters(); 1579 break; 1580 1581 case R.id.zoom_in_menu_id: 1582 getTopWindow().zoomIn(); 1583 break; 1584 1585 case R.id.zoom_out_menu_id: 1586 getTopWindow().zoomOut(); 1587 break; 1588 1589 case R.id.view_downloads_menu_id: 1590 viewDownloads(null); 1591 break; 1592 1593 case R.id.window_one_menu_id: 1594 case R.id.window_two_menu_id: 1595 case R.id.window_three_menu_id: 1596 case R.id.window_four_menu_id: 1597 case R.id.window_five_menu_id: 1598 case R.id.window_six_menu_id: 1599 case R.id.window_seven_menu_id: 1600 case R.id.window_eight_menu_id: 1601 { 1602 int menuid = item.getItemId(); 1603 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) { 1604 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) { 1605 Tab desiredTab = mTabControl.getTab(id); 1606 if (desiredTab != null && 1607 desiredTab != mTabControl.getCurrentTab()) { 1608 switchToTab(id); 1609 } 1610 break; 1611 } 1612 } 1613 } 1614 break; 1615 1616 default: 1617 if (!super.onOptionsItemSelected(item)) { 1618 return false; 1619 } 1620 // Otherwise fall through. 1621 } 1622 mCanChord = false; 1623 return true; 1624 } 1625 1626 public void closeFind() { 1627 mMenuState = R.id.MAIN_MENU; 1628 } 1629 1630 @Override 1631 public boolean onPrepareOptionsMenu(Menu menu) { 1632 // This happens when the user begins to hold down the menu key, so 1633 // allow them to chord to get a shortcut. 1634 mCanChord = true; 1635 // Note: setVisible will decide whether an item is visible; while 1636 // setEnabled() will decide whether an item is enabled, which also means 1637 // whether the matching shortcut key will function. 1638 super.onPrepareOptionsMenu(menu); 1639 switch (mMenuState) { 1640 case EMPTY_MENU: 1641 if (mCurrentMenuState != mMenuState) { 1642 menu.setGroupVisible(R.id.MAIN_MENU, false); 1643 menu.setGroupEnabled(R.id.MAIN_MENU, false); 1644 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false); 1645 } 1646 break; 1647 default: 1648 if (mCurrentMenuState != mMenuState) { 1649 menu.setGroupVisible(R.id.MAIN_MENU, true); 1650 menu.setGroupEnabled(R.id.MAIN_MENU, true); 1651 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true); 1652 } 1653 final WebView w = getTopWindow(); 1654 boolean canGoBack = false; 1655 boolean canGoForward = false; 1656 boolean isHome = false; 1657 if (w != null) { 1658 canGoBack = w.canGoBack(); 1659 canGoForward = w.canGoForward(); 1660 isHome = mSettings.getHomePage().equals(w.getUrl()); 1661 } 1662 final MenuItem back = menu.findItem(R.id.back_menu_id); 1663 back.setEnabled(canGoBack); 1664 1665 final MenuItem home = menu.findItem(R.id.homepage_menu_id); 1666 home.setEnabled(!isHome); 1667 1668 menu.findItem(R.id.forward_menu_id) 1669 .setEnabled(canGoForward); 1670 1671 menu.findItem(R.id.new_tab_menu_id).setEnabled( 1672 mTabControl.canCreateNewTab()); 1673 1674 // decide whether to show the share link option 1675 PackageManager pm = getPackageManager(); 1676 Intent send = new Intent(Intent.ACTION_SEND); 1677 send.setType("text/plain"); 1678 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); 1679 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null); 1680 1681 boolean isNavDump = mSettings.isNavDump(); 1682 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id); 1683 nav.setVisible(isNavDump); 1684 nav.setEnabled(isNavDump); 1685 1686 boolean showDebugSettings = mSettings.showDebugSettings(); 1687 final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id); 1688 counter.setVisible(showDebugSettings); 1689 counter.setEnabled(showDebugSettings); 1690 1691 break; 1692 } 1693 mCurrentMenuState = mMenuState; 1694 return true; 1695 } 1696 1697 @Override 1698 public void onCreateContextMenu(ContextMenu menu, View v, 1699 ContextMenuInfo menuInfo) { 1700 WebView webview = (WebView) v; 1701 WebView.HitTestResult result = webview.getHitTestResult(); 1702 if (result == null) { 1703 return; 1704 } 1705 1706 int type = result.getType(); 1707 if (type == WebView.HitTestResult.UNKNOWN_TYPE) { 1708 Log.w(LOGTAG, 1709 "We should not show context menu when nothing is touched"); 1710 return; 1711 } 1712 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) { 1713 // let TextView handles context menu 1714 return; 1715 } 1716 1717 // Note, http://b/issue?id=1106666 is requesting that 1718 // an inflated menu can be used again. This is not available 1719 // yet, so inflate each time (yuk!) 1720 MenuInflater inflater = getMenuInflater(); 1721 inflater.inflate(R.menu.browsercontext, menu); 1722 1723 // Show the correct menu group 1724 String extra = result.getExtra(); 1725 menu.setGroupVisible(R.id.PHONE_MENU, 1726 type == WebView.HitTestResult.PHONE_TYPE); 1727 menu.setGroupVisible(R.id.EMAIL_MENU, 1728 type == WebView.HitTestResult.EMAIL_TYPE); 1729 menu.setGroupVisible(R.id.GEO_MENU, 1730 type == WebView.HitTestResult.GEO_TYPE); 1731 menu.setGroupVisible(R.id.IMAGE_MENU, 1732 type == WebView.HitTestResult.IMAGE_TYPE 1733 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1734 menu.setGroupVisible(R.id.ANCHOR_MENU, 1735 type == WebView.HitTestResult.SRC_ANCHOR_TYPE 1736 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1737 1738 // Setup custom handling depending on the type 1739 switch (type) { 1740 case WebView.HitTestResult.PHONE_TYPE: 1741 menu.setHeaderTitle(Uri.decode(extra)); 1742 menu.findItem(R.id.dial_context_menu_id).setIntent( 1743 new Intent(Intent.ACTION_VIEW, Uri 1744 .parse(WebView.SCHEME_TEL + extra))); 1745 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 1746 addIntent.putExtra(Insert.PHONE, Uri.decode(extra)); 1747 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); 1748 menu.findItem(R.id.add_contact_context_menu_id).setIntent( 1749 addIntent); 1750 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener( 1751 new Copy(extra)); 1752 break; 1753 1754 case WebView.HitTestResult.EMAIL_TYPE: 1755 menu.setHeaderTitle(extra); 1756 menu.findItem(R.id.email_context_menu_id).setIntent( 1757 new Intent(Intent.ACTION_VIEW, Uri 1758 .parse(WebView.SCHEME_MAILTO + extra))); 1759 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener( 1760 new Copy(extra)); 1761 break; 1762 1763 case WebView.HitTestResult.GEO_TYPE: 1764 menu.setHeaderTitle(extra); 1765 menu.findItem(R.id.map_context_menu_id).setIntent( 1766 new Intent(Intent.ACTION_VIEW, Uri 1767 .parse(WebView.SCHEME_GEO 1768 + URLEncoder.encode(extra)))); 1769 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener( 1770 new Copy(extra)); 1771 break; 1772 1773 case WebView.HitTestResult.SRC_ANCHOR_TYPE: 1774 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: 1775 TextView titleView = (TextView) LayoutInflater.from(this) 1776 .inflate(android.R.layout.browser_link_context_header, 1777 null); 1778 titleView.setText(extra); 1779 menu.setHeaderView(titleView); 1780 // decide whether to show the open link in new tab option 1781 menu.findItem(R.id.open_newtab_context_menu_id).setVisible( 1782 mTabControl.canCreateNewTab()); 1783 menu.findItem(R.id.bookmark_context_menu_id).setVisible( 1784 Bookmarks.urlHasAcceptableScheme(extra)); 1785 PackageManager pm = getPackageManager(); 1786 Intent send = new Intent(Intent.ACTION_SEND); 1787 send.setType("text/plain"); 1788 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); 1789 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null); 1790 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) { 1791 break; 1792 } 1793 // otherwise fall through to handle image part 1794 case WebView.HitTestResult.IMAGE_TYPE: 1795 if (type == WebView.HitTestResult.IMAGE_TYPE) { 1796 menu.setHeaderTitle(extra); 1797 } 1798 menu.findItem(R.id.view_image_context_menu_id).setIntent( 1799 new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); 1800 menu.findItem(R.id.download_context_menu_id). 1801 setOnMenuItemClickListener(new Download(extra)); 1802 menu.findItem(R.id.set_wallpaper_context_menu_id). 1803 setOnMenuItemClickListener(new SetAsWallpaper(extra)); 1804 break; 1805 1806 default: 1807 Log.w(LOGTAG, "We should not get here."); 1808 break; 1809 } 1810 hideFakeTitleBar(); 1811 } 1812 1813 // Attach the given tab to the content view. 1814 // this should only be called for the current tab. 1815 private void attachTabToContentView(Tab t) { 1816 // Attach the container that contains the main WebView and any other UI 1817 // associated with the tab. 1818 t.attachTabToContentView(mContentView); 1819 1820 if (mShouldShowErrorConsole) { 1821 ErrorConsoleView errorConsole = t.getErrorConsole(true); 1822 if (errorConsole.numberOfErrors() == 0) { 1823 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 1824 } else { 1825 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 1826 } 1827 1828 mErrorConsoleContainer.addView(errorConsole, 1829 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1830 ViewGroup.LayoutParams.WRAP_CONTENT)); 1831 } 1832 1833 WebView view = t.getWebView(); 1834 view.setEmbeddedTitleBar(mTitleBar); 1835 if (t.isInVoiceSearchMode()) { 1836 showVoiceTitleBar(t.getVoiceDisplayTitle()); 1837 } else { 1838 revertVoiceTitleBar(); 1839 } 1840 // Request focus on the top window. 1841 t.getTopWindow().requestFocus(); 1842 } 1843 1844 // Attach a sub window to the main WebView of the given tab. 1845 void attachSubWindow(Tab t) { 1846 t.attachSubWindow(mContentView); 1847 getTopWindow().requestFocus(); 1848 } 1849 1850 // Remove the given tab from the content view. 1851 private void removeTabFromContentView(Tab t) { 1852 // Remove the container that contains the main WebView. 1853 t.removeTabFromContentView(mContentView); 1854 1855 ErrorConsoleView errorConsole = t.getErrorConsole(false); 1856 if (errorConsole != null) { 1857 mErrorConsoleContainer.removeView(errorConsole); 1858 } 1859 1860 WebView view = t.getWebView(); 1861 if (view != null) { 1862 view.setEmbeddedTitleBar(null); 1863 } 1864 } 1865 1866 // Remove the sub window if it exists. Also called by TabControl when the 1867 // user clicks the 'X' to dismiss a sub window. 1868 /* package */ void dismissSubWindow(Tab t) { 1869 t.removeSubWindow(mContentView); 1870 // dismiss the subwindow. This will destroy the WebView. 1871 t.dismissSubWindow(); 1872 getTopWindow().requestFocus(); 1873 } 1874 1875 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)} 1876 // that accepts url as string. 1877 private Tab openTabAndShow(String url, boolean closeOnExit, String appId) { 1878 return openTabAndShow(new UrlData(url), closeOnExit, appId); 1879 } 1880 1881 // This method does a ton of stuff. It will attempt to create a new tab 1882 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If 1883 // url isn't null, it will load the given url. 1884 /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit, 1885 String appId) { 1886 final Tab currentTab = mTabControl.getCurrentTab(); 1887 if (mTabControl.canCreateNewTab()) { 1888 final Tab tab = mTabControl.createNewTab(closeOnExit, appId, 1889 urlData.mUrl); 1890 WebView webview = tab.getWebView(); 1891 // If the last tab was removed from the active tabs page, currentTab 1892 // will be null. 1893 if (currentTab != null) { 1894 removeTabFromContentView(currentTab); 1895 } 1896 // We must set the new tab as the current tab to reflect the old 1897 // animation behavior. 1898 mTabControl.setCurrentTab(tab); 1899 attachTabToContentView(tab); 1900 if (!urlData.isEmpty()) { 1901 urlData.loadIn(tab); 1902 } 1903 return tab; 1904 } else { 1905 // Get rid of the subwindow if it exists 1906 dismissSubWindow(currentTab); 1907 if (!urlData.isEmpty()) { 1908 // Load the given url. 1909 urlData.loadIn(currentTab); 1910 } 1911 return currentTab; 1912 } 1913 } 1914 1915 private Tab openTab(String url) { 1916 if (mSettings.openInBackground()) { 1917 Tab t = mTabControl.createNewTab(); 1918 if (t != null) { 1919 WebView view = t.getWebView(); 1920 view.loadUrl(url); 1921 } 1922 return t; 1923 } else { 1924 return openTabAndShow(url, false, null); 1925 } 1926 } 1927 1928 private class Copy implements OnMenuItemClickListener { 1929 private CharSequence mText; 1930 1931 public boolean onMenuItemClick(MenuItem item) { 1932 copy(mText); 1933 return true; 1934 } 1935 1936 public Copy(CharSequence toCopy) { 1937 mText = toCopy; 1938 } 1939 } 1940 1941 private class Download implements OnMenuItemClickListener { 1942 private String mText; 1943 1944 public boolean onMenuItemClick(MenuItem item) { 1945 onDownloadStartNoStream(mText, null, null, null, -1); 1946 return true; 1947 } 1948 1949 public Download(String toDownload) { 1950 mText = toDownload; 1951 } 1952 } 1953 1954 private class SetAsWallpaper extends Thread implements 1955 OnMenuItemClickListener, DialogInterface.OnCancelListener { 1956 private URL mUrl; 1957 private ProgressDialog mWallpaperProgress; 1958 private boolean mCanceled = false; 1959 1960 public SetAsWallpaper(String url) { 1961 try { 1962 mUrl = new URL(url); 1963 } catch (MalformedURLException e) { 1964 mUrl = null; 1965 } 1966 } 1967 1968 public void onCancel(DialogInterface dialog) { 1969 mCanceled = true; 1970 } 1971 1972 public boolean onMenuItemClick(MenuItem item) { 1973 if (mUrl != null) { 1974 // The user may have tried to set a image with a large file size as their 1975 // background so it may take a few moments to perform the operation. Display 1976 // a progress spinner while it is working. 1977 mWallpaperProgress = new ProgressDialog(BrowserActivity.this); 1978 mWallpaperProgress.setIndeterminate(true); 1979 mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper)); 1980 mWallpaperProgress.setCancelable(true); 1981 mWallpaperProgress.setOnCancelListener(this); 1982 mWallpaperProgress.show(); 1983 start(); 1984 } 1985 return true; 1986 } 1987 1988 public void run() { 1989 Drawable oldWallpaper = BrowserActivity.this.getWallpaper(); 1990 try { 1991 // TODO: This will cause the resource to be downloaded again, when we 1992 // should in most cases be able to grab it from the cache. To fix this 1993 // we should query WebCore to see if we can access a cached version and 1994 // instead open an input stream on that. This pattern could also be used 1995 // in the download manager where the same problem exists. 1996 InputStream inputstream = mUrl.openStream(); 1997 if (inputstream != null) { 1998 setWallpaper(inputstream); 1999 } 2000 } catch (IOException e) { 2001 Log.e(LOGTAG, "Unable to set new wallpaper"); 2002 // Act as though the user canceled the operation so we try to 2003 // restore the old wallpaper. 2004 mCanceled = true; 2005 } 2006 2007 if (mCanceled) { 2008 // Restore the old wallpaper if the user cancelled whilst we were setting 2009 // the new wallpaper. 2010 int width = oldWallpaper.getIntrinsicWidth(); 2011 int height = oldWallpaper.getIntrinsicHeight(); 2012 Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); 2013 Canvas canvas = new Canvas(bm); 2014 oldWallpaper.setBounds(0, 0, width, height); 2015 oldWallpaper.draw(canvas); 2016 try { 2017 setWallpaper(bm); 2018 } catch (IOException e) { 2019 Log.e(LOGTAG, "Unable to restore old wallpaper."); 2020 } 2021 mCanceled = false; 2022 } 2023 2024 if (mWallpaperProgress.isShowing()) { 2025 mWallpaperProgress.dismiss(); 2026 } 2027 } 2028 } 2029 2030 private void copy(CharSequence text) { 2031 try { 2032 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard")); 2033 if (clip != null) { 2034 clip.setClipboardText(text); 2035 } 2036 } catch (android.os.RemoteException e) { 2037 Log.e(LOGTAG, "Copy failed", e); 2038 } 2039 } 2040 2041 /** 2042 * Resets the browser title-view to whatever it must be 2043 * (for example, if we had a loading error) 2044 * When we have a new page, we call resetTitle, when we 2045 * have to reset the titlebar to whatever it used to be 2046 * (for example, if the user chose to stop loading), we 2047 * call resetTitleAndRevertLockIcon. 2048 */ 2049 /* package */ void resetTitleAndRevertLockIcon() { 2050 mTabControl.getCurrentTab().revertLockIcon(); 2051 updateLockIconToLatest(); 2052 resetTitleIconAndProgress(); 2053 } 2054 2055 /** 2056 * Reset the title, favicon, and progress. 2057 */ 2058 private void resetTitleIconAndProgress() { 2059 WebView current = mTabControl.getCurrentWebView(); 2060 if (current == null) { 2061 return; 2062 } 2063 resetTitleAndIcon(current); 2064 int progress = current.getProgress(); 2065 current.getWebChromeClient().onProgressChanged(current, progress); 2066 } 2067 2068 // Reset the title and the icon based on the given item. 2069 private void resetTitleAndIcon(WebView view) { 2070 WebHistoryItem item = view.copyBackForwardList().getCurrentItem(); 2071 if (item != null) { 2072 setUrlTitle(item.getUrl(), item.getTitle()); 2073 setFavicon(item.getFavicon()); 2074 } else { 2075 setUrlTitle(null, null); 2076 setFavicon(null); 2077 } 2078 } 2079 2080 /** 2081 * Sets a title composed of the URL and the title string. 2082 * @param url The URL of the site being loaded. 2083 * @param title The title of the site being loaded. 2084 */ 2085 void setUrlTitle(String url, String title) { 2086 mUrl = url; 2087 mTitle = title; 2088 2089 // If we are in voice search mode, the title has already been set. 2090 if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return; 2091 mTitleBar.setDisplayTitle(url); 2092 mFakeTitleBar.setDisplayTitle(url); 2093 } 2094 2095 /** 2096 * @param url The URL to build a title version of the URL from. 2097 * @return The title version of the URL or null if fails. 2098 * The title version of the URL can be either the URL hostname, 2099 * or the hostname with an "https://" prefix (for secure URLs), 2100 * or an empty string if, for example, the URL in question is a 2101 * file:// URL with no hostname. 2102 */ 2103 /* package */ static String buildTitleUrl(String url) { 2104 String titleUrl = null; 2105 2106 if (url != null) { 2107 try { 2108 // parse the url string 2109 URL urlObj = new URL(url); 2110 if (urlObj != null) { 2111 titleUrl = ""; 2112 2113 String protocol = urlObj.getProtocol(); 2114 String host = urlObj.getHost(); 2115 2116 if (host != null && 0 < host.length()) { 2117 titleUrl = host; 2118 if (protocol != null) { 2119 // if a secure site, add an "https://" prefix! 2120 if (protocol.equalsIgnoreCase("https")) { 2121 titleUrl = protocol + "://" + host; 2122 } 2123 } 2124 } 2125 } 2126 } catch (MalformedURLException e) {} 2127 } 2128 2129 return titleUrl; 2130 } 2131 2132 // Set the favicon in the title bar. 2133 void setFavicon(Bitmap icon) { 2134 mTitleBar.setFavicon(icon); 2135 mFakeTitleBar.setFavicon(icon); 2136 } 2137 2138 /** 2139 * Close the tab, remove its associated title bar, and adjust mTabControl's 2140 * current tab to a valid value. 2141 */ 2142 /* package */ void closeTab(Tab t) { 2143 int currentIndex = mTabControl.getCurrentIndex(); 2144 int removeIndex = mTabControl.getTabIndex(t); 2145 mTabControl.removeTab(t); 2146 if (currentIndex >= removeIndex && currentIndex != 0) { 2147 currentIndex--; 2148 } 2149 mTabControl.setCurrentTab(mTabControl.getTab(currentIndex)); 2150 resetTitleIconAndProgress(); 2151 } 2152 2153 private void goBackOnePageOrQuit() { 2154 Tab current = mTabControl.getCurrentTab(); 2155 if (current == null) { 2156 /* 2157 * Instead of finishing the activity, simply push this to the back 2158 * of the stack and let ActivityManager to choose the foreground 2159 * activity. As BrowserActivity is singleTask, it will be always the 2160 * root of the task. So we can use either true or false for 2161 * moveTaskToBack(). 2162 */ 2163 moveTaskToBack(true); 2164 return; 2165 } 2166 WebView w = current.getWebView(); 2167 if (w.canGoBack()) { 2168 w.goBack(); 2169 } else { 2170 // Check to see if we are closing a window that was created by 2171 // another window. If so, we switch back to that window. 2172 Tab parent = current.getParentTab(); 2173 if (parent != null) { 2174 switchToTab(mTabControl.getTabIndex(parent)); 2175 // Now we close the other tab 2176 closeTab(current); 2177 } else { 2178 if (current.closeOnExit()) { 2179 // force the tab's inLoad() to be false as we are going to 2180 // either finish the activity or remove the tab. This will 2181 // ensure pauseWebViewTimers() taking action. 2182 mTabControl.getCurrentTab().clearInLoad(); 2183 if (mTabControl.getTabCount() == 1) { 2184 finish(); 2185 return; 2186 } 2187 // call pauseWebViewTimers() now, we won't be able to call 2188 // it in onPause() as the WebView won't be valid. 2189 // Temporarily change mActivityInPause to be true as 2190 // pauseWebViewTimers() will do nothing if mActivityInPause 2191 // is false. 2192 boolean savedState = mActivityInPause; 2193 if (savedState) { 2194 Log.e(LOGTAG, "BrowserActivity is already paused " 2195 + "while handing goBackOnePageOrQuit."); 2196 } 2197 mActivityInPause = true; 2198 pauseWebViewTimers(); 2199 mActivityInPause = savedState; 2200 removeTabFromContentView(current); 2201 mTabControl.removeTab(current); 2202 } 2203 /* 2204 * Instead of finishing the activity, simply push this to the back 2205 * of the stack and let ActivityManager to choose the foreground 2206 * activity. As BrowserActivity is singleTask, it will be always the 2207 * root of the task. So we can use either true or false for 2208 * moveTaskToBack(). 2209 */ 2210 moveTaskToBack(true); 2211 } 2212 } 2213 } 2214 2215 boolean isMenuDown() { 2216 return mMenuIsDown; 2217 } 2218 2219 @Override 2220 public boolean onKeyDown(int keyCode, KeyEvent event) { 2221 // Even if MENU is already held down, we need to call to super to open 2222 // the IME on long press. 2223 if (KeyEvent.KEYCODE_MENU == keyCode) { 2224 mMenuIsDown = true; 2225 return super.onKeyDown(keyCode, event); 2226 } 2227 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is 2228 // still down, we don't want to trigger the search. Pretend to consume 2229 // the key and do nothing. 2230 if (mMenuIsDown) return true; 2231 2232 switch(keyCode) { 2233 case KeyEvent.KEYCODE_SPACE: 2234 // WebView/WebTextView handle the keys in the KeyDown. As 2235 // the Activity's shortcut keys are only handled when WebView 2236 // doesn't, have to do it in onKeyDown instead of onKeyUp. 2237 if (event.isShiftPressed()) { 2238 getTopWindow().pageUp(false); 2239 } else { 2240 getTopWindow().pageDown(false); 2241 } 2242 return true; 2243 case KeyEvent.KEYCODE_BACK: 2244 if (event.getRepeatCount() == 0) { 2245 event.startTracking(); 2246 return true; 2247 } else if (mCustomView == null && mActiveTabsPage == null 2248 && event.isLongPress()) { 2249 bookmarksOrHistoryPicker(true); 2250 return true; 2251 } 2252 break; 2253 } 2254 return super.onKeyDown(keyCode, event); 2255 } 2256 2257 @Override 2258 public boolean onKeyUp(int keyCode, KeyEvent event) { 2259 switch(keyCode) { 2260 case KeyEvent.KEYCODE_MENU: 2261 mMenuIsDown = false; 2262 break; 2263 case KeyEvent.KEYCODE_BACK: 2264 if (event.isTracking() && !event.isCanceled()) { 2265 if (mCustomView != null) { 2266 // if a custom view is showing, hide it 2267 mTabControl.getCurrentWebView().getWebChromeClient() 2268 .onHideCustomView(); 2269 } else if (mActiveTabsPage != null) { 2270 // if tab page is showing, hide it 2271 removeActiveTabPage(true); 2272 } else { 2273 WebView subwindow = mTabControl.getCurrentSubWindow(); 2274 if (subwindow != null) { 2275 if (subwindow.canGoBack()) { 2276 subwindow.goBack(); 2277 } else { 2278 dismissSubWindow(mTabControl.getCurrentTab()); 2279 } 2280 } else { 2281 goBackOnePageOrQuit(); 2282 } 2283 } 2284 return true; 2285 } 2286 break; 2287 } 2288 return super.onKeyUp(keyCode, event); 2289 } 2290 2291 /* package */ void stopLoading() { 2292 mDidStopLoad = true; 2293 resetTitleAndRevertLockIcon(); 2294 WebView w = getTopWindow(); 2295 w.stopLoading(); 2296 // FIXME: before refactor, it is using mWebViewClient. So I keep the 2297 // same logic here. But for subwindow case, should we call into the main 2298 // WebView's onPageFinished as we never call its onPageStarted and if 2299 // the page finishes itself, we don't call onPageFinished. 2300 mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w, 2301 w.getUrl()); 2302 2303 cancelStopToast(); 2304 mStopToast = Toast 2305 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT); 2306 mStopToast.show(); 2307 } 2308 2309 boolean didUserStopLoading() { 2310 return mDidStopLoad; 2311 } 2312 2313 private void cancelStopToast() { 2314 if (mStopToast != null) { 2315 mStopToast.cancel(); 2316 mStopToast = null; 2317 } 2318 } 2319 2320 // called by a UI or non-UI thread to post the message 2321 public void postMessage(int what, int arg1, int arg2, Object obj, 2322 long delayMillis) { 2323 mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2, 2324 obj), delayMillis); 2325 } 2326 2327 // called by a UI or non-UI thread to remove the message 2328 void removeMessages(int what, Object object) { 2329 mHandler.removeMessages(what, object); 2330 } 2331 2332 // public message ids 2333 public final static int LOAD_URL = 1001; 2334 public final static int STOP_LOAD = 1002; 2335 2336 // Message Ids 2337 private static final int FOCUS_NODE_HREF = 102; 2338 private static final int CANCEL_CREDS_REQUEST = 103; 2339 private static final int RELEASE_WAKELOCK = 107; 2340 2341 static final int UPDATE_BOOKMARK_THUMBNAIL = 108; 2342 2343 // Private handler for handling javascript and saving passwords 2344 private Handler mHandler = new Handler() { 2345 2346 public void handleMessage(Message msg) { 2347 switch (msg.what) { 2348 case FOCUS_NODE_HREF: 2349 { 2350 String url = (String) msg.getData().get("url"); 2351 String title = (String) msg.getData().get("title"); 2352 if (url == null || url.length() == 0) { 2353 break; 2354 } 2355 HashMap focusNodeMap = (HashMap) msg.obj; 2356 WebView view = (WebView) focusNodeMap.get("webview"); 2357 // Only apply the action if the top window did not change. 2358 if (getTopWindow() != view) { 2359 break; 2360 } 2361 switch (msg.arg1) { 2362 case R.id.open_context_menu_id: 2363 case R.id.view_image_context_menu_id: 2364 loadURL(getTopWindow(), url); 2365 break; 2366 case R.id.open_newtab_context_menu_id: 2367 final Tab parent = mTabControl.getCurrentTab(); 2368 final Tab newTab = openTab(url); 2369 if (newTab != parent) { 2370 parent.addChildTab(newTab); 2371 } 2372 break; 2373 case R.id.bookmark_context_menu_id: 2374 Intent intent = new Intent(BrowserActivity.this, 2375 AddBookmarkPage.class); 2376 intent.putExtra("url", url); 2377 intent.putExtra("title", title); 2378 startActivity(intent); 2379 break; 2380 case R.id.share_link_context_menu_id: 2381 // See if this site has been visited before 2382 StringBuilder sb = new StringBuilder( 2383 Browser.BookmarkColumns.URL + " = "); 2384 DatabaseUtils.appendEscapedSQLString(sb, url); 2385 Cursor c = mResolver.query(Browser.BOOKMARKS_URI, 2386 Browser.HISTORY_PROJECTION, 2387 sb.toString(), 2388 null, 2389 null); 2390 if (c.moveToFirst()) { 2391 // The site has been visited before, so grab the 2392 // info from the database. 2393 Bitmap favicon = null; 2394 Bitmap thumbnail = null; 2395 String linkTitle = c.getString(Browser. 2396 HISTORY_PROJECTION_TITLE_INDEX); 2397 byte[] data = c.getBlob(Browser. 2398 HISTORY_PROJECTION_FAVICON_INDEX); 2399 if (data != null) { 2400 favicon = BitmapFactory.decodeByteArray( 2401 data, 0, data.length); 2402 } 2403 data = c.getBlob(Browser. 2404 HISTORY_PROJECTION_THUMBNAIL_INDEX); 2405 if (data != null) { 2406 thumbnail = BitmapFactory.decodeByteArray( 2407 data, 0, data.length); 2408 } 2409 sharePage(BrowserActivity.this, 2410 linkTitle, url, favicon, thumbnail); 2411 } else { 2412 Browser.sendString(BrowserActivity.this, url, 2413 getString( 2414 R.string.choosertitle_sharevia)); 2415 } 2416 break; 2417 case R.id.copy_link_context_menu_id: 2418 copy(url); 2419 break; 2420 case R.id.save_link_context_menu_id: 2421 case R.id.download_context_menu_id: 2422 onDownloadStartNoStream(url, null, null, null, -1); 2423 break; 2424 } 2425 break; 2426 } 2427 2428 case LOAD_URL: 2429 loadURL(getTopWindow(), (String) msg.obj); 2430 break; 2431 2432 case STOP_LOAD: 2433 stopLoading(); 2434 break; 2435 2436 case CANCEL_CREDS_REQUEST: 2437 resumeAfterCredentials(); 2438 break; 2439 2440 case RELEASE_WAKELOCK: 2441 if (mWakeLock.isHeld()) { 2442 mWakeLock.release(); 2443 // if we reach here, Browser should be still in the 2444 // background loading after WAKELOCK_TIMEOUT (5-min). 2445 // To avoid burning the battery, stop loading. 2446 mTabControl.stopAllLoading(); 2447 } 2448 break; 2449 2450 case UPDATE_BOOKMARK_THUMBNAIL: 2451 WebView view = (WebView) msg.obj; 2452 if (view != null) { 2453 updateScreenshot(view); 2454 } 2455 break; 2456 } 2457 } 2458 }; 2459 2460 /** 2461 * Share a page, providing the title, url, favicon, and a screenshot. Uses 2462 * an {@link Intent} to launch the Activity chooser. 2463 * @param c Context used to launch a new Activity. 2464 * @param title Title of the page. Stored in the Intent with 2465 * {@link Browser#EXTRA_SHARE_TITLE} 2466 * @param url URL of the page. Stored in the Intent with 2467 * {@link Intent#EXTRA_TEXT} 2468 * @param favicon Bitmap of the favicon for the page. Stored in the Intent 2469 * with {@link Browser#EXTRA_SHARE_FAVICON} 2470 * @param screenshot Bitmap of a screenshot of the page. Stored in the 2471 * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT} 2472 */ 2473 public static final void sharePage(Context c, String title, String url, 2474 Bitmap favicon, Bitmap screenshot) { 2475 Intent send = new Intent(Intent.ACTION_SEND); 2476 send.setType("text/plain"); 2477 send.putExtra(Intent.EXTRA_TEXT, url); 2478 send.putExtra(Browser.EXTRA_SHARE_TITLE, title); 2479 send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon); 2480 send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot); 2481 try { 2482 c.startActivity(Intent.createChooser(send, c.getString( 2483 R.string.choosertitle_sharevia))); 2484 } catch(android.content.ActivityNotFoundException ex) { 2485 // if no app handles it, do nothing 2486 } 2487 } 2488 2489 private void updateScreenshot(WebView view) { 2490 // If this is a bookmarked site, add a screenshot to the database. 2491 // FIXME: When should we update? Every time? 2492 // FIXME: Would like to make sure there is actually something to 2493 // draw, but the API for that (WebViewCore.pictureReady()) is not 2494 // currently accessible here. 2495 2496 ContentResolver cr = getContentResolver(); 2497 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl( 2498 cr, view.getOriginalUrl(), view.getUrl(), true); 2499 if (c != null) { 2500 boolean succeed = c.moveToFirst(); 2501 ContentValues values = null; 2502 while (succeed) { 2503 if (values == null) { 2504 final ByteArrayOutputStream os 2505 = new ByteArrayOutputStream(); 2506 Bitmap bm = createScreenshot(view); 2507 if (bm == null) { 2508 c.close(); 2509 return; 2510 } 2511 bm.compress(Bitmap.CompressFormat.PNG, 100, os); 2512 values = new ContentValues(); 2513 values.put(Browser.BookmarkColumns.THUMBNAIL, 2514 os.toByteArray()); 2515 } 2516 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, 2517 c.getInt(0)), values, null, null); 2518 succeed = c.moveToNext(); 2519 } 2520 c.close(); 2521 } 2522 } 2523 2524 /** 2525 * Values for the size of the thumbnail created when taking a screenshot. 2526 * Lazily initialized. Instead of using these directly, use 2527 * getDesiredThumbnailWidth() or getDesiredThumbnailHeight(). 2528 */ 2529 private static int THUMBNAIL_WIDTH = 0; 2530 private static int THUMBNAIL_HEIGHT = 0; 2531 2532 /** 2533 * Return the desired width for thumbnail screenshots, which are stored in 2534 * the database, and used on the bookmarks screen. 2535 * @param context Context for finding out the density of the screen. 2536 * @return int desired width for thumbnail screenshot. 2537 */ 2538 /* package */ static int getDesiredThumbnailWidth(Context context) { 2539 if (THUMBNAIL_WIDTH == 0) { 2540 float density = context.getResources().getDisplayMetrics().density; 2541 THUMBNAIL_WIDTH = (int) (90 * density); 2542 THUMBNAIL_HEIGHT = (int) (80 * density); 2543 } 2544 return THUMBNAIL_WIDTH; 2545 } 2546 2547 /** 2548 * Return the desired height for thumbnail screenshots, which are stored in 2549 * the database, and used on the bookmarks screen. 2550 * @param context Context for finding out the density of the screen. 2551 * @return int desired height for thumbnail screenshot. 2552 */ 2553 /* package */ static int getDesiredThumbnailHeight(Context context) { 2554 // To ensure that they are both initialized. 2555 getDesiredThumbnailWidth(context); 2556 return THUMBNAIL_HEIGHT; 2557 } 2558 2559 private Bitmap createScreenshot(WebView view) { 2560 Picture thumbnail = view.capturePicture(); 2561 if (thumbnail == null) { 2562 return null; 2563 } 2564 Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this), 2565 getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444); 2566 Canvas canvas = new Canvas(bm); 2567 // May need to tweak these values to determine what is the 2568 // best scale factor 2569 int thumbnailWidth = thumbnail.getWidth(); 2570 int thumbnailHeight = thumbnail.getHeight(); 2571 float scaleFactorX = 1.0f; 2572 float scaleFactorY = 1.0f; 2573 if (thumbnailWidth > 0) { 2574 scaleFactorX = (float) getDesiredThumbnailWidth(this) / 2575 (float)thumbnailWidth; 2576 } else { 2577 return null; 2578 } 2579 2580 if (view.getWidth() > view.getHeight() && 2581 thumbnailHeight < view.getHeight() && thumbnailHeight > 0) { 2582 // If the device is in landscape and the page is shorter 2583 // than the height of the view, stretch the thumbnail to fill the 2584 // space. 2585 scaleFactorY = (float) getDesiredThumbnailHeight(this) / 2586 (float)thumbnailHeight; 2587 } else { 2588 // In the portrait case, this looks nice. 2589 scaleFactorY = scaleFactorX; 2590 } 2591 2592 canvas.scale(scaleFactorX, scaleFactorY); 2593 2594 thumbnail.draw(canvas); 2595 return bm; 2596 } 2597 2598 // ------------------------------------------------------------------------- 2599 // Helper function for WebViewClient. 2600 //------------------------------------------------------------------------- 2601 2602 // Use in overrideUrlLoading 2603 /* package */ final static String SCHEME_WTAI = "wtai://wp/"; 2604 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;"; 2605 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;"; 2606 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;"; 2607 2608 void onPageStarted(WebView view, String url, Bitmap favicon) { 2609 // when BrowserActivity just starts, onPageStarted may be called before 2610 // onResume as it is triggered from onCreate. Call resumeWebViewTimers 2611 // to start the timer. As we won't switch tabs while an activity is in 2612 // pause state, we can ensure calling resume and pause in pair. 2613 if (mActivityInPause) resumeWebViewTimers(); 2614 2615 resetLockIcon(url); 2616 setUrlTitle(url, null); 2617 setFavicon(favicon); 2618 // Keep this initial progress in sync with initialProgressValue (* 100) 2619 // in ProgressTracker.cpp 2620 // Show some progress so that the user knows the page is beginning to 2621 // load 2622 onProgressChanged(view, 10); 2623 mDidStopLoad = false; 2624 if (!mIsNetworkUp) createAndShowNetworkDialog(); 2625 2626 if (mSettings.isTracing()) { 2627 String host; 2628 try { 2629 WebAddress uri = new WebAddress(url); 2630 host = uri.mHost; 2631 } catch (android.net.ParseException ex) { 2632 host = "browser"; 2633 } 2634 host = host.replace('.', '_'); 2635 host += ".trace"; 2636 mInTrace = true; 2637 Debug.startMethodTracing(host, 20 * 1024 * 1024); 2638 } 2639 2640 // Performance probe 2641 if (false) { 2642 mStart = SystemClock.uptimeMillis(); 2643 mProcessStart = Process.getElapsedCpuTime(); 2644 long[] sysCpu = new long[7]; 2645 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2646 sysCpu, null)) { 2647 mUserStart = sysCpu[0] + sysCpu[1]; 2648 mSystemStart = sysCpu[2]; 2649 mIdleStart = sysCpu[3]; 2650 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6]; 2651 } 2652 mUiStart = SystemClock.currentThreadTimeMillis(); 2653 } 2654 } 2655 2656 void onPageFinished(WebView view, String url) { 2657 // Reset the title and icon in case we stopped a provisional load. 2658 resetTitleAndIcon(view); 2659 // Update the lock icon image only once we are done loading 2660 updateLockIconToLatest(); 2661 // pause the WebView timer and release the wake lock if it is finished 2662 // while BrowserActivity is in pause state. 2663 if (mActivityInPause && pauseWebViewTimers()) { 2664 if (mWakeLock.isHeld()) { 2665 mHandler.removeMessages(RELEASE_WAKELOCK); 2666 mWakeLock.release(); 2667 } 2668 } 2669 2670 // Performance probe 2671 if (false) { 2672 long[] sysCpu = new long[7]; 2673 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2674 sysCpu, null)) { 2675 String uiInfo = "UI thread used " 2676 + (SystemClock.currentThreadTimeMillis() - mUiStart) 2677 + " ms"; 2678 if (LOGD_ENABLED) { 2679 Log.d(LOGTAG, uiInfo); 2680 } 2681 //The string that gets written to the log 2682 String performanceString = "It took total " 2683 + (SystemClock.uptimeMillis() - mStart) 2684 + " ms clock time to load the page." 2685 + "\nbrowser process used " 2686 + (Process.getElapsedCpuTime() - mProcessStart) 2687 + " ms, user processes used " 2688 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10 2689 + " ms, kernel used " 2690 + (sysCpu[2] - mSystemStart) * 10 2691 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10 2692 + " ms and irq took " 2693 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart) 2694 * 10 + " ms, " + uiInfo; 2695 if (LOGD_ENABLED) { 2696 Log.d(LOGTAG, performanceString + "\nWebpage: " + url); 2697 } 2698 if (url != null) { 2699 // strip the url to maintain consistency 2700 String newUrl = new String(url); 2701 if (newUrl.startsWith("http://www.")) { 2702 newUrl = newUrl.substring(11); 2703 } else if (newUrl.startsWith("http://")) { 2704 newUrl = newUrl.substring(7); 2705 } else if (newUrl.startsWith("https://www.")) { 2706 newUrl = newUrl.substring(12); 2707 } else if (newUrl.startsWith("https://")) { 2708 newUrl = newUrl.substring(8); 2709 } 2710 if (LOGD_ENABLED) { 2711 Log.d(LOGTAG, newUrl + " loaded"); 2712 } 2713 } 2714 } 2715 } 2716 2717 if (mInTrace) { 2718 mInTrace = false; 2719 Debug.stopMethodTracing(); 2720 } 2721 } 2722 2723 boolean shouldOverrideUrlLoading(WebView view, String url) { 2724 if (url.startsWith(SCHEME_WTAI)) { 2725 // wtai://wp/mc;number 2726 // number=string(phone-number) 2727 if (url.startsWith(SCHEME_WTAI_MC)) { 2728 Intent intent = new Intent(Intent.ACTION_VIEW, 2729 Uri.parse(WebView.SCHEME_TEL + 2730 url.substring(SCHEME_WTAI_MC.length()))); 2731 startActivity(intent); 2732 return true; 2733 } 2734 // wtai://wp/sd;dtmf 2735 // dtmf=string(dialstring) 2736 if (url.startsWith(SCHEME_WTAI_SD)) { 2737 // TODO: only send when there is active voice connection 2738 return false; 2739 } 2740 // wtai://wp/ap;number;name 2741 // number=string(phone-number) 2742 // name=string 2743 if (url.startsWith(SCHEME_WTAI_AP)) { 2744 // TODO 2745 return false; 2746 } 2747 } 2748 2749 // The "about:" schemes are internal to the browser; don't want these to 2750 // be dispatched to other apps. 2751 if (url.startsWith("about:")) { 2752 return false; 2753 } 2754 2755 Intent intent; 2756 // perform generic parsing of the URI to turn it into an Intent. 2757 try { 2758 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); 2759 } catch (URISyntaxException ex) { 2760 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage()); 2761 return false; 2762 } 2763 2764 // check whether the intent can be resolved. If not, we will see 2765 // whether we can download it from the Market. 2766 if (getPackageManager().resolveActivity(intent, 0) == null) { 2767 String packagename = intent.getPackage(); 2768 if (packagename != null) { 2769 intent = new Intent(Intent.ACTION_VIEW, Uri 2770 .parse("market://search?q=pname:" + packagename)); 2771 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2772 startActivity(intent); 2773 return true; 2774 } else { 2775 return false; 2776 } 2777 } 2778 2779 // sanitize the Intent, ensuring web pages can not bypass browser 2780 // security (only access to BROWSABLE activities). 2781 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2782 intent.setComponent(null); 2783 try { 2784 if (startActivityIfNeeded(intent, -1)) { 2785 return true; 2786 } 2787 } catch (ActivityNotFoundException ex) { 2788 // ignore the error. If no application can handle the URL, 2789 // eg about:blank, assume the browser can handle it. 2790 } 2791 2792 if (mMenuIsDown) { 2793 openTab(url); 2794 closeOptionsMenu(); 2795 return true; 2796 } 2797 return false; 2798 } 2799 2800 // ------------------------------------------------------------------------- 2801 // Helper function for WebChromeClient 2802 // ------------------------------------------------------------------------- 2803 2804 void onProgressChanged(WebView view, int newProgress) { 2805 mTitleBar.setProgress(newProgress); 2806 mFakeTitleBar.setProgress(newProgress); 2807 2808 if (newProgress == 100) { 2809 // onProgressChanged() may continue to be called after the main 2810 // frame has finished loading, as any remaining sub frames continue 2811 // to load. We'll only get called once though with newProgress as 2812 // 100 when everything is loaded. (onPageFinished is called once 2813 // when the main frame completes loading regardless of the state of 2814 // any sub frames so calls to onProgressChanges may continue after 2815 // onPageFinished has executed) 2816 if (mInLoad) { 2817 mInLoad = false; 2818 updateInLoadMenuItems(); 2819 // If the options menu is open, leave the title bar 2820 if (!mOptionsMenuOpen || !mIconView) { 2821 hideFakeTitleBar(); 2822 } 2823 } 2824 } else if (!mInLoad) { 2825 // onPageFinished may have already been called but a subframe is 2826 // still loading and updating the progress. Reset mInLoad and update 2827 // the menu items. 2828 mInLoad = true; 2829 updateInLoadMenuItems(); 2830 if (!mOptionsMenuOpen || mIconView) { 2831 // This page has begun to load, so show the title bar 2832 showFakeTitleBar(); 2833 } 2834 } 2835 } 2836 2837 void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 2838 // if a view already exists then immediately terminate the new one 2839 if (mCustomView != null) { 2840 callback.onCustomViewHidden(); 2841 return; 2842 } 2843 2844 // Add the custom view to its container. 2845 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); 2846 mCustomView = view; 2847 mCustomViewCallback = callback; 2848 // Save the menu state and set it to empty while the custom 2849 // view is showing. 2850 mOldMenuState = mMenuState; 2851 mMenuState = EMPTY_MENU; 2852 // Hide the content view. 2853 mContentView.setVisibility(View.GONE); 2854 // Finally show the custom view container. 2855 setStatusBarVisibility(false); 2856 mCustomViewContainer.setVisibility(View.VISIBLE); 2857 mCustomViewContainer.bringToFront(); 2858 } 2859 2860 void onHideCustomView() { 2861 if (mCustomView == null) 2862 return; 2863 2864 // Hide the custom view. 2865 mCustomView.setVisibility(View.GONE); 2866 // Remove the custom view from its container. 2867 mCustomViewContainer.removeView(mCustomView); 2868 mCustomView = null; 2869 // Reset the old menu state. 2870 mMenuState = mOldMenuState; 2871 mOldMenuState = EMPTY_MENU; 2872 mCustomViewContainer.setVisibility(View.GONE); 2873 mCustomViewCallback.onCustomViewHidden(); 2874 // Show the content view. 2875 setStatusBarVisibility(true); 2876 mContentView.setVisibility(View.VISIBLE); 2877 } 2878 2879 Bitmap getDefaultVideoPoster() { 2880 if (mDefaultVideoPoster == null) { 2881 mDefaultVideoPoster = BitmapFactory.decodeResource( 2882 getResources(), R.drawable.default_video_poster); 2883 } 2884 return mDefaultVideoPoster; 2885 } 2886 2887 View getVideoLoadingProgressView() { 2888 if (mVideoProgressView == null) { 2889 LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this); 2890 mVideoProgressView = inflater.inflate( 2891 R.layout.video_loading_progress, null); 2892 } 2893 return mVideoProgressView; 2894 } 2895 2896 /* 2897 * The Object used to inform the WebView of the file to upload. 2898 */ 2899 private ValueCallback<Uri> mUploadMessage; 2900 2901 void openFileChooser(ValueCallback<Uri> uploadMsg) { 2902 if (mUploadMessage != null) return; 2903 mUploadMessage = uploadMsg; 2904 Intent i = new Intent(Intent.ACTION_GET_CONTENT); 2905 i.addCategory(Intent.CATEGORY_OPENABLE); 2906 i.setType("*/*"); 2907 BrowserActivity.this.startActivityForResult(Intent.createChooser(i, 2908 getString(R.string.choose_upload)), FILE_SELECTED); 2909 } 2910 2911 // ------------------------------------------------------------------------- 2912 // Implement functions for DownloadListener 2913 // ------------------------------------------------------------------------- 2914 2915 /** 2916 * Notify the host application a download should be done, or that 2917 * the data should be streamed if a streaming viewer is available. 2918 * @param url The full url to the content that should be downloaded 2919 * @param contentDisposition Content-disposition http header, if 2920 * present. 2921 * @param mimetype The mimetype of the content reported by the server 2922 * @param contentLength The file size reported by the server 2923 */ 2924 public void onDownloadStart(String url, String userAgent, 2925 String contentDisposition, String mimetype, long contentLength) { 2926 // if we're dealing wih A/V content that's not explicitly marked 2927 // for download, check if it's streamable. 2928 if (contentDisposition == null 2929 || !contentDisposition.regionMatches( 2930 true, 0, "attachment", 0, 10)) { 2931 // query the package manager to see if there's a registered handler 2932 // that matches. 2933 Intent intent = new Intent(Intent.ACTION_VIEW); 2934 intent.setDataAndType(Uri.parse(url), mimetype); 2935 ResolveInfo info = getPackageManager().resolveActivity(intent, 2936 PackageManager.MATCH_DEFAULT_ONLY); 2937 if (info != null) { 2938 ComponentName myName = getComponentName(); 2939 // If we resolved to ourselves, we don't want to attempt to 2940 // load the url only to try and download it again. 2941 if (!myName.getPackageName().equals( 2942 info.activityInfo.packageName) 2943 || !myName.getClassName().equals( 2944 info.activityInfo.name)) { 2945 // someone (other than us) knows how to handle this mime 2946 // type with this scheme, don't download. 2947 try { 2948 startActivity(intent); 2949 return; 2950 } catch (ActivityNotFoundException ex) { 2951 if (LOGD_ENABLED) { 2952 Log.d(LOGTAG, "activity not found for " + mimetype 2953 + " over " + Uri.parse(url).getScheme(), 2954 ex); 2955 } 2956 // Best behavior is to fall back to a download in this 2957 // case 2958 } 2959 } 2960 } 2961 } 2962 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength); 2963 } 2964 2965 /** 2966 * Notify the host application a download should be done, even if there 2967 * is a streaming viewer available for thise type. 2968 * @param url The full url to the content that should be downloaded 2969 * @param contentDisposition Content-disposition http header, if 2970 * present. 2971 * @param mimetype The mimetype of the content reported by the server 2972 * @param contentLength The file size reported by the server 2973 */ 2974 /*package */ void onDownloadStartNoStream(String url, String userAgent, 2975 String contentDisposition, String mimetype, long contentLength) { 2976 2977 String filename = URLUtil.guessFileName(url, 2978 contentDisposition, mimetype); 2979 2980 // Check to see if we have an SDCard 2981 String status = Environment.getExternalStorageState(); 2982 if (!status.equals(Environment.MEDIA_MOUNTED)) { 2983 int title; 2984 String msg; 2985 2986 // Check to see if the SDCard is busy, same as the music app 2987 if (status.equals(Environment.MEDIA_SHARED)) { 2988 msg = getString(R.string.download_sdcard_busy_dlg_msg); 2989 title = R.string.download_sdcard_busy_dlg_title; 2990 } else { 2991 msg = getString(R.string.download_no_sdcard_dlg_msg, filename); 2992 title = R.string.download_no_sdcard_dlg_title; 2993 } 2994 2995 new AlertDialog.Builder(this) 2996 .setTitle(title) 2997 .setIcon(android.R.drawable.ic_dialog_alert) 2998 .setMessage(msg) 2999 .setPositiveButton(R.string.ok, null) 3000 .show(); 3001 return; 3002 } 3003 3004 // java.net.URI is a lot stricter than KURL so we have to undo 3005 // KURL's percent-encoding and redo the encoding using java.net.URI. 3006 URI uri = null; 3007 try { 3008 // Undo the percent-encoding that KURL may have done. 3009 String newUrl = new String(URLUtil.decode(url.getBytes())); 3010 // Parse the url into pieces 3011 WebAddress w = new WebAddress(newUrl); 3012 String frag = null; 3013 String query = null; 3014 String path = w.mPath; 3015 // Break the path into path, query, and fragment 3016 if (path.length() > 0) { 3017 // Strip the fragment 3018 int idx = path.lastIndexOf('#'); 3019 if (idx != -1) { 3020 frag = path.substring(idx + 1); 3021 path = path.substring(0, idx); 3022 } 3023 idx = path.lastIndexOf('?'); 3024 if (idx != -1) { 3025 query = path.substring(idx + 1); 3026 path = path.substring(0, idx); 3027 } 3028 } 3029 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path, 3030 query, frag); 3031 } catch (Exception e) { 3032 Log.e(LOGTAG, "Could not parse url for download: " + url, e); 3033 return; 3034 } 3035 3036 // XXX: Have to use the old url since the cookies were stored using the 3037 // old percent-encoded url. 3038 String cookies = CookieManager.getInstance().getCookie(url); 3039 3040 ContentValues values = new ContentValues(); 3041 values.put(Downloads.Impl.COLUMN_URI, uri.toString()); 3042 values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies); 3043 values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent); 3044 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, 3045 getPackageName()); 3046 values.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, 3047 BrowserDownloadPage.class.getCanonicalName()); 3048 values.put(Downloads.Impl.COLUMN_VISIBILITY, 3049 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 3050 values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype); 3051 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename); 3052 values.put(Downloads.Impl.COLUMN_DESCRIPTION, uri.getHost()); 3053 if (contentLength > 0) { 3054 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength); 3055 } 3056 if (mimetype == null) { 3057 // We must have long pressed on a link or image to download it. We 3058 // are not sure of the mimetype in this case, so do a head request 3059 new FetchUrlMimeType(this).execute(values); 3060 } else { 3061 final Uri contentUri = 3062 getContentResolver().insert(Downloads.Impl.CONTENT_URI, values); 3063 } 3064 3065 } 3066 3067 // ------------------------------------------------------------------------- 3068 3069 /** 3070 * Resets the lock icon. This method is called when we start a new load and 3071 * know the url to be loaded. 3072 */ 3073 private void resetLockIcon(String url) { 3074 // Save the lock-icon state (we revert to it if the load gets cancelled) 3075 mTabControl.getCurrentTab().resetLockIcon(url); 3076 updateLockIconImage(LOCK_ICON_UNSECURE); 3077 } 3078 3079 /** 3080 * Update the lock icon to correspond to our latest state. 3081 */ 3082 private void updateLockIconToLatest() { 3083 updateLockIconImage(mTabControl.getCurrentTab().getLockIconType()); 3084 } 3085 3086 /** 3087 * Updates the lock-icon image in the title-bar. 3088 */ 3089 private void updateLockIconImage(int lockIconType) { 3090 Drawable d = null; 3091 if (lockIconType == LOCK_ICON_SECURE) { 3092 d = mSecLockIcon; 3093 } else if (lockIconType == LOCK_ICON_MIXED) { 3094 d = mMixLockIcon; 3095 } 3096 mTitleBar.setLock(d); 3097 mFakeTitleBar.setLock(d); 3098 } 3099 3100 /** 3101 * Displays a page-info dialog. 3102 * @param tab The tab to show info about 3103 * @param fromShowSSLCertificateOnError The flag that indicates whether 3104 * this dialog was opened from the SSL-certificate-on-error dialog or 3105 * not. This is important, since we need to know whether to return to 3106 * the parent dialog or simply dismiss. 3107 */ 3108 private void showPageInfo(final Tab tab, 3109 final boolean fromShowSSLCertificateOnError) { 3110 final LayoutInflater factory = LayoutInflater 3111 .from(this); 3112 3113 final View pageInfoView = factory.inflate(R.layout.page_info, null); 3114 3115 final WebView view = tab.getWebView(); 3116 3117 String url = null; 3118 String title = null; 3119 3120 if (view == null) { 3121 url = tab.getUrl(); 3122 title = tab.getTitle(); 3123 } else if (view == mTabControl.getCurrentWebView()) { 3124 // Use the cached title and url if this is the current WebView 3125 url = mUrl; 3126 title = mTitle; 3127 } else { 3128 url = view.getUrl(); 3129 title = view.getTitle(); 3130 } 3131 3132 if (url == null) { 3133 url = ""; 3134 } 3135 if (title == null) { 3136 title = ""; 3137 } 3138 3139 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url); 3140 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title); 3141 3142 mPageInfoView = tab; 3143 mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError; 3144 3145 AlertDialog.Builder alertDialogBuilder = 3146 new AlertDialog.Builder(this) 3147 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info) 3148 .setView(pageInfoView) 3149 .setPositiveButton( 3150 R.string.ok, 3151 new DialogInterface.OnClickListener() { 3152 public void onClick(DialogInterface dialog, 3153 int whichButton) { 3154 mPageInfoDialog = null; 3155 mPageInfoView = null; 3156 3157 // if we came here from the SSL error dialog 3158 if (fromShowSSLCertificateOnError) { 3159 // go back to the SSL error dialog 3160 showSSLCertificateOnError( 3161 mSSLCertificateOnErrorView, 3162 mSSLCertificateOnErrorHandler, 3163 mSSLCertificateOnErrorError); 3164 } 3165 } 3166 }) 3167 .setOnCancelListener( 3168 new DialogInterface.OnCancelListener() { 3169 public void onCancel(DialogInterface dialog) { 3170 mPageInfoDialog = null; 3171 mPageInfoView = null; 3172 3173 // if we came here from the SSL error dialog 3174 if (fromShowSSLCertificateOnError) { 3175 // go back to the SSL error dialog 3176 showSSLCertificateOnError( 3177 mSSLCertificateOnErrorView, 3178 mSSLCertificateOnErrorHandler, 3179 mSSLCertificateOnErrorError); 3180 } 3181 } 3182 }); 3183 3184 // if we have a main top-level page SSL certificate set or a certificate 3185 // error 3186 if (fromShowSSLCertificateOnError || 3187 (view != null && view.getCertificate() != null)) { 3188 // add a 'View Certificate' button 3189 alertDialogBuilder.setNeutralButton( 3190 R.string.view_certificate, 3191 new DialogInterface.OnClickListener() { 3192 public void onClick(DialogInterface dialog, 3193 int whichButton) { 3194 mPageInfoDialog = null; 3195 mPageInfoView = null; 3196 3197 // if we came here from the SSL error dialog 3198 if (fromShowSSLCertificateOnError) { 3199 // go back to the SSL error dialog 3200 showSSLCertificateOnError( 3201 mSSLCertificateOnErrorView, 3202 mSSLCertificateOnErrorHandler, 3203 mSSLCertificateOnErrorError); 3204 } else { 3205 // otherwise, display the top-most certificate from 3206 // the chain 3207 if (view.getCertificate() != null) { 3208 showSSLCertificate(tab); 3209 } 3210 } 3211 } 3212 }); 3213 } 3214 3215 mPageInfoDialog = alertDialogBuilder.show(); 3216 } 3217 3218 /** 3219 * Displays the main top-level page SSL certificate dialog 3220 * (accessible from the Page-Info dialog). 3221 * @param tab The tab to show certificate for. 3222 */ 3223 private void showSSLCertificate(final Tab tab) { 3224 final View certificateView = 3225 inflateCertificateView(tab.getWebView().getCertificate()); 3226 if (certificateView == null) { 3227 return; 3228 } 3229 3230 LayoutInflater factory = LayoutInflater.from(this); 3231 3232 final LinearLayout placeholder = 3233 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3234 3235 LinearLayout ll = (LinearLayout) factory.inflate( 3236 R.layout.ssl_success, placeholder); 3237 ((TextView)ll.findViewById(R.id.success)) 3238 .setText(R.string.ssl_certificate_is_valid); 3239 3240 mSSLCertificateView = tab; 3241 mSSLCertificateDialog = 3242 new AlertDialog.Builder(this) 3243 .setTitle(R.string.ssl_certificate).setIcon( 3244 R.drawable.ic_dialog_browser_certificate_secure) 3245 .setView(certificateView) 3246 .setPositiveButton(R.string.ok, 3247 new DialogInterface.OnClickListener() { 3248 public void onClick(DialogInterface dialog, 3249 int whichButton) { 3250 mSSLCertificateDialog = null; 3251 mSSLCertificateView = null; 3252 3253 showPageInfo(tab, false); 3254 } 3255 }) 3256 .setOnCancelListener( 3257 new DialogInterface.OnCancelListener() { 3258 public void onCancel(DialogInterface dialog) { 3259 mSSLCertificateDialog = null; 3260 mSSLCertificateView = null; 3261 3262 showPageInfo(tab, false); 3263 } 3264 }) 3265 .show(); 3266 } 3267 3268 /** 3269 * Displays the SSL error certificate dialog. 3270 * @param view The target web-view. 3271 * @param handler The SSL error handler responsible for cancelling the 3272 * connection that resulted in an SSL error or proceeding per user request. 3273 * @param error The SSL error object. 3274 */ 3275 void showSSLCertificateOnError( 3276 final WebView view, final SslErrorHandler handler, final SslError error) { 3277 3278 final View certificateView = 3279 inflateCertificateView(error.getCertificate()); 3280 if (certificateView == null) { 3281 return; 3282 } 3283 3284 LayoutInflater factory = LayoutInflater.from(this); 3285 3286 final LinearLayout placeholder = 3287 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3288 3289 if (error.hasError(SslError.SSL_UNTRUSTED)) { 3290 LinearLayout ll = (LinearLayout)factory 3291 .inflate(R.layout.ssl_warning, placeholder); 3292 ((TextView)ll.findViewById(R.id.warning)) 3293 .setText(R.string.ssl_untrusted); 3294 } 3295 3296 if (error.hasError(SslError.SSL_IDMISMATCH)) { 3297 LinearLayout ll = (LinearLayout)factory 3298 .inflate(R.layout.ssl_warning, placeholder); 3299 ((TextView)ll.findViewById(R.id.warning)) 3300 .setText(R.string.ssl_mismatch); 3301 } 3302 3303 if (error.hasError(SslError.SSL_EXPIRED)) { 3304 LinearLayout ll = (LinearLayout)factory 3305 .inflate(R.layout.ssl_warning, placeholder); 3306 ((TextView)ll.findViewById(R.id.warning)) 3307 .setText(R.string.ssl_expired); 3308 } 3309 3310 if (error.hasError(SslError.SSL_NOTYETVALID)) { 3311 LinearLayout ll = (LinearLayout)factory 3312 .inflate(R.layout.ssl_warning, placeholder); 3313 ((TextView)ll.findViewById(R.id.warning)) 3314 .setText(R.string.ssl_not_yet_valid); 3315 } 3316 3317 mSSLCertificateOnErrorHandler = handler; 3318 mSSLCertificateOnErrorView = view; 3319 mSSLCertificateOnErrorError = error; 3320 mSSLCertificateOnErrorDialog = 3321 new AlertDialog.Builder(this) 3322 .setTitle(R.string.ssl_certificate).setIcon( 3323 R.drawable.ic_dialog_browser_certificate_partially_secure) 3324 .setView(certificateView) 3325 .setPositiveButton(R.string.ok, 3326 new DialogInterface.OnClickListener() { 3327 public void onClick(DialogInterface dialog, 3328 int whichButton) { 3329 mSSLCertificateOnErrorDialog = null; 3330 mSSLCertificateOnErrorView = null; 3331 mSSLCertificateOnErrorHandler = null; 3332 mSSLCertificateOnErrorError = null; 3333 3334 view.getWebViewClient().onReceivedSslError( 3335 view, handler, error); 3336 } 3337 }) 3338 .setNeutralButton(R.string.page_info_view, 3339 new DialogInterface.OnClickListener() { 3340 public void onClick(DialogInterface dialog, 3341 int whichButton) { 3342 mSSLCertificateOnErrorDialog = null; 3343 3344 // do not clear the dialog state: we will 3345 // need to show the dialog again once the 3346 // user is done exploring the page-info details 3347 3348 showPageInfo(mTabControl.getTabFromView(view), 3349 true); 3350 } 3351 }) 3352 .setOnCancelListener( 3353 new DialogInterface.OnCancelListener() { 3354 public void onCancel(DialogInterface dialog) { 3355 mSSLCertificateOnErrorDialog = null; 3356 mSSLCertificateOnErrorView = null; 3357 mSSLCertificateOnErrorHandler = null; 3358 mSSLCertificateOnErrorError = null; 3359 3360 view.getWebViewClient().onReceivedSslError( 3361 view, handler, error); 3362 } 3363 }) 3364 .show(); 3365 } 3366 3367 /** 3368 * Inflates the SSL certificate view (helper method). 3369 * @param certificate The SSL certificate. 3370 * @return The resultant certificate view with issued-to, issued-by, 3371 * issued-on, expires-on, and possibly other fields set. 3372 * If the input certificate is null, returns null. 3373 */ 3374 private View inflateCertificateView(SslCertificate certificate) { 3375 if (certificate == null) { 3376 return null; 3377 } 3378 3379 LayoutInflater factory = LayoutInflater.from(this); 3380 3381 View certificateView = factory.inflate( 3382 R.layout.ssl_certificate, null); 3383 3384 // issued to: 3385 SslCertificate.DName issuedTo = certificate.getIssuedTo(); 3386 if (issuedTo != null) { 3387 ((TextView) certificateView.findViewById(R.id.to_common)) 3388 .setText(issuedTo.getCName()); 3389 ((TextView) certificateView.findViewById(R.id.to_org)) 3390 .setText(issuedTo.getOName()); 3391 ((TextView) certificateView.findViewById(R.id.to_org_unit)) 3392 .setText(issuedTo.getUName()); 3393 } 3394 3395 // issued by: 3396 SslCertificate.DName issuedBy = certificate.getIssuedBy(); 3397 if (issuedBy != null) { 3398 ((TextView) certificateView.findViewById(R.id.by_common)) 3399 .setText(issuedBy.getCName()); 3400 ((TextView) certificateView.findViewById(R.id.by_org)) 3401 .setText(issuedBy.getOName()); 3402 ((TextView) certificateView.findViewById(R.id.by_org_unit)) 3403 .setText(issuedBy.getUName()); 3404 } 3405 3406 // issued on: 3407 String issuedOn = reformatCertificateDate( 3408 certificate.getValidNotBefore()); 3409 ((TextView) certificateView.findViewById(R.id.issued_on)) 3410 .setText(issuedOn); 3411 3412 // expires on: 3413 String expiresOn = reformatCertificateDate( 3414 certificate.getValidNotAfter()); 3415 ((TextView) certificateView.findViewById(R.id.expires_on)) 3416 .setText(expiresOn); 3417 3418 return certificateView; 3419 } 3420 3421 /** 3422 * Re-formats the certificate date (Date.toString()) string to 3423 * a properly localized date string. 3424 * @return Properly localized version of the certificate date string and 3425 * the original certificate date string if fails to localize. 3426 * If the original string is null, returns an empty string "". 3427 */ 3428 private String reformatCertificateDate(String certificateDate) { 3429 String reformattedDate = null; 3430 3431 if (certificateDate != null) { 3432 Date date = null; 3433 try { 3434 date = java.text.DateFormat.getInstance().parse(certificateDate); 3435 } catch (ParseException e) { 3436 date = null; 3437 } 3438 3439 if (date != null) { 3440 reformattedDate = 3441 DateFormat.getDateFormat(this).format(date); 3442 } 3443 } 3444 3445 return reformattedDate != null ? reformattedDate : 3446 (certificateDate != null ? certificateDate : ""); 3447 } 3448 3449 /** 3450 * Displays an http-authentication dialog. 3451 */ 3452 void showHttpAuthentication(final HttpAuthHandler handler, 3453 final String host, final String realm, final String title, 3454 final String name, final String password, int focusId) { 3455 LayoutInflater factory = LayoutInflater.from(this); 3456 final View v = factory 3457 .inflate(R.layout.http_authentication, null); 3458 if (name != null) { 3459 ((EditText) v.findViewById(R.id.username_edit)).setText(name); 3460 } 3461 if (password != null) { 3462 ((EditText) v.findViewById(R.id.password_edit)).setText(password); 3463 } 3464 3465 String titleText = title; 3466 if (titleText == null) { 3467 titleText = getText(R.string.sign_in_to).toString().replace( 3468 "%s1", host).replace("%s2", realm); 3469 } 3470 3471 mHttpAuthHandler = handler; 3472 AlertDialog dialog = new AlertDialog.Builder(this) 3473 .setTitle(titleText) 3474 .setIcon(android.R.drawable.ic_dialog_alert) 3475 .setView(v) 3476 .setPositiveButton(R.string.action, 3477 new DialogInterface.OnClickListener() { 3478 public void onClick(DialogInterface dialog, 3479 int whichButton) { 3480 String nm = ((EditText) v 3481 .findViewById(R.id.username_edit)) 3482 .getText().toString(); 3483 String pw = ((EditText) v 3484 .findViewById(R.id.password_edit)) 3485 .getText().toString(); 3486 BrowserActivity.this.setHttpAuthUsernamePassword 3487 (host, realm, nm, pw); 3488 handler.proceed(nm, pw); 3489 mHttpAuthenticationDialog = null; 3490 mHttpAuthHandler = null; 3491 }}) 3492 .setNegativeButton(R.string.cancel, 3493 new DialogInterface.OnClickListener() { 3494 public void onClick(DialogInterface dialog, 3495 int whichButton) { 3496 handler.cancel(); 3497 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3498 mHttpAuthenticationDialog = null; 3499 mHttpAuthHandler = null; 3500 }}) 3501 .setOnCancelListener(new DialogInterface.OnCancelListener() { 3502 public void onCancel(DialogInterface dialog) { 3503 handler.cancel(); 3504 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3505 mHttpAuthenticationDialog = null; 3506 mHttpAuthHandler = null; 3507 }}) 3508 .create(); 3509 // Make the IME appear when the dialog is displayed if applicable. 3510 dialog.getWindow().setSoftInputMode( 3511 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 3512 dialog.show(); 3513 if (focusId != 0) { 3514 dialog.findViewById(focusId).requestFocus(); 3515 } else { 3516 v.findViewById(R.id.username_edit).requestFocus(); 3517 } 3518 mHttpAuthenticationDialog = dialog; 3519 } 3520 3521 public int getProgress() { 3522 WebView w = mTabControl.getCurrentWebView(); 3523 if (w != null) { 3524 return w.getProgress(); 3525 } else { 3526 return 100; 3527 } 3528 } 3529 3530 /** 3531 * Set HTTP authentication password. 3532 * 3533 * @param host The host for the password 3534 * @param realm The realm for the password 3535 * @param username The username for the password. If it is null, it means 3536 * password can't be saved. 3537 * @param password The password 3538 */ 3539 public void setHttpAuthUsernamePassword(String host, String realm, 3540 String username, 3541 String password) { 3542 WebView w = mTabControl.getCurrentWebView(); 3543 if (w != null) { 3544 w.setHttpAuthUsernamePassword(host, realm, username, password); 3545 } 3546 } 3547 3548 /** 3549 * connectivity manager says net has come or gone... inform the user 3550 * @param up true if net has come up, false if net has gone down 3551 */ 3552 public void onNetworkToggle(boolean up) { 3553 if (up == mIsNetworkUp) { 3554 return; 3555 } else if (up) { 3556 mIsNetworkUp = true; 3557 if (mAlertDialog != null) { 3558 mAlertDialog.cancel(); 3559 mAlertDialog = null; 3560 } 3561 } else { 3562 mIsNetworkUp = false; 3563 if (mInLoad) { 3564 createAndShowNetworkDialog(); 3565 } 3566 } 3567 WebView w = mTabControl.getCurrentWebView(); 3568 if (w != null) { 3569 w.setNetworkAvailable(up); 3570 } 3571 } 3572 3573 boolean isNetworkUp() { 3574 return mIsNetworkUp; 3575 } 3576 3577 // This method shows the network dialog alerting the user that the net is 3578 // down. It will only show the dialog if mAlertDialog is null. 3579 private void createAndShowNetworkDialog() { 3580 if (mAlertDialog == null) { 3581 mAlertDialog = new AlertDialog.Builder(this) 3582 .setTitle(R.string.loadSuspendedTitle) 3583 .setMessage(R.string.loadSuspended) 3584 .setPositiveButton(R.string.ok, null) 3585 .show(); 3586 } 3587 } 3588 3589 @Override 3590 protected void onActivityResult(int requestCode, int resultCode, 3591 Intent intent) { 3592 if (getTopWindow() == null) return; 3593 3594 switch (requestCode) { 3595 case COMBO_PAGE: 3596 if (resultCode == RESULT_OK && intent != null) { 3597 String data = intent.getAction(); 3598 Bundle extras = intent.getExtras(); 3599 if (extras != null && extras.getBoolean("new_window", false)) { 3600 openTab(data); 3601 } else { 3602 final Tab currentTab = 3603 mTabControl.getCurrentTab(); 3604 dismissSubWindow(currentTab); 3605 if (data != null && data.length() != 0) { 3606 getTopWindow().loadUrl(data); 3607 } 3608 } 3609 } 3610 // Deliberately fall through to PREFERENCES_PAGE, since the 3611 // same extra may be attached to the COMBO_PAGE 3612 case PREFERENCES_PAGE: 3613 if (resultCode == RESULT_OK && intent != null) { 3614 String action = intent.getStringExtra(Intent.EXTRA_TEXT); 3615 if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) { 3616 mTabControl.removeParentChildRelationShips(); 3617 } 3618 } 3619 break; 3620 // Choose a file from the file picker. 3621 case FILE_SELECTED: 3622 if (null == mUploadMessage) break; 3623 Uri result = intent == null || resultCode != RESULT_OK ? null 3624 : intent.getData(); 3625 mUploadMessage.onReceiveValue(result); 3626 mUploadMessage = null; 3627 break; 3628 default: 3629 break; 3630 } 3631 getTopWindow().requestFocus(); 3632 } 3633 3634 /* 3635 * This method is called as a result of the user selecting the options 3636 * menu to see the download window. It shows the download window on top of 3637 * the current window. 3638 */ 3639 private void viewDownloads(Uri downloadRecord) { 3640 Intent intent = new Intent(this, 3641 BrowserDownloadPage.class); 3642 intent.setData(downloadRecord); 3643 startActivityForResult(intent, BrowserActivity.DOWNLOAD_PAGE); 3644 3645 } 3646 3647 /** 3648 * Open the Go page. 3649 * @param startWithHistory If true, open starting on the history tab. 3650 * Otherwise, start with the bookmarks tab. 3651 */ 3652 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) { 3653 WebView current = mTabControl.getCurrentWebView(); 3654 if (current == null) { 3655 return; 3656 } 3657 Intent intent = new Intent(this, 3658 CombinedBookmarkHistoryActivity.class); 3659 String title = current.getTitle(); 3660 String url = current.getUrl(); 3661 Bitmap thumbnail = createScreenshot(current); 3662 3663 // Just in case the user opens bookmarks before a page finishes loading 3664 // so the current history item, and therefore the page, is null. 3665 if (null == url) { 3666 url = mLastEnteredUrl; 3667 // This can happen. 3668 if (null == url) { 3669 url = mSettings.getHomePage(); 3670 } 3671 } 3672 // In case the web page has not yet received its associated title. 3673 if (title == null) { 3674 title = url; 3675 } 3676 intent.putExtra("title", title); 3677 intent.putExtra("url", url); 3678 intent.putExtra("thumbnail", thumbnail); 3679 // Disable opening in a new window if we have maxed out the windows 3680 intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab()); 3681 intent.putExtra("touch_icon_url", current.getTouchIconUrl()); 3682 if (startWithHistory) { 3683 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB, 3684 CombinedBookmarkHistoryActivity.HISTORY_TAB); 3685 } 3686 startActivityForResult(intent, COMBO_PAGE); 3687 } 3688 3689 // Called when loading from context menu or LOAD_URL message 3690 private void loadURL(WebView view, String url) { 3691 // In case the user enters nothing. 3692 if (url != null && url.length() != 0 && view != null) { 3693 url = smartUrlFilter(url); 3694 if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) { 3695 view.loadUrl(url); 3696 } 3697 } 3698 } 3699 3700 private String smartUrlFilter(Uri inUri) { 3701 if (inUri != null) { 3702 return smartUrlFilter(inUri.toString()); 3703 } 3704 return null; 3705 } 3706 3707 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( 3708 "(?i)" + // switch on case insensitive matching 3709 "(" + // begin group for schema 3710 "(?:http|https|file):\\/\\/" + 3711 "|(?:inline|data|about|content|javascript):" + 3712 ")" + 3713 "(.*)" ); 3714 3715 /** 3716 * Attempts to determine whether user input is a URL or search 3717 * terms. Anything with a space is passed to search. 3718 * 3719 * Converts to lowercase any mistakenly uppercased schema (i.e., 3720 * "Http://" converts to "http://" 3721 * 3722 * @return Original or modified URL 3723 * 3724 */ 3725 String smartUrlFilter(String url) { 3726 3727 String inUrl = url.trim(); 3728 boolean hasSpace = inUrl.indexOf(' ') != -1; 3729 3730 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); 3731 if (matcher.matches()) { 3732 // force scheme to lowercase 3733 String scheme = matcher.group(1); 3734 String lcScheme = scheme.toLowerCase(); 3735 if (!lcScheme.equals(scheme)) { 3736 inUrl = lcScheme + matcher.group(2); 3737 } 3738 if (hasSpace) { 3739 inUrl = inUrl.replace(" ", "%20"); 3740 } 3741 return inUrl; 3742 } 3743 if (hasSpace) { 3744 // FIXME: Is this the correct place to add to searches? 3745 // what if someone else calls this function? 3746 int shortcut = parseUrlShortcut(inUrl); 3747 if (shortcut != SHORTCUT_INVALID) { 3748 Browser.addSearchUrl(mResolver, inUrl); 3749 String query = inUrl.substring(2); 3750 switch (shortcut) { 3751 case SHORTCUT_GOOGLE_SEARCH: 3752 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER); 3753 case SHORTCUT_WIKIPEDIA_SEARCH: 3754 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER); 3755 case SHORTCUT_DICTIONARY_SEARCH: 3756 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER); 3757 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH: 3758 // FIXME: we need location in this case 3759 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER); 3760 } 3761 } 3762 } else { 3763 if (Patterns.WEB_URL.matcher(inUrl).matches()) { 3764 return URLUtil.guessUrl(inUrl); 3765 } 3766 } 3767 3768 Browser.addSearchUrl(mResolver, inUrl); 3769 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER); 3770 } 3771 3772 /* package */ void setShouldShowErrorConsole(boolean flag) { 3773 if (flag == mShouldShowErrorConsole) { 3774 // Nothing to do. 3775 return; 3776 } 3777 3778 mShouldShowErrorConsole = flag; 3779 3780 ErrorConsoleView errorConsole = mTabControl.getCurrentTab() 3781 .getErrorConsole(true); 3782 3783 if (flag) { 3784 // Setting the show state of the console will cause it's the layout to be inflated. 3785 if (errorConsole.numberOfErrors() > 0) { 3786 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 3787 } else { 3788 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 3789 } 3790 3791 // Now we can add it to the main view. 3792 mErrorConsoleContainer.addView(errorConsole, 3793 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 3794 ViewGroup.LayoutParams.WRAP_CONTENT)); 3795 } else { 3796 mErrorConsoleContainer.removeView(errorConsole); 3797 } 3798 3799 } 3800 3801 boolean shouldShowErrorConsole() { 3802 return mShouldShowErrorConsole; 3803 } 3804 3805 private void setStatusBarVisibility(boolean visible) { 3806 int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN; 3807 getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN); 3808 } 3809 3810 3811 private void sendNetworkType(String type, String subtype) { 3812 WebView w = mTabControl.getCurrentWebView(); 3813 if (w != null) { 3814 w.setNetworkType(type, subtype); 3815 } 3816 } 3817 3818 private void packageChanged(String packageName, boolean wasAdded) { 3819 WebView w = mTabControl.getCurrentWebView(); 3820 if (w == null) { 3821 return; 3822 } 3823 3824 if (wasAdded) { 3825 w.addPackageName(packageName); 3826 } else { 3827 w.removePackageName(packageName); 3828 } 3829 } 3830 3831 private void addPackageNames(Set<String> packageNames) { 3832 WebView w = mTabControl.getCurrentWebView(); 3833 if (w == null) { 3834 return; 3835 } 3836 3837 w.addPackageNames(packageNames); 3838 } 3839 3840 private void getInstalledPackages() { 3841 AsyncTask<Void, Void, Set<String> > task = 3842 new AsyncTask<Void, Void, Set<String> >() { 3843 protected Set<String> doInBackground(Void... unused) { 3844 Set<String> installedPackages = new HashSet<String>(); 3845 PackageManager pm = BrowserActivity.this.getPackageManager(); 3846 if (pm != null) { 3847 List<PackageInfo> packages = pm.getInstalledPackages(0); 3848 for (PackageInfo p : packages) { 3849 if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) { 3850 installedPackages.add(p.packageName); 3851 } 3852 } 3853 } 3854 3855 return installedPackages; 3856 } 3857 3858 // Executes on the UI thread 3859 protected void onPostExecute(Set<String> installedPackages) { 3860 addPackageNames(installedPackages); 3861 } 3862 }; 3863 task.execute(); 3864 } 3865 3866 final static int LOCK_ICON_UNSECURE = 0; 3867 final static int LOCK_ICON_SECURE = 1; 3868 final static int LOCK_ICON_MIXED = 2; 3869 3870 private BrowserSettings mSettings; 3871 private TabControl mTabControl; 3872 private ContentResolver mResolver; 3873 private FrameLayout mContentView; 3874 private View mCustomView; 3875 private FrameLayout mCustomViewContainer; 3876 private WebChromeClient.CustomViewCallback mCustomViewCallback; 3877 3878 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of 3879 // view, we should rewrite this. 3880 private int mCurrentMenuState = 0; 3881 private int mMenuState = R.id.MAIN_MENU; 3882 private int mOldMenuState = EMPTY_MENU; 3883 private static final int EMPTY_MENU = -1; 3884 private Menu mMenu; 3885 3886 private FindDialog mFindDialog; 3887 // Used to prevent chording to result in firing two shortcuts immediately 3888 // one after another. Fixes bug 1211714. 3889 boolean mCanChord; 3890 3891 private boolean mInLoad; 3892 private boolean mIsNetworkUp; 3893 private boolean mDidStopLoad; 3894 3895 private boolean mActivityInPause = true; 3896 3897 private boolean mMenuIsDown; 3898 3899 private static boolean mInTrace; 3900 3901 // Performance probe 3902 private static final int[] SYSTEM_CPU_FORMAT = new int[] { 3903 Process.PROC_SPACE_TERM | Process.PROC_COMBINE, 3904 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time 3905 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time 3906 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time 3907 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time 3908 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time 3909 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time 3910 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time 3911 }; 3912 3913 private long mStart; 3914 private long mProcessStart; 3915 private long mUserStart; 3916 private long mSystemStart; 3917 private long mIdleStart; 3918 private long mIrqStart; 3919 3920 private long mUiStart; 3921 3922 private Drawable mMixLockIcon; 3923 private Drawable mSecLockIcon; 3924 3925 /* hold a ref so we can auto-cancel if necessary */ 3926 private AlertDialog mAlertDialog; 3927 3928 // Wait for credentials before loading google.com 3929 private ProgressDialog mCredsDlg; 3930 3931 // The up-to-date URL and title (these can be different from those stored 3932 // in WebView, since it takes some time for the information in WebView to 3933 // get updated) 3934 private String mUrl; 3935 private String mTitle; 3936 3937 // As PageInfo has different style for landscape / portrait, we have 3938 // to re-open it when configuration changed 3939 private AlertDialog mPageInfoDialog; 3940 private Tab mPageInfoView; 3941 // If the Page-Info dialog is launched from the SSL-certificate-on-error 3942 // dialog, we should not just dismiss it, but should get back to the 3943 // SSL-certificate-on-error dialog. This flag is used to store this state 3944 private boolean mPageInfoFromShowSSLCertificateOnError; 3945 3946 // as SSLCertificateOnError has different style for landscape / portrait, 3947 // we have to re-open it when configuration changed 3948 private AlertDialog mSSLCertificateOnErrorDialog; 3949 private WebView mSSLCertificateOnErrorView; 3950 private SslErrorHandler mSSLCertificateOnErrorHandler; 3951 private SslError mSSLCertificateOnErrorError; 3952 3953 // as SSLCertificate has different style for landscape / portrait, we 3954 // have to re-open it when configuration changed 3955 private AlertDialog mSSLCertificateDialog; 3956 private Tab mSSLCertificateView; 3957 3958 // as HttpAuthentication has different style for landscape / portrait, we 3959 // have to re-open it when configuration changed 3960 private AlertDialog mHttpAuthenticationDialog; 3961 private HttpAuthHandler mHttpAuthHandler; 3962 3963 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = 3964 new FrameLayout.LayoutParams( 3965 ViewGroup.LayoutParams.MATCH_PARENT, 3966 ViewGroup.LayoutParams.MATCH_PARENT); 3967 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = 3968 new FrameLayout.LayoutParams( 3969 ViewGroup.LayoutParams.MATCH_PARENT, 3970 ViewGroup.LayoutParams.MATCH_PARENT, 3971 Gravity.CENTER); 3972 // Google search 3973 final static String QuickSearch_G = "http://www.google.com/m?q=%s"; 3974 // Wikipedia search 3975 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go"; 3976 // Dictionary search 3977 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s"; 3978 // Google Mobile Local search 3979 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view"; 3980 3981 final static String QUERY_PLACE_HOLDER = "%s"; 3982 3983 // "source" parameter for Google search through search key 3984 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key"; 3985 // "source" parameter for Google search through goto menu 3986 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto"; 3987 // "source" parameter for Google search through simplily type 3988 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type"; 3989 // "source" parameter for Google search suggested by the browser 3990 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest"; 3991 // "source" parameter for Google search from unknown source 3992 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; 3993 3994 private final static String LOGTAG = "browser"; 3995 3996 private String mLastEnteredUrl; 3997 3998 private PowerManager.WakeLock mWakeLock; 3999 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes 4000 4001 private Toast mStopToast; 4002 4003 private TitleBar mTitleBar; 4004 4005 private LinearLayout mErrorConsoleContainer = null; 4006 private boolean mShouldShowErrorConsole = false; 4007 4008 // As the ids are dynamically created, we can't guarantee that they will 4009 // be in sequence, so this static array maps ids to a window number. 4010 final static private int[] WINDOW_SHORTCUT_ID_ARRAY = 4011 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id, 4012 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id, 4013 R.id.window_seven_menu_id, R.id.window_eight_menu_id }; 4014 4015 // monitor platform changes 4016 private IntentFilter mNetworkStateChangedFilter; 4017 private BroadcastReceiver mNetworkStateIntentReceiver; 4018 4019 private BroadcastReceiver mPackageInstallationReceiver; 4020 4021 // activity requestCode 4022 final static int COMBO_PAGE = 1; 4023 final static int DOWNLOAD_PAGE = 2; 4024 final static int PREFERENCES_PAGE = 3; 4025 final static int FILE_SELECTED = 4; 4026 4027 // the default <video> poster 4028 private Bitmap mDefaultVideoPoster; 4029 // the video progress view 4030 private View mVideoProgressView; 4031 4032 // The Google packages we monitor for the navigator.isApplicationInstalled() 4033 // API. Add as needed. 4034 private static Set<String> sGoogleApps; 4035 static { 4036 sGoogleApps = new HashSet<String>(); 4037 sGoogleApps.add("com.google.android.youtube"); 4038 } 4039 4040 /** 4041 * A UrlData class to abstract how the content will be set to WebView. 4042 * This base class uses loadUrl to show the content. 4043 */ 4044 private static class UrlData { 4045 final String mUrl; 4046 final Map<String, String> mHeaders; 4047 final Intent mVoiceIntent; 4048 4049 UrlData(String url) { 4050 this.mUrl = url; 4051 this.mHeaders = null; 4052 this.mVoiceIntent = null; 4053 } 4054 4055 UrlData(String url, Map<String, String> headers, Intent intent) { 4056 this.mUrl = url; 4057 this.mHeaders = headers; 4058 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS 4059 .equals(intent.getAction())) { 4060 this.mVoiceIntent = intent; 4061 } else { 4062 this.mVoiceIntent = null; 4063 } 4064 } 4065 4066 boolean isEmpty() { 4067 return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0); 4068 } 4069 4070 public void loadIn(Tab t) { 4071 if (mVoiceIntent != null) { 4072 t.activateVoiceSearchMode(mVoiceIntent); 4073 } else { 4074 t.getWebView().loadUrl(mUrl, mHeaders); 4075 } 4076 } 4077 }; 4078 4079 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); 4080} 4081