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