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