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