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