BrowserActivity.java revision 5942df0c38dff7e4335e352e2d03f100b07b8907
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.BitmapFactory; 49import android.graphics.Canvas; 50import android.graphics.DrawFilter; 51import android.graphics.Paint; 52import android.graphics.PaintFlagsDrawFilter; 53import android.graphics.Picture; 54import android.graphics.PixelFormat; 55import android.graphics.Rect; 56import android.graphics.drawable.Drawable; 57import android.hardware.SensorListener; 58import android.hardware.SensorManager; 59import android.net.ConnectivityManager; 60import android.net.NetworkInfo; 61import android.net.Uri; 62import android.net.WebAddress; 63import android.net.http.EventHandler; 64import android.net.http.SslCertificate; 65import android.net.http.SslError; 66import android.os.AsyncTask; 67import android.os.Bundle; 68import android.os.Debug; 69import android.os.Environment; 70import android.os.Handler; 71import android.os.IBinder; 72import android.os.Message; 73import android.os.PowerManager; 74import android.os.Process; 75import android.os.RemoteException; 76import android.os.ServiceManager; 77import android.os.SystemClock; 78import android.provider.Browser; 79import android.provider.Contacts; 80import android.provider.Downloads; 81import android.provider.MediaStore; 82import android.provider.Contacts.Intents.Insert; 83import android.text.IClipboard; 84import android.text.TextUtils; 85import android.text.format.DateFormat; 86import android.text.util.Regex; 87import android.util.Log; 88import android.view.ContextMenu; 89import android.view.Gravity; 90import android.view.KeyEvent; 91import android.view.LayoutInflater; 92import android.view.Menu; 93import android.view.MenuInflater; 94import android.view.MenuItem; 95import android.view.View; 96import android.view.ViewGroup; 97import android.view.Window; 98import android.view.WindowManager; 99import android.view.ContextMenu.ContextMenuInfo; 100import android.view.MenuItem.OnMenuItemClickListener; 101import android.view.animation.AlphaAnimation; 102import android.view.animation.Animation; 103import android.view.animation.AnimationSet; 104import android.view.animation.DecelerateInterpolator; 105import android.view.animation.ScaleAnimation; 106import android.view.animation.TranslateAnimation; 107import android.webkit.CookieManager; 108import android.webkit.CookieSyncManager; 109import android.webkit.DownloadListener; 110import android.webkit.GeolocationPermissions; 111import android.webkit.HttpAuthHandler; 112import android.webkit.PluginManager; 113import android.webkit.SslErrorHandler; 114import android.webkit.URLUtil; 115import android.webkit.WebChromeClient; 116import android.webkit.WebChromeClient.CustomViewCallback; 117import android.webkit.WebHistoryItem; 118import android.webkit.WebIconDatabase; 119import android.webkit.WebStorage; 120import android.webkit.WebView; 121import android.webkit.WebViewClient; 122import android.widget.EditText; 123import android.widget.FrameLayout; 124import android.widget.LinearLayout; 125import android.widget.TextView; 126import android.widget.Toast; 127 128import java.io.BufferedOutputStream; 129import java.io.ByteArrayOutputStream; 130import java.io.File; 131import java.io.FileInputStream; 132import java.io.FileOutputStream; 133import java.io.IOException; 134import java.io.InputStream; 135import java.net.MalformedURLException; 136import java.net.URI; 137import java.net.URISyntaxException; 138import java.net.URL; 139import java.net.URLEncoder; 140import java.text.ParseException; 141import java.util.Date; 142import java.util.Enumeration; 143import java.util.HashMap; 144import java.util.LinkedList; 145import java.util.Vector; 146import java.util.regex.Matcher; 147import java.util.regex.Pattern; 148import java.util.zip.ZipEntry; 149import java.util.zip.ZipFile; 150 151public class BrowserActivity extends Activity 152 implements 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 @Override 2133 public boolean onKeyDown(int keyCode, KeyEvent event) { 2134 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is 2135 // still down, we don't want to trigger the search. Pretend to consume 2136 // the key and do nothing. 2137 if (mMenuIsDown) return true; 2138 2139 switch(keyCode) { 2140 case KeyEvent.KEYCODE_MENU: 2141 mMenuIsDown = true; 2142 break; 2143 case KeyEvent.KEYCODE_SPACE: 2144 // Browser's hidden shortcut key. Don't call super so that 2145 // search won't be triggered. 2146 return true; 2147 case KeyEvent.KEYCODE_BACK: 2148 if (event.getRepeatCount() == 0) { 2149 event.startTracking(); 2150 return true; 2151 } else if (mCustomView == null && mActiveTabsPage == null 2152 && event.isLongPress()) { 2153 bookmarksOrHistoryPicker(true); 2154 return true; 2155 } 2156 break; 2157 } 2158 return super.onKeyDown(keyCode, event); 2159 } 2160 2161 @Override 2162 public boolean onKeyUp(int keyCode, KeyEvent event) { 2163 switch(keyCode) { 2164 case KeyEvent.KEYCODE_MENU: 2165 mMenuIsDown = false; 2166 break; 2167 case KeyEvent.KEYCODE_SPACE: 2168 if (event.isShiftPressed()) { 2169 getTopWindow().pageUp(false); 2170 } else { 2171 getTopWindow().pageDown(false); 2172 } 2173 return true; 2174 case KeyEvent.KEYCODE_BACK: 2175 if (event.isTracking() && !event.isCanceled()) { 2176 if (mCustomView != null) { 2177 // if a custom view is showing, hide it 2178 mWebChromeClient.onHideCustomView(); 2179 } else if (mActiveTabsPage != null) { 2180 // if tab page is showing, hide it 2181 removeActiveTabPage(true); 2182 } else { 2183 WebView subwindow = mTabControl.getCurrentSubWindow(); 2184 if (subwindow != null) { 2185 if (subwindow.canGoBack()) { 2186 subwindow.goBack(); 2187 } else { 2188 dismissSubWindow(mTabControl.getCurrentTab()); 2189 } 2190 } else { 2191 goBackOnePageOrQuit(); 2192 } 2193 } 2194 return true; 2195 } 2196 break; 2197 } 2198 return super.onKeyUp(keyCode, event); 2199 } 2200 2201 /* package */ void stopLoading() { 2202 resetTitleAndRevertLockIcon(); 2203 WebView w = getTopWindow(); 2204 w.stopLoading(); 2205 mWebViewClient.onPageFinished(w, w.getUrl()); 2206 2207 cancelStopToast(); 2208 mStopToast = Toast 2209 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT); 2210 mStopToast.show(); 2211 } 2212 2213 private void cancelStopToast() { 2214 if (mStopToast != null) { 2215 mStopToast.cancel(); 2216 mStopToast = null; 2217 } 2218 } 2219 2220 // called by a non-UI thread to post the message 2221 public void postMessage(int what, int arg1, int arg2, Object obj) { 2222 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj)); 2223 } 2224 2225 // public message ids 2226 public final static int LOAD_URL = 1001; 2227 public final static int STOP_LOAD = 1002; 2228 2229 // Message Ids 2230 private static final int FOCUS_NODE_HREF = 102; 2231 private static final int CANCEL_CREDS_REQUEST = 103; 2232 private static final int RELEASE_WAKELOCK = 107; 2233 2234 // Private handler for handling javascript and saving passwords 2235 private Handler mHandler = new Handler() { 2236 2237 public void handleMessage(Message msg) { 2238 switch (msg.what) { 2239 case FOCUS_NODE_HREF: 2240 String url = (String) msg.getData().get("url"); 2241 if (url == null || url.length() == 0) { 2242 break; 2243 } 2244 HashMap focusNodeMap = (HashMap) msg.obj; 2245 WebView view = (WebView) focusNodeMap.get("webview"); 2246 // Only apply the action if the top window did not change. 2247 if (getTopWindow() != view) { 2248 break; 2249 } 2250 switch (msg.arg1) { 2251 case R.id.open_context_menu_id: 2252 case R.id.view_image_context_menu_id: 2253 loadURL(getTopWindow(), url); 2254 break; 2255 case R.id.open_newtab_context_menu_id: 2256 final TabControl.Tab parent = mTabControl 2257 .getCurrentTab(); 2258 final TabControl.Tab newTab = openTab(url); 2259 if (newTab != parent) { 2260 parent.addChildTab(newTab); 2261 } 2262 break; 2263 case R.id.bookmark_context_menu_id: 2264 Intent intent = new Intent(BrowserActivity.this, 2265 AddBookmarkPage.class); 2266 intent.putExtra("url", url); 2267 startActivity(intent); 2268 break; 2269 case R.id.share_link_context_menu_id: 2270 Browser.sendString(BrowserActivity.this, url); 2271 break; 2272 case R.id.copy_link_context_menu_id: 2273 copy(url); 2274 break; 2275 case R.id.save_link_context_menu_id: 2276 case R.id.download_context_menu_id: 2277 onDownloadStartNoStream(url, null, null, null, -1); 2278 break; 2279 } 2280 break; 2281 2282 case LOAD_URL: 2283 loadURL(getTopWindow(), (String) msg.obj); 2284 break; 2285 2286 case STOP_LOAD: 2287 stopLoading(); 2288 break; 2289 2290 case CANCEL_CREDS_REQUEST: 2291 resumeAfterCredentials(); 2292 break; 2293 2294 case RELEASE_WAKELOCK: 2295 if (mWakeLock.isHeld()) { 2296 mWakeLock.release(); 2297 } 2298 break; 2299 } 2300 } 2301 }; 2302 2303 private void updateScreenshot(WebView view) { 2304 // If this is a bookmarked site, add a screenshot to the database. 2305 // FIXME: When should we update? Every time? 2306 // FIXME: Would like to make sure there is actually something to 2307 // draw, but the API for that (WebViewCore.pictureReady()) is not 2308 // currently accessible here. 2309 ContentResolver cr = getContentResolver(); 2310 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl( 2311 cr, view.getOriginalUrl(), view.getUrl(), false); 2312 if (c != null) { 2313 boolean succeed = c.moveToFirst(); 2314 ContentValues values = null; 2315 while (succeed) { 2316 if (values == null) { 2317 final ByteArrayOutputStream os 2318 = new ByteArrayOutputStream(); 2319 Picture thumbnail = view.capturePicture(); 2320 // Keep width and height in sync with BrowserBookmarksPage 2321 // and bookmark_thumb 2322 Bitmap bm = Bitmap.createBitmap(100, 80, 2323 Bitmap.Config.ARGB_4444); 2324 Canvas canvas = new Canvas(bm); 2325 // May need to tweak these values to determine what is the 2326 // best scale factor 2327 canvas.scale(.5f, .5f); 2328 thumbnail.draw(canvas); 2329 bm.compress(Bitmap.CompressFormat.PNG, 100, os); 2330 values = new ContentValues(); 2331 values.put(Browser.BookmarkColumns.THUMBNAIL, 2332 os.toByteArray()); 2333 } 2334 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, 2335 c.getInt(0)), values, null, null); 2336 succeed = c.moveToNext(); 2337 } 2338 c.close(); 2339 } 2340 } 2341 2342 // ------------------------------------------------------------------------- 2343 // WebViewClient implementation. 2344 //------------------------------------------------------------------------- 2345 2346 // Use in overrideUrlLoading 2347 /* package */ final static String SCHEME_WTAI = "wtai://wp/"; 2348 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;"; 2349 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;"; 2350 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;"; 2351 2352 /* package */ WebViewClient getWebViewClient() { 2353 return mWebViewClient; 2354 } 2355 2356 private void updateIcon(WebView view, Bitmap icon) { 2357 if (icon != null) { 2358 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver, 2359 view, icon); 2360 } 2361 setFavicon(icon); 2362 } 2363 2364 private final WebViewClient mWebViewClient = new WebViewClient() { 2365 @Override 2366 public void onPageStarted(WebView view, String url, Bitmap favicon) { 2367 resetLockIcon(url); 2368 setUrlTitle(url, null); 2369 2370 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false); 2371 if (errorConsole != null) { 2372 errorConsole.clearErrorMessages(); 2373 if (mShouldShowErrorConsole) { 2374 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 2375 } 2376 } 2377 2378 // Call updateIcon instead of setFavicon so the bookmark 2379 // database can be updated. 2380 updateIcon(view, favicon); 2381 2382 if (mSettings.isTracing()) { 2383 String host; 2384 try { 2385 WebAddress uri = new WebAddress(url); 2386 host = uri.mHost; 2387 } catch (android.net.ParseException ex) { 2388 host = "browser"; 2389 } 2390 host = host.replace('.', '_'); 2391 host += ".trace"; 2392 mInTrace = true; 2393 Debug.startMethodTracing(host, 20 * 1024 * 1024); 2394 } 2395 2396 // Performance probe 2397 if (false) { 2398 mStart = SystemClock.uptimeMillis(); 2399 mProcessStart = Process.getElapsedCpuTime(); 2400 long[] sysCpu = new long[7]; 2401 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2402 sysCpu, null)) { 2403 mUserStart = sysCpu[0] + sysCpu[1]; 2404 mSystemStart = sysCpu[2]; 2405 mIdleStart = sysCpu[3]; 2406 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6]; 2407 } 2408 mUiStart = SystemClock.currentThreadTimeMillis(); 2409 } 2410 2411 if (!mPageStarted) { 2412 mPageStarted = true; 2413 // if onResume() has been called, resumeWebViewTimers() does 2414 // nothing. 2415 resumeWebViewTimers(); 2416 } 2417 2418 // reset sync timer to avoid sync starts during loading a page 2419 CookieSyncManager.getInstance().resetSync(); 2420 2421 mInLoad = true; 2422 WebView currentWebView = mTabControl.getCurrentWebView(); 2423 if (currentWebView == null || currentWebView.getScrollY() != 0) { 2424 // This page has begun to load, so show the title bar 2425 showFakeTitleBar(); 2426 } 2427 updateInLoadMenuItems(); 2428 if (!mIsNetworkUp) { 2429 createAndShowNetworkDialog(); 2430 if (view != null) { 2431 view.setNetworkAvailable(false); 2432 } 2433 } 2434 } 2435 2436 @Override 2437 public void onPageFinished(WebView view, String url) { 2438 // Reset the title and icon in case we stopped a provisional 2439 // load. 2440 resetTitleAndIcon(view); 2441 2442 // Update the lock icon image only once we are done loading 2443 updateLockIconToLatest(); 2444 updateScreenshot(view); 2445 2446 // Performance probe 2447 if (false) { 2448 long[] sysCpu = new long[7]; 2449 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2450 sysCpu, null)) { 2451 String uiInfo = "UI thread used " 2452 + (SystemClock.currentThreadTimeMillis() - mUiStart) 2453 + " ms"; 2454 if (LOGD_ENABLED) { 2455 Log.d(LOGTAG, uiInfo); 2456 } 2457 //The string that gets written to the log 2458 String performanceString = "It took total " 2459 + (SystemClock.uptimeMillis() - mStart) 2460 + " ms clock time to load the page." 2461 + "\nbrowser process used " 2462 + (Process.getElapsedCpuTime() - mProcessStart) 2463 + " ms, user processes used " 2464 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10 2465 + " ms, kernel used " 2466 + (sysCpu[2] - mSystemStart) * 10 2467 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10 2468 + " ms and irq took " 2469 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart) 2470 * 10 + " ms, " + uiInfo; 2471 if (LOGD_ENABLED) { 2472 Log.d(LOGTAG, performanceString + "\nWebpage: " + url); 2473 } 2474 if (url != null) { 2475 // strip the url to maintain consistency 2476 String newUrl = new String(url); 2477 if (newUrl.startsWith("http://www.")) { 2478 newUrl = newUrl.substring(11); 2479 } else if (newUrl.startsWith("http://")) { 2480 newUrl = newUrl.substring(7); 2481 } else if (newUrl.startsWith("https://www.")) { 2482 newUrl = newUrl.substring(12); 2483 } else if (newUrl.startsWith("https://")) { 2484 newUrl = newUrl.substring(8); 2485 } 2486 if (LOGD_ENABLED) { 2487 Log.d(LOGTAG, newUrl + " loaded"); 2488 } 2489 /* 2490 if (sWhiteList.contains(newUrl)) { 2491 // The string that gets pushed to the statistcs 2492 // service 2493 performanceString = performanceString 2494 + "\nWebpage: " 2495 + newUrl 2496 + "\nCarrier: " 2497 + android.os.SystemProperties 2498 .get("gsm.sim.operator.alpha"); 2499 if (mWebView != null 2500 && mWebView.getContext() != null 2501 && mWebView.getContext().getSystemService( 2502 Context.CONNECTIVITY_SERVICE) != null) { 2503 ConnectivityManager cManager = 2504 (ConnectivityManager) mWebView 2505 .getContext().getSystemService( 2506 Context.CONNECTIVITY_SERVICE); 2507 NetworkInfo nInfo = cManager 2508 .getActiveNetworkInfo(); 2509 if (nInfo != null) { 2510 performanceString = performanceString 2511 + "\nNetwork Type: " 2512 + nInfo.getType().toString(); 2513 } 2514 } 2515 Checkin.logEvent(mResolver, 2516 Checkin.Events.Tag.WEBPAGE_LOAD, 2517 performanceString); 2518 Log.w(LOGTAG, "pushed to the statistics service"); 2519 } 2520 */ 2521 } 2522 } 2523 } 2524 2525 if (mInTrace) { 2526 mInTrace = false; 2527 Debug.stopMethodTracing(); 2528 } 2529 2530 if (mPageStarted) { 2531 mPageStarted = false; 2532 // pauseWebViewTimers() will do nothing and return false if 2533 // onPause() is not called yet. 2534 if (pauseWebViewTimers()) { 2535 if (mWakeLock.isHeld()) { 2536 mHandler.removeMessages(RELEASE_WAKELOCK); 2537 mWakeLock.release(); 2538 } 2539 } 2540 } 2541 } 2542 2543 // return true if want to hijack the url to let another app to handle it 2544 @Override 2545 public boolean shouldOverrideUrlLoading(WebView view, String url) { 2546 if (url.startsWith(SCHEME_WTAI)) { 2547 // wtai://wp/mc;number 2548 // number=string(phone-number) 2549 if (url.startsWith(SCHEME_WTAI_MC)) { 2550 Intent intent = new Intent(Intent.ACTION_VIEW, 2551 Uri.parse(WebView.SCHEME_TEL + 2552 url.substring(SCHEME_WTAI_MC.length()))); 2553 startActivity(intent); 2554 return true; 2555 } 2556 // wtai://wp/sd;dtmf 2557 // dtmf=string(dialstring) 2558 if (url.startsWith(SCHEME_WTAI_SD)) { 2559 // TODO 2560 // only send when there is active voice connection 2561 return false; 2562 } 2563 // wtai://wp/ap;number;name 2564 // number=string(phone-number) 2565 // name=string 2566 if (url.startsWith(SCHEME_WTAI_AP)) { 2567 // TODO 2568 return false; 2569 } 2570 } 2571 2572 // The "about:" schemes are internal to the browser; don't 2573 // want these to be dispatched to other apps. 2574 if (url.startsWith("about:")) { 2575 return false; 2576 } 2577 2578 Intent intent; 2579 2580 // perform generic parsing of the URI to turn it into an Intent. 2581 try { 2582 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); 2583 } catch (URISyntaxException ex) { 2584 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage()); 2585 return false; 2586 } 2587 2588 // check whether the intent can be resolved. If not, we will see 2589 // whether we can download it from the Market. 2590 if (getPackageManager().resolveActivity(intent, 0) == null) { 2591 String packagename = intent.getPackage(); 2592 if (packagename != null) { 2593 intent = new Intent(Intent.ACTION_VIEW, Uri 2594 .parse("market://search?q=pname:" + packagename)); 2595 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2596 startActivity(intent); 2597 return true; 2598 } else { 2599 return false; 2600 } 2601 } 2602 2603 // sanitize the Intent, ensuring web pages can not bypass browser 2604 // security (only access to BROWSABLE activities). 2605 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2606 intent.setComponent(null); 2607 try { 2608 if (startActivityIfNeeded(intent, -1)) { 2609 return true; 2610 } 2611 } catch (ActivityNotFoundException ex) { 2612 // ignore the error. If no application can handle the URL, 2613 // eg about:blank, assume the browser can handle it. 2614 } 2615 2616 if (mMenuIsDown) { 2617 openTab(url); 2618 closeOptionsMenu(); 2619 return true; 2620 } 2621 2622 return false; 2623 } 2624 2625 /** 2626 * Updates the lock icon. This method is called when we discover another 2627 * resource to be loaded for this page (for example, javascript). While 2628 * we update the icon type, we do not update the lock icon itself until 2629 * we are done loading, it is slightly more secure this way. 2630 */ 2631 @Override 2632 public void onLoadResource(WebView view, String url) { 2633 if (url != null && url.length() > 0) { 2634 // It is only if the page claims to be secure 2635 // that we may have to update the lock: 2636 if (mLockIconType == LOCK_ICON_SECURE) { 2637 // If NOT a 'safe' url, change the lock to mixed content! 2638 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) { 2639 mLockIconType = LOCK_ICON_MIXED; 2640 if (LOGV_ENABLED) { 2641 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" + 2642 " updated lock icon to " + mLockIconType + " due to " + url); 2643 } 2644 } 2645 } 2646 } 2647 } 2648 2649 /** 2650 * Show the dialog, asking the user if they would like to continue after 2651 * an excessive number of HTTP redirects. 2652 */ 2653 @Override 2654 public void onTooManyRedirects(WebView view, final Message cancelMsg, 2655 final Message continueMsg) { 2656 new AlertDialog.Builder(BrowserActivity.this) 2657 .setTitle(R.string.browserFrameRedirect) 2658 .setMessage(R.string.browserFrame307Post) 2659 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 2660 public void onClick(DialogInterface dialog, int which) { 2661 continueMsg.sendToTarget(); 2662 }}) 2663 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 2664 public void onClick(DialogInterface dialog, int which) { 2665 cancelMsg.sendToTarget(); 2666 }}) 2667 .setOnCancelListener(new OnCancelListener() { 2668 public void onCancel(DialogInterface dialog) { 2669 cancelMsg.sendToTarget(); 2670 }}) 2671 .show(); 2672 } 2673 2674 // Container class for the next error dialog that needs to be 2675 // displayed. 2676 class ErrorDialog { 2677 public final int mTitle; 2678 public final String mDescription; 2679 public final int mError; 2680 ErrorDialog(int title, String desc, int error) { 2681 mTitle = title; 2682 mDescription = desc; 2683 mError = error; 2684 } 2685 }; 2686 2687 private void processNextError() { 2688 if (mQueuedErrors == null) { 2689 return; 2690 } 2691 // The first one is currently displayed so just remove it. 2692 mQueuedErrors.removeFirst(); 2693 if (mQueuedErrors.size() == 0) { 2694 mQueuedErrors = null; 2695 return; 2696 } 2697 showError(mQueuedErrors.getFirst()); 2698 } 2699 2700 private DialogInterface.OnDismissListener mDialogListener = 2701 new DialogInterface.OnDismissListener() { 2702 public void onDismiss(DialogInterface d) { 2703 processNextError(); 2704 } 2705 }; 2706 private LinkedList<ErrorDialog> mQueuedErrors; 2707 2708 private void queueError(int err, String desc) { 2709 if (mQueuedErrors == null) { 2710 mQueuedErrors = new LinkedList<ErrorDialog>(); 2711 } 2712 for (ErrorDialog d : mQueuedErrors) { 2713 if (d.mError == err) { 2714 // Already saw a similar error, ignore the new one. 2715 return; 2716 } 2717 } 2718 ErrorDialog errDialog = new ErrorDialog( 2719 err == WebViewClient.ERROR_FILE_NOT_FOUND ? 2720 R.string.browserFrameFileErrorLabel : 2721 R.string.browserFrameNetworkErrorLabel, 2722 desc, err); 2723 mQueuedErrors.addLast(errDialog); 2724 2725 // Show the dialog now if the queue was empty. 2726 if (mQueuedErrors.size() == 1) { 2727 showError(errDialog); 2728 } 2729 } 2730 2731 private void showError(ErrorDialog errDialog) { 2732 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this) 2733 .setTitle(errDialog.mTitle) 2734 .setMessage(errDialog.mDescription) 2735 .setPositiveButton(R.string.ok, null) 2736 .create(); 2737 d.setOnDismissListener(mDialogListener); 2738 d.show(); 2739 } 2740 2741 /** 2742 * Show a dialog informing the user of the network error reported by 2743 * WebCore. 2744 */ 2745 @Override 2746 public void onReceivedError(WebView view, int errorCode, 2747 String description, String failingUrl) { 2748 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP && 2749 errorCode != WebViewClient.ERROR_CONNECT && 2750 errorCode != WebViewClient.ERROR_BAD_URL && 2751 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME && 2752 errorCode != WebViewClient.ERROR_FILE) { 2753 queueError(errorCode, description); 2754 } 2755 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl 2756 + " " + description); 2757 2758 // We need to reset the title after an error. 2759 resetTitleAndRevertLockIcon(); 2760 } 2761 2762 /** 2763 * Check with the user if it is ok to resend POST data as the page they 2764 * are trying to navigate to is the result of a POST. 2765 */ 2766 @Override 2767 public void onFormResubmission(WebView view, final Message dontResend, 2768 final Message resend) { 2769 new AlertDialog.Builder(BrowserActivity.this) 2770 .setTitle(R.string.browserFrameFormResubmitLabel) 2771 .setMessage(R.string.browserFrameFormResubmitMessage) 2772 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 2773 public void onClick(DialogInterface dialog, int which) { 2774 resend.sendToTarget(); 2775 }}) 2776 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 2777 public void onClick(DialogInterface dialog, int which) { 2778 dontResend.sendToTarget(); 2779 }}) 2780 .setOnCancelListener(new OnCancelListener() { 2781 public void onCancel(DialogInterface dialog) { 2782 dontResend.sendToTarget(); 2783 }}) 2784 .show(); 2785 } 2786 2787 /** 2788 * Insert the url into the visited history database. 2789 * @param url The url to be inserted. 2790 * @param isReload True if this url is being reloaded. 2791 * FIXME: Not sure what to do when reloading the page. 2792 */ 2793 @Override 2794 public void doUpdateVisitedHistory(WebView view, String url, 2795 boolean isReload) { 2796 if (url.regionMatches(true, 0, "about:", 0, 6)) { 2797 return; 2798 } 2799 // remove "client" before updating it to the history so that it wont 2800 // show up in the auto-complete list. 2801 int index = url.indexOf("client=ms-"); 2802 if (index > 0 && url.contains(".google.")) { 2803 int end = url.indexOf('&', index); 2804 if (end > 0) { 2805 url = url.substring(0, index-1).concat(url.substring(end)); 2806 } else { 2807 url = url.substring(0, index-1); 2808 } 2809 } 2810 Browser.updateVisitedHistory(mResolver, url, true); 2811 WebIconDatabase.getInstance().retainIconForPageUrl(url); 2812 } 2813 2814 /** 2815 * Displays SSL error(s) dialog to the user. 2816 */ 2817 @Override 2818 public void onReceivedSslError( 2819 final WebView view, final SslErrorHandler handler, final SslError error) { 2820 2821 if (mSettings.showSecurityWarnings()) { 2822 final LayoutInflater factory = 2823 LayoutInflater.from(BrowserActivity.this); 2824 final View warningsView = 2825 factory.inflate(R.layout.ssl_warnings, null); 2826 final LinearLayout placeholder = 2827 (LinearLayout)warningsView.findViewById(R.id.placeholder); 2828 2829 if (error.hasError(SslError.SSL_UNTRUSTED)) { 2830 LinearLayout ll = (LinearLayout)factory 2831 .inflate(R.layout.ssl_warning, null); 2832 ((TextView)ll.findViewById(R.id.warning)) 2833 .setText(R.string.ssl_untrusted); 2834 placeholder.addView(ll); 2835 } 2836 2837 if (error.hasError(SslError.SSL_IDMISMATCH)) { 2838 LinearLayout ll = (LinearLayout)factory 2839 .inflate(R.layout.ssl_warning, null); 2840 ((TextView)ll.findViewById(R.id.warning)) 2841 .setText(R.string.ssl_mismatch); 2842 placeholder.addView(ll); 2843 } 2844 2845 if (error.hasError(SslError.SSL_EXPIRED)) { 2846 LinearLayout ll = (LinearLayout)factory 2847 .inflate(R.layout.ssl_warning, null); 2848 ((TextView)ll.findViewById(R.id.warning)) 2849 .setText(R.string.ssl_expired); 2850 placeholder.addView(ll); 2851 } 2852 2853 if (error.hasError(SslError.SSL_NOTYETVALID)) { 2854 LinearLayout ll = (LinearLayout)factory 2855 .inflate(R.layout.ssl_warning, null); 2856 ((TextView)ll.findViewById(R.id.warning)) 2857 .setText(R.string.ssl_not_yet_valid); 2858 placeholder.addView(ll); 2859 } 2860 2861 new AlertDialog.Builder(BrowserActivity.this) 2862 .setTitle(R.string.security_warning) 2863 .setIcon(android.R.drawable.ic_dialog_alert) 2864 .setView(warningsView) 2865 .setPositiveButton(R.string.ssl_continue, 2866 new DialogInterface.OnClickListener() { 2867 public void onClick(DialogInterface dialog, int whichButton) { 2868 handler.proceed(); 2869 } 2870 }) 2871 .setNeutralButton(R.string.view_certificate, 2872 new DialogInterface.OnClickListener() { 2873 public void onClick(DialogInterface dialog, int whichButton) { 2874 showSSLCertificateOnError(view, handler, error); 2875 } 2876 }) 2877 .setNegativeButton(R.string.cancel, 2878 new DialogInterface.OnClickListener() { 2879 public void onClick(DialogInterface dialog, int whichButton) { 2880 handler.cancel(); 2881 BrowserActivity.this.resetTitleAndRevertLockIcon(); 2882 } 2883 }) 2884 .setOnCancelListener( 2885 new DialogInterface.OnCancelListener() { 2886 public void onCancel(DialogInterface dialog) { 2887 handler.cancel(); 2888 BrowserActivity.this.resetTitleAndRevertLockIcon(); 2889 } 2890 }) 2891 .show(); 2892 } else { 2893 handler.proceed(); 2894 } 2895 } 2896 2897 /** 2898 * Handles an HTTP authentication request. 2899 * 2900 * @param handler The authentication handler 2901 * @param host The host 2902 * @param realm The realm 2903 */ 2904 @Override 2905 public void onReceivedHttpAuthRequest(WebView view, 2906 final HttpAuthHandler handler, final String host, final String realm) { 2907 String username = null; 2908 String password = null; 2909 2910 boolean reuseHttpAuthUsernamePassword = 2911 handler.useHttpAuthUsernamePassword(); 2912 2913 if (reuseHttpAuthUsernamePassword && 2914 (mTabControl.getCurrentWebView() != null)) { 2915 String[] credentials = 2916 mTabControl.getCurrentWebView() 2917 .getHttpAuthUsernamePassword(host, realm); 2918 if (credentials != null && credentials.length == 2) { 2919 username = credentials[0]; 2920 password = credentials[1]; 2921 } 2922 } 2923 2924 if (username != null && password != null) { 2925 handler.proceed(username, password); 2926 } else { 2927 showHttpAuthentication(handler, host, realm, null, null, null, 0); 2928 } 2929 } 2930 2931 @Override 2932 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 2933 if (mMenuIsDown) { 2934 // only check shortcut key when MENU is held 2935 return getWindow().isShortcutKey(event.getKeyCode(), event); 2936 } else { 2937 return false; 2938 } 2939 } 2940 2941 @Override 2942 public void onUnhandledKeyEvent(WebView view, KeyEvent event) { 2943 if (view != mTabControl.getCurrentTopWebView()) { 2944 return; 2945 } 2946 if (event.isDown()) { 2947 BrowserActivity.this.onKeyDown(event.getKeyCode(), event); 2948 } else { 2949 BrowserActivity.this.onKeyUp(event.getKeyCode(), event); 2950 } 2951 } 2952 }; 2953 2954 //-------------------------------------------------------------------------- 2955 // WebChromeClient implementation 2956 //-------------------------------------------------------------------------- 2957 2958 /* package */ WebChromeClient getWebChromeClient() { 2959 return mWebChromeClient; 2960 } 2961 2962 private final WebChromeClient mWebChromeClient = new WebChromeClient() { 2963 // Helper method to create a new tab or sub window. 2964 private void createWindow(final boolean dialog, final Message msg) { 2965 if (dialog) { 2966 mTabControl.createSubWindow(); 2967 final TabControl.Tab t = mTabControl.getCurrentTab(); 2968 attachSubWindow(t); 2969 WebView.WebViewTransport transport = 2970 (WebView.WebViewTransport) msg.obj; 2971 transport.setWebView(t.getSubWebView()); 2972 msg.sendToTarget(); 2973 } else { 2974 final TabControl.Tab parent = mTabControl.getCurrentTab(); 2975 final TabControl.Tab newTab 2976 = openTabAndShow(EMPTY_URL_DATA, false, null); 2977 if (newTab != parent) { 2978 parent.addChildTab(newTab); 2979 } 2980 WebView.WebViewTransport transport = 2981 (WebView.WebViewTransport) msg.obj; 2982 transport.setWebView(mTabControl.getCurrentWebView()); 2983 msg.sendToTarget(); 2984 } 2985 } 2986 2987 @Override 2988 public boolean onCreateWindow(WebView view, final boolean dialog, 2989 final boolean userGesture, final Message resultMsg) { 2990 // Short-circuit if we can't create any more tabs or sub windows. 2991 if (dialog && mTabControl.getCurrentSubWindow() != null) { 2992 new AlertDialog.Builder(BrowserActivity.this) 2993 .setTitle(R.string.too_many_subwindows_dialog_title) 2994 .setIcon(android.R.drawable.ic_dialog_alert) 2995 .setMessage(R.string.too_many_subwindows_dialog_message) 2996 .setPositiveButton(R.string.ok, null) 2997 .show(); 2998 return false; 2999 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) { 3000 new AlertDialog.Builder(BrowserActivity.this) 3001 .setTitle(R.string.too_many_windows_dialog_title) 3002 .setIcon(android.R.drawable.ic_dialog_alert) 3003 .setMessage(R.string.too_many_windows_dialog_message) 3004 .setPositiveButton(R.string.ok, null) 3005 .show(); 3006 return false; 3007 } 3008 3009 // Short-circuit if this was a user gesture. 3010 if (userGesture) { 3011 createWindow(dialog, resultMsg); 3012 return true; 3013 } 3014 3015 // Allow the popup and create the appropriate window. 3016 final AlertDialog.OnClickListener allowListener = 3017 new AlertDialog.OnClickListener() { 3018 public void onClick(DialogInterface d, 3019 int which) { 3020 createWindow(dialog, resultMsg); 3021 } 3022 }; 3023 3024 // Block the popup by returning a null WebView. 3025 final AlertDialog.OnClickListener blockListener = 3026 new AlertDialog.OnClickListener() { 3027 public void onClick(DialogInterface d, int which) { 3028 resultMsg.sendToTarget(); 3029 } 3030 }; 3031 3032 // Build a confirmation dialog to display to the user. 3033 final AlertDialog d = 3034 new AlertDialog.Builder(BrowserActivity.this) 3035 .setTitle(R.string.attention) 3036 .setIcon(android.R.drawable.ic_dialog_alert) 3037 .setMessage(R.string.popup_window_attempt) 3038 .setPositiveButton(R.string.allow, allowListener) 3039 .setNegativeButton(R.string.block, blockListener) 3040 .setCancelable(false) 3041 .create(); 3042 3043 // Show the confirmation dialog. 3044 d.show(); 3045 return true; 3046 } 3047 3048 @Override 3049 public void onCloseWindow(WebView window) { 3050 final TabControl.Tab current = mTabControl.getCurrentTab(); 3051 final TabControl.Tab parent = current.getParentTab(); 3052 if (parent != null) { 3053 // JavaScript can only close popup window. 3054 switchToTab(mTabControl.getTabIndex(parent)); 3055 // Now we need to close the window 3056 closeTab(current); 3057 } 3058 } 3059 3060 @Override 3061 public void onProgressChanged(WebView view, int newProgress) { 3062 mTitleBar.setProgress(newProgress); 3063 if (mFakeTitleBar != null) { 3064 mFakeTitleBar.setProgress(newProgress); 3065 } 3066 3067 if (newProgress == 100) { 3068 // onProgressChanged() is called for sub-frame too while 3069 // onPageFinished() is only called for the main frame. sync 3070 // cookie and cache promptly here. 3071 CookieSyncManager.getInstance().sync(); 3072 if (mInLoad) { 3073 mInLoad = false; 3074 updateInLoadMenuItems(); 3075 // If the options menu is open, leave the title bar 3076 if (!mOptionsMenuOpen || !mIconView) { 3077 hideFakeTitleBar(); 3078 } 3079 } 3080 } else if (!mInLoad) { 3081 // onPageFinished may have already been called but a subframe 3082 // is still loading and updating the progress. Reset mInLoad 3083 // and update the menu items. 3084 mInLoad = true; 3085 updateInLoadMenuItems(); 3086 WebView currentWebView = mTabControl.getCurrentWebView(); 3087 if ((currentWebView == null || currentWebView.getScrollY() != 0) 3088 && (!mOptionsMenuOpen || mIconView)) { 3089 // This page has begun to load, so show the title bar 3090 showFakeTitleBar(); 3091 } 3092 } 3093 } 3094 3095 @Override 3096 public void onReceivedTitle(WebView view, String title) { 3097 String url = view.getUrl(); 3098 3099 // here, if url is null, we want to reset the title 3100 setUrlTitle(url, title); 3101 3102 if (url == null || 3103 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { 3104 return; 3105 } 3106 // See if we can find the current url in our history database and 3107 // add the new title to it. 3108 if (url.startsWith("http://www.")) { 3109 url = url.substring(11); 3110 } else if (url.startsWith("http://")) { 3111 url = url.substring(4); 3112 } 3113 try { 3114 url = "%" + url; 3115 String [] selArgs = new String[] { url }; 3116 3117 String where = Browser.BookmarkColumns.URL + " LIKE ? AND " 3118 + Browser.BookmarkColumns.BOOKMARK + " = 0"; 3119 Cursor c = mResolver.query(Browser.BOOKMARKS_URI, 3120 Browser.HISTORY_PROJECTION, where, selArgs, null); 3121 if (c.moveToFirst()) { 3122 // Current implementation of database only has one entry per 3123 // url. 3124 ContentValues map = new ContentValues(); 3125 map.put(Browser.BookmarkColumns.TITLE, title); 3126 mResolver.update(Browser.BOOKMARKS_URI, map, 3127 "_id = " + c.getInt(0), null); 3128 } 3129 c.close(); 3130 } catch (IllegalStateException e) { 3131 Log.e(LOGTAG, "BrowserActivity onReceived title", e); 3132 } catch (SQLiteException ex) { 3133 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex); 3134 } 3135 } 3136 3137 @Override 3138 public void onReceivedIcon(WebView view, Bitmap icon) { 3139 updateIcon(view, icon); 3140 } 3141 3142 @Override 3143 public void onReceivedTouchIconUrl(WebView view, String url) { 3144 final ContentResolver cr = getContentResolver(); 3145 final Cursor c = 3146 BrowserBookmarksAdapter.queryBookmarksForUrl(cr, 3147 view.getOriginalUrl(), view.getUrl(), true); 3148 if (c != null) { 3149 if (c.getCount() > 0) { 3150 new DownloadTouchIcon(cr, c, view).execute(url); 3151 } else { 3152 c.close(); 3153 } 3154 } 3155 } 3156 3157 @Override 3158 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 3159 if (mCustomView != null) 3160 return; 3161 3162 // Add the custom view to its container. 3163 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); 3164 mCustomView = view; 3165 mCustomViewCallback = callback; 3166 // Save the menu state and set it to empty while the custom 3167 // view is showing. 3168 mOldMenuState = mMenuState; 3169 mMenuState = EMPTY_MENU; 3170 // Hide the content view. 3171 mContentView.setVisibility(View.GONE); 3172 // Finally show the custom view container. 3173 mCustomViewContainer.setVisibility(View.VISIBLE); 3174 mCustomViewContainer.bringToFront(); 3175 } 3176 3177 @Override 3178 public void onHideCustomView() { 3179 if (mCustomView == null) 3180 return; 3181 3182 // Hide the custom view. 3183 mCustomView.setVisibility(View.GONE); 3184 // Remove the custom view from its container. 3185 mCustomViewContainer.removeView(mCustomView); 3186 mCustomView = null; 3187 // Reset the old menu state. 3188 mMenuState = mOldMenuState; 3189 mOldMenuState = EMPTY_MENU; 3190 mCustomViewContainer.setVisibility(View.GONE); 3191 mCustomViewCallback.onCustomViewHidden(); 3192 // Show the content view. 3193 mContentView.setVisibility(View.VISIBLE); 3194 } 3195 3196 /** 3197 * The origin has exceeded its database quota. 3198 * @param url the URL that exceeded the quota 3199 * @param databaseIdentifier the identifier of the database on 3200 * which the transaction that caused the quota overflow was run 3201 * @param currentQuota the current quota for the origin. 3202 * @param estimatedSize the estimated size of the database. 3203 * @param totalUsedQuota is the sum of all origins' quota. 3204 * @param quotaUpdater The callback to run when a decision to allow or 3205 * deny quota has been made. Don't forget to call this! 3206 */ 3207 @Override 3208 public void onExceededDatabaseQuota(String url, 3209 String databaseIdentifier, long currentQuota, long estimatedSize, 3210 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 3211 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota( 3212 url, databaseIdentifier, currentQuota, estimatedSize, 3213 totalUsedQuota, quotaUpdater); 3214 } 3215 3216 /** 3217 * The Application Cache has exceeded its max size. 3218 * @param spaceNeeded is the amount of disk space that would be needed 3219 * in order for the last appcache operation to succeed. 3220 * @param totalUsedQuota is the sum of all origins' quota. 3221 * @param quotaUpdater A callback to inform the WebCore thread that a new 3222 * app cache size is available. This callback must always be executed at 3223 * some point to ensure that the sleeping WebCore thread is woken up. 3224 */ 3225 @Override 3226 public void onReachedMaxAppCacheSize(long spaceNeeded, 3227 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 3228 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize( 3229 spaceNeeded, totalUsedQuota, quotaUpdater); 3230 } 3231 3232 /** 3233 * Instructs the browser to show a prompt to ask the user to set the 3234 * Geolocation permission state for the specified origin. 3235 * @param origin The origin for which Geolocation permissions are 3236 * requested. 3237 * @param callback The callback to call once the user has set the 3238 * Geolocation permission state. 3239 */ 3240 @Override 3241 public void onGeolocationPermissionsShowPrompt(String origin, 3242 GeolocationPermissions.Callback callback) { 3243 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show( 3244 origin, callback); 3245 } 3246 3247 /** 3248 * Instructs the browser to hide the Geolocation permissions prompt. 3249 */ 3250 @Override 3251 public void onGeolocationPermissionsHidePrompt() { 3252 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide(); 3253 } 3254 3255 /* Adds a JavaScript error message to the system log. 3256 * @param message The error message to report. 3257 * @param lineNumber The line number of the error. 3258 * @param sourceID The name of the source file that caused the error. 3259 */ 3260 @Override 3261 public void addMessageToConsole(String message, int lineNumber, String sourceID) { 3262 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true); 3263 errorConsole.addErrorMessage(message, sourceID, lineNumber); 3264 if (mShouldShowErrorConsole && 3265 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) { 3266 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 3267 } 3268 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber); 3269 } 3270 3271 /** 3272 * Ask the browser for an icon to represent a <video> element. 3273 * This icon will be used if the Web page did not specify a poster attribute. 3274 * 3275 * @return Bitmap The icon or null if no such icon is available. 3276 * @hide pending API Council approval 3277 */ 3278 @Override 3279 public Bitmap getDefaultVideoPoster() { 3280 if (mDefaultVideoPoster == null) { 3281 mDefaultVideoPoster = BitmapFactory.decodeResource( 3282 getResources(), R.drawable.default_video_poster); 3283 } 3284 return mDefaultVideoPoster; 3285 } 3286 3287 /** 3288 * Ask the host application for a custom progress view to show while 3289 * a <video> is loading. 3290 * 3291 * @return View The progress view. 3292 * @hide pending API Council approval 3293 */ 3294 @Override 3295 public View getVideoLoadingProgressView() { 3296 if (mVideoProgressView == null) { 3297 LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this); 3298 mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null); 3299 } 3300 return mVideoProgressView; 3301 } 3302 }; 3303 3304 /** 3305 * Notify the host application a download should be done, or that 3306 * the data should be streamed if a streaming viewer is available. 3307 * @param url The full url to the content that should be downloaded 3308 * @param contentDisposition Content-disposition http header, if 3309 * present. 3310 * @param mimetype The mimetype of the content reported by the server 3311 * @param contentLength The file size reported by the server 3312 */ 3313 public void onDownloadStart(String url, String userAgent, 3314 String contentDisposition, String mimetype, long contentLength) { 3315 // if we're dealing wih A/V content that's not explicitly marked 3316 // for download, check if it's streamable. 3317 if (contentDisposition == null 3318 || !contentDisposition.regionMatches( 3319 true, 0, "attachment", 0, 10)) { 3320 // query the package manager to see if there's a registered handler 3321 // that matches. 3322 Intent intent = new Intent(Intent.ACTION_VIEW); 3323 intent.setDataAndType(Uri.parse(url), mimetype); 3324 ResolveInfo info = getPackageManager().resolveActivity(intent, 3325 PackageManager.MATCH_DEFAULT_ONLY); 3326 if (info != null) { 3327 ComponentName myName = getComponentName(); 3328 // If we resolved to ourselves, we don't want to attempt to 3329 // load the url only to try and download it again. 3330 if (!myName.getPackageName().equals( 3331 info.activityInfo.packageName) 3332 || !myName.getClassName().equals( 3333 info.activityInfo.name)) { 3334 // someone (other than us) knows how to handle this mime 3335 // type with this scheme, don't download. 3336 try { 3337 startActivity(intent); 3338 return; 3339 } catch (ActivityNotFoundException ex) { 3340 if (LOGD_ENABLED) { 3341 Log.d(LOGTAG, "activity not found for " + mimetype 3342 + " over " + Uri.parse(url).getScheme(), 3343 ex); 3344 } 3345 // Best behavior is to fall back to a download in this 3346 // case 3347 } 3348 } 3349 } 3350 } 3351 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength); 3352 } 3353 3354 /** 3355 * Notify the host application a download should be done, even if there 3356 * is a streaming viewer available for thise type. 3357 * @param url The full url to the content that should be downloaded 3358 * @param contentDisposition Content-disposition http header, if 3359 * present. 3360 * @param mimetype The mimetype of the content reported by the server 3361 * @param contentLength The file size reported by the server 3362 */ 3363 /*package */ void onDownloadStartNoStream(String url, String userAgent, 3364 String contentDisposition, String mimetype, long contentLength) { 3365 3366 String filename = URLUtil.guessFileName(url, 3367 contentDisposition, mimetype); 3368 3369 // Check to see if we have an SDCard 3370 String status = Environment.getExternalStorageState(); 3371 if (!status.equals(Environment.MEDIA_MOUNTED)) { 3372 int title; 3373 String msg; 3374 3375 // Check to see if the SDCard is busy, same as the music app 3376 if (status.equals(Environment.MEDIA_SHARED)) { 3377 msg = getString(R.string.download_sdcard_busy_dlg_msg); 3378 title = R.string.download_sdcard_busy_dlg_title; 3379 } else { 3380 msg = getString(R.string.download_no_sdcard_dlg_msg, filename); 3381 title = R.string.download_no_sdcard_dlg_title; 3382 } 3383 3384 new AlertDialog.Builder(this) 3385 .setTitle(title) 3386 .setIcon(android.R.drawable.ic_dialog_alert) 3387 .setMessage(msg) 3388 .setPositiveButton(R.string.ok, null) 3389 .show(); 3390 return; 3391 } 3392 3393 // java.net.URI is a lot stricter than KURL so we have to undo 3394 // KURL's percent-encoding and redo the encoding using java.net.URI. 3395 URI uri = null; 3396 try { 3397 // Undo the percent-encoding that KURL may have done. 3398 String newUrl = new String(URLUtil.decode(url.getBytes())); 3399 // Parse the url into pieces 3400 WebAddress w = new WebAddress(newUrl); 3401 String frag = null; 3402 String query = null; 3403 String path = w.mPath; 3404 // Break the path into path, query, and fragment 3405 if (path.length() > 0) { 3406 // Strip the fragment 3407 int idx = path.lastIndexOf('#'); 3408 if (idx != -1) { 3409 frag = path.substring(idx + 1); 3410 path = path.substring(0, idx); 3411 } 3412 idx = path.lastIndexOf('?'); 3413 if (idx != -1) { 3414 query = path.substring(idx + 1); 3415 path = path.substring(0, idx); 3416 } 3417 } 3418 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path, 3419 query, frag); 3420 } catch (Exception e) { 3421 Log.e(LOGTAG, "Could not parse url for download: " + url, e); 3422 return; 3423 } 3424 3425 // XXX: Have to use the old url since the cookies were stored using the 3426 // old percent-encoded url. 3427 String cookies = CookieManager.getInstance().getCookie(url); 3428 3429 ContentValues values = new ContentValues(); 3430 values.put(Downloads.COLUMN_URI, uri.toString()); 3431 values.put(Downloads.COLUMN_COOKIE_DATA, cookies); 3432 values.put(Downloads.COLUMN_USER_AGENT, userAgent); 3433 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, 3434 getPackageName()); 3435 values.put(Downloads.COLUMN_NOTIFICATION_CLASS, 3436 BrowserDownloadPage.class.getCanonicalName()); 3437 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 3438 values.put(Downloads.COLUMN_MIME_TYPE, mimetype); 3439 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename); 3440 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost()); 3441 if (contentLength > 0) { 3442 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength); 3443 } 3444 if (mimetype == null) { 3445 // We must have long pressed on a link or image to download it. We 3446 // are not sure of the mimetype in this case, so do a head request 3447 new FetchUrlMimeType(this).execute(values); 3448 } else { 3449 final Uri contentUri = 3450 getContentResolver().insert(Downloads.CONTENT_URI, values); 3451 viewDownloads(contentUri); 3452 } 3453 3454 } 3455 3456 /** 3457 * Resets the lock icon. This method is called when we start a new load and 3458 * know the url to be loaded. 3459 */ 3460 private void resetLockIcon(String url) { 3461 // Save the lock-icon state (we revert to it if the load gets cancelled) 3462 saveLockIcon(); 3463 3464 mLockIconType = LOCK_ICON_UNSECURE; 3465 if (URLUtil.isHttpsUrl(url)) { 3466 mLockIconType = LOCK_ICON_SECURE; 3467 if (LOGV_ENABLED) { 3468 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" + 3469 " reset lock icon to " + mLockIconType); 3470 } 3471 } 3472 3473 updateLockIconImage(LOCK_ICON_UNSECURE); 3474 } 3475 3476 /* package */ void setLockIconType(int type) { 3477 mLockIconType = type; 3478 } 3479 3480 /* package */ int getLockIconType() { 3481 return mLockIconType; 3482 } 3483 3484 /* package */ void setPrevLockType(int type) { 3485 mPrevLockType = type; 3486 } 3487 3488 /* package */ int getPrevLockType() { 3489 return mPrevLockType; 3490 } 3491 3492 /** 3493 * Update the lock icon to correspond to our latest state. 3494 */ 3495 /* package */ void updateLockIconToLatest() { 3496 updateLockIconImage(mLockIconType); 3497 } 3498 3499 /** 3500 * Updates the lock-icon image in the title-bar. 3501 */ 3502 private void updateLockIconImage(int lockIconType) { 3503 Drawable d = null; 3504 if (lockIconType == LOCK_ICON_SECURE) { 3505 d = mSecLockIcon; 3506 } else if (lockIconType == LOCK_ICON_MIXED) { 3507 d = mMixLockIcon; 3508 } 3509 mTitleBar.setLock(d); 3510 if (mFakeTitleBar != null) { 3511 mFakeTitleBar.setLock(d); 3512 } 3513 } 3514 3515 /** 3516 * Displays a page-info dialog. 3517 * @param tab The tab to show info about 3518 * @param fromShowSSLCertificateOnError The flag that indicates whether 3519 * this dialog was opened from the SSL-certificate-on-error dialog or 3520 * not. This is important, since we need to know whether to return to 3521 * the parent dialog or simply dismiss. 3522 */ 3523 private void showPageInfo(final TabControl.Tab tab, 3524 final boolean fromShowSSLCertificateOnError) { 3525 final LayoutInflater factory = LayoutInflater 3526 .from(this); 3527 3528 final View pageInfoView = factory.inflate(R.layout.page_info, null); 3529 3530 final WebView view = tab.getWebView(); 3531 3532 String url = null; 3533 String title = null; 3534 3535 if (view == null) { 3536 url = tab.getUrl(); 3537 title = tab.getTitle(); 3538 } else if (view == mTabControl.getCurrentWebView()) { 3539 // Use the cached title and url if this is the current WebView 3540 url = mUrl; 3541 title = mTitle; 3542 } else { 3543 url = view.getUrl(); 3544 title = view.getTitle(); 3545 } 3546 3547 if (url == null) { 3548 url = ""; 3549 } 3550 if (title == null) { 3551 title = ""; 3552 } 3553 3554 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url); 3555 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title); 3556 3557 mPageInfoView = tab; 3558 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError); 3559 3560 AlertDialog.Builder alertDialogBuilder = 3561 new AlertDialog.Builder(this) 3562 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info) 3563 .setView(pageInfoView) 3564 .setPositiveButton( 3565 R.string.ok, 3566 new DialogInterface.OnClickListener() { 3567 public void onClick(DialogInterface dialog, 3568 int whichButton) { 3569 mPageInfoDialog = null; 3570 mPageInfoView = null; 3571 mPageInfoFromShowSSLCertificateOnError = null; 3572 3573 // if we came here from the SSL error dialog 3574 if (fromShowSSLCertificateOnError) { 3575 // go back to the SSL error dialog 3576 showSSLCertificateOnError( 3577 mSSLCertificateOnErrorView, 3578 mSSLCertificateOnErrorHandler, 3579 mSSLCertificateOnErrorError); 3580 } 3581 } 3582 }) 3583 .setOnCancelListener( 3584 new DialogInterface.OnCancelListener() { 3585 public void onCancel(DialogInterface dialog) { 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 } 3598 } 3599 }); 3600 3601 // if we have a main top-level page SSL certificate set or a certificate 3602 // error 3603 if (fromShowSSLCertificateOnError || 3604 (view != null && view.getCertificate() != null)) { 3605 // add a 'View Certificate' button 3606 alertDialogBuilder.setNeutralButton( 3607 R.string.view_certificate, 3608 new DialogInterface.OnClickListener() { 3609 public void onClick(DialogInterface dialog, 3610 int whichButton) { 3611 mPageInfoDialog = null; 3612 mPageInfoView = null; 3613 mPageInfoFromShowSSLCertificateOnError = null; 3614 3615 // if we came here from the SSL error dialog 3616 if (fromShowSSLCertificateOnError) { 3617 // go back to the SSL error dialog 3618 showSSLCertificateOnError( 3619 mSSLCertificateOnErrorView, 3620 mSSLCertificateOnErrorHandler, 3621 mSSLCertificateOnErrorError); 3622 } else { 3623 // otherwise, display the top-most certificate from 3624 // the chain 3625 if (view.getCertificate() != null) { 3626 showSSLCertificate(tab); 3627 } 3628 } 3629 } 3630 }); 3631 } 3632 3633 mPageInfoDialog = alertDialogBuilder.show(); 3634 } 3635 3636 /** 3637 * Displays the main top-level page SSL certificate dialog 3638 * (accessible from the Page-Info dialog). 3639 * @param tab The tab to show certificate for. 3640 */ 3641 private void showSSLCertificate(final TabControl.Tab tab) { 3642 final View certificateView = 3643 inflateCertificateView(tab.getWebView().getCertificate()); 3644 if (certificateView == null) { 3645 return; 3646 } 3647 3648 LayoutInflater factory = LayoutInflater.from(this); 3649 3650 final LinearLayout placeholder = 3651 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3652 3653 LinearLayout ll = (LinearLayout) factory.inflate( 3654 R.layout.ssl_success, placeholder); 3655 ((TextView)ll.findViewById(R.id.success)) 3656 .setText(R.string.ssl_certificate_is_valid); 3657 3658 mSSLCertificateView = tab; 3659 mSSLCertificateDialog = 3660 new AlertDialog.Builder(this) 3661 .setTitle(R.string.ssl_certificate).setIcon( 3662 R.drawable.ic_dialog_browser_certificate_secure) 3663 .setView(certificateView) 3664 .setPositiveButton(R.string.ok, 3665 new DialogInterface.OnClickListener() { 3666 public void onClick(DialogInterface dialog, 3667 int whichButton) { 3668 mSSLCertificateDialog = null; 3669 mSSLCertificateView = null; 3670 3671 showPageInfo(tab, false); 3672 } 3673 }) 3674 .setOnCancelListener( 3675 new DialogInterface.OnCancelListener() { 3676 public void onCancel(DialogInterface dialog) { 3677 mSSLCertificateDialog = null; 3678 mSSLCertificateView = null; 3679 3680 showPageInfo(tab, false); 3681 } 3682 }) 3683 .show(); 3684 } 3685 3686 /** 3687 * Displays the SSL error certificate dialog. 3688 * @param view The target web-view. 3689 * @param handler The SSL error handler responsible for cancelling the 3690 * connection that resulted in an SSL error or proceeding per user request. 3691 * @param error The SSL error object. 3692 */ 3693 private void showSSLCertificateOnError( 3694 final WebView view, final SslErrorHandler handler, final SslError error) { 3695 3696 final View certificateView = 3697 inflateCertificateView(error.getCertificate()); 3698 if (certificateView == null) { 3699 return; 3700 } 3701 3702 LayoutInflater factory = LayoutInflater.from(this); 3703 3704 final LinearLayout placeholder = 3705 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3706 3707 if (error.hasError(SslError.SSL_UNTRUSTED)) { 3708 LinearLayout ll = (LinearLayout)factory 3709 .inflate(R.layout.ssl_warning, placeholder); 3710 ((TextView)ll.findViewById(R.id.warning)) 3711 .setText(R.string.ssl_untrusted); 3712 } 3713 3714 if (error.hasError(SslError.SSL_IDMISMATCH)) { 3715 LinearLayout ll = (LinearLayout)factory 3716 .inflate(R.layout.ssl_warning, placeholder); 3717 ((TextView)ll.findViewById(R.id.warning)) 3718 .setText(R.string.ssl_mismatch); 3719 } 3720 3721 if (error.hasError(SslError.SSL_EXPIRED)) { 3722 LinearLayout ll = (LinearLayout)factory 3723 .inflate(R.layout.ssl_warning, placeholder); 3724 ((TextView)ll.findViewById(R.id.warning)) 3725 .setText(R.string.ssl_expired); 3726 } 3727 3728 if (error.hasError(SslError.SSL_NOTYETVALID)) { 3729 LinearLayout ll = (LinearLayout)factory 3730 .inflate(R.layout.ssl_warning, placeholder); 3731 ((TextView)ll.findViewById(R.id.warning)) 3732 .setText(R.string.ssl_not_yet_valid); 3733 } 3734 3735 mSSLCertificateOnErrorHandler = handler; 3736 mSSLCertificateOnErrorView = view; 3737 mSSLCertificateOnErrorError = error; 3738 mSSLCertificateOnErrorDialog = 3739 new AlertDialog.Builder(this) 3740 .setTitle(R.string.ssl_certificate).setIcon( 3741 R.drawable.ic_dialog_browser_certificate_partially_secure) 3742 .setView(certificateView) 3743 .setPositiveButton(R.string.ok, 3744 new DialogInterface.OnClickListener() { 3745 public void onClick(DialogInterface dialog, 3746 int whichButton) { 3747 mSSLCertificateOnErrorDialog = null; 3748 mSSLCertificateOnErrorView = null; 3749 mSSLCertificateOnErrorHandler = null; 3750 mSSLCertificateOnErrorError = null; 3751 3752 mWebViewClient.onReceivedSslError( 3753 view, handler, error); 3754 } 3755 }) 3756 .setNeutralButton(R.string.page_info_view, 3757 new DialogInterface.OnClickListener() { 3758 public void onClick(DialogInterface dialog, 3759 int whichButton) { 3760 mSSLCertificateOnErrorDialog = null; 3761 3762 // do not clear the dialog state: we will 3763 // need to show the dialog again once the 3764 // user is done exploring the page-info details 3765 3766 showPageInfo(mTabControl.getTabFromView(view), 3767 true); 3768 } 3769 }) 3770 .setOnCancelListener( 3771 new DialogInterface.OnCancelListener() { 3772 public void onCancel(DialogInterface dialog) { 3773 mSSLCertificateOnErrorDialog = null; 3774 mSSLCertificateOnErrorView = null; 3775 mSSLCertificateOnErrorHandler = null; 3776 mSSLCertificateOnErrorError = null; 3777 3778 mWebViewClient.onReceivedSslError( 3779 view, handler, error); 3780 } 3781 }) 3782 .show(); 3783 } 3784 3785 /** 3786 * Inflates the SSL certificate view (helper method). 3787 * @param certificate The SSL certificate. 3788 * @return The resultant certificate view with issued-to, issued-by, 3789 * issued-on, expires-on, and possibly other fields set. 3790 * If the input certificate is null, returns null. 3791 */ 3792 private View inflateCertificateView(SslCertificate certificate) { 3793 if (certificate == null) { 3794 return null; 3795 } 3796 3797 LayoutInflater factory = LayoutInflater.from(this); 3798 3799 View certificateView = factory.inflate( 3800 R.layout.ssl_certificate, null); 3801 3802 // issued to: 3803 SslCertificate.DName issuedTo = certificate.getIssuedTo(); 3804 if (issuedTo != null) { 3805 ((TextView) certificateView.findViewById(R.id.to_common)) 3806 .setText(issuedTo.getCName()); 3807 ((TextView) certificateView.findViewById(R.id.to_org)) 3808 .setText(issuedTo.getOName()); 3809 ((TextView) certificateView.findViewById(R.id.to_org_unit)) 3810 .setText(issuedTo.getUName()); 3811 } 3812 3813 // issued by: 3814 SslCertificate.DName issuedBy = certificate.getIssuedBy(); 3815 if (issuedBy != null) { 3816 ((TextView) certificateView.findViewById(R.id.by_common)) 3817 .setText(issuedBy.getCName()); 3818 ((TextView) certificateView.findViewById(R.id.by_org)) 3819 .setText(issuedBy.getOName()); 3820 ((TextView) certificateView.findViewById(R.id.by_org_unit)) 3821 .setText(issuedBy.getUName()); 3822 } 3823 3824 // issued on: 3825 String issuedOn = reformatCertificateDate( 3826 certificate.getValidNotBefore()); 3827 ((TextView) certificateView.findViewById(R.id.issued_on)) 3828 .setText(issuedOn); 3829 3830 // expires on: 3831 String expiresOn = reformatCertificateDate( 3832 certificate.getValidNotAfter()); 3833 ((TextView) certificateView.findViewById(R.id.expires_on)) 3834 .setText(expiresOn); 3835 3836 return certificateView; 3837 } 3838 3839 /** 3840 * Re-formats the certificate date (Date.toString()) string to 3841 * a properly localized date string. 3842 * @return Properly localized version of the certificate date string and 3843 * the original certificate date string if fails to localize. 3844 * If the original string is null, returns an empty string "". 3845 */ 3846 private String reformatCertificateDate(String certificateDate) { 3847 String reformattedDate = null; 3848 3849 if (certificateDate != null) { 3850 Date date = null; 3851 try { 3852 date = java.text.DateFormat.getInstance().parse(certificateDate); 3853 } catch (ParseException e) { 3854 date = null; 3855 } 3856 3857 if (date != null) { 3858 reformattedDate = 3859 DateFormat.getDateFormat(this).format(date); 3860 } 3861 } 3862 3863 return reformattedDate != null ? reformattedDate : 3864 (certificateDate != null ? certificateDate : ""); 3865 } 3866 3867 /** 3868 * Displays an http-authentication dialog. 3869 */ 3870 private void showHttpAuthentication(final HttpAuthHandler handler, 3871 final String host, final String realm, final String title, 3872 final String name, final String password, int focusId) { 3873 LayoutInflater factory = LayoutInflater.from(this); 3874 final View v = factory 3875 .inflate(R.layout.http_authentication, null); 3876 if (name != null) { 3877 ((EditText) v.findViewById(R.id.username_edit)).setText(name); 3878 } 3879 if (password != null) { 3880 ((EditText) v.findViewById(R.id.password_edit)).setText(password); 3881 } 3882 3883 String titleText = title; 3884 if (titleText == null) { 3885 titleText = getText(R.string.sign_in_to).toString().replace( 3886 "%s1", host).replace("%s2", realm); 3887 } 3888 3889 mHttpAuthHandler = handler; 3890 AlertDialog dialog = new AlertDialog.Builder(this) 3891 .setTitle(titleText) 3892 .setIcon(android.R.drawable.ic_dialog_alert) 3893 .setView(v) 3894 .setPositiveButton(R.string.action, 3895 new DialogInterface.OnClickListener() { 3896 public void onClick(DialogInterface dialog, 3897 int whichButton) { 3898 String nm = ((EditText) v 3899 .findViewById(R.id.username_edit)) 3900 .getText().toString(); 3901 String pw = ((EditText) v 3902 .findViewById(R.id.password_edit)) 3903 .getText().toString(); 3904 BrowserActivity.this.setHttpAuthUsernamePassword 3905 (host, realm, nm, pw); 3906 handler.proceed(nm, pw); 3907 mHttpAuthenticationDialog = null; 3908 mHttpAuthHandler = null; 3909 }}) 3910 .setNegativeButton(R.string.cancel, 3911 new DialogInterface.OnClickListener() { 3912 public void onClick(DialogInterface dialog, 3913 int whichButton) { 3914 handler.cancel(); 3915 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3916 mHttpAuthenticationDialog = null; 3917 mHttpAuthHandler = null; 3918 }}) 3919 .setOnCancelListener(new DialogInterface.OnCancelListener() { 3920 public void onCancel(DialogInterface dialog) { 3921 handler.cancel(); 3922 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3923 mHttpAuthenticationDialog = null; 3924 mHttpAuthHandler = null; 3925 }}) 3926 .create(); 3927 // Make the IME appear when the dialog is displayed if applicable. 3928 dialog.getWindow().setSoftInputMode( 3929 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 3930 dialog.show(); 3931 if (focusId != 0) { 3932 dialog.findViewById(focusId).requestFocus(); 3933 } else { 3934 v.findViewById(R.id.username_edit).requestFocus(); 3935 } 3936 mHttpAuthenticationDialog = dialog; 3937 } 3938 3939 public int getProgress() { 3940 WebView w = mTabControl.getCurrentWebView(); 3941 if (w != null) { 3942 return w.getProgress(); 3943 } else { 3944 return 100; 3945 } 3946 } 3947 3948 /** 3949 * Set HTTP authentication password. 3950 * 3951 * @param host The host for the password 3952 * @param realm The realm for the password 3953 * @param username The username for the password. If it is null, it means 3954 * password can't be saved. 3955 * @param password The password 3956 */ 3957 public void setHttpAuthUsernamePassword(String host, String realm, 3958 String username, 3959 String password) { 3960 WebView w = mTabControl.getCurrentWebView(); 3961 if (w != null) { 3962 w.setHttpAuthUsernamePassword(host, realm, username, password); 3963 } 3964 } 3965 3966 /** 3967 * connectivity manager says net has come or gone... inform the user 3968 * @param up true if net has come up, false if net has gone down 3969 */ 3970 public void onNetworkToggle(boolean up) { 3971 if (up == mIsNetworkUp) { 3972 return; 3973 } else if (up) { 3974 mIsNetworkUp = true; 3975 if (mAlertDialog != null) { 3976 mAlertDialog.cancel(); 3977 mAlertDialog = null; 3978 } 3979 } else { 3980 mIsNetworkUp = false; 3981 if (mInLoad) { 3982 createAndShowNetworkDialog(); 3983 } 3984 } 3985 WebView w = mTabControl.getCurrentWebView(); 3986 if (w != null) { 3987 w.setNetworkAvailable(up); 3988 } 3989 } 3990 3991 // This method shows the network dialog alerting the user that the net is 3992 // down. It will only show the dialog if mAlertDialog is null. 3993 private void createAndShowNetworkDialog() { 3994 if (mAlertDialog == null) { 3995 mAlertDialog = new AlertDialog.Builder(this) 3996 .setTitle(R.string.loadSuspendedTitle) 3997 .setMessage(R.string.loadSuspended) 3998 .setPositiveButton(R.string.ok, null) 3999 .show(); 4000 } 4001 } 4002 4003 @Override 4004 protected void onActivityResult(int requestCode, int resultCode, 4005 Intent intent) { 4006 switch (requestCode) { 4007 case COMBO_PAGE: 4008 if (resultCode == RESULT_OK && intent != null) { 4009 String data = intent.getAction(); 4010 Bundle extras = intent.getExtras(); 4011 if (extras != null && extras.getBoolean("new_window", false)) { 4012 openTab(data); 4013 } else { 4014 final TabControl.Tab currentTab = 4015 mTabControl.getCurrentTab(); 4016 dismissSubWindow(currentTab); 4017 if (data != null && data.length() != 0) { 4018 getTopWindow().loadUrl(data); 4019 } 4020 } 4021 } 4022 break; 4023 default: 4024 break; 4025 } 4026 getTopWindow().requestFocus(); 4027 } 4028 4029 /* 4030 * This method is called as a result of the user selecting the options 4031 * menu to see the download window, or when a download changes state. It 4032 * shows the download window ontop of the current window. 4033 */ 4034 /* package */ void viewDownloads(Uri downloadRecord) { 4035 Intent intent = new Intent(this, 4036 BrowserDownloadPage.class); 4037 intent.setData(downloadRecord); 4038 startActivityForResult(intent, this.DOWNLOAD_PAGE); 4039 4040 } 4041 4042 /** 4043 * Open the Go page. 4044 * @param startWithHistory If true, open starting on the history tab. 4045 * Otherwise, start with the bookmarks tab. 4046 */ 4047 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) { 4048 WebView current = mTabControl.getCurrentWebView(); 4049 if (current == null) { 4050 return; 4051 } 4052 Intent intent = new Intent(this, 4053 CombinedBookmarkHistoryActivity.class); 4054 String title = current.getTitle(); 4055 String url = current.getUrl(); 4056 // Just in case the user opens bookmarks before a page finishes loading 4057 // so the current history item, and therefore the page, is null. 4058 if (null == url) { 4059 url = mLastEnteredUrl; 4060 // This can happen. 4061 if (null == url) { 4062 url = mSettings.getHomePage(); 4063 } 4064 } 4065 // In case the web page has not yet received its associated title. 4066 if (title == null) { 4067 title = url; 4068 } 4069 intent.putExtra("title", title); 4070 intent.putExtra("url", url); 4071 // Disable opening in a new window if we have maxed out the windows 4072 intent.putExtra("disable_new_window", mTabControl.getTabCount() 4073 >= TabControl.MAX_TABS); 4074 intent.putExtra("touch_icon_url", current.getTouchIconUrl()); 4075 if (startWithHistory) { 4076 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB, 4077 CombinedBookmarkHistoryActivity.HISTORY_TAB); 4078 } 4079 startActivityForResult(intent, COMBO_PAGE); 4080 } 4081 4082 // Called when loading from context menu or LOAD_URL message 4083 private void loadURL(WebView view, String url) { 4084 // In case the user enters nothing. 4085 if (url != null && url.length() != 0 && view != null) { 4086 url = smartUrlFilter(url); 4087 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) { 4088 view.loadUrl(url); 4089 } 4090 } 4091 } 4092 4093 private String smartUrlFilter(Uri inUri) { 4094 if (inUri != null) { 4095 return smartUrlFilter(inUri.toString()); 4096 } 4097 return null; 4098 } 4099 4100 4101 // get window count 4102 4103 int getWindowCount(){ 4104 if(mTabControl != null){ 4105 return mTabControl.getTabCount(); 4106 } 4107 return 0; 4108 } 4109 4110 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( 4111 "(?i)" + // switch on case insensitive matching 4112 "(" + // begin group for schema 4113 "(?:http|https|file):\\/\\/" + 4114 "|(?:inline|data|about|content|javascript):" + 4115 ")" + 4116 "(.*)" ); 4117 4118 /** 4119 * Attempts to determine whether user input is a URL or search 4120 * terms. Anything with a space is passed to search. 4121 * 4122 * Converts to lowercase any mistakenly uppercased schema (i.e., 4123 * "Http://" converts to "http://" 4124 * 4125 * @return Original or modified URL 4126 * 4127 */ 4128 String smartUrlFilter(String url) { 4129 4130 String inUrl = url.trim(); 4131 boolean hasSpace = inUrl.indexOf(' ') != -1; 4132 4133 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); 4134 if (matcher.matches()) { 4135 // force scheme to lowercase 4136 String scheme = matcher.group(1); 4137 String lcScheme = scheme.toLowerCase(); 4138 if (!lcScheme.equals(scheme)) { 4139 inUrl = lcScheme + matcher.group(2); 4140 } 4141 if (hasSpace) { 4142 inUrl = inUrl.replace(" ", "%20"); 4143 } 4144 return inUrl; 4145 } 4146 if (hasSpace) { 4147 // FIXME: Is this the correct place to add to searches? 4148 // what if someone else calls this function? 4149 int shortcut = parseUrlShortcut(inUrl); 4150 if (shortcut != SHORTCUT_INVALID) { 4151 Browser.addSearchUrl(mResolver, inUrl); 4152 String query = inUrl.substring(2); 4153 switch (shortcut) { 4154 case SHORTCUT_GOOGLE_SEARCH: 4155 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER); 4156 case SHORTCUT_WIKIPEDIA_SEARCH: 4157 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER); 4158 case SHORTCUT_DICTIONARY_SEARCH: 4159 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER); 4160 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH: 4161 // FIXME: we need location in this case 4162 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER); 4163 } 4164 } 4165 } else { 4166 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) { 4167 return URLUtil.guessUrl(inUrl); 4168 } 4169 } 4170 4171 Browser.addSearchUrl(mResolver, inUrl); 4172 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER); 4173 } 4174 4175 /* package */ void setShouldShowErrorConsole(boolean flag) { 4176 if (flag == mShouldShowErrorConsole) { 4177 // Nothing to do. 4178 return; 4179 } 4180 4181 mShouldShowErrorConsole = flag; 4182 4183 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true); 4184 4185 if (flag) { 4186 // Setting the show state of the console will cause it's the layout to be inflated. 4187 if (errorConsole.numberOfErrors() > 0) { 4188 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 4189 } else { 4190 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 4191 } 4192 4193 // Now we can add it to the main view. 4194 mErrorConsoleContainer.addView(errorConsole, 4195 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 4196 ViewGroup.LayoutParams.WRAP_CONTENT)); 4197 } else { 4198 mErrorConsoleContainer.removeView(errorConsole); 4199 } 4200 4201 } 4202 4203 final static int LOCK_ICON_UNSECURE = 0; 4204 final static int LOCK_ICON_SECURE = 1; 4205 final static int LOCK_ICON_MIXED = 2; 4206 4207 private int mLockIconType = LOCK_ICON_UNSECURE; 4208 private int mPrevLockType = LOCK_ICON_UNSECURE; 4209 4210 private BrowserSettings mSettings; 4211 private TabControl mTabControl; 4212 private ContentResolver mResolver; 4213 private FrameLayout mContentView; 4214 private View mCustomView; 4215 private FrameLayout mCustomViewContainer; 4216 private WebChromeClient.CustomViewCallback mCustomViewCallback; 4217 4218 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of 4219 // view, we should rewrite this. 4220 private int mCurrentMenuState = 0; 4221 private int mMenuState = R.id.MAIN_MENU; 4222 private int mOldMenuState = EMPTY_MENU; 4223 private static final int EMPTY_MENU = -1; 4224 private Menu mMenu; 4225 4226 private FindDialog mFindDialog; 4227 // Used to prevent chording to result in firing two shortcuts immediately 4228 // one after another. Fixes bug 1211714. 4229 boolean mCanChord; 4230 4231 private boolean mInLoad; 4232 private boolean mIsNetworkUp; 4233 4234 private boolean mPageStarted; 4235 private boolean mActivityInPause = true; 4236 4237 private boolean mMenuIsDown; 4238 4239 private static boolean mInTrace; 4240 4241 // Performance probe 4242 private static final int[] SYSTEM_CPU_FORMAT = new int[] { 4243 Process.PROC_SPACE_TERM | Process.PROC_COMBINE, 4244 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time 4245 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time 4246 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time 4247 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time 4248 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time 4249 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time 4250 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time 4251 }; 4252 4253 private long mStart; 4254 private long mProcessStart; 4255 private long mUserStart; 4256 private long mSystemStart; 4257 private long mIdleStart; 4258 private long mIrqStart; 4259 4260 private long mUiStart; 4261 4262 private Drawable mMixLockIcon; 4263 private Drawable mSecLockIcon; 4264 4265 /* hold a ref so we can auto-cancel if necessary */ 4266 private AlertDialog mAlertDialog; 4267 4268 // Wait for credentials before loading google.com 4269 private ProgressDialog mCredsDlg; 4270 4271 // The up-to-date URL and title (these can be different from those stored 4272 // in WebView, since it takes some time for the information in WebView to 4273 // get updated) 4274 private String mUrl; 4275 private String mTitle; 4276 4277 // As PageInfo has different style for landscape / portrait, we have 4278 // to re-open it when configuration changed 4279 private AlertDialog mPageInfoDialog; 4280 private TabControl.Tab mPageInfoView; 4281 // If the Page-Info dialog is launched from the SSL-certificate-on-error 4282 // dialog, we should not just dismiss it, but should get back to the 4283 // SSL-certificate-on-error dialog. This flag is used to store this state 4284 private Boolean mPageInfoFromShowSSLCertificateOnError; 4285 4286 // as SSLCertificateOnError has different style for landscape / portrait, 4287 // we have to re-open it when configuration changed 4288 private AlertDialog mSSLCertificateOnErrorDialog; 4289 private WebView mSSLCertificateOnErrorView; 4290 private SslErrorHandler mSSLCertificateOnErrorHandler; 4291 private SslError mSSLCertificateOnErrorError; 4292 4293 // as SSLCertificate has different style for landscape / portrait, we 4294 // have to re-open it when configuration changed 4295 private AlertDialog mSSLCertificateDialog; 4296 private TabControl.Tab mSSLCertificateView; 4297 4298 // as HttpAuthentication has different style for landscape / portrait, we 4299 // have to re-open it when configuration changed 4300 private AlertDialog mHttpAuthenticationDialog; 4301 private HttpAuthHandler mHttpAuthHandler; 4302 4303 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = 4304 new FrameLayout.LayoutParams( 4305 ViewGroup.LayoutParams.FILL_PARENT, 4306 ViewGroup.LayoutParams.FILL_PARENT); 4307 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = 4308 new FrameLayout.LayoutParams( 4309 ViewGroup.LayoutParams.FILL_PARENT, 4310 ViewGroup.LayoutParams.FILL_PARENT, 4311 Gravity.CENTER); 4312 // Google search 4313 final static String QuickSearch_G = "http://www.google.com/m?q=%s"; 4314 // Wikipedia search 4315 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go"; 4316 // Dictionary search 4317 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s"; 4318 // Google Mobile Local search 4319 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view"; 4320 4321 final static String QUERY_PLACE_HOLDER = "%s"; 4322 4323 // "source" parameter for Google search through search key 4324 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key"; 4325 // "source" parameter for Google search through goto menu 4326 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto"; 4327 // "source" parameter for Google search through simplily type 4328 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type"; 4329 // "source" parameter for Google search suggested by the browser 4330 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest"; 4331 // "source" parameter for Google search from unknown source 4332 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; 4333 4334 private final static String LOGTAG = "browser"; 4335 4336 private String mLastEnteredUrl; 4337 4338 private PowerManager.WakeLock mWakeLock; 4339 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes 4340 4341 private Toast mStopToast; 4342 4343 private TitleBar mTitleBar; 4344 4345 private LinearLayout mErrorConsoleContainer = null; 4346 private boolean mShouldShowErrorConsole = false; 4347 4348 // As the ids are dynamically created, we can't guarantee that they will 4349 // be in sequence, so this static array maps ids to a window number. 4350 final static private int[] WINDOW_SHORTCUT_ID_ARRAY = 4351 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id, 4352 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id, 4353 R.id.window_seven_menu_id, R.id.window_eight_menu_id }; 4354 4355 // monitor platform changes 4356 private IntentFilter mNetworkStateChangedFilter; 4357 private BroadcastReceiver mNetworkStateIntentReceiver; 4358 4359 private BroadcastReceiver mPackageInstallationReceiver; 4360 4361 // activity requestCode 4362 final static int COMBO_PAGE = 1; 4363 final static int DOWNLOAD_PAGE = 2; 4364 final static int PREFERENCES_PAGE = 3; 4365 4366 // the default <video> poster 4367 private Bitmap mDefaultVideoPoster; 4368 // the video progress view 4369 private View mVideoProgressView; 4370 4371 /** 4372 * A UrlData class to abstract how the content will be set to WebView. 4373 * This base class uses loadUrl to show the content. 4374 */ 4375 private static class UrlData { 4376 String mUrl; 4377 byte[] mPostData; 4378 4379 UrlData(String url) { 4380 this.mUrl = url; 4381 } 4382 4383 void setPostData(byte[] postData) { 4384 mPostData = postData; 4385 } 4386 4387 boolean isEmpty() { 4388 return mUrl == null || mUrl.length() == 0; 4389 } 4390 4391 public void loadIn(WebView webView) { 4392 if (mPostData != null) { 4393 webView.postUrl(mUrl, mPostData); 4394 } else { 4395 webView.loadUrl(mUrl); 4396 } 4397 } 4398 }; 4399 4400 /** 4401 * A subclass of UrlData class that can display inlined content using 4402 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}. 4403 */ 4404 private static class InlinedUrlData extends UrlData { 4405 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) { 4406 super(failUrl); 4407 mInlined = inlined; 4408 mMimeType = mimeType; 4409 mEncoding = encoding; 4410 } 4411 String mMimeType; 4412 String mInlined; 4413 String mEncoding; 4414 @Override 4415 boolean isEmpty() { 4416 return mInlined == null || mInlined.length() == 0 || super.isEmpty(); 4417 } 4418 4419 @Override 4420 public void loadIn(WebView webView) { 4421 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl); 4422 } 4423 } 4424 4425 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); 4426} 4427