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