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 17 package com.android.browser; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.DownloadManager; 22 import android.app.ProgressDialog; 23 import android.app.SearchManager; 24 import android.content.ActivityNotFoundException; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.ContentProvider; 28 import android.content.ContentProviderClient; 29 import android.content.ContentResolver; 30 import android.content.ContentUris; 31 import android.content.ContentValues; 32 import android.content.Context; 33 import android.content.DialogInterface; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.pm.PackageInfo; 37 import android.content.pm.PackageManager; 38 import android.content.pm.ResolveInfo; 39 import android.content.res.Configuration; 40 import android.content.res.Resources; 41 import android.database.Cursor; 42 import android.database.DatabaseUtils; 43 import android.graphics.Bitmap; 44 import android.graphics.BitmapFactory; 45 import android.graphics.Canvas; 46 import android.graphics.Picture; 47 import android.graphics.PixelFormat; 48 import android.graphics.Rect; 49 import android.graphics.drawable.Drawable; 50 import android.net.ConnectivityManager; 51 import android.net.NetworkInfo; 52 import android.net.Uri; 53 import android.net.WebAddress; 54 import android.net.http.SslCertificate; 55 import android.net.http.SslError; 56 import android.os.AsyncTask; 57 import android.os.Bundle; 58 import android.os.Debug; 59 import android.os.Environment; 60 import android.os.Handler; 61 import android.os.Message; 62 import android.os.PowerManager; 63 import android.os.Process; 64 import android.os.ServiceManager; 65 import android.os.SystemClock; 66 import android.provider.Browser; 67 import android.provider.ContactsContract; 68 import android.provider.ContactsContract.Intents.Insert; 69 import android.provider.Downloads; 70 import android.provider.MediaStore; 71 import android.speech.RecognizerResultsIntent; 72 import android.text.IClipboard; 73 import android.text.TextUtils; 74 import android.text.format.DateFormat; 75 import android.util.AttributeSet; 76 import android.util.Log; 77 import android.util.Patterns; 78 import android.view.ContextMenu; 79 import android.view.Gravity; 80 import android.view.KeyEvent; 81 import android.view.LayoutInflater; 82 import android.view.Menu; 83 import android.view.MenuInflater; 84 import android.view.MenuItem; 85 import android.view.View; 86 import android.view.ViewGroup; 87 import android.view.Window; 88 import android.view.WindowManager; 89 import android.view.ContextMenu.ContextMenuInfo; 90 import android.view.MenuItem.OnMenuItemClickListener; 91 import android.webkit.CookieManager; 92 import android.webkit.CookieSyncManager; 93 import android.webkit.DownloadListener; 94 import android.webkit.HttpAuthHandler; 95 import android.webkit.PluginManager; 96 import android.webkit.SslErrorHandler; 97 import android.webkit.URLUtil; 98 import android.webkit.ValueCallback; 99 import android.webkit.WebChromeClient; 100 import android.webkit.WebHistoryItem; 101 import android.webkit.WebIconDatabase; 102 import android.webkit.WebView; 103 import android.widget.EditText; 104 import android.widget.FrameLayout; 105 import android.widget.LinearLayout; 106 import android.widget.TextView; 107 import android.widget.Toast; 108 import android.accounts.Account; 109 import android.accounts.AccountManager; 110 import android.accounts.AccountManagerFuture; 111 import android.accounts.AuthenticatorException; 112 import android.accounts.OperationCanceledException; 113 import android.accounts.AccountManagerCallback; 114 115 import com.android.browser.search.SearchEngine; 116 import com.android.common.Search; 117 import com.android.common.speech.LoggingEvents; 118 119 import java.io.ByteArrayOutputStream; 120 import java.io.File; 121 import java.io.IOException; 122 import java.io.InputStream; 123 import java.net.MalformedURLException; 124 import java.net.URI; 125 import java.net.URISyntaxException; 126 import java.net.URL; 127 import java.net.URLEncoder; 128 import java.text.ParseException; 129 import java.util.Date; 130 import java.util.HashMap; 131 import java.util.HashSet; 132 import java.util.Iterator; 133 import java.util.List; 134 import java.util.Map; 135 import java.util.Set; 136 import java.util.regex.Matcher; 137 import java.util.regex.Pattern; 138 139 public class BrowserActivity extends Activity 140 implements View.OnCreateContextMenuListener, DownloadListener { 141 142 /* Define some aliases to make these debugging flags easier to refer to. 143 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG". 144 */ 145 private final static boolean DEBUG = com.android.browser.Browser.DEBUG; 146 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED; 147 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED; 148 149 private static class ClearThumbnails extends AsyncTask<File, Void, Void> { 150 @Override doInBackground(File... files)151 public Void doInBackground(File... files) { 152 if (files != null) { 153 for (File f : files) { 154 if (!f.delete()) { 155 Log.e(LOGTAG, f.getPath() + " was not deleted"); 156 } 157 } 158 } 159 return null; 160 } 161 } 162 163 /** 164 * This layout holds everything you see below the status bar, including the 165 * error console, the custom view container, and the webviews. 166 */ 167 private FrameLayout mBrowserFrameLayout; 168 169 @Override onCreate(Bundle icicle)170 public void onCreate(Bundle icicle) { 171 if (LOGV_ENABLED) { 172 Log.v(LOGTAG, this + " onStart"); 173 } 174 super.onCreate(icicle); 175 // test the browser in OpenGL 176 // requestWindowFeature(Window.FEATURE_OPENGL); 177 178 // enable this to test the browser in 32bit 179 if (false) { 180 getWindow().setFormat(PixelFormat.RGBX_8888); 181 BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888); 182 } 183 184 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); 185 186 mResolver = getContentResolver(); 187 188 // Keep a settings instance handy. 189 mSettings = BrowserSettings.getInstance(); 190 191 mSettings.updateRlzValues(this); 192 193 // If this was a web search request, pass it on to the default web 194 // search provider and finish this activity. 195 if (handleWebSearchIntent(getIntent())) { 196 finish(); 197 return; 198 } 199 200 mSecLockIcon = Resources.getSystem().getDrawable( 201 android.R.drawable.ic_secure); 202 mMixLockIcon = Resources.getSystem().getDrawable( 203 android.R.drawable.ic_partial_secure); 204 205 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView() 206 .findViewById(com.android.internal.R.id.content); 207 mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this) 208 .inflate(R.layout.custom_screen, null); 209 mContentView = (FrameLayout) mBrowserFrameLayout.findViewById( 210 R.id.main_content); 211 mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout 212 .findViewById(R.id.error_console); 213 mCustomViewContainer = (FrameLayout) mBrowserFrameLayout 214 .findViewById(R.id.fullscreen_custom_content); 215 frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); 216 mTitleBar = new TitleBar(this); 217 // mTitleBar will be always shown in the fully loaded mode 218 mTitleBar.setProgress(100); 219 mFakeTitleBar = new TitleBar(this); 220 221 // Create the tab control and our initial tab 222 mTabControl = new TabControl(this); 223 224 // Open the icon database and retain all the bookmark urls for favicons 225 retainIconsOnStartup(); 226 227 mSettings.setTabControl(mTabControl); 228 229 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 230 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser"); 231 232 // Find out if the network is currently up. 233 ConnectivityManager cm = (ConnectivityManager) getSystemService( 234 Context.CONNECTIVITY_SERVICE); 235 NetworkInfo info = cm.getActiveNetworkInfo(); 236 if (info != null) { 237 mIsNetworkUp = info.isAvailable(); 238 } 239 240 /* enables registration for changes in network status from 241 http stack */ 242 mNetworkStateChangedFilter = new IntentFilter(); 243 mNetworkStateChangedFilter.addAction( 244 ConnectivityManager.CONNECTIVITY_ACTION); 245 mNetworkStateIntentReceiver = new BroadcastReceiver() { 246 @Override 247 public void onReceive(Context context, Intent intent) { 248 if (intent.getAction().equals( 249 ConnectivityManager.CONNECTIVITY_ACTION)) { 250 251 NetworkInfo info = intent.getParcelableExtra( 252 ConnectivityManager.EXTRA_NETWORK_INFO); 253 String typeName = info.getTypeName(); 254 String subtypeName = info.getSubtypeName(); 255 sendNetworkType(typeName.toLowerCase(), 256 (subtypeName != null ? subtypeName.toLowerCase() : "")); 257 258 onNetworkToggle(info.isAvailable()); 259 } 260 } 261 }; 262 263 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 264 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 265 filter.addDataScheme("package"); 266 mPackageInstallationReceiver = new BroadcastReceiver() { 267 @Override 268 public void onReceive(Context context, Intent intent) { 269 final String action = intent.getAction(); 270 final String packageName = intent.getData() 271 .getSchemeSpecificPart(); 272 final boolean replacing = intent.getBooleanExtra( 273 Intent.EXTRA_REPLACING, false); 274 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) { 275 // if it is replacing, refreshPlugins() when adding 276 return; 277 } 278 279 if (sGoogleApps.contains(packageName)) { 280 BrowserActivity.this.packageChanged(packageName, 281 Intent.ACTION_PACKAGE_ADDED.equals(action)); 282 } 283 284 PackageManager pm = BrowserActivity.this.getPackageManager(); 285 PackageInfo pkgInfo = null; 286 try { 287 pkgInfo = pm.getPackageInfo(packageName, 288 PackageManager.GET_PERMISSIONS); 289 } catch (PackageManager.NameNotFoundException e) { 290 return; 291 } 292 if (pkgInfo != null) { 293 String permissions[] = pkgInfo.requestedPermissions; 294 if (permissions == null) { 295 return; 296 } 297 boolean permissionOk = false; 298 for (String permit : permissions) { 299 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) { 300 permissionOk = true; 301 break; 302 } 303 } 304 if (permissionOk) { 305 PluginManager.getInstance(BrowserActivity.this) 306 .refreshPlugins( 307 Intent.ACTION_PACKAGE_ADDED 308 .equals(action)); 309 } 310 } 311 } 312 }; 313 registerReceiver(mPackageInstallationReceiver, filter); 314 315 if (!mTabControl.restoreState(icicle)) { 316 // clear up the thumbnail directory if we can't restore the state as 317 // none of the files in the directory are referenced any more. 318 new ClearThumbnails().execute( 319 mTabControl.getThumbnailDir().listFiles()); 320 // there is no quit on Android. But if we can't restore the state, 321 // we can treat it as a new Browser, remove the old session cookies. 322 CookieManager.getInstance().removeSessionCookie(); 323 final Intent intent = getIntent(); 324 final Bundle extra = intent.getExtras(); 325 // Create an initial tab. 326 // If the intent is ACTION_VIEW and data is not null, the Browser is 327 // invoked to view the content by another application. In this case, 328 // the tab will be close when exit. 329 UrlData urlData = getUrlDataFromIntent(intent); 330 331 String action = intent.getAction(); 332 final Tab t = mTabControl.createNewTab( 333 (Intent.ACTION_VIEW.equals(action) && 334 intent.getData() != null) 335 || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS 336 .equals(action), 337 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl); 338 mTabControl.setCurrentTab(t); 339 attachTabToContentView(t); 340 WebView webView = t.getWebView(); 341 if (extra != null) { 342 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0); 343 if (scale > 0 && scale <= 1000) { 344 webView.setInitialScale(scale); 345 } 346 } 347 348 if (urlData.isEmpty()) { 349 loadUrl(webView, mSettings.getHomePage()); 350 } else { 351 loadUrlDataIn(t, urlData); 352 } 353 } else { 354 // TabControl.restoreState() will create a new tab even if 355 // restoring the state fails. 356 attachTabToContentView(mTabControl.getCurrentTab()); 357 } 358 359 // Delete old thumbnails to save space 360 File dir = mTabControl.getThumbnailDir(); 361 if (dir.exists()) { 362 for (String child : dir.list()) { 363 File f = new File(dir, child); 364 f.delete(); 365 } 366 } 367 368 // Read JavaScript flags if it exists. 369 String jsFlags = mSettings.getJsFlags(); 370 if (jsFlags.trim().length() != 0) { 371 mTabControl.getCurrentWebView().setJsFlags(jsFlags); 372 } 373 // Work out which packages are installed on the system. 374 getInstalledPackages(); 375 376 // Start watching the default geolocation permissions 377 mSystemAllowGeolocationOrigins 378 = new SystemAllowGeolocationOrigins(getApplicationContext()); 379 mSystemAllowGeolocationOrigins.start(); 380 } 381 382 /** 383 * Feed the previously stored results strings to the BrowserProvider so that 384 * the SearchDialog will show them instead of the standard searches. 385 * @param result String to show on the editable line of the SearchDialog. 386 */ showVoiceSearchResults(String result)387 /* package */ void showVoiceSearchResults(String result) { 388 ContentProviderClient client = mResolver.acquireContentProviderClient( 389 Browser.BOOKMARKS_URI); 390 ContentProvider prov = client.getLocalContentProvider(); 391 BrowserProvider bp = (BrowserProvider) prov; 392 bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults()); 393 client.release(); 394 395 Bundle bundle = createGoogleSearchSourceBundle( 396 GOOGLE_SEARCH_SOURCE_SEARCHKEY); 397 bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true); 398 startSearch(result, false, bundle, false); 399 } 400 401 @Override onNewIntent(Intent intent)402 protected void onNewIntent(Intent intent) { 403 Tab current = mTabControl.getCurrentTab(); 404 // When a tab is closed on exit, the current tab index is set to -1. 405 // Reset before proceed as Browser requires the current tab to be set. 406 if (current == null) { 407 // Try to reset the tab in case the index was incorrect. 408 current = mTabControl.getTab(0); 409 if (current == null) { 410 // No tabs at all so just ignore this intent. 411 return; 412 } 413 mTabControl.setCurrentTab(current); 414 attachTabToContentView(current); 415 resetTitleAndIcon(current.getWebView()); 416 } 417 final String action = intent.getAction(); 418 final int flags = intent.getFlags(); 419 if (Intent.ACTION_MAIN.equals(action) || 420 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { 421 // just resume the browser 422 return; 423 } 424 // In case the SearchDialog is open. 425 ((SearchManager) getSystemService(Context.SEARCH_SERVICE)) 426 .stopSearch(); 427 boolean activateVoiceSearch = RecognizerResultsIntent 428 .ACTION_VOICE_SEARCH_RESULTS.equals(action); 429 if (Intent.ACTION_VIEW.equals(action) 430 || Intent.ACTION_SEARCH.equals(action) 431 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 432 || Intent.ACTION_WEB_SEARCH.equals(action) 433 || activateVoiceSearch) { 434 if (current.isInVoiceSearchMode()) { 435 String title = current.getVoiceDisplayTitle(); 436 if (title != null && title.equals(intent.getStringExtra( 437 SearchManager.QUERY))) { 438 // The user submitted the same search as the last voice 439 // search, so do nothing. 440 return; 441 } 442 if (Intent.ACTION_SEARCH.equals(action) 443 && current.voiceSearchSourceIsGoogle()) { 444 Intent logIntent = new Intent( 445 LoggingEvents.ACTION_LOG_EVENT); 446 logIntent.putExtra(LoggingEvents.EXTRA_EVENT, 447 LoggingEvents.VoiceSearch.QUERY_UPDATED); 448 logIntent.putExtra( 449 LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE, 450 intent.getDataString()); 451 sendBroadcast(logIntent); 452 // Note, onPageStarted will revert the voice title bar 453 // When http://b/issue?id=2379215 is fixed, we should update 454 // the title bar here. 455 } 456 } 457 // If this was a search request (e.g. search query directly typed into the address bar), 458 // pass it on to the default web search provider. 459 if (handleWebSearchIntent(intent)) { 460 return; 461 } 462 463 UrlData urlData = getUrlDataFromIntent(intent); 464 if (urlData.isEmpty()) { 465 urlData = new UrlData(mSettings.getHomePage()); 466 } 467 468 final String appId = intent 469 .getStringExtra(Browser.EXTRA_APPLICATION_ID); 470 if (!TextUtils.isEmpty(urlData.mUrl) && 471 urlData.mUrl.startsWith("javascript:")) { 472 // Always open javascript: URIs in new tabs 473 openTabAndShow(urlData, true, appId); 474 return; 475 } 476 if ((Intent.ACTION_VIEW.equals(action) 477 // If a voice search has no appId, it means that it came 478 // from the browser. In that case, reuse the current tab. 479 || (activateVoiceSearch && appId != null)) 480 && !getPackageName().equals(appId) 481 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { 482 Tab appTab = mTabControl.getTabFromId(appId); 483 if (appTab != null) { 484 Log.i(LOGTAG, "Reusing tab for " + appId); 485 // Dismiss the subwindow if applicable. 486 dismissSubWindow(appTab); 487 // Since we might kill the WebView, remove it from the 488 // content view first. 489 removeTabFromContentView(appTab); 490 // Recreate the main WebView after destroying the old one. 491 // If the WebView has the same original url and is on that 492 // page, it can be reused. 493 boolean needsLoad = 494 mTabControl.recreateWebView(appTab, urlData); 495 496 if (current != appTab) { 497 switchToTab(mTabControl.getTabIndex(appTab)); 498 if (needsLoad) { 499 loadUrlDataIn(appTab, urlData); 500 } 501 } else { 502 // If the tab was the current tab, we have to attach 503 // it to the view system again. 504 attachTabToContentView(appTab); 505 if (needsLoad) { 506 loadUrlDataIn(appTab, urlData); 507 } 508 } 509 return; 510 } else { 511 // No matching application tab, try to find a regular tab 512 // with a matching url. 513 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl); 514 if (appTab != null) { 515 if (current != appTab) { 516 switchToTab(mTabControl.getTabIndex(appTab)); 517 } 518 // Otherwise, we are already viewing the correct tab. 519 } else { 520 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url 521 // will be opened in a new tab unless we have reached 522 // MAX_TABS. Then the url will be opened in the current 523 // tab. If a new tab is created, it will have "true" for 524 // exit on close. 525 openTabAndShow(urlData, true, appId); 526 } 527 } 528 } else { 529 if (!urlData.isEmpty() 530 && urlData.mUrl.startsWith("about:debug")) { 531 if ("about:debug.dom".equals(urlData.mUrl)) { 532 current.getWebView().dumpDomTree(false); 533 } else if ("about:debug.dom.file".equals(urlData.mUrl)) { 534 current.getWebView().dumpDomTree(true); 535 } else if ("about:debug.render".equals(urlData.mUrl)) { 536 current.getWebView().dumpRenderTree(false); 537 } else if ("about:debug.render.file".equals(urlData.mUrl)) { 538 current.getWebView().dumpRenderTree(true); 539 } else if ("about:debug.display".equals(urlData.mUrl)) { 540 current.getWebView().dumpDisplayTree(); 541 } else if (urlData.mUrl.startsWith("about:debug.drag")) { 542 int index = urlData.mUrl.codePointAt(16) - '0'; 543 if (index <= 0 || index > 9) { 544 current.getWebView().setDragTracker(null); 545 } else { 546 current.getWebView().setDragTracker(new MeshTracker(index)); 547 } 548 } else { 549 mSettings.toggleDebugSettings(); 550 } 551 return; 552 } 553 // Get rid of the subwindow if it exists 554 dismissSubWindow(current); 555 // If the current Tab is being used as an application tab, 556 // remove the association, since the new Intent means that it is 557 // no longer associated with that application. 558 current.setAppId(null); 559 loadUrlDataIn(current, urlData); 560 } 561 } 562 } 563 564 /** 565 * Launches the default web search activity with the query parameters if the given intent's data 566 * are identified as plain search terms and not URLs/shortcuts. 567 * @return true if the intent was handled and web search activity was launched, false if not. 568 */ handleWebSearchIntent(Intent intent)569 private boolean handleWebSearchIntent(Intent intent) { 570 if (intent == null) return false; 571 572 String url = null; 573 final String action = intent.getAction(); 574 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals( 575 action)) { 576 return false; 577 } 578 if (Intent.ACTION_VIEW.equals(action)) { 579 Uri data = intent.getData(); 580 if (data != null) url = data.toString(); 581 } else if (Intent.ACTION_SEARCH.equals(action) 582 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 583 || Intent.ACTION_WEB_SEARCH.equals(action)) { 584 url = intent.getStringExtra(SearchManager.QUERY); 585 } 586 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA), 587 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); 588 } 589 590 /** 591 * Launches the default web search activity with the query parameters if the given url string 592 * was identified as plain search terms and not URL/shortcut. 593 * @return true if the request was handled and web search activity was launched, false if not. 594 */ handleWebSearchRequest(String inUrl, Bundle appData, String extraData)595 private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) { 596 if (inUrl == null) return false; 597 598 // In general, we shouldn't modify URL from Intent. 599 // But currently, we get the user-typed URL from search box as well. 600 String url = fixUrl(inUrl).trim(); 601 602 // URLs are handled by the regular flow of control, so 603 // return early. 604 if (Patterns.WEB_URL.matcher(url).matches() 605 || ACCEPTED_URI_SCHEMA.matcher(url).matches()) { 606 return false; 607 } 608 609 final ContentResolver cr = mResolver; 610 final String newUrl = url; 611 new AsyncTask<Void, Void, Void>() { 612 protected Void doInBackground(Void... unused) { 613 Browser.updateVisitedHistory(cr, newUrl, false); 614 Browser.addSearchUrl(cr, newUrl); 615 return null; 616 } 617 }.execute(); 618 619 SearchEngine searchEngine = mSettings.getSearchEngine(); 620 if (searchEngine == null) return false; 621 searchEngine.startSearch(this, url, appData, extraData); 622 623 return true; 624 } 625 getUrlDataFromIntent(Intent intent)626 private UrlData getUrlDataFromIntent(Intent intent) { 627 String url = ""; 628 Map<String, String> headers = null; 629 if (intent != null) { 630 final String action = intent.getAction(); 631 if (Intent.ACTION_VIEW.equals(action)) { 632 url = smartUrlFilter(intent.getData()); 633 if (url != null && url.startsWith("http")) { 634 final Bundle pairs = intent 635 .getBundleExtra(Browser.EXTRA_HEADERS); 636 if (pairs != null && !pairs.isEmpty()) { 637 Iterator<String> iter = pairs.keySet().iterator(); 638 headers = new HashMap<String, String>(); 639 while (iter.hasNext()) { 640 String key = iter.next(); 641 headers.put(key, pairs.getString(key)); 642 } 643 } 644 645 // AppId will be set to the Browser for Search Bar initiated searches 646 final String appId = intent.getStringExtra(Browser.EXTRA_APPLICATION_ID); 647 if (getPackageName().equals(appId)) { 648 String rlz = mSettings.getRlzValue(); 649 Uri uri = Uri.parse(url); 650 if (!rlz.isEmpty() && needsRlz(uri)) { 651 Uri rlzUri = addRlzParameter(uri, rlz); 652 url = rlzUri.toString(); 653 } 654 } 655 } 656 } else if (Intent.ACTION_SEARCH.equals(action) 657 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) 658 || Intent.ACTION_WEB_SEARCH.equals(action)) { 659 url = intent.getStringExtra(SearchManager.QUERY); 660 if (url != null) { 661 mLastEnteredUrl = url; 662 // In general, we shouldn't modify URL from Intent. 663 // But currently, we get the user-typed URL from search box as well. 664 url = fixUrl(url); 665 url = smartUrlFilter(url); 666 final ContentResolver cr = mResolver; 667 final String newUrl = url; 668 new AsyncTask<Void, Void, Void>() { 669 protected Void doInBackground(Void... unused) { 670 Browser.updateVisitedHistory(cr, newUrl, false); 671 return null; 672 } 673 }.execute(); 674 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&"; 675 if (url.contains(searchSource)) { 676 String source = null; 677 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); 678 if (appData != null) { 679 source = appData.getString(Search.SOURCE); 680 } 681 if (TextUtils.isEmpty(source)) { 682 source = GOOGLE_SEARCH_SOURCE_UNKNOWN; 683 } 684 url = url.replace(searchSource, "&source=android-"+source+"&"); 685 } 686 } 687 } 688 } 689 return new UrlData(url, headers, intent); 690 } showVoiceTitleBar(String title)691 /* package */ void showVoiceTitleBar(String title) { 692 mTitleBar.setInVoiceMode(true); 693 mFakeTitleBar.setInVoiceMode(true); 694 695 mTitleBar.setDisplayTitle(title); 696 mFakeTitleBar.setDisplayTitle(title); 697 } revertVoiceTitleBar()698 /* package */ void revertVoiceTitleBar() { 699 mTitleBar.setInVoiceMode(false); 700 mFakeTitleBar.setInVoiceMode(false); 701 702 mTitleBar.setDisplayTitle(mUrl); 703 mFakeTitleBar.setDisplayTitle(mUrl); 704 } fixUrl(String inUrl)705 /* package */ static String fixUrl(String inUrl) { 706 // FIXME: Converting the url to lower case 707 // duplicates functionality in smartUrlFilter(). 708 // However, changing all current callers of fixUrl to 709 // call smartUrlFilter in addition may have unwanted 710 // consequences, and is deferred for now. 711 int colon = inUrl.indexOf(':'); 712 boolean allLower = true; 713 for (int index = 0; index < colon; index++) { 714 char ch = inUrl.charAt(index); 715 if (!Character.isLetter(ch)) { 716 break; 717 } 718 allLower &= Character.isLowerCase(ch); 719 if (index == colon - 1 && !allLower) { 720 inUrl = inUrl.substring(0, colon).toLowerCase() 721 + inUrl.substring(colon); 722 } 723 } 724 if (inUrl.startsWith("http://") || inUrl.startsWith("https://")) 725 return inUrl; 726 if (inUrl.startsWith("http:") || 727 inUrl.startsWith("https:")) { 728 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) { 729 inUrl = inUrl.replaceFirst("/", "//"); 730 } else inUrl = inUrl.replaceFirst(":", "://"); 731 } 732 return inUrl; 733 } 734 735 @Override onResume()736 protected void onResume() { 737 super.onResume(); 738 if (LOGV_ENABLED) { 739 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this); 740 } 741 742 if (!mActivityInPause) { 743 Log.e(LOGTAG, "BrowserActivity is already resumed."); 744 return; 745 } 746 747 mTabControl.resumeCurrentTab(); 748 mActivityInPause = false; 749 resumeWebViewTimers(); 750 751 if (mWakeLock.isHeld()) { 752 mHandler.removeMessages(RELEASE_WAKELOCK); 753 mWakeLock.release(); 754 } 755 756 registerReceiver(mNetworkStateIntentReceiver, 757 mNetworkStateChangedFilter); 758 WebView.enablePlatformNotifications(); 759 } 760 761 /** 762 * Since the actual title bar is embedded in the WebView, and removing it 763 * would change its appearance, use a different TitleBar to show overlayed 764 * at the top of the screen, when the menu is open or the page is loading. 765 */ 766 private TitleBar mFakeTitleBar; 767 768 /** 769 * Keeps track of whether the options menu is open. This is important in 770 * determining whether to show or hide the title bar overlay. 771 */ 772 private boolean mOptionsMenuOpen; 773 774 /** 775 * Only meaningful when mOptionsMenuOpen is true. This variable keeps track 776 * of whether the configuration has changed. The first onMenuOpened call 777 * after a configuration change is simply a reopening of the same menu 778 * (i.e. mIconView did not change). 779 */ 780 private boolean mConfigChanged; 781 782 /** 783 * Whether or not the options menu is in its smaller, icon menu form. When 784 * true, we want the title bar overlay to be up. When false, we do not. 785 * Only meaningful if mOptionsMenuOpen is true. 786 */ 787 private boolean mIconView; 788 789 @Override onMenuOpened(int featureId, Menu menu)790 public boolean onMenuOpened(int featureId, Menu menu) { 791 if (Window.FEATURE_OPTIONS_PANEL == featureId) { 792 if (mOptionsMenuOpen) { 793 if (mConfigChanged) { 794 // We do not need to make any changes to the state of the 795 // title bar, since the only thing that happened was a 796 // change in orientation 797 mConfigChanged = false; 798 } else { 799 if (mIconView) { 800 // Switching the menu to expanded view, so hide the 801 // title bar. 802 hideFakeTitleBar(); 803 mIconView = false; 804 } else { 805 // Switching the menu back to icon view, so show the 806 // title bar once again. 807 showFakeTitleBar(); 808 mIconView = true; 809 } 810 } 811 } else { 812 // The options menu is closed, so open it, and show the title 813 showFakeTitleBar(); 814 mOptionsMenuOpen = true; 815 mConfigChanged = false; 816 mIconView = true; 817 } 818 } 819 return true; 820 } 821 showFakeTitleBar()822 private void showFakeTitleBar() { 823 if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null 824 && !mActivityInPause) { 825 WebView mainView = mTabControl.getCurrentWebView(); 826 // if there is no current WebView, don't show the faked title bar; 827 if (mainView == null) { 828 return; 829 } 830 // Do not need to check for null, since the current tab will have 831 // at least a main WebView, or we would have returned above. 832 if (dialogIsUp()) { 833 // Do not show the fake title bar, which would cover up the 834 // find or select dialog. 835 return; 836 } 837 838 WindowManager manager 839 = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 840 841 // Add the title bar to the window manager so it can receive touches 842 // while the menu is up 843 WindowManager.LayoutParams params 844 = new WindowManager.LayoutParams( 845 ViewGroup.LayoutParams.MATCH_PARENT, 846 ViewGroup.LayoutParams.WRAP_CONTENT, 847 WindowManager.LayoutParams.TYPE_APPLICATION, 848 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 849 PixelFormat.TRANSLUCENT); 850 params.gravity = Gravity.TOP; 851 boolean atTop = mainView.getScrollY() == 0; 852 params.windowAnimations = atTop ? 0 : R.style.TitleBar; 853 manager.addView(mFakeTitleBar, params); 854 } 855 } 856 857 @Override onOptionsMenuClosed(Menu menu)858 public void onOptionsMenuClosed(Menu menu) { 859 mOptionsMenuOpen = false; 860 if (!mInLoad) { 861 hideFakeTitleBar(); 862 } else if (!mIconView) { 863 // The page is currently loading, and we are in expanded mode, so 864 // we were not showing the menu. Show it once again. It will be 865 // removed when the page finishes. 866 showFakeTitleBar(); 867 } 868 } 869 hideFakeTitleBar()870 private void hideFakeTitleBar() { 871 if (mFakeTitleBar.getParent() == null) return; 872 WindowManager.LayoutParams params = (WindowManager.LayoutParams) 873 mFakeTitleBar.getLayoutParams(); 874 WebView mainView = mTabControl.getCurrentWebView(); 875 // Although we decided whether or not to animate based on the current 876 // scroll position, the scroll position may have changed since the 877 // fake title bar was displayed. Make sure it has the appropriate 878 // animation/lack thereof before removing. 879 params.windowAnimations = mainView != null && mainView.getScrollY() == 0 880 ? 0 : R.style.TitleBar; 881 WindowManager manager 882 = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 883 manager.updateViewLayout(mFakeTitleBar, params); 884 manager.removeView(mFakeTitleBar); 885 } 886 887 /** 888 * Special method for the fake title bar to call when displaying its context 889 * menu, since it is in its own Window, and its parent does not show a 890 * context menu. 891 */ showTitleBarContextMenu()892 /* package */ void showTitleBarContextMenu() { 893 if (null == mTitleBar.getParent()) { 894 return; 895 } 896 openContextMenu(mTitleBar); 897 } 898 899 @Override onContextMenuClosed(Menu menu)900 public void onContextMenuClosed(Menu menu) { 901 super.onContextMenuClosed(menu); 902 if (mInLoad) { 903 showFakeTitleBar(); 904 } 905 } 906 907 /** 908 * onSaveInstanceState(Bundle map) 909 * onSaveInstanceState is called right before onStop(). The map contains 910 * the saved state. 911 */ 912 @Override onSaveInstanceState(Bundle outState)913 protected void onSaveInstanceState(Bundle outState) { 914 if (LOGV_ENABLED) { 915 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this); 916 } 917 // the default implementation requires each view to have an id. As the 918 // browser handles the state itself and it doesn't use id for the views, 919 // don't call the default implementation. Otherwise it will trigger the 920 // warning like this, "couldn't save which view has focus because the 921 // focused view XXX has no id". 922 923 // Save all the tabs 924 mTabControl.saveState(outState); 925 } 926 927 @Override onPause()928 protected void onPause() { 929 super.onPause(); 930 931 if (mActivityInPause) { 932 Log.e(LOGTAG, "BrowserActivity is already paused."); 933 return; 934 } 935 936 mTabControl.pauseCurrentTab(); 937 mActivityInPause = true; 938 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) { 939 mWakeLock.acquire(); 940 mHandler.sendMessageDelayed(mHandler 941 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT); 942 } 943 944 // FIXME: This removes the active tabs page and resets the menu to 945 // MAIN_MENU. A better solution might be to do this work in onNewIntent 946 // but then we would need to save it in onSaveInstanceState and restore 947 // it in onCreate/onRestoreInstanceState 948 if (mActiveTabsPage != null) { 949 removeActiveTabPage(true); 950 } 951 952 cancelStopToast(); 953 954 // unregister network state listener 955 unregisterReceiver(mNetworkStateIntentReceiver); 956 WebView.disablePlatformNotifications(); 957 958 if (mCustomView != null) { 959 mTabControl.getCurrentWebView().getWebChromeClient().onHideCustomView(); 960 } 961 } 962 963 @Override onDestroy()964 protected void onDestroy() { 965 if (LOGV_ENABLED) { 966 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this); 967 } 968 super.onDestroy(); 969 970 if (mUploadMessage != null) { 971 mUploadMessage.onReceiveValue(null); 972 mUploadMessage = null; 973 } 974 975 if (mTabControl == null) return; 976 977 // Remove the fake title bar if it is there 978 hideFakeTitleBar(); 979 980 // Remove the current tab and sub window 981 Tab t = mTabControl.getCurrentTab(); 982 if (t != null) { 983 dismissSubWindow(t); 984 removeTabFromContentView(t); 985 } 986 // Destroy all the tabs 987 mTabControl.destroy(); 988 WebIconDatabase.getInstance().close(); 989 990 unregisterReceiver(mPackageInstallationReceiver); 991 992 // Stop watching the default geolocation permissions 993 mSystemAllowGeolocationOrigins.stop(); 994 mSystemAllowGeolocationOrigins = null; 995 } 996 997 @Override onConfigurationChanged(Configuration newConfig)998 public void onConfigurationChanged(Configuration newConfig) { 999 mConfigChanged = true; 1000 super.onConfigurationChanged(newConfig); 1001 1002 if (mPageInfoDialog != null) { 1003 mPageInfoDialog.dismiss(); 1004 showPageInfo( 1005 mPageInfoView, 1006 mPageInfoFromShowSSLCertificateOnError); 1007 } 1008 if (mSSLCertificateDialog != null) { 1009 mSSLCertificateDialog.dismiss(); 1010 showSSLCertificate( 1011 mSSLCertificateView); 1012 } 1013 if (mSSLCertificateOnErrorDialog != null) { 1014 mSSLCertificateOnErrorDialog.dismiss(); 1015 showSSLCertificateOnError( 1016 mSSLCertificateOnErrorView, 1017 mSSLCertificateOnErrorHandler, 1018 mSSLCertificateOnErrorError); 1019 } 1020 if (mHttpAuthenticationDialog != null) { 1021 String title = ((TextView) mHttpAuthenticationDialog 1022 .findViewById(com.android.internal.R.id.alertTitle)).getText() 1023 .toString(); 1024 String name = ((TextView) mHttpAuthenticationDialog 1025 .findViewById(R.id.username_edit)).getText().toString(); 1026 String password = ((TextView) mHttpAuthenticationDialog 1027 .findViewById(R.id.password_edit)).getText().toString(); 1028 int focusId = mHttpAuthenticationDialog.getCurrentFocus() 1029 .getId(); 1030 mHttpAuthenticationDialog.dismiss(); 1031 showHttpAuthentication(mHttpAuthHandler, null, null, title, 1032 name, password, focusId); 1033 } 1034 } 1035 1036 @Override onLowMemory()1037 public void onLowMemory() { 1038 super.onLowMemory(); 1039 mTabControl.freeMemory(); 1040 } 1041 resumeWebViewTimers()1042 private void resumeWebViewTimers() { 1043 Tab tab = mTabControl.getCurrentTab(); 1044 if (tab == null) return; // monkey can trigger this 1045 boolean inLoad = tab.inLoad(); 1046 if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) { 1047 CookieSyncManager.getInstance().startSync(); 1048 WebView w = tab.getWebView(); 1049 if (w != null) { 1050 w.resumeTimers(); 1051 } 1052 } 1053 } 1054 pauseWebViewTimers()1055 private boolean pauseWebViewTimers() { 1056 Tab tab = mTabControl.getCurrentTab(); 1057 boolean inLoad = tab.inLoad(); 1058 if (mActivityInPause && !inLoad) { 1059 CookieSyncManager.getInstance().stopSync(); 1060 WebView w = mTabControl.getCurrentWebView(); 1061 if (w != null) { 1062 w.pauseTimers(); 1063 } 1064 return true; 1065 } else { 1066 return false; 1067 } 1068 } 1069 1070 // Open the icon database and retain all the icons for visited sites. retainIconsOnStartup()1071 private void retainIconsOnStartup() { 1072 final WebIconDatabase db = WebIconDatabase.getInstance(); 1073 db.open(getDir("icons", 0).getPath()); 1074 Cursor c = null; 1075 try { 1076 c = Browser.getAllBookmarks(mResolver); 1077 if (c.moveToFirst()) { 1078 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); 1079 do { 1080 String url = c.getString(urlIndex); 1081 db.retainIconForPageUrl(url); 1082 } while (c.moveToNext()); 1083 } 1084 } catch (IllegalStateException e) { 1085 Log.e(LOGTAG, "retainIconsOnStartup", e); 1086 } finally { 1087 if (c!= null) c.close(); 1088 } 1089 } 1090 1091 // Helper method for getting the top window. getTopWindow()1092 WebView getTopWindow() { 1093 return mTabControl.getCurrentTopWebView(); 1094 } 1095 getTabControl()1096 TabControl getTabControl() { 1097 return mTabControl; 1098 } 1099 1100 @Override onCreateOptionsMenu(Menu menu)1101 public boolean onCreateOptionsMenu(Menu menu) { 1102 super.onCreateOptionsMenu(menu); 1103 1104 MenuInflater inflater = getMenuInflater(); 1105 inflater.inflate(R.menu.browser, menu); 1106 mMenu = menu; 1107 updateInLoadMenuItems(); 1108 return true; 1109 } 1110 1111 /** 1112 * As the menu can be open when loading state changes 1113 * we must manually update the state of the stop/reload menu 1114 * item 1115 */ updateInLoadMenuItems()1116 private void updateInLoadMenuItems() { 1117 if (mMenu == null) { 1118 return; 1119 } 1120 MenuItem src = mInLoad ? 1121 mMenu.findItem(R.id.stop_menu_id): 1122 mMenu.findItem(R.id.reload_menu_id); 1123 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id); 1124 dest.setIcon(src.getIcon()); 1125 dest.setTitle(src.getTitle()); 1126 } 1127 1128 @Override onContextItemSelected(MenuItem item)1129 public boolean onContextItemSelected(MenuItem item) { 1130 // chording is not an issue with context menus, but we use the same 1131 // options selector, so set mCanChord to true so we can access them. 1132 mCanChord = true; 1133 int id = item.getItemId(); 1134 boolean result = true; 1135 switch (id) { 1136 // For the context menu from the title bar 1137 case R.id.title_bar_copy_page_url: 1138 Tab currentTab = mTabControl.getCurrentTab(); 1139 if (null == currentTab) { 1140 result = false; 1141 break; 1142 } 1143 WebView mainView = currentTab.getWebView(); 1144 if (null == mainView) { 1145 result = false; 1146 break; 1147 } 1148 copy(mainView.getUrl()); 1149 break; 1150 // -- Browser context menu 1151 case R.id.open_context_menu_id: 1152 case R.id.open_newtab_context_menu_id: 1153 case R.id.bookmark_context_menu_id: 1154 case R.id.save_link_context_menu_id: 1155 case R.id.share_link_context_menu_id: 1156 case R.id.copy_link_context_menu_id: 1157 final WebView webView = getTopWindow(); 1158 if (null == webView) { 1159 result = false; 1160 break; 1161 } 1162 final HashMap hrefMap = new HashMap(); 1163 hrefMap.put("webview", webView); 1164 final Message msg = mHandler.obtainMessage( 1165 FOCUS_NODE_HREF, id, 0, hrefMap); 1166 webView.requestFocusNodeHref(msg); 1167 break; 1168 1169 default: 1170 // For other context menus 1171 result = onOptionsItemSelected(item); 1172 } 1173 mCanChord = false; 1174 return result; 1175 } 1176 createGoogleSearchSourceBundle(String source)1177 private Bundle createGoogleSearchSourceBundle(String source) { 1178 Bundle bundle = new Bundle(); 1179 bundle.putString(Search.SOURCE, source); 1180 return bundle; 1181 } 1182 editUrl()1183 /* package */ void editUrl() { 1184 if (mOptionsMenuOpen) closeOptionsMenu(); 1185 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl(); 1186 startSearch(mSettings.getHomePage().equals(url) ? null : url, true, 1187 null, false); 1188 } 1189 1190 /** 1191 * Overriding this to insert a local information bundle 1192 */ 1193 @Override startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)1194 public void startSearch(String initialQuery, boolean selectInitialQuery, 1195 Bundle appSearchData, boolean globalSearch) { 1196 if (appSearchData == null) { 1197 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE); 1198 } 1199 1200 SearchEngine searchEngine = mSettings.getSearchEngine(); 1201 if (searchEngine != null && !searchEngine.supportsVoiceSearch()) { 1202 appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true); 1203 } 1204 1205 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 1206 } 1207 1208 /** 1209 * Switch tabs. Called by the TitleBarSet when sliding the title bar 1210 * results in changing tabs. 1211 * @param index Index of the tab to change to, as defined by 1212 * mTabControl.getTabIndex(Tab t). 1213 * @return boolean True if we successfully switched to a different tab. If 1214 * the indexth tab is null, or if that tab is the same as 1215 * the current one, return false. 1216 */ switchToTab(int index)1217 /* package */ boolean switchToTab(int index) { 1218 Tab tab = mTabControl.getTab(index); 1219 Tab currentTab = mTabControl.getCurrentTab(); 1220 if (tab == null || tab == currentTab) { 1221 return false; 1222 } 1223 if (currentTab != null) { 1224 // currentTab may be null if it was just removed. In that case, 1225 // we do not need to remove it 1226 removeTabFromContentView(currentTab); 1227 } 1228 mTabControl.setCurrentTab(tab); 1229 attachTabToContentView(tab); 1230 resetTitleIconAndProgress(); 1231 updateLockIconToLatest(); 1232 return true; 1233 } 1234 openTabToHomePage()1235 /* package */ Tab openTabToHomePage() { 1236 return openTabAndShow(mSettings.getHomePage(), false, null); 1237 } 1238 closeCurrentWindow()1239 /* package */ void closeCurrentWindow() { 1240 final Tab current = mTabControl.getCurrentTab(); 1241 if (mTabControl.getTabCount() == 1) { 1242 // This is the last tab. Open a new one, with the home 1243 // page and close the current one. 1244 openTabToHomePage(); 1245 closeTab(current); 1246 return; 1247 } 1248 final Tab parent = current.getParentTab(); 1249 int indexToShow = -1; 1250 if (parent != null) { 1251 indexToShow = mTabControl.getTabIndex(parent); 1252 } else { 1253 final int currentIndex = mTabControl.getCurrentIndex(); 1254 // Try to move to the tab to the right 1255 indexToShow = currentIndex + 1; 1256 if (indexToShow > mTabControl.getTabCount() - 1) { 1257 // Try to move to the tab to the left 1258 indexToShow = currentIndex - 1; 1259 } 1260 } 1261 if (switchToTab(indexToShow)) { 1262 // Close window 1263 closeTab(current); 1264 } 1265 } 1266 1267 private ActiveTabsPage mActiveTabsPage; 1268 1269 /** 1270 * Remove the active tabs page. 1271 * @param needToAttach If true, the active tabs page did not attach a tab 1272 * to the content view, so we need to do that here. 1273 */ removeActiveTabPage(boolean needToAttach)1274 /* package */ void removeActiveTabPage(boolean needToAttach) { 1275 mContentView.removeView(mActiveTabsPage); 1276 mActiveTabsPage = null; 1277 mMenuState = R.id.MAIN_MENU; 1278 if (needToAttach) { 1279 attachTabToContentView(mTabControl.getCurrentTab()); 1280 } 1281 getTopWindow().requestFocus(); 1282 } 1283 showDialog(WebDialog dialog)1284 private WebView showDialog(WebDialog dialog) { 1285 Tab tab = mTabControl.getCurrentTab(); 1286 if (tab.getSubWebView() == null) { 1287 // If the find or select is being performed on the main webview, 1288 // remove the embedded title bar. 1289 WebView mainView = tab.getWebView(); 1290 if (mainView != null) { 1291 mainView.setEmbeddedTitleBar(null); 1292 } 1293 } 1294 hideFakeTitleBar(); 1295 mMenuState = EMPTY_MENU; 1296 return tab.showDialog(dialog); 1297 } 1298 1299 @Override onOptionsItemSelected(MenuItem item)1300 public boolean onOptionsItemSelected(MenuItem item) { 1301 if (!mCanChord) { 1302 // The user has already fired a shortcut with this hold down of the 1303 // menu key. 1304 return false; 1305 } 1306 if (null == getTopWindow()) { 1307 return false; 1308 } 1309 if (mMenuIsDown) { 1310 // The shortcut action consumes the MENU. Even if it is still down, 1311 // it won't trigger the next shortcut action. In the case of the 1312 // shortcut action triggering a new activity, like Bookmarks, we 1313 // won't get onKeyUp for MENU. So it is important to reset it here. 1314 mMenuIsDown = false; 1315 } 1316 switch (item.getItemId()) { 1317 // -- Main menu 1318 case R.id.new_tab_menu_id: 1319 openTabToHomePage(); 1320 break; 1321 1322 case R.id.goto_menu_id: 1323 editUrl(); 1324 break; 1325 1326 case R.id.bookmarks_menu_id: 1327 bookmarksOrHistoryPicker(false); 1328 break; 1329 1330 case R.id.active_tabs_menu_id: 1331 mActiveTabsPage = new ActiveTabsPage(this, mTabControl); 1332 removeTabFromContentView(mTabControl.getCurrentTab()); 1333 hideFakeTitleBar(); 1334 mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); 1335 mActiveTabsPage.requestFocus(); 1336 mMenuState = EMPTY_MENU; 1337 break; 1338 1339 case R.id.add_bookmark_menu_id: 1340 Intent i = new Intent(BrowserActivity.this, 1341 AddBookmarkPage.class); 1342 WebView w = getTopWindow(); 1343 i.putExtra("url", w.getUrl()); 1344 i.putExtra("title", w.getTitle()); 1345 i.putExtra("touch_icon_url", w.getTouchIconUrl()); 1346 i.putExtra("thumbnail", createScreenshot(w)); 1347 startActivity(i); 1348 break; 1349 1350 case R.id.stop_reload_menu_id: 1351 if (mInLoad) { 1352 stopLoading(); 1353 } else { 1354 getTopWindow().reload(); 1355 } 1356 break; 1357 1358 case R.id.back_menu_id: 1359 getTopWindow().goBack(); 1360 break; 1361 1362 case R.id.forward_menu_id: 1363 getTopWindow().goForward(); 1364 break; 1365 1366 case R.id.close_menu_id: 1367 // Close the subwindow if it exists. 1368 if (mTabControl.getCurrentSubWindow() != null) { 1369 dismissSubWindow(mTabControl.getCurrentTab()); 1370 break; 1371 } 1372 closeCurrentWindow(); 1373 break; 1374 1375 case R.id.homepage_menu_id: 1376 Tab current = mTabControl.getCurrentTab(); 1377 if (current != null) { 1378 dismissSubWindow(current); 1379 loadUrl(current.getWebView(), mSettings.getHomePage()); 1380 } 1381 break; 1382 1383 case R.id.preferences_menu_id: 1384 Intent intent = new Intent(this, 1385 BrowserPreferencesPage.class); 1386 intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE, 1387 getTopWindow().getUrl()); 1388 startActivityForResult(intent, PREFERENCES_PAGE); 1389 break; 1390 1391 case R.id.find_menu_id: 1392 showFindDialog(); 1393 break; 1394 1395 case R.id.select_text_id: 1396 if (true) { 1397 Tab currentTab = mTabControl.getCurrentTab(); 1398 if (currentTab != null) { 1399 currentTab.getWebView().setUpSelect(); 1400 } 1401 } else { 1402 showSelectDialog(); 1403 } 1404 break; 1405 1406 case R.id.page_info_menu_id: 1407 showPageInfo(mTabControl.getCurrentTab(), false); 1408 break; 1409 1410 case R.id.classic_history_menu_id: 1411 bookmarksOrHistoryPicker(true); 1412 break; 1413 1414 case R.id.title_bar_share_page_url: 1415 case R.id.share_page_menu_id: 1416 Tab currentTab = mTabControl.getCurrentTab(); 1417 if (null == currentTab) { 1418 mCanChord = false; 1419 return false; 1420 } 1421 currentTab.populatePickerData(); 1422 sharePage(this, currentTab.getTitle(), 1423 currentTab.getUrl(), currentTab.getFavicon(), 1424 createScreenshot(currentTab.getWebView())); 1425 break; 1426 1427 case R.id.dump_nav_menu_id: 1428 getTopWindow().debugDump(); 1429 break; 1430 1431 case R.id.dump_counters_menu_id: 1432 getTopWindow().dumpV8Counters(); 1433 break; 1434 1435 case R.id.zoom_in_menu_id: 1436 getTopWindow().zoomIn(); 1437 break; 1438 1439 case R.id.zoom_out_menu_id: 1440 getTopWindow().zoomOut(); 1441 break; 1442 1443 case R.id.view_downloads_menu_id: 1444 viewDownloads(); 1445 break; 1446 1447 case R.id.window_one_menu_id: 1448 case R.id.window_two_menu_id: 1449 case R.id.window_three_menu_id: 1450 case R.id.window_four_menu_id: 1451 case R.id.window_five_menu_id: 1452 case R.id.window_six_menu_id: 1453 case R.id.window_seven_menu_id: 1454 case R.id.window_eight_menu_id: 1455 { 1456 int menuid = item.getItemId(); 1457 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) { 1458 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) { 1459 Tab desiredTab = mTabControl.getTab(id); 1460 if (desiredTab != null && 1461 desiredTab != mTabControl.getCurrentTab()) { 1462 switchToTab(id); 1463 } 1464 break; 1465 } 1466 } 1467 } 1468 break; 1469 1470 default: 1471 if (!super.onOptionsItemSelected(item)) { 1472 return false; 1473 } 1474 // Otherwise fall through. 1475 } 1476 mCanChord = false; 1477 return true; 1478 } 1479 dialogIsUp()1480 private boolean dialogIsUp() { 1481 return null != mFindDialog && mFindDialog.isVisible() || 1482 null != mSelectDialog && mSelectDialog.isVisible(); 1483 } 1484 closeDialog(WebDialog dialog)1485 private boolean closeDialog(WebDialog dialog) { 1486 if (null == dialog || !dialog.isVisible()) return false; 1487 Tab currentTab = mTabControl.getCurrentTab(); 1488 currentTab.closeDialog(dialog); 1489 dialog.dismiss(); 1490 return true; 1491 } 1492 1493 /* 1494 * Remove the find dialog or select dialog. 1495 */ closeDialogs()1496 public void closeDialogs() { 1497 if (!(closeDialog(mFindDialog) || closeDialog(mSelectDialog))) return; 1498 // If the Find was being performed in the main WebView, replace the 1499 // embedded title bar. 1500 Tab currentTab = mTabControl.getCurrentTab(); 1501 if (currentTab.getSubWebView() == null) { 1502 WebView mainView = currentTab.getWebView(); 1503 if (mainView != null) { 1504 mainView.setEmbeddedTitleBar(mTitleBar); 1505 } 1506 } 1507 mMenuState = R.id.MAIN_MENU; 1508 if (mInLoad) { 1509 // The title bar was hidden, because otherwise it would cover up the 1510 // find or select dialog. Now that the dialog has been removed, 1511 // show the fake title bar once again. 1512 showFakeTitleBar(); 1513 } 1514 } 1515 showFindDialog()1516 public void showFindDialog() { 1517 if (null == mFindDialog) { 1518 mFindDialog = new FindDialog(this); 1519 } 1520 showDialog(mFindDialog).setFindIsUp(true); 1521 } 1522 setFindDialogText(String text)1523 public void setFindDialogText(String text) { 1524 mFindDialog.setText(text); 1525 } 1526 showSelectDialog()1527 public void showSelectDialog() { 1528 if (null == mSelectDialog) { 1529 mSelectDialog = new SelectDialog(this); 1530 } 1531 showDialog(mSelectDialog).setUpSelect(); 1532 mSelectDialog.hideSoftInput(); 1533 } 1534 1535 @Override onPrepareOptionsMenu(Menu menu)1536 public boolean onPrepareOptionsMenu(Menu menu) { 1537 // This happens when the user begins to hold down the menu key, so 1538 // allow them to chord to get a shortcut. 1539 mCanChord = true; 1540 // Note: setVisible will decide whether an item is visible; while 1541 // setEnabled() will decide whether an item is enabled, which also means 1542 // whether the matching shortcut key will function. 1543 super.onPrepareOptionsMenu(menu); 1544 switch (mMenuState) { 1545 case EMPTY_MENU: 1546 if (mCurrentMenuState != mMenuState) { 1547 menu.setGroupVisible(R.id.MAIN_MENU, false); 1548 menu.setGroupEnabled(R.id.MAIN_MENU, false); 1549 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false); 1550 } 1551 break; 1552 default: 1553 if (mCurrentMenuState != mMenuState) { 1554 menu.setGroupVisible(R.id.MAIN_MENU, true); 1555 menu.setGroupEnabled(R.id.MAIN_MENU, true); 1556 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true); 1557 } 1558 final WebView w = getTopWindow(); 1559 boolean canGoBack = false; 1560 boolean canGoForward = false; 1561 boolean isHome = false; 1562 if (w != null) { 1563 canGoBack = w.canGoBack(); 1564 canGoForward = w.canGoForward(); 1565 isHome = mSettings.getHomePage().equals(w.getUrl()); 1566 } 1567 final MenuItem back = menu.findItem(R.id.back_menu_id); 1568 back.setEnabled(canGoBack); 1569 1570 final MenuItem home = menu.findItem(R.id.homepage_menu_id); 1571 home.setEnabled(!isHome); 1572 1573 menu.findItem(R.id.forward_menu_id) 1574 .setEnabled(canGoForward); 1575 1576 menu.findItem(R.id.new_tab_menu_id).setEnabled( 1577 mTabControl.canCreateNewTab()); 1578 1579 // decide whether to show the share link option 1580 PackageManager pm = getPackageManager(); 1581 Intent send = new Intent(Intent.ACTION_SEND); 1582 send.setType("text/plain"); 1583 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); 1584 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null); 1585 1586 boolean isNavDump = mSettings.isNavDump(); 1587 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id); 1588 nav.setVisible(isNavDump); 1589 nav.setEnabled(isNavDump); 1590 1591 boolean showDebugSettings = mSettings.showDebugSettings(); 1592 final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id); 1593 counter.setVisible(showDebugSettings); 1594 counter.setEnabled(showDebugSettings); 1595 1596 break; 1597 } 1598 mCurrentMenuState = mMenuState; 1599 return true; 1600 } 1601 1602 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1603 public void onCreateContextMenu(ContextMenu menu, View v, 1604 ContextMenuInfo menuInfo) { 1605 if (v instanceof TitleBar) { 1606 return; 1607 } 1608 WebView webview = (WebView) v; 1609 WebView.HitTestResult result = webview.getHitTestResult(); 1610 if (result == null) { 1611 return; 1612 } 1613 1614 int type = result.getType(); 1615 if (type == WebView.HitTestResult.UNKNOWN_TYPE) { 1616 Log.w(LOGTAG, 1617 "We should not show context menu when nothing is touched"); 1618 return; 1619 } 1620 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) { 1621 // let TextView handles context menu 1622 return; 1623 } 1624 1625 // Note, http://b/issue?id=1106666 is requesting that 1626 // an inflated menu can be used again. This is not available 1627 // yet, so inflate each time (yuk!) 1628 MenuInflater inflater = getMenuInflater(); 1629 inflater.inflate(R.menu.browsercontext, menu); 1630 1631 // Show the correct menu group 1632 String extra = result.getExtra(); 1633 menu.setGroupVisible(R.id.PHONE_MENU, 1634 type == WebView.HitTestResult.PHONE_TYPE); 1635 menu.setGroupVisible(R.id.EMAIL_MENU, 1636 type == WebView.HitTestResult.EMAIL_TYPE); 1637 menu.setGroupVisible(R.id.GEO_MENU, 1638 type == WebView.HitTestResult.GEO_TYPE); 1639 menu.setGroupVisible(R.id.IMAGE_MENU, 1640 type == WebView.HitTestResult.IMAGE_TYPE 1641 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1642 menu.setGroupVisible(R.id.ANCHOR_MENU, 1643 type == WebView.HitTestResult.SRC_ANCHOR_TYPE 1644 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1645 1646 // Setup custom handling depending on the type 1647 switch (type) { 1648 case WebView.HitTestResult.PHONE_TYPE: 1649 menu.setHeaderTitle(Uri.decode(extra)); 1650 menu.findItem(R.id.dial_context_menu_id).setIntent( 1651 new Intent(Intent.ACTION_VIEW, Uri 1652 .parse(WebView.SCHEME_TEL + extra))); 1653 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 1654 addIntent.putExtra(Insert.PHONE, Uri.decode(extra)); 1655 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); 1656 menu.findItem(R.id.add_contact_context_menu_id).setIntent( 1657 addIntent); 1658 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener( 1659 new Copy(extra)); 1660 break; 1661 1662 case WebView.HitTestResult.EMAIL_TYPE: 1663 menu.setHeaderTitle(extra); 1664 menu.findItem(R.id.email_context_menu_id).setIntent( 1665 new Intent(Intent.ACTION_VIEW, Uri 1666 .parse(WebView.SCHEME_MAILTO + extra))); 1667 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener( 1668 new Copy(extra)); 1669 break; 1670 1671 case WebView.HitTestResult.GEO_TYPE: 1672 menu.setHeaderTitle(extra); 1673 menu.findItem(R.id.map_context_menu_id).setIntent( 1674 new Intent(Intent.ACTION_VIEW, Uri 1675 .parse(WebView.SCHEME_GEO 1676 + URLEncoder.encode(extra)))); 1677 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener( 1678 new Copy(extra)); 1679 break; 1680 1681 case WebView.HitTestResult.SRC_ANCHOR_TYPE: 1682 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: 1683 TextView titleView = (TextView) LayoutInflater.from(this) 1684 .inflate(android.R.layout.browser_link_context_header, 1685 null); 1686 titleView.setText(extra); 1687 menu.setHeaderView(titleView); 1688 // decide whether to show the open link in new tab option 1689 menu.findItem(R.id.open_newtab_context_menu_id).setVisible( 1690 mTabControl.canCreateNewTab()); 1691 menu.findItem(R.id.bookmark_context_menu_id).setVisible( 1692 Bookmarks.urlHasAcceptableScheme(extra)); 1693 PackageManager pm = getPackageManager(); 1694 Intent send = new Intent(Intent.ACTION_SEND); 1695 send.setType("text/plain"); 1696 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); 1697 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null); 1698 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) { 1699 break; 1700 } 1701 // otherwise fall through to handle image part 1702 case WebView.HitTestResult.IMAGE_TYPE: 1703 if (type == WebView.HitTestResult.IMAGE_TYPE) { 1704 menu.setHeaderTitle(extra); 1705 } 1706 menu.findItem(R.id.view_image_context_menu_id).setIntent( 1707 new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); 1708 menu.findItem(R.id.download_context_menu_id). 1709 setOnMenuItemClickListener(new Download(extra)); 1710 menu.findItem(R.id.set_wallpaper_context_menu_id). 1711 setOnMenuItemClickListener(new SetAsWallpaper(extra)); 1712 break; 1713 1714 default: 1715 Log.w(LOGTAG, "We should not get here."); 1716 break; 1717 } 1718 hideFakeTitleBar(); 1719 } 1720 1721 // Attach the given tab to the content view. 1722 // this should only be called for the current tab. attachTabToContentView(Tab t)1723 private void attachTabToContentView(Tab t) { 1724 // Attach the container that contains the main WebView and any other UI 1725 // associated with the tab. 1726 t.attachTabToContentView(mContentView); 1727 1728 if (mShouldShowErrorConsole) { 1729 ErrorConsoleView errorConsole = t.getErrorConsole(true); 1730 if (errorConsole.numberOfErrors() == 0) { 1731 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 1732 } else { 1733 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 1734 } 1735 1736 mErrorConsoleContainer.addView(errorConsole, 1737 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1738 ViewGroup.LayoutParams.WRAP_CONTENT)); 1739 } 1740 1741 WebView view = t.getWebView(); 1742 view.setEmbeddedTitleBar(mTitleBar); 1743 if (t.isInVoiceSearchMode()) { 1744 showVoiceTitleBar(t.getVoiceDisplayTitle()); 1745 } else { 1746 revertVoiceTitleBar(); 1747 } 1748 // Request focus on the top window. 1749 t.getTopWindow().requestFocus(); 1750 } 1751 1752 // Attach a sub window to the main WebView of the given tab. attachSubWindow(Tab t)1753 void attachSubWindow(Tab t) { 1754 t.attachSubWindow(mContentView); 1755 getTopWindow().requestFocus(); 1756 } 1757 1758 // Remove the given tab from the content view. removeTabFromContentView(Tab t)1759 private void removeTabFromContentView(Tab t) { 1760 // Remove the container that contains the main WebView. 1761 t.removeTabFromContentView(mContentView); 1762 1763 ErrorConsoleView errorConsole = t.getErrorConsole(false); 1764 if (errorConsole != null) { 1765 mErrorConsoleContainer.removeView(errorConsole); 1766 } 1767 1768 WebView view = t.getWebView(); 1769 if (view != null) { 1770 view.setEmbeddedTitleBar(null); 1771 } 1772 } 1773 1774 // Remove the sub window if it exists. Also called by TabControl when the 1775 // user clicks the 'X' to dismiss a sub window. dismissSubWindow(Tab t)1776 /* package */ void dismissSubWindow(Tab t) { 1777 t.removeSubWindow(mContentView); 1778 // dismiss the subwindow. This will destroy the WebView. 1779 t.dismissSubWindow(); 1780 getTopWindow().requestFocus(); 1781 } 1782 1783 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)} 1784 // that accepts url as string. openTabAndShow(String url, boolean closeOnExit, String appId)1785 private Tab openTabAndShow(String url, boolean closeOnExit, String appId) { 1786 return openTabAndShow(new UrlData(url), closeOnExit, appId); 1787 } 1788 1789 // This method does a ton of stuff. It will attempt to create a new tab 1790 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If 1791 // url isn't null, it will load the given url. openTabAndShow(UrlData urlData, boolean closeOnExit, String appId)1792 /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit, 1793 String appId) { 1794 Tab currentTab = mTabControl.getCurrentTab(); 1795 if (!mTabControl.canCreateNewTab()) { 1796 Tab closeTab = mTabControl.getTab(0); 1797 closeTab(closeTab); 1798 if (closeTab == currentTab) { 1799 currentTab = null; 1800 } 1801 } 1802 final Tab tab = mTabControl.createNewTab(closeOnExit, appId, 1803 urlData.mUrl); 1804 WebView webview = tab.getWebView(); 1805 // If the last tab was removed from the active tabs page, currentTab 1806 // will be null. 1807 if (currentTab != null) { 1808 removeTabFromContentView(currentTab); 1809 } 1810 // We must set the new tab as the current tab to reflect the old 1811 // animation behavior. 1812 mTabControl.setCurrentTab(tab); 1813 attachTabToContentView(tab); 1814 if (!urlData.isEmpty()) { 1815 loadUrlDataIn(tab, urlData); 1816 } 1817 return tab; 1818 } 1819 openTab(String url)1820 private Tab openTab(String url) { 1821 if (mSettings.openInBackground()) { 1822 Tab t = mTabControl.createNewTab(); 1823 if (t != null) { 1824 WebView view = t.getWebView(); 1825 loadUrl(view, url); 1826 } 1827 return t; 1828 } else { 1829 return openTabAndShow(url, false, null); 1830 } 1831 } 1832 1833 private class Copy implements OnMenuItemClickListener { 1834 private CharSequence mText; 1835 onMenuItemClick(MenuItem item)1836 public boolean onMenuItemClick(MenuItem item) { 1837 copy(mText); 1838 return true; 1839 } 1840 Copy(CharSequence toCopy)1841 public Copy(CharSequence toCopy) { 1842 mText = toCopy; 1843 } 1844 } 1845 1846 private class Download implements OnMenuItemClickListener { 1847 private String mText; 1848 onMenuItemClick(MenuItem item)1849 public boolean onMenuItemClick(MenuItem item) { 1850 onDownloadStartNoStream(mText, null, null, null, -1); 1851 return true; 1852 } 1853 Download(String toDownload)1854 public Download(String toDownload) { 1855 mText = toDownload; 1856 } 1857 } 1858 1859 private class SetAsWallpaper extends Thread implements 1860 OnMenuItemClickListener, DialogInterface.OnCancelListener { 1861 private URL mUrl; 1862 private ProgressDialog mWallpaperProgress; 1863 private boolean mCanceled = false; 1864 SetAsWallpaper(String url)1865 public SetAsWallpaper(String url) { 1866 try { 1867 mUrl = new URL(url); 1868 } catch (MalformedURLException e) { 1869 mUrl = null; 1870 } 1871 } 1872 onCancel(DialogInterface dialog)1873 public void onCancel(DialogInterface dialog) { 1874 mCanceled = true; 1875 } 1876 onMenuItemClick(MenuItem item)1877 public boolean onMenuItemClick(MenuItem item) { 1878 if (mUrl != null) { 1879 // The user may have tried to set a image with a large file size as their 1880 // background so it may take a few moments to perform the operation. Display 1881 // a progress spinner while it is working. 1882 mWallpaperProgress = new ProgressDialog(BrowserActivity.this); 1883 mWallpaperProgress.setIndeterminate(true); 1884 mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper)); 1885 mWallpaperProgress.setCancelable(true); 1886 mWallpaperProgress.setOnCancelListener(this); 1887 mWallpaperProgress.show(); 1888 start(); 1889 } 1890 return true; 1891 } 1892 run()1893 public void run() { 1894 Drawable oldWallpaper = BrowserActivity.this.getWallpaper(); 1895 try { 1896 // TODO: This will cause the resource to be downloaded again, when we 1897 // should in most cases be able to grab it from the cache. To fix this 1898 // we should query WebCore to see if we can access a cached version and 1899 // instead open an input stream on that. This pattern could also be used 1900 // in the download manager where the same problem exists. 1901 InputStream inputstream = mUrl.openStream(); 1902 if (inputstream != null) { 1903 setWallpaper(inputstream); 1904 } 1905 } catch (IOException e) { 1906 Log.e(LOGTAG, "Unable to set new wallpaper"); 1907 // Act as though the user canceled the operation so we try to 1908 // restore the old wallpaper. 1909 mCanceled = true; 1910 } 1911 1912 if (mCanceled) { 1913 // Restore the old wallpaper if the user cancelled whilst we were setting 1914 // the new wallpaper. 1915 int width = oldWallpaper.getIntrinsicWidth(); 1916 int height = oldWallpaper.getIntrinsicHeight(); 1917 Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); 1918 Canvas canvas = new Canvas(bm); 1919 oldWallpaper.setBounds(0, 0, width, height); 1920 oldWallpaper.draw(canvas); 1921 try { 1922 setWallpaper(bm); 1923 } catch (IOException e) { 1924 Log.e(LOGTAG, "Unable to restore old wallpaper."); 1925 } 1926 mCanceled = false; 1927 } 1928 1929 if (mWallpaperProgress.isShowing()) { 1930 mWallpaperProgress.dismiss(); 1931 } 1932 } 1933 } 1934 copy(CharSequence text)1935 private void copy(CharSequence text) { 1936 try { 1937 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard")); 1938 if (clip != null) { 1939 clip.setClipboardText(text); 1940 } 1941 } catch (android.os.RemoteException e) { 1942 Log.e(LOGTAG, "Copy failed", e); 1943 } 1944 } 1945 1946 /** 1947 * Resets the browser title-view to whatever it must be 1948 * (for example, if we had a loading error) 1949 * When we have a new page, we call resetTitle, when we 1950 * have to reset the titlebar to whatever it used to be 1951 * (for example, if the user chose to stop loading), we 1952 * call resetTitleAndRevertLockIcon. 1953 */ resetTitleAndRevertLockIcon()1954 /* package */ void resetTitleAndRevertLockIcon() { 1955 mTabControl.getCurrentTab().revertLockIcon(); 1956 updateLockIconToLatest(); 1957 resetTitleIconAndProgress(); 1958 } 1959 1960 /** 1961 * Reset the title, favicon, and progress. 1962 */ resetTitleIconAndProgress()1963 private void resetTitleIconAndProgress() { 1964 WebView current = mTabControl.getCurrentWebView(); 1965 if (current == null) { 1966 return; 1967 } 1968 resetTitleAndIcon(current); 1969 int progress = current.getProgress(); 1970 current.getWebChromeClient().onProgressChanged(current, progress); 1971 } 1972 1973 // Reset the title and the icon based on the given item. resetTitleAndIcon(WebView view)1974 private void resetTitleAndIcon(WebView view) { 1975 WebHistoryItem item = view.copyBackForwardList().getCurrentItem(); 1976 if (item != null) { 1977 setUrlTitle(item.getUrl(), item.getTitle()); 1978 setFavicon(item.getFavicon()); 1979 } else { 1980 setUrlTitle(null, null); 1981 setFavicon(null); 1982 } 1983 } 1984 1985 /** 1986 * Sets a title composed of the URL and the title string. 1987 * @param url The URL of the site being loaded. 1988 * @param title The title of the site being loaded. 1989 */ setUrlTitle(String url, String title)1990 void setUrlTitle(String url, String title) { 1991 mUrl = url; 1992 mTitle = title; 1993 1994 // If we are in voice search mode, the title has already been set. 1995 if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return; 1996 mTitleBar.setDisplayTitle(url); 1997 mFakeTitleBar.setDisplayTitle(url); 1998 } 1999 2000 /** 2001 * @param url The URL to build a title version of the URL from. 2002 * @return The title version of the URL or null if fails. 2003 * The title version of the URL can be either the URL hostname, 2004 * or the hostname with an "https://" prefix (for secure URLs), 2005 * or an empty string if, for example, the URL in question is a 2006 * file:// URL with no hostname. 2007 */ buildTitleUrl(String url)2008 /* package */ static String buildTitleUrl(String url) { 2009 String titleUrl = null; 2010 2011 if (url != null) { 2012 try { 2013 // parse the url string 2014 URL urlObj = new URL(url); 2015 if (urlObj != null) { 2016 titleUrl = ""; 2017 2018 String protocol = urlObj.getProtocol(); 2019 String host = urlObj.getHost(); 2020 2021 if (host != null && 0 < host.length()) { 2022 titleUrl = host; 2023 if (protocol != null) { 2024 // if a secure site, add an "https://" prefix! 2025 if (protocol.equalsIgnoreCase("https")) { 2026 titleUrl = protocol + "://" + host; 2027 } 2028 } 2029 } 2030 } 2031 } catch (MalformedURLException e) {} 2032 } 2033 2034 return titleUrl; 2035 } 2036 2037 // Set the favicon in the title bar. setFavicon(Bitmap icon)2038 void setFavicon(Bitmap icon) { 2039 mTitleBar.setFavicon(icon); 2040 mFakeTitleBar.setFavicon(icon); 2041 } 2042 2043 /** 2044 * Close the tab, remove its associated title bar, and adjust mTabControl's 2045 * current tab to a valid value. 2046 */ closeTab(Tab t)2047 /* package */ void closeTab(Tab t) { 2048 int currentIndex = mTabControl.getCurrentIndex(); 2049 int removeIndex = mTabControl.getTabIndex(t); 2050 mTabControl.removeTab(t); 2051 if (currentIndex >= removeIndex && currentIndex != 0) { 2052 currentIndex--; 2053 } 2054 mTabControl.setCurrentTab(mTabControl.getTab(currentIndex)); 2055 resetTitleIconAndProgress(); 2056 } 2057 goBackOnePageOrQuit()2058 /* package */ void goBackOnePageOrQuit() { 2059 Tab current = mTabControl.getCurrentTab(); 2060 if (current == null) { 2061 /* 2062 * Instead of finishing the activity, simply push this to the back 2063 * of the stack and let ActivityManager to choose the foreground 2064 * activity. As BrowserActivity is singleTask, it will be always the 2065 * root of the task. So we can use either true or false for 2066 * moveTaskToBack(). 2067 */ 2068 moveTaskToBack(true); 2069 return; 2070 } 2071 WebView w = current.getWebView(); 2072 if (w.canGoBack()) { 2073 w.goBack(); 2074 } else { 2075 // Check to see if we are closing a window that was created by 2076 // another window. If so, we switch back to that window. 2077 Tab parent = current.getParentTab(); 2078 if (parent != null) { 2079 switchToTab(mTabControl.getTabIndex(parent)); 2080 // Now we close the other tab 2081 closeTab(current); 2082 } else { 2083 if (current.closeOnExit()) { 2084 // force the tab's inLoad() to be false as we are going to 2085 // either finish the activity or remove the tab. This will 2086 // ensure pauseWebViewTimers() taking action. 2087 mTabControl.getCurrentTab().clearInLoad(); 2088 if (mTabControl.getTabCount() == 1) { 2089 finish(); 2090 return; 2091 } 2092 // call pauseWebViewTimers() now, we won't be able to call 2093 // it in onPause() as the WebView won't be valid. 2094 // Temporarily change mActivityInPause to be true as 2095 // pauseWebViewTimers() will do nothing if mActivityInPause 2096 // is false. 2097 boolean savedState = mActivityInPause; 2098 if (savedState) { 2099 Log.e(LOGTAG, "BrowserActivity is already paused " 2100 + "while handing goBackOnePageOrQuit."); 2101 } 2102 mActivityInPause = true; 2103 pauseWebViewTimers(); 2104 mActivityInPause = savedState; 2105 removeTabFromContentView(current); 2106 mTabControl.removeTab(current); 2107 } 2108 /* 2109 * Instead of finishing the activity, simply push this to the back 2110 * of the stack and let ActivityManager to choose the foreground 2111 * activity. As BrowserActivity is singleTask, it will be always the 2112 * root of the task. So we can use either true or false for 2113 * moveTaskToBack(). 2114 */ 2115 moveTaskToBack(true); 2116 } 2117 } 2118 } 2119 isMenuDown()2120 boolean isMenuDown() { 2121 return mMenuIsDown; 2122 } 2123 2124 @Override onKeyDown(int keyCode, KeyEvent event)2125 public boolean onKeyDown(int keyCode, KeyEvent event) { 2126 // Even if MENU is already held down, we need to call to super to open 2127 // the IME on long press. 2128 if (KeyEvent.KEYCODE_MENU == keyCode) { 2129 mMenuIsDown = true; 2130 return super.onKeyDown(keyCode, event); 2131 } 2132 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is 2133 // still down, we don't want to trigger the search. Pretend to consume 2134 // the key and do nothing. 2135 if (mMenuIsDown) return true; 2136 2137 switch(keyCode) { 2138 case KeyEvent.KEYCODE_SPACE: 2139 // WebView/WebTextView handle the keys in the KeyDown. As 2140 // the Activity's shortcut keys are only handled when WebView 2141 // doesn't, have to do it in onKeyDown instead of onKeyUp. 2142 if (event.isShiftPressed()) { 2143 getTopWindow().pageUp(false); 2144 } else { 2145 getTopWindow().pageDown(false); 2146 } 2147 return true; 2148 case KeyEvent.KEYCODE_BACK: 2149 if (event.getRepeatCount() == 0) { 2150 event.startTracking(); 2151 return true; 2152 } else if (mCustomView == null && mActiveTabsPage == null 2153 && event.isLongPress()) { 2154 bookmarksOrHistoryPicker(true); 2155 return true; 2156 } 2157 break; 2158 } 2159 return super.onKeyDown(keyCode, event); 2160 } 2161 2162 @Override onKeyUp(int keyCode, KeyEvent event)2163 public boolean onKeyUp(int keyCode, KeyEvent event) { 2164 switch(keyCode) { 2165 case KeyEvent.KEYCODE_MENU: 2166 mMenuIsDown = false; 2167 break; 2168 case KeyEvent.KEYCODE_BACK: 2169 if (event.isTracking() && !event.isCanceled()) { 2170 if (mCustomView != null) { 2171 // if a custom view is showing, hide it 2172 mTabControl.getCurrentWebView().getWebChromeClient() 2173 .onHideCustomView(); 2174 } else if (mActiveTabsPage != null) { 2175 // if tab page is showing, hide it 2176 removeActiveTabPage(true); 2177 } else { 2178 WebView subwindow = mTabControl.getCurrentSubWindow(); 2179 if (subwindow != null) { 2180 if (subwindow.canGoBack()) { 2181 subwindow.goBack(); 2182 } else { 2183 dismissSubWindow(mTabControl.getCurrentTab()); 2184 } 2185 } else { 2186 goBackOnePageOrQuit(); 2187 } 2188 } 2189 return true; 2190 } 2191 break; 2192 } 2193 return super.onKeyUp(keyCode, event); 2194 } 2195 stopLoading()2196 /* package */ void stopLoading() { 2197 mDidStopLoad = true; 2198 resetTitleAndRevertLockIcon(); 2199 WebView w = getTopWindow(); 2200 w.stopLoading(); 2201 // FIXME: before refactor, it is using mWebViewClient. So I keep the 2202 // same logic here. But for subwindow case, should we call into the main 2203 // WebView's onPageFinished as we never call its onPageStarted and if 2204 // the page finishes itself, we don't call onPageFinished. 2205 mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w, 2206 w.getUrl()); 2207 2208 cancelStopToast(); 2209 mStopToast = Toast 2210 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT); 2211 mStopToast.show(); 2212 } 2213 didUserStopLoading()2214 boolean didUserStopLoading() { 2215 return mDidStopLoad; 2216 } 2217 cancelStopToast()2218 private void cancelStopToast() { 2219 if (mStopToast != null) { 2220 mStopToast.cancel(); 2221 mStopToast = null; 2222 } 2223 } 2224 2225 // called by a UI or non-UI thread to post the message postMessage(int what, int arg1, int arg2, Object obj, long delayMillis)2226 public void postMessage(int what, int arg1, int arg2, Object obj, 2227 long delayMillis) { 2228 mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2, 2229 obj), delayMillis); 2230 } 2231 2232 // called by a UI or non-UI thread to remove the message removeMessages(int what, Object object)2233 void removeMessages(int what, Object object) { 2234 mHandler.removeMessages(what, object); 2235 } 2236 2237 // public message ids 2238 public final static int LOAD_URL = 1001; 2239 public final static int STOP_LOAD = 1002; 2240 2241 // Message Ids 2242 private static final int FOCUS_NODE_HREF = 102; 2243 private static final int RELEASE_WAKELOCK = 107; 2244 2245 static final int UPDATE_BOOKMARK_THUMBNAIL = 108; 2246 2247 // Private handler for handling javascript and saving passwords 2248 private Handler mHandler = new Handler() { 2249 2250 public void handleMessage(Message msg) { 2251 switch (msg.what) { 2252 case FOCUS_NODE_HREF: 2253 { 2254 String url = (String) msg.getData().get("url"); 2255 String title = (String) msg.getData().get("title"); 2256 if (url == null || url.length() == 0) { 2257 break; 2258 } 2259 HashMap focusNodeMap = (HashMap) msg.obj; 2260 WebView view = (WebView) focusNodeMap.get("webview"); 2261 // Only apply the action if the top window did not change. 2262 if (getTopWindow() != view) { 2263 break; 2264 } 2265 switch (msg.arg1) { 2266 case R.id.open_context_menu_id: 2267 case R.id.view_image_context_menu_id: 2268 loadUrlFromContext(getTopWindow(), url); 2269 break; 2270 case R.id.open_newtab_context_menu_id: 2271 final Tab parent = mTabControl.getCurrentTab(); 2272 final Tab newTab = openTab(url); 2273 if (newTab != parent) { 2274 parent.addChildTab(newTab); 2275 } 2276 break; 2277 case R.id.bookmark_context_menu_id: 2278 Intent intent = new Intent(BrowserActivity.this, 2279 AddBookmarkPage.class); 2280 intent.putExtra("url", url); 2281 intent.putExtra("title", title); 2282 startActivity(intent); 2283 break; 2284 case R.id.share_link_context_menu_id: 2285 // See if this site has been visited before 2286 StringBuilder sb = new StringBuilder( 2287 Browser.BookmarkColumns.URL + " = "); 2288 DatabaseUtils.appendEscapedSQLString(sb, url); 2289 Cursor c = mResolver.query(Browser.BOOKMARKS_URI, 2290 Browser.HISTORY_PROJECTION, 2291 sb.toString(), 2292 null, 2293 null); 2294 if (c.moveToFirst()) { 2295 // The site has been visited before, so grab the 2296 // info from the database. 2297 Bitmap favicon = null; 2298 Bitmap thumbnail = null; 2299 String linkTitle = c.getString(Browser. 2300 HISTORY_PROJECTION_TITLE_INDEX); 2301 byte[] data = c.getBlob(Browser. 2302 HISTORY_PROJECTION_FAVICON_INDEX); 2303 if (data != null) { 2304 favicon = BitmapFactory.decodeByteArray( 2305 data, 0, data.length); 2306 } 2307 data = c.getBlob(Browser. 2308 HISTORY_PROJECTION_THUMBNAIL_INDEX); 2309 if (data != null) { 2310 thumbnail = BitmapFactory.decodeByteArray( 2311 data, 0, data.length); 2312 } 2313 sharePage(BrowserActivity.this, 2314 linkTitle, url, favicon, thumbnail); 2315 } else { 2316 Browser.sendString(BrowserActivity.this, url, 2317 getString( 2318 R.string.choosertitle_sharevia)); 2319 } 2320 break; 2321 case R.id.copy_link_context_menu_id: 2322 copy(url); 2323 break; 2324 case R.id.save_link_context_menu_id: 2325 case R.id.download_context_menu_id: 2326 onDownloadStartNoStream(url, null, null, null, -1); 2327 break; 2328 } 2329 break; 2330 } 2331 2332 case LOAD_URL: 2333 loadUrlFromContext(getTopWindow(), (String) msg.obj); 2334 break; 2335 2336 case STOP_LOAD: 2337 stopLoading(); 2338 break; 2339 2340 case RELEASE_WAKELOCK: 2341 if (mWakeLock.isHeld()) { 2342 mWakeLock.release(); 2343 // if we reach here, Browser should be still in the 2344 // background loading after WAKELOCK_TIMEOUT (5-min). 2345 // To avoid burning the battery, stop loading. 2346 mTabControl.stopAllLoading(); 2347 } 2348 break; 2349 2350 case UPDATE_BOOKMARK_THUMBNAIL: 2351 WebView view = (WebView) msg.obj; 2352 if (view != null) { 2353 updateScreenshot(view); 2354 } 2355 break; 2356 } 2357 } 2358 }; 2359 2360 /** 2361 * Share a page, providing the title, url, favicon, and a screenshot. Uses 2362 * an {@link Intent} to launch the Activity chooser. 2363 * @param c Context used to launch a new Activity. 2364 * @param title Title of the page. Stored in the Intent with 2365 * {@link Intent#EXTRA_SUBJECT} 2366 * @param url URL of the page. Stored in the Intent with 2367 * {@link Intent#EXTRA_TEXT} 2368 * @param favicon Bitmap of the favicon for the page. Stored in the Intent 2369 * with {@link Browser#EXTRA_SHARE_FAVICON} 2370 * @param screenshot Bitmap of a screenshot of the page. Stored in the 2371 * Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT} 2372 */ sharePage(Context c, String title, String url, Bitmap favicon, Bitmap screenshot)2373 public static final void sharePage(Context c, String title, String url, 2374 Bitmap favicon, Bitmap screenshot) { 2375 Intent send = new Intent(Intent.ACTION_SEND); 2376 send.setType("text/plain"); 2377 send.putExtra(Intent.EXTRA_TEXT, url); 2378 send.putExtra(Intent.EXTRA_SUBJECT, title); 2379 send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon); 2380 send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot); 2381 try { 2382 c.startActivity(Intent.createChooser(send, c.getString( 2383 R.string.choosertitle_sharevia))); 2384 } catch(android.content.ActivityNotFoundException ex) { 2385 // if no app handles it, do nothing 2386 } 2387 } 2388 updateScreenshot(WebView view)2389 private void updateScreenshot(WebView view) { 2390 // If this is a bookmarked site, add a screenshot to the database. 2391 // FIXME: When should we update? Every time? 2392 // FIXME: Would like to make sure there is actually something to 2393 // draw, but the API for that (WebViewCore.pictureReady()) is not 2394 // currently accessible here. 2395 2396 final Bitmap bm = createScreenshot(view); 2397 if (bm == null) { 2398 return; 2399 } 2400 2401 final ContentResolver cr = getContentResolver(); 2402 final String url = view.getUrl(); 2403 final String originalUrl = view.getOriginalUrl(); 2404 2405 new AsyncTask<Void, Void, Void>() { 2406 @Override 2407 protected Void doInBackground(Void... unused) { 2408 Cursor c = null; 2409 try { 2410 c = BrowserBookmarksAdapter.queryBookmarksForUrl( 2411 cr, originalUrl, url, true); 2412 if (c != null) { 2413 if (c.moveToFirst()) { 2414 ContentValues values = new ContentValues(); 2415 final ByteArrayOutputStream os 2416 = new ByteArrayOutputStream(); 2417 bm.compress(Bitmap.CompressFormat.PNG, 100, os); 2418 values.put(Browser.BookmarkColumns.THUMBNAIL, 2419 os.toByteArray()); 2420 do { 2421 cr.update(ContentUris.withAppendedId( 2422 Browser.BOOKMARKS_URI, c.getInt(0)), 2423 values, null, null); 2424 } while (c.moveToNext()); 2425 } 2426 } 2427 } catch (IllegalStateException e) { 2428 // Ignore 2429 } finally { 2430 if (c != null) c.close(); 2431 } 2432 return null; 2433 } 2434 }.execute(); 2435 } 2436 2437 /** 2438 * Values for the size of the thumbnail created when taking a screenshot. 2439 * Lazily initialized. Instead of using these directly, use 2440 * getDesiredThumbnailWidth() or getDesiredThumbnailHeight(). 2441 */ 2442 private static int THUMBNAIL_WIDTH = 0; 2443 private static int THUMBNAIL_HEIGHT = 0; 2444 2445 /** 2446 * Return the desired width for thumbnail screenshots, which are stored in 2447 * the database, and used on the bookmarks screen. 2448 * @param context Context for finding out the density of the screen. 2449 * @return int desired width for thumbnail screenshot. 2450 */ getDesiredThumbnailWidth(Context context)2451 /* package */ static int getDesiredThumbnailWidth(Context context) { 2452 if (THUMBNAIL_WIDTH == 0) { 2453 float density = context.getResources().getDisplayMetrics().density; 2454 THUMBNAIL_WIDTH = (int) (90 * density); 2455 THUMBNAIL_HEIGHT = (int) (80 * density); 2456 } 2457 return THUMBNAIL_WIDTH; 2458 } 2459 2460 /** 2461 * Return the desired height for thumbnail screenshots, which are stored in 2462 * the database, and used on the bookmarks screen. 2463 * @param context Context for finding out the density of the screen. 2464 * @return int desired height for thumbnail screenshot. 2465 */ getDesiredThumbnailHeight(Context context)2466 /* package */ static int getDesiredThumbnailHeight(Context context) { 2467 // To ensure that they are both initialized. 2468 getDesiredThumbnailWidth(context); 2469 return THUMBNAIL_HEIGHT; 2470 } 2471 createScreenshot(WebView view)2472 private Bitmap createScreenshot(WebView view) { 2473 Picture thumbnail = view.capturePicture(); 2474 if (thumbnail == null) { 2475 return null; 2476 } 2477 Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this), 2478 getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565); 2479 Canvas canvas = new Canvas(bm); 2480 // May need to tweak these values to determine what is the 2481 // best scale factor 2482 int thumbnailWidth = thumbnail.getWidth(); 2483 int thumbnailHeight = thumbnail.getHeight(); 2484 float scaleFactorX = 1.0f; 2485 float scaleFactorY = 1.0f; 2486 if (thumbnailWidth > 0) { 2487 scaleFactorX = (float) getDesiredThumbnailWidth(this) / 2488 (float)thumbnailWidth; 2489 } else { 2490 return null; 2491 } 2492 2493 if (view.getWidth() > view.getHeight() && 2494 thumbnailHeight < view.getHeight() && thumbnailHeight > 0) { 2495 // If the device is in landscape and the page is shorter 2496 // than the height of the view, stretch the thumbnail to fill the 2497 // space. 2498 scaleFactorY = (float) getDesiredThumbnailHeight(this) / 2499 (float)thumbnailHeight; 2500 } else { 2501 // In the portrait case, this looks nice. 2502 scaleFactorY = scaleFactorX; 2503 } 2504 2505 canvas.scale(scaleFactorX, scaleFactorY); 2506 2507 thumbnail.draw(canvas); 2508 return bm; 2509 } 2510 2511 // ------------------------------------------------------------------------- 2512 // Helper function for WebViewClient. 2513 //------------------------------------------------------------------------- 2514 2515 // Use in overrideUrlLoading 2516 /* package */ final static String SCHEME_WTAI = "wtai://wp/"; 2517 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;"; 2518 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;"; 2519 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;"; 2520 2521 // Keep this initial progress in sync with initialProgressValue (* 100) 2522 // in ProgressTracker.cpp 2523 private final static int INITIAL_PROGRESS = 10; 2524 onPageStarted(WebView view, String url, Bitmap favicon)2525 void onPageStarted(WebView view, String url, Bitmap favicon) { 2526 // when BrowserActivity just starts, onPageStarted may be called before 2527 // onResume as it is triggered from onCreate. Call resumeWebViewTimers 2528 // to start the timer. As we won't switch tabs while an activity is in 2529 // pause state, we can ensure calling resume and pause in pair. 2530 if (mActivityInPause) resumeWebViewTimers(); 2531 2532 resetLockIcon(url); 2533 setUrlTitle(url, null); 2534 setFavicon(favicon); 2535 // Show some progress so that the user knows the page is beginning to 2536 // load 2537 onProgressChanged(view, INITIAL_PROGRESS); 2538 mDidStopLoad = false; 2539 if (!mIsNetworkUp) createAndShowNetworkDialog(); 2540 closeDialogs(); 2541 if (mSettings.isTracing()) { 2542 String host; 2543 try { 2544 WebAddress uri = new WebAddress(url); 2545 host = uri.mHost; 2546 } catch (android.net.ParseException ex) { 2547 host = "browser"; 2548 } 2549 host = host.replace('.', '_'); 2550 host += ".trace"; 2551 mInTrace = true; 2552 Debug.startMethodTracing(host, 20 * 1024 * 1024); 2553 } 2554 2555 // Performance probe 2556 if (false) { 2557 mStart = SystemClock.uptimeMillis(); 2558 mProcessStart = Process.getElapsedCpuTime(); 2559 long[] sysCpu = new long[7]; 2560 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2561 sysCpu, null)) { 2562 mUserStart = sysCpu[0] + sysCpu[1]; 2563 mSystemStart = sysCpu[2]; 2564 mIdleStart = sysCpu[3]; 2565 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6]; 2566 } 2567 mUiStart = SystemClock.currentThreadTimeMillis(); 2568 } 2569 } 2570 onPageFinished(WebView view, String url)2571 void onPageFinished(WebView view, String url) { 2572 // Reset the title and icon in case we stopped a provisional load. 2573 resetTitleAndIcon(view); 2574 // Update the lock icon image only once we are done loading 2575 updateLockIconToLatest(); 2576 // pause the WebView timer and release the wake lock if it is finished 2577 // while BrowserActivity is in pause state. 2578 if (mActivityInPause && pauseWebViewTimers()) { 2579 if (mWakeLock.isHeld()) { 2580 mHandler.removeMessages(RELEASE_WAKELOCK); 2581 mWakeLock.release(); 2582 } 2583 } 2584 2585 // Performance probe 2586 if (false) { 2587 long[] sysCpu = new long[7]; 2588 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null, 2589 sysCpu, null)) { 2590 String uiInfo = "UI thread used " 2591 + (SystemClock.currentThreadTimeMillis() - mUiStart) 2592 + " ms"; 2593 if (LOGD_ENABLED) { 2594 Log.d(LOGTAG, uiInfo); 2595 } 2596 //The string that gets written to the log 2597 String performanceString = "It took total " 2598 + (SystemClock.uptimeMillis() - mStart) 2599 + " ms clock time to load the page." 2600 + "\nbrowser process used " 2601 + (Process.getElapsedCpuTime() - mProcessStart) 2602 + " ms, user processes used " 2603 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10 2604 + " ms, kernel used " 2605 + (sysCpu[2] - mSystemStart) * 10 2606 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10 2607 + " ms and irq took " 2608 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart) 2609 * 10 + " ms, " + uiInfo; 2610 if (LOGD_ENABLED) { 2611 Log.d(LOGTAG, performanceString + "\nWebpage: " + url); 2612 } 2613 if (url != null) { 2614 // strip the url to maintain consistency 2615 String newUrl = new String(url); 2616 if (newUrl.startsWith("http://www.")) { 2617 newUrl = newUrl.substring(11); 2618 } else if (newUrl.startsWith("http://")) { 2619 newUrl = newUrl.substring(7); 2620 } else if (newUrl.startsWith("https://www.")) { 2621 newUrl = newUrl.substring(12); 2622 } else if (newUrl.startsWith("https://")) { 2623 newUrl = newUrl.substring(8); 2624 } 2625 if (LOGD_ENABLED) { 2626 Log.d(LOGTAG, newUrl + " loaded"); 2627 } 2628 } 2629 } 2630 } 2631 2632 if (mInTrace) { 2633 mInTrace = false; 2634 Debug.stopMethodTracing(); 2635 } 2636 } 2637 shouldOverrideUrlLoading(WebView view, String url)2638 boolean shouldOverrideUrlLoading(WebView view, String url) { 2639 if (url.startsWith(SCHEME_WTAI)) { 2640 // wtai://wp/mc;number 2641 // number=string(phone-number) 2642 if (url.startsWith(SCHEME_WTAI_MC)) { 2643 Intent intent = new Intent(Intent.ACTION_VIEW, 2644 Uri.parse(WebView.SCHEME_TEL + 2645 url.substring(SCHEME_WTAI_MC.length()))); 2646 startActivity(intent); 2647 return true; 2648 } 2649 // wtai://wp/sd;dtmf 2650 // dtmf=string(dialstring) 2651 if (url.startsWith(SCHEME_WTAI_SD)) { 2652 // TODO: only send when there is active voice connection 2653 return false; 2654 } 2655 // wtai://wp/ap;number;name 2656 // number=string(phone-number) 2657 // name=string 2658 if (url.startsWith(SCHEME_WTAI_AP)) { 2659 // TODO 2660 return false; 2661 } 2662 } 2663 2664 // The "about:" schemes are internal to the browser; don't want these to 2665 // be dispatched to other apps. Similarly, javascript: schemas are private 2666 // to the page 2667 if (url.startsWith("about:") || url.startsWith("javascript:")) { 2668 return false; 2669 } 2670 2671 Intent intent; 2672 // perform generic parsing of the URI to turn it into an Intent. 2673 try { 2674 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); 2675 } catch (URISyntaxException ex) { 2676 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage()); 2677 return false; 2678 } 2679 2680 // check whether the intent can be resolved. If not, we will see 2681 // whether we can download it from the Market. 2682 if (getPackageManager().resolveActivity(intent, 0) == null) { 2683 String packagename = intent.getPackage(); 2684 if (packagename != null) { 2685 intent = new Intent(Intent.ACTION_VIEW, Uri 2686 .parse("market://search?q=pname:" + packagename)); 2687 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2688 startActivity(intent); 2689 return true; 2690 } else { 2691 return false; 2692 } 2693 } 2694 2695 // sanitize the Intent, ensuring web pages can not bypass browser 2696 // security (only access to BROWSABLE activities). 2697 intent.addCategory(Intent.CATEGORY_BROWSABLE); 2698 intent.setComponent(null); 2699 try { 2700 if (startActivityIfNeeded(intent, -1)) { 2701 return true; 2702 } 2703 } catch (ActivityNotFoundException ex) { 2704 // ignore the error. If no application can handle the URL, 2705 // eg about:blank, assume the browser can handle it. 2706 } 2707 2708 if (mMenuIsDown) { 2709 openTab(url); 2710 closeOptionsMenu(); 2711 return true; 2712 } 2713 return false; 2714 } 2715 2716 // ------------------------------------------------------------------------- 2717 // Helper function for WebChromeClient 2718 // ------------------------------------------------------------------------- 2719 onProgressChanged(WebView view, int newProgress)2720 void onProgressChanged(WebView view, int newProgress) { 2721 mFakeTitleBar.setProgress(newProgress); 2722 2723 if (newProgress == 100) { 2724 // onProgressChanged() may continue to be called after the main 2725 // frame has finished loading, as any remaining sub frames continue 2726 // to load. We'll only get called once though with newProgress as 2727 // 100 when everything is loaded. (onPageFinished is called once 2728 // when the main frame completes loading regardless of the state of 2729 // any sub frames so calls to onProgressChanges may continue after 2730 // onPageFinished has executed) 2731 if (mInLoad) { 2732 mInLoad = false; 2733 updateInLoadMenuItems(); 2734 // If the options menu is open, leave the title bar 2735 if (!mOptionsMenuOpen || !mIconView) { 2736 hideFakeTitleBar(); 2737 } 2738 } 2739 } else { 2740 if (!mInLoad) { 2741 // onPageFinished may have already been called but a subframe is 2742 // still loading and updating the progress. Reset mInLoad and 2743 // update the menu items. 2744 mInLoad = true; 2745 updateInLoadMenuItems(); 2746 } 2747 // When the page first begins to load, the Activity may still be 2748 // paused, in which case showFakeTitleBar will do nothing. Call 2749 // again as the page continues to load so that it will be shown. 2750 // (Calling it will the fake title bar is already showing will also 2751 // do nothing. 2752 if (!mOptionsMenuOpen || mIconView) { 2753 // This page has begun to load, so show the title bar 2754 showFakeTitleBar(); 2755 } 2756 } 2757 } 2758 onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)2759 void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { 2760 // if a view already exists then immediately terminate the new one 2761 if (mCustomView != null) { 2762 callback.onCustomViewHidden(); 2763 return; 2764 } 2765 2766 // Add the custom view to its container. 2767 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); 2768 mCustomView = view; 2769 mCustomViewCallback = callback; 2770 // Save the menu state and set it to empty while the custom 2771 // view is showing. 2772 mOldMenuState = mMenuState; 2773 mMenuState = EMPTY_MENU; 2774 // Hide the content view. 2775 mContentView.setVisibility(View.GONE); 2776 // Finally show the custom view container. 2777 setStatusBarVisibility(false); 2778 mCustomViewContainer.setVisibility(View.VISIBLE); 2779 mCustomViewContainer.bringToFront(); 2780 } 2781 onHideCustomView()2782 void onHideCustomView() { 2783 if (mCustomView == null) 2784 return; 2785 // Hide the custom view. 2786 mCustomView.setVisibility(View.GONE); 2787 // Remove the custom view from its container. 2788 mCustomViewContainer.removeView(mCustomView); 2789 mCustomView = null; 2790 // Reset the old menu state. 2791 mMenuState = mOldMenuState; 2792 mOldMenuState = EMPTY_MENU; 2793 mCustomViewContainer.setVisibility(View.GONE); 2794 mCustomViewCallback.onCustomViewHidden(); 2795 // Show the content view. 2796 setStatusBarVisibility(true); 2797 mContentView.setVisibility(View.VISIBLE); 2798 } 2799 getDefaultVideoPoster()2800 Bitmap getDefaultVideoPoster() { 2801 if (mDefaultVideoPoster == null) { 2802 mDefaultVideoPoster = BitmapFactory.decodeResource( 2803 getResources(), R.drawable.default_video_poster); 2804 } 2805 return mDefaultVideoPoster; 2806 } 2807 getVideoLoadingProgressView()2808 View getVideoLoadingProgressView() { 2809 if (mVideoProgressView == null) { 2810 LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this); 2811 mVideoProgressView = inflater.inflate( 2812 R.layout.video_loading_progress, null); 2813 } 2814 return mVideoProgressView; 2815 } 2816 2817 /* 2818 * The Object used to inform the WebView of the file to upload. 2819 */ 2820 private ValueCallback<Uri> mUploadMessage; 2821 openFileChooser(ValueCallback<Uri> uploadMsg)2822 void openFileChooser(ValueCallback<Uri> uploadMsg) { 2823 if (mUploadMessage != null) return; 2824 mUploadMessage = uploadMsg; 2825 Intent i = new Intent(Intent.ACTION_GET_CONTENT); 2826 i.addCategory(Intent.CATEGORY_OPENABLE); 2827 i.setType("*/*"); 2828 BrowserActivity.this.startActivityForResult(Intent.createChooser(i, 2829 getString(R.string.choose_upload)), FILE_SELECTED); 2830 } 2831 2832 // ------------------------------------------------------------------------- 2833 // Implement functions for DownloadListener 2834 // ------------------------------------------------------------------------- 2835 2836 /** 2837 * Notify the host application a download should be done, or that 2838 * the data should be streamed if a streaming viewer is available. 2839 * @param url The full url to the content that should be downloaded 2840 * @param contentDisposition Content-disposition http header, if 2841 * present. 2842 * @param mimetype The mimetype of the content reported by the server 2843 * @param contentLength The file size reported by the server 2844 */ onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength)2845 public void onDownloadStart(String url, String userAgent, 2846 String contentDisposition, String mimetype, long contentLength) { 2847 // if we're dealing wih A/V content that's not explicitly marked 2848 // for download, check if it's streamable. 2849 if (contentDisposition == null 2850 || !contentDisposition.regionMatches( 2851 true, 0, "attachment", 0, 10)) { 2852 // query the package manager to see if there's a registered handler 2853 // that matches. 2854 Intent intent = new Intent(Intent.ACTION_VIEW); 2855 intent.setDataAndType(Uri.parse(url), mimetype); 2856 ResolveInfo info = getPackageManager().resolveActivity(intent, 2857 PackageManager.MATCH_DEFAULT_ONLY); 2858 if (info != null) { 2859 ComponentName myName = getComponentName(); 2860 // If we resolved to ourselves, we don't want to attempt to 2861 // load the url only to try and download it again. 2862 if (!myName.getPackageName().equals( 2863 info.activityInfo.packageName) 2864 || !myName.getClassName().equals( 2865 info.activityInfo.name)) { 2866 // someone (other than us) knows how to handle this mime 2867 // type with this scheme, don't download. 2868 try { 2869 startActivity(intent); 2870 return; 2871 } catch (ActivityNotFoundException ex) { 2872 if (LOGD_ENABLED) { 2873 Log.d(LOGTAG, "activity not found for " + mimetype 2874 + " over " + Uri.parse(url).getScheme(), 2875 ex); 2876 } 2877 // Best behavior is to fall back to a download in this 2878 // case 2879 } 2880 } 2881 } 2882 } 2883 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength); 2884 } 2885 2886 // This is to work around the fact that java.net.URI throws Exceptions 2887 // instead of just encoding URL's properly 2888 // Helper method for onDownloadStartNoStream encodePath(String path)2889 private static String encodePath(String path) { 2890 char[] chars = path.toCharArray(); 2891 2892 boolean needed = false; 2893 for (char c : chars) { 2894 if (c == '[' || c == ']') { 2895 needed = true; 2896 break; 2897 } 2898 } 2899 if (needed == false) { 2900 return path; 2901 } 2902 2903 StringBuilder sb = new StringBuilder(""); 2904 for (char c : chars) { 2905 if (c == '[' || c == ']') { 2906 sb.append('%'); 2907 sb.append(Integer.toHexString(c)); 2908 } else { 2909 sb.append(c); 2910 } 2911 } 2912 2913 return sb.toString(); 2914 } 2915 2916 /** 2917 * Notify the host application a download should be done, even if there 2918 * is a streaming viewer available for thise type. 2919 * @param url The full url to the content that should be downloaded 2920 * @param contentDisposition Content-disposition http header, if 2921 * present. 2922 * @param mimetype The mimetype of the content reported by the server 2923 * @param contentLength The file size reported by the server 2924 */ onDownloadStartNoStream(String url, String userAgent, String contentDisposition, String mimetype, long contentLength)2925 /*package */ void onDownloadStartNoStream(String url, String userAgent, 2926 String contentDisposition, String mimetype, long contentLength) { 2927 2928 String filename = URLUtil.guessFileName(url, 2929 contentDisposition, mimetype); 2930 2931 // Check to see if we have an SDCard 2932 String status = Environment.getExternalStorageState(); 2933 if (!status.equals(Environment.MEDIA_MOUNTED)) { 2934 int title; 2935 String msg; 2936 2937 // Check to see if the SDCard is busy, same as the music app 2938 if (status.equals(Environment.MEDIA_SHARED)) { 2939 msg = getString(R.string.download_sdcard_busy_dlg_msg); 2940 title = R.string.download_sdcard_busy_dlg_title; 2941 } else { 2942 msg = getString(R.string.download_no_sdcard_dlg_msg, filename); 2943 title = R.string.download_no_sdcard_dlg_title; 2944 } 2945 2946 new AlertDialog.Builder(this) 2947 .setTitle(title) 2948 .setIcon(android.R.drawable.ic_dialog_alert) 2949 .setMessage(msg) 2950 .setPositiveButton(R.string.ok, null) 2951 .show(); 2952 return; 2953 } 2954 2955 // java.net.URI is a lot stricter than KURL so we have to encode some 2956 // extra characters. Fix for b 2538060 and b 1634719 2957 WebAddress webAddress; 2958 try { 2959 webAddress = new WebAddress(url); 2960 webAddress.mPath = encodePath(webAddress.mPath); 2961 } catch (Exception e) { 2962 // This only happens for very bad urls, we want to chatch the 2963 // exception here 2964 Log.e(LOGTAG, "Exception trying to parse url:" + url); 2965 return; 2966 } 2967 2968 // XXX: Have to use the old url since the cookies were stored using the 2969 // old percent-encoded url. 2970 String cookies = CookieManager.getInstance().getCookie(url); 2971 2972 ContentValues values = new ContentValues(); 2973 values.put(Downloads.Impl.COLUMN_URI, webAddress.toString()); 2974 values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies); 2975 values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent); 2976 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, 2977 getPackageName()); 2978 values.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, 2979 OpenDownloadReceiver.class.getCanonicalName()); 2980 values.put(Downloads.Impl.COLUMN_VISIBILITY, 2981 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 2982 values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype); 2983 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename); 2984 values.put(Downloads.Impl.COLUMN_DESCRIPTION, webAddress.mHost); 2985 if (contentLength > 0) { 2986 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength); 2987 } 2988 if (mimetype == null) { 2989 // We must have long pressed on a link or image to download it. We 2990 // are not sure of the mimetype in this case, so do a head request 2991 new FetchUrlMimeType(this).execute(values); 2992 } else { 2993 final Uri contentUri = 2994 getContentResolver().insert(Downloads.Impl.CONTENT_URI, values); 2995 } 2996 Toast.makeText(this, R.string.download_pending, Toast.LENGTH_SHORT) 2997 .show(); 2998 } 2999 3000 // ------------------------------------------------------------------------- 3001 3002 /** 3003 * Resets the lock icon. This method is called when we start a new load and 3004 * know the url to be loaded. 3005 */ resetLockIcon(String url)3006 private void resetLockIcon(String url) { 3007 // Save the lock-icon state (we revert to it if the load gets cancelled) 3008 mTabControl.getCurrentTab().resetLockIcon(url); 3009 updateLockIconImage(LOCK_ICON_UNSECURE); 3010 } 3011 3012 /** 3013 * Update the lock icon to correspond to our latest state. 3014 */ updateLockIconToLatest()3015 private void updateLockIconToLatest() { 3016 updateLockIconImage(mTabControl.getCurrentTab().getLockIconType()); 3017 } 3018 3019 /** 3020 * Updates the lock-icon image in the title-bar. 3021 */ updateLockIconImage(int lockIconType)3022 private void updateLockIconImage(int lockIconType) { 3023 Drawable d = null; 3024 if (lockIconType == LOCK_ICON_SECURE) { 3025 d = mSecLockIcon; 3026 } else if (lockIconType == LOCK_ICON_MIXED) { 3027 d = mMixLockIcon; 3028 } 3029 mTitleBar.setLock(d); 3030 mFakeTitleBar.setLock(d); 3031 } 3032 3033 /** 3034 * Displays a page-info dialog. 3035 * @param tab The tab to show info about 3036 * @param fromShowSSLCertificateOnError The flag that indicates whether 3037 * this dialog was opened from the SSL-certificate-on-error dialog or 3038 * not. This is important, since we need to know whether to return to 3039 * the parent dialog or simply dismiss. 3040 */ showPageInfo(final Tab tab, final boolean fromShowSSLCertificateOnError)3041 private void showPageInfo(final Tab tab, 3042 final boolean fromShowSSLCertificateOnError) { 3043 final LayoutInflater factory = LayoutInflater 3044 .from(this); 3045 3046 final View pageInfoView = factory.inflate(R.layout.page_info, null); 3047 3048 final WebView view = tab.getWebView(); 3049 3050 String url = null; 3051 String title = null; 3052 3053 if (view == null) { 3054 url = tab.getUrl(); 3055 title = tab.getTitle(); 3056 } else if (view == mTabControl.getCurrentWebView()) { 3057 // Use the cached title and url if this is the current WebView 3058 url = mUrl; 3059 title = mTitle; 3060 } else { 3061 url = view.getUrl(); 3062 title = view.getTitle(); 3063 } 3064 3065 if (url == null) { 3066 url = ""; 3067 } 3068 if (title == null) { 3069 title = ""; 3070 } 3071 3072 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url); 3073 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title); 3074 3075 mPageInfoView = tab; 3076 mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError; 3077 3078 AlertDialog.Builder alertDialogBuilder = 3079 new AlertDialog.Builder(this) 3080 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info) 3081 .setView(pageInfoView) 3082 .setPositiveButton( 3083 R.string.ok, 3084 new DialogInterface.OnClickListener() { 3085 public void onClick(DialogInterface dialog, 3086 int whichButton) { 3087 mPageInfoDialog = null; 3088 mPageInfoView = null; 3089 3090 // if we came here from the SSL error dialog 3091 if (fromShowSSLCertificateOnError) { 3092 // go back to the SSL error dialog 3093 showSSLCertificateOnError( 3094 mSSLCertificateOnErrorView, 3095 mSSLCertificateOnErrorHandler, 3096 mSSLCertificateOnErrorError); 3097 } 3098 } 3099 }) 3100 .setOnCancelListener( 3101 new DialogInterface.OnCancelListener() { 3102 public void onCancel(DialogInterface dialog) { 3103 mPageInfoDialog = null; 3104 mPageInfoView = null; 3105 3106 // if we came here from the SSL error dialog 3107 if (fromShowSSLCertificateOnError) { 3108 // go back to the SSL error dialog 3109 showSSLCertificateOnError( 3110 mSSLCertificateOnErrorView, 3111 mSSLCertificateOnErrorHandler, 3112 mSSLCertificateOnErrorError); 3113 } 3114 } 3115 }); 3116 3117 // if we have a main top-level page SSL certificate set or a certificate 3118 // error 3119 if (fromShowSSLCertificateOnError || 3120 (view != null && view.getCertificate() != null)) { 3121 // add a 'View Certificate' button 3122 alertDialogBuilder.setNeutralButton( 3123 R.string.view_certificate, 3124 new DialogInterface.OnClickListener() { 3125 public void onClick(DialogInterface dialog, 3126 int whichButton) { 3127 mPageInfoDialog = null; 3128 mPageInfoView = null; 3129 3130 // if we came here from the SSL error dialog 3131 if (fromShowSSLCertificateOnError) { 3132 // go back to the SSL error dialog 3133 showSSLCertificateOnError( 3134 mSSLCertificateOnErrorView, 3135 mSSLCertificateOnErrorHandler, 3136 mSSLCertificateOnErrorError); 3137 } else { 3138 // otherwise, display the top-most certificate from 3139 // the chain 3140 if (view.getCertificate() != null) { 3141 showSSLCertificate(tab); 3142 } 3143 } 3144 } 3145 }); 3146 } 3147 3148 mPageInfoDialog = alertDialogBuilder.show(); 3149 } 3150 3151 /** 3152 * Displays the main top-level page SSL certificate dialog 3153 * (accessible from the Page-Info dialog). 3154 * @param tab The tab to show certificate for. 3155 */ showSSLCertificate(final Tab tab)3156 private void showSSLCertificate(final Tab tab) { 3157 final View certificateView = 3158 inflateCertificateView(tab.getWebView().getCertificate()); 3159 if (certificateView == null) { 3160 return; 3161 } 3162 3163 LayoutInflater factory = LayoutInflater.from(this); 3164 3165 final LinearLayout placeholder = 3166 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3167 3168 LinearLayout ll = (LinearLayout) factory.inflate( 3169 R.layout.ssl_success, placeholder); 3170 ((TextView)ll.findViewById(R.id.success)) 3171 .setText(R.string.ssl_certificate_is_valid); 3172 3173 mSSLCertificateView = tab; 3174 mSSLCertificateDialog = 3175 new AlertDialog.Builder(this) 3176 .setTitle(R.string.ssl_certificate).setIcon( 3177 R.drawable.ic_dialog_browser_certificate_secure) 3178 .setView(certificateView) 3179 .setPositiveButton(R.string.ok, 3180 new DialogInterface.OnClickListener() { 3181 public void onClick(DialogInterface dialog, 3182 int whichButton) { 3183 mSSLCertificateDialog = null; 3184 mSSLCertificateView = null; 3185 3186 showPageInfo(tab, false); 3187 } 3188 }) 3189 .setOnCancelListener( 3190 new DialogInterface.OnCancelListener() { 3191 public void onCancel(DialogInterface dialog) { 3192 mSSLCertificateDialog = null; 3193 mSSLCertificateView = null; 3194 3195 showPageInfo(tab, false); 3196 } 3197 }) 3198 .show(); 3199 } 3200 3201 /** 3202 * Displays the SSL error certificate dialog. 3203 * @param view The target web-view. 3204 * @param handler The SSL error handler responsible for cancelling the 3205 * connection that resulted in an SSL error or proceeding per user request. 3206 * @param error The SSL error object. 3207 */ showSSLCertificateOnError( final WebView view, final SslErrorHandler handler, final SslError error)3208 void showSSLCertificateOnError( 3209 final WebView view, final SslErrorHandler handler, final SslError error) { 3210 3211 final View certificateView = 3212 inflateCertificateView(error.getCertificate()); 3213 if (certificateView == null) { 3214 return; 3215 } 3216 3217 LayoutInflater factory = LayoutInflater.from(this); 3218 3219 final LinearLayout placeholder = 3220 (LinearLayout)certificateView.findViewById(R.id.placeholder); 3221 3222 if (error.hasError(SslError.SSL_UNTRUSTED)) { 3223 LinearLayout ll = (LinearLayout)factory 3224 .inflate(R.layout.ssl_warning, placeholder); 3225 ((TextView)ll.findViewById(R.id.warning)) 3226 .setText(R.string.ssl_untrusted); 3227 } 3228 3229 if (error.hasError(SslError.SSL_IDMISMATCH)) { 3230 LinearLayout ll = (LinearLayout)factory 3231 .inflate(R.layout.ssl_warning, placeholder); 3232 ((TextView)ll.findViewById(R.id.warning)) 3233 .setText(R.string.ssl_mismatch); 3234 } 3235 3236 if (error.hasError(SslError.SSL_EXPIRED)) { 3237 LinearLayout ll = (LinearLayout)factory 3238 .inflate(R.layout.ssl_warning, placeholder); 3239 ((TextView)ll.findViewById(R.id.warning)) 3240 .setText(R.string.ssl_expired); 3241 } 3242 3243 if (error.hasError(SslError.SSL_NOTYETVALID)) { 3244 LinearLayout ll = (LinearLayout)factory 3245 .inflate(R.layout.ssl_warning, placeholder); 3246 ((TextView)ll.findViewById(R.id.warning)) 3247 .setText(R.string.ssl_not_yet_valid); 3248 } 3249 3250 mSSLCertificateOnErrorHandler = handler; 3251 mSSLCertificateOnErrorView = view; 3252 mSSLCertificateOnErrorError = error; 3253 mSSLCertificateOnErrorDialog = 3254 new AlertDialog.Builder(this) 3255 .setTitle(R.string.ssl_certificate).setIcon( 3256 R.drawable.ic_dialog_browser_certificate_partially_secure) 3257 .setView(certificateView) 3258 .setPositiveButton(R.string.ok, 3259 new DialogInterface.OnClickListener() { 3260 public void onClick(DialogInterface dialog, 3261 int whichButton) { 3262 mSSLCertificateOnErrorDialog = null; 3263 mSSLCertificateOnErrorView = null; 3264 mSSLCertificateOnErrorHandler = null; 3265 mSSLCertificateOnErrorError = null; 3266 3267 view.getWebViewClient().onReceivedSslError( 3268 view, handler, error); 3269 } 3270 }) 3271 .setNeutralButton(R.string.page_info_view, 3272 new DialogInterface.OnClickListener() { 3273 public void onClick(DialogInterface dialog, 3274 int whichButton) { 3275 mSSLCertificateOnErrorDialog = null; 3276 3277 // do not clear the dialog state: we will 3278 // need to show the dialog again once the 3279 // user is done exploring the page-info details 3280 3281 showPageInfo(mTabControl.getTabFromView(view), 3282 true); 3283 } 3284 }) 3285 .setOnCancelListener( 3286 new DialogInterface.OnCancelListener() { 3287 public void onCancel(DialogInterface dialog) { 3288 mSSLCertificateOnErrorDialog = null; 3289 mSSLCertificateOnErrorView = null; 3290 mSSLCertificateOnErrorHandler = null; 3291 mSSLCertificateOnErrorError = null; 3292 3293 view.getWebViewClient().onReceivedSslError( 3294 view, handler, error); 3295 } 3296 }) 3297 .show(); 3298 } 3299 3300 /** 3301 * Inflates the SSL certificate view (helper method). 3302 * @param certificate The SSL certificate. 3303 * @return The resultant certificate view with issued-to, issued-by, 3304 * issued-on, expires-on, and possibly other fields set. 3305 * If the input certificate is null, returns null. 3306 */ inflateCertificateView(SslCertificate certificate)3307 private View inflateCertificateView(SslCertificate certificate) { 3308 if (certificate == null) { 3309 return null; 3310 } 3311 3312 LayoutInflater factory = LayoutInflater.from(this); 3313 3314 View certificateView = factory.inflate( 3315 R.layout.ssl_certificate, null); 3316 3317 // issued to: 3318 SslCertificate.DName issuedTo = certificate.getIssuedTo(); 3319 if (issuedTo != null) { 3320 ((TextView) certificateView.findViewById(R.id.to_common)) 3321 .setText(issuedTo.getCName()); 3322 ((TextView) certificateView.findViewById(R.id.to_org)) 3323 .setText(issuedTo.getOName()); 3324 ((TextView) certificateView.findViewById(R.id.to_org_unit)) 3325 .setText(issuedTo.getUName()); 3326 } 3327 3328 // issued by: 3329 SslCertificate.DName issuedBy = certificate.getIssuedBy(); 3330 if (issuedBy != null) { 3331 ((TextView) certificateView.findViewById(R.id.by_common)) 3332 .setText(issuedBy.getCName()); 3333 ((TextView) certificateView.findViewById(R.id.by_org)) 3334 .setText(issuedBy.getOName()); 3335 ((TextView) certificateView.findViewById(R.id.by_org_unit)) 3336 .setText(issuedBy.getUName()); 3337 } 3338 3339 // issued on: 3340 String issuedOn = formatCertificateDate( 3341 certificate.getValidNotBeforeDate()); 3342 ((TextView) certificateView.findViewById(R.id.issued_on)) 3343 .setText(issuedOn); 3344 3345 // expires on: 3346 String expiresOn = formatCertificateDate( 3347 certificate.getValidNotAfterDate()); 3348 ((TextView) certificateView.findViewById(R.id.expires_on)) 3349 .setText(expiresOn); 3350 3351 return certificateView; 3352 } 3353 3354 /** 3355 * Formats the certificate date to a properly localized date string. 3356 * @return Properly localized version of the certificate date string and 3357 * the "" if it fails to localize. 3358 */ formatCertificateDate(Date certificateDate)3359 private String formatCertificateDate(Date certificateDate) { 3360 if (certificateDate == null) { 3361 return ""; 3362 } 3363 String formattedDate = DateFormat.getDateFormat(this).format(certificateDate); 3364 if (formattedDate == null) { 3365 return ""; 3366 } 3367 return formattedDate; 3368 } 3369 3370 /** 3371 * Displays an http-authentication dialog. 3372 */ showHttpAuthentication(final HttpAuthHandler handler, final String host, final String realm, final String title, final String name, final String password, int focusId)3373 void showHttpAuthentication(final HttpAuthHandler handler, 3374 final String host, final String realm, final String title, 3375 final String name, final String password, int focusId) { 3376 LayoutInflater factory = LayoutInflater.from(this); 3377 final View v = factory 3378 .inflate(R.layout.http_authentication, null); 3379 if (name != null) { 3380 ((EditText) v.findViewById(R.id.username_edit)).setText(name); 3381 } 3382 if (password != null) { 3383 ((EditText) v.findViewById(R.id.password_edit)).setText(password); 3384 } 3385 3386 String titleText = title; 3387 if (titleText == null) { 3388 titleText = getText(R.string.sign_in_to).toString().replace( 3389 "%s1", host).replace("%s2", realm); 3390 } 3391 3392 mHttpAuthHandler = handler; 3393 AlertDialog dialog = new AlertDialog.Builder(this) 3394 .setTitle(titleText) 3395 .setIcon(android.R.drawable.ic_dialog_alert) 3396 .setView(v) 3397 .setPositiveButton(R.string.action, 3398 new DialogInterface.OnClickListener() { 3399 public void onClick(DialogInterface dialog, 3400 int whichButton) { 3401 String nm = ((EditText) v 3402 .findViewById(R.id.username_edit)) 3403 .getText().toString(); 3404 String pw = ((EditText) v 3405 .findViewById(R.id.password_edit)) 3406 .getText().toString(); 3407 BrowserActivity.this.setHttpAuthUsernamePassword 3408 (host, realm, nm, pw); 3409 handler.proceed(nm, pw); 3410 mHttpAuthenticationDialog = null; 3411 mHttpAuthHandler = null; 3412 }}) 3413 .setNegativeButton(R.string.cancel, 3414 new DialogInterface.OnClickListener() { 3415 public void onClick(DialogInterface dialog, 3416 int whichButton) { 3417 handler.cancel(); 3418 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3419 mHttpAuthenticationDialog = null; 3420 mHttpAuthHandler = null; 3421 }}) 3422 .setOnCancelListener(new DialogInterface.OnCancelListener() { 3423 public void onCancel(DialogInterface dialog) { 3424 handler.cancel(); 3425 BrowserActivity.this.resetTitleAndRevertLockIcon(); 3426 mHttpAuthenticationDialog = null; 3427 mHttpAuthHandler = null; 3428 }}) 3429 .create(); 3430 // Make the IME appear when the dialog is displayed if applicable. 3431 dialog.getWindow().setSoftInputMode( 3432 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 3433 dialog.show(); 3434 if (focusId != 0) { 3435 dialog.findViewById(focusId).requestFocus(); 3436 } else { 3437 v.findViewById(R.id.username_edit).requestFocus(); 3438 } 3439 mHttpAuthenticationDialog = dialog; 3440 } 3441 getProgress()3442 public int getProgress() { 3443 WebView w = mTabControl.getCurrentWebView(); 3444 if (w != null) { 3445 return w.getProgress(); 3446 } else { 3447 return 100; 3448 } 3449 } 3450 3451 /** 3452 * Set HTTP authentication password. 3453 * 3454 * @param host The host for the password 3455 * @param realm The realm for the password 3456 * @param username The username for the password. If it is null, it means 3457 * password can't be saved. 3458 * @param password The password 3459 */ setHttpAuthUsernamePassword(String host, String realm, String username, String password)3460 public void setHttpAuthUsernamePassword(String host, String realm, 3461 String username, 3462 String password) { 3463 WebView w = getTopWindow(); 3464 if (w != null) { 3465 w.setHttpAuthUsernamePassword(host, realm, username, password); 3466 } 3467 } 3468 3469 /** 3470 * connectivity manager says net has come or gone... inform the user 3471 * @param up true if net has come up, false if net has gone down 3472 */ onNetworkToggle(boolean up)3473 public void onNetworkToggle(boolean up) { 3474 if (up == mIsNetworkUp) { 3475 return; 3476 } else if (up) { 3477 mIsNetworkUp = true; 3478 if (mAlertDialog != null) { 3479 mAlertDialog.cancel(); 3480 mAlertDialog = null; 3481 } 3482 } else { 3483 mIsNetworkUp = false; 3484 if (mInLoad) { 3485 createAndShowNetworkDialog(); 3486 } 3487 } 3488 WebView w = mTabControl.getCurrentWebView(); 3489 if (w != null) { 3490 w.setNetworkAvailable(up); 3491 } 3492 } 3493 isNetworkUp()3494 boolean isNetworkUp() { 3495 return mIsNetworkUp; 3496 } 3497 3498 // This method shows the network dialog alerting the user that the net is 3499 // down. It will only show the dialog if mAlertDialog is null. createAndShowNetworkDialog()3500 private void createAndShowNetworkDialog() { 3501 if (mAlertDialog == null) { 3502 mAlertDialog = new AlertDialog.Builder(this) 3503 .setTitle(R.string.loadSuspendedTitle) 3504 .setMessage(R.string.loadSuspended) 3505 .setPositiveButton(R.string.ok, null) 3506 .show(); 3507 } 3508 } 3509 3510 @Override onActivityResult(int requestCode, int resultCode, Intent intent)3511 protected void onActivityResult(int requestCode, int resultCode, 3512 Intent intent) { 3513 if (getTopWindow() == null) return; 3514 3515 switch (requestCode) { 3516 case COMBO_PAGE: 3517 if (resultCode == RESULT_OK && intent != null) { 3518 String data = intent.getAction(); 3519 Bundle extras = intent.getExtras(); 3520 if (extras != null && extras.getBoolean("new_window", false)) { 3521 openTab(data); 3522 } else { 3523 final Tab currentTab = 3524 mTabControl.getCurrentTab(); 3525 dismissSubWindow(currentTab); 3526 if (data != null && data.length() != 0) { 3527 loadUrl(getTopWindow(), data); 3528 } 3529 } 3530 } 3531 // Deliberately fall through to PREFERENCES_PAGE, since the 3532 // same extra may be attached to the COMBO_PAGE 3533 case PREFERENCES_PAGE: 3534 if (resultCode == RESULT_OK && intent != null) { 3535 String action = intent.getStringExtra(Intent.EXTRA_TEXT); 3536 if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) { 3537 mTabControl.removeParentChildRelationShips(); 3538 } 3539 } 3540 break; 3541 // Choose a file from the file picker. 3542 case FILE_SELECTED: 3543 if (null == mUploadMessage) break; 3544 Uri result = intent == null || resultCode != RESULT_OK ? null 3545 : intent.getData(); 3546 mUploadMessage.onReceiveValue(result); 3547 mUploadMessage = null; 3548 break; 3549 default: 3550 break; 3551 } 3552 getTopWindow().requestFocus(); 3553 } 3554 3555 /* 3556 * This method is called as a result of the user selecting the options 3557 * menu to see the download window. It shows the download window on top of 3558 * the current window. 3559 */ viewDownloads()3560 private void viewDownloads() { 3561 Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); 3562 startActivity(intent); 3563 } 3564 3565 /** 3566 * Open the Go page. 3567 * @param startWithHistory If true, open starting on the history tab. 3568 * Otherwise, start with the bookmarks tab. 3569 */ bookmarksOrHistoryPicker(boolean startWithHistory)3570 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) { 3571 WebView current = mTabControl.getCurrentWebView(); 3572 if (current == null) { 3573 return; 3574 } 3575 Intent intent = new Intent(this, 3576 CombinedBookmarkHistoryActivity.class); 3577 String title = current.getTitle(); 3578 String url = current.getUrl(); 3579 Bitmap thumbnail = createScreenshot(current); 3580 3581 // Just in case the user opens bookmarks before a page finishes loading 3582 // so the current history item, and therefore the page, is null. 3583 if (null == url) { 3584 url = mLastEnteredUrl; 3585 // This can happen. 3586 if (null == url) { 3587 url = mSettings.getHomePage(); 3588 } 3589 } 3590 // In case the web page has not yet received its associated title. 3591 if (title == null) { 3592 title = url; 3593 } 3594 intent.putExtra("title", title); 3595 intent.putExtra("url", url); 3596 intent.putExtra("thumbnail", thumbnail); 3597 // Disable opening in a new window if we have maxed out the windows 3598 intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab()); 3599 intent.putExtra("touch_icon_url", current.getTouchIconUrl()); 3600 if (startWithHistory) { 3601 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB, 3602 CombinedBookmarkHistoryActivity.HISTORY_TAB); 3603 } 3604 startActivityForResult(intent, COMBO_PAGE); 3605 } 3606 3607 // Called when loading from context menu or LOAD_URL message loadUrlFromContext(WebView view, String url)3608 private void loadUrlFromContext(WebView view, String url) { 3609 // In case the user enters nothing. 3610 if (url != null && url.length() != 0 && view != null) { 3611 url = smartUrlFilter(url); 3612 if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) { 3613 loadUrl(view, url); 3614 } 3615 } 3616 } 3617 3618 /** 3619 * Load the URL into the given WebView and update the title bar 3620 * to reflect the new load. Call this instead of WebView.loadUrl 3621 * directly. 3622 * @param view The WebView used to load url. 3623 * @param url The URL to load. 3624 */ loadUrl(WebView view, String url)3625 private void loadUrl(WebView view, String url) { 3626 updateTitleBarForNewLoad(view, url); 3627 view.loadUrl(url); 3628 } 3629 3630 /** 3631 * Load UrlData into a Tab and update the title bar to reflect the new 3632 * load. Call this instead of UrlData.loadIn directly. 3633 * @param t The Tab used to load. 3634 * @param data The UrlData being loaded. 3635 */ loadUrlDataIn(Tab t, UrlData data)3636 private void loadUrlDataIn(Tab t, UrlData data) { 3637 updateTitleBarForNewLoad(t.getWebView(), data.mUrl); 3638 data.loadIn(t); 3639 } 3640 3641 /** 3642 * If the WebView is the top window, update the title bar to reflect 3643 * loading the new URL. i.e. set its text, clear the favicon (which 3644 * will be set once the page begins loading), and set the progress to 3645 * INITIAL_PROGRESS to show that the page has begun to load. Called 3646 * by loadUrl and loadUrlDataIn. 3647 * @param view The WebView that is starting a load. 3648 * @param url The URL that is being loaded. 3649 */ updateTitleBarForNewLoad(WebView view, String url)3650 private void updateTitleBarForNewLoad(WebView view, String url) { 3651 if (view == getTopWindow()) { 3652 setUrlTitle(url, null); 3653 setFavicon(null); 3654 onProgressChanged(view, INITIAL_PROGRESS); 3655 } 3656 } 3657 smartUrlFilter(Uri inUri)3658 private String smartUrlFilter(Uri inUri) { 3659 if (inUri != null) { 3660 return smartUrlFilter(inUri.toString()); 3661 } 3662 return null; 3663 } 3664 3665 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( 3666 "(?i)" + // switch on case insensitive matching 3667 "(" + // begin group for schema 3668 "(?:http|https|file):\\/\\/" + 3669 "|(?:inline|data|about|javascript):" + 3670 ")" + 3671 "(.*)" ); 3672 3673 /** 3674 * Attempts to determine whether user input is a URL or search 3675 * terms. Anything with a space is passed to search. 3676 * 3677 * Converts to lowercase any mistakenly uppercased schema (i.e., 3678 * "Http://" converts to "http://" 3679 * 3680 * @return Original or modified URL 3681 * 3682 */ smartUrlFilter(String url)3683 String smartUrlFilter(String url) { 3684 3685 String inUrl = url.trim(); 3686 boolean hasSpace = inUrl.indexOf(' ') != -1; 3687 3688 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl); 3689 if (matcher.matches()) { 3690 // force scheme to lowercase 3691 String scheme = matcher.group(1); 3692 String lcScheme = scheme.toLowerCase(); 3693 if (!lcScheme.equals(scheme)) { 3694 inUrl = lcScheme + matcher.group(2); 3695 } 3696 if (hasSpace) { 3697 inUrl = inUrl.replace(" ", "%20"); 3698 } 3699 return inUrl; 3700 } 3701 if (!hasSpace) { 3702 if (Patterns.WEB_URL.matcher(inUrl).matches()) { 3703 return URLUtil.guessUrl(inUrl); 3704 } 3705 } 3706 3707 // FIXME: Is this the correct place to add to searches? 3708 // what if someone else calls this function? 3709 3710 Browser.addSearchUrl(mResolver, inUrl); 3711 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER); 3712 } 3713 setShouldShowErrorConsole(boolean flag)3714 /* package */ void setShouldShowErrorConsole(boolean flag) { 3715 if (flag == mShouldShowErrorConsole) { 3716 // Nothing to do. 3717 return; 3718 } 3719 Tab t = mTabControl.getCurrentTab(); 3720 if (t == null) { 3721 // There is no current tab so we cannot toggle the error console 3722 return; 3723 } 3724 3725 mShouldShowErrorConsole = flag; 3726 3727 ErrorConsoleView errorConsole = t.getErrorConsole(true); 3728 3729 if (flag) { 3730 // Setting the show state of the console will cause it's the layout to be inflated. 3731 if (errorConsole.numberOfErrors() > 0) { 3732 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 3733 } else { 3734 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 3735 } 3736 3737 // Now we can add it to the main view. 3738 mErrorConsoleContainer.addView(errorConsole, 3739 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 3740 ViewGroup.LayoutParams.WRAP_CONTENT)); 3741 } else { 3742 mErrorConsoleContainer.removeView(errorConsole); 3743 } 3744 3745 } 3746 shouldShowErrorConsole()3747 boolean shouldShowErrorConsole() { 3748 return mShouldShowErrorConsole; 3749 } 3750 setStatusBarVisibility(boolean visible)3751 private void setStatusBarVisibility(boolean visible) { 3752 int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN; 3753 getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN); 3754 } 3755 3756 sendNetworkType(String type, String subtype)3757 private void sendNetworkType(String type, String subtype) { 3758 WebView w = mTabControl.getCurrentWebView(); 3759 if (w != null) { 3760 w.setNetworkType(type, subtype); 3761 } 3762 } 3763 packageChanged(String packageName, boolean wasAdded)3764 private void packageChanged(String packageName, boolean wasAdded) { 3765 WebView w = mTabControl.getCurrentWebView(); 3766 if (w == null) { 3767 return; 3768 } 3769 3770 if (wasAdded) { 3771 w.addPackageName(packageName); 3772 } else { 3773 w.removePackageName(packageName); 3774 } 3775 } 3776 addPackageNames(Set<String> packageNames)3777 private void addPackageNames(Set<String> packageNames) { 3778 WebView w = mTabControl.getCurrentWebView(); 3779 if (w == null) { 3780 return; 3781 } 3782 3783 w.addPackageNames(packageNames); 3784 } 3785 getInstalledPackages()3786 private void getInstalledPackages() { 3787 AsyncTask<Void, Void, Set<String> > task = 3788 new AsyncTask<Void, Void, Set<String> >() { 3789 protected Set<String> doInBackground(Void... unused) { 3790 Set<String> installedPackages = new HashSet<String>(); 3791 PackageManager pm = BrowserActivity.this.getPackageManager(); 3792 if (pm != null) { 3793 List<PackageInfo> packages = pm.getInstalledPackages(0); 3794 for (PackageInfo p : packages) { 3795 if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) { 3796 installedPackages.add(p.packageName); 3797 } 3798 } 3799 } 3800 3801 return installedPackages; 3802 } 3803 3804 // Executes on the UI thread 3805 protected void onPostExecute(Set<String> installedPackages) { 3806 addPackageNames(installedPackages); 3807 } 3808 }; 3809 task.execute(); 3810 } 3811 3812 final static int LOCK_ICON_UNSECURE = 0; 3813 final static int LOCK_ICON_SECURE = 1; 3814 final static int LOCK_ICON_MIXED = 2; 3815 3816 private BrowserSettings mSettings; 3817 private TabControl mTabControl; 3818 private ContentResolver mResolver; 3819 private FrameLayout mContentView; 3820 private View mCustomView; 3821 private FrameLayout mCustomViewContainer; 3822 private WebChromeClient.CustomViewCallback mCustomViewCallback; 3823 3824 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of 3825 // view, we should rewrite this. 3826 private int mCurrentMenuState = 0; 3827 private int mMenuState = R.id.MAIN_MENU; 3828 private int mOldMenuState = EMPTY_MENU; 3829 private static final int EMPTY_MENU = -1; 3830 private Menu mMenu; 3831 3832 private FindDialog mFindDialog; 3833 private SelectDialog mSelectDialog; 3834 // Used to prevent chording to result in firing two shortcuts immediately 3835 // one after another. Fixes bug 1211714. 3836 boolean mCanChord; 3837 3838 private boolean mInLoad; 3839 private boolean mIsNetworkUp; 3840 private boolean mDidStopLoad; 3841 3842 /* package */ boolean mActivityInPause = true; 3843 3844 private boolean mMenuIsDown; 3845 3846 private static boolean mInTrace; 3847 3848 // Performance probe 3849 private static final int[] SYSTEM_CPU_FORMAT = new int[] { 3850 Process.PROC_SPACE_TERM | Process.PROC_COMBINE, 3851 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time 3852 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time 3853 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time 3854 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time 3855 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time 3856 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time 3857 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time 3858 }; 3859 3860 private long mStart; 3861 private long mProcessStart; 3862 private long mUserStart; 3863 private long mSystemStart; 3864 private long mIdleStart; 3865 private long mIrqStart; 3866 3867 private long mUiStart; 3868 3869 private Drawable mMixLockIcon; 3870 private Drawable mSecLockIcon; 3871 3872 /* hold a ref so we can auto-cancel if necessary */ 3873 private AlertDialog mAlertDialog; 3874 3875 // The up-to-date URL and title (these can be different from those stored 3876 // in WebView, since it takes some time for the information in WebView to 3877 // get updated) 3878 private String mUrl; 3879 private String mTitle; 3880 3881 // As PageInfo has different style for landscape / portrait, we have 3882 // to re-open it when configuration changed 3883 private AlertDialog mPageInfoDialog; 3884 private Tab mPageInfoView; 3885 // If the Page-Info dialog is launched from the SSL-certificate-on-error 3886 // dialog, we should not just dismiss it, but should get back to the 3887 // SSL-certificate-on-error dialog. This flag is used to store this state 3888 private boolean mPageInfoFromShowSSLCertificateOnError; 3889 3890 // as SSLCertificateOnError has different style for landscape / portrait, 3891 // we have to re-open it when configuration changed 3892 private AlertDialog mSSLCertificateOnErrorDialog; 3893 private WebView mSSLCertificateOnErrorView; 3894 private SslErrorHandler mSSLCertificateOnErrorHandler; 3895 private SslError mSSLCertificateOnErrorError; 3896 3897 // as SSLCertificate has different style for landscape / portrait, we 3898 // have to re-open it when configuration changed 3899 private AlertDialog mSSLCertificateDialog; 3900 private Tab mSSLCertificateView; 3901 3902 // as HttpAuthentication has different style for landscape / portrait, we 3903 // have to re-open it when configuration changed 3904 private AlertDialog mHttpAuthenticationDialog; 3905 private HttpAuthHandler mHttpAuthHandler; 3906 3907 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = 3908 new FrameLayout.LayoutParams( 3909 ViewGroup.LayoutParams.MATCH_PARENT, 3910 ViewGroup.LayoutParams.MATCH_PARENT); 3911 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = 3912 new FrameLayout.LayoutParams( 3913 ViewGroup.LayoutParams.MATCH_PARENT, 3914 ViewGroup.LayoutParams.MATCH_PARENT, 3915 Gravity.CENTER); 3916 // Google search 3917 final static String QuickSearch_G = "http://www.google.com/m?q=%s"; 3918 3919 final static String QUERY_PLACE_HOLDER = "%s"; 3920 3921 // "source" parameter for Google search through search key 3922 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key"; 3923 // "source" parameter for Google search through goto menu 3924 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto"; 3925 // "source" parameter for Google search through simplily type 3926 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type"; 3927 // "source" parameter for Google search suggested by the browser 3928 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest"; 3929 // "source" parameter for Google search from unknown source 3930 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"; 3931 3932 private final static String LOGTAG = "browser"; 3933 3934 private String mLastEnteredUrl; 3935 3936 private PowerManager.WakeLock mWakeLock; 3937 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes 3938 3939 private Toast mStopToast; 3940 3941 private TitleBar mTitleBar; 3942 3943 private LinearLayout mErrorConsoleContainer = null; 3944 private boolean mShouldShowErrorConsole = false; 3945 3946 // As the ids are dynamically created, we can't guarantee that they will 3947 // be in sequence, so this static array maps ids to a window number. 3948 final static private int[] WINDOW_SHORTCUT_ID_ARRAY = 3949 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id, 3950 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id, 3951 R.id.window_seven_menu_id, R.id.window_eight_menu_id }; 3952 3953 // monitor platform changes 3954 private IntentFilter mNetworkStateChangedFilter; 3955 private BroadcastReceiver mNetworkStateIntentReceiver; 3956 3957 private BroadcastReceiver mPackageInstallationReceiver; 3958 3959 private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins; 3960 3961 // activity requestCode 3962 final static int COMBO_PAGE = 1; 3963 final static int PREFERENCES_PAGE = 3; 3964 final static int FILE_SELECTED = 4; 3965 3966 // the default <video> poster 3967 private Bitmap mDefaultVideoPoster; 3968 // the video progress view 3969 private View mVideoProgressView; 3970 3971 // The Google packages we monitor for the navigator.isApplicationInstalled() 3972 // API. Add as needed. 3973 private static Set<String> sGoogleApps; 3974 static { 3975 sGoogleApps = new HashSet<String>(); 3976 sGoogleApps.add("com.google.android.youtube"); 3977 } 3978 3979 /** 3980 * A UrlData class to abstract how the content will be set to WebView. 3981 * This base class uses loadUrl to show the content. 3982 */ 3983 /* package */ static class UrlData { 3984 final String mUrl; 3985 final Map<String, String> mHeaders; 3986 final Intent mVoiceIntent; 3987 UrlData(String url)3988 UrlData(String url) { 3989 this.mUrl = url; 3990 this.mHeaders = null; 3991 this.mVoiceIntent = null; 3992 } 3993 UrlData(String url, Map<String, String> headers, Intent intent)3994 UrlData(String url, Map<String, String> headers, Intent intent) { 3995 this.mUrl = url; 3996 this.mHeaders = headers; 3997 if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS 3998 .equals(intent.getAction())) { 3999 this.mVoiceIntent = intent; 4000 } else { 4001 this.mVoiceIntent = null; 4002 } 4003 } 4004 isEmpty()4005 boolean isEmpty() { 4006 return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0); 4007 } 4008 4009 /** 4010 * Load this UrlData into the given Tab. Use loadUrlDataIn to update 4011 * the title bar as well. 4012 */ loadIn(Tab t)4013 public void loadIn(Tab t) { 4014 if (mVoiceIntent != null) { 4015 t.activateVoiceSearchMode(mVoiceIntent); 4016 } else { 4017 t.getWebView().loadUrl(mUrl, mHeaders); 4018 } 4019 } 4020 }; 4021 4022 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null); 4023 needsRlz(Uri uri)4024 private static boolean needsRlz(Uri uri) { 4025 if ((uri.getQueryParameter("rlz") == null) && 4026 (uri.getQueryParameter("q") != null) && 4027 UrlUtils.isGoogleUri(uri)) { 4028 return true; 4029 } 4030 return false; 4031 } 4032 addRlzParameter(Uri uri, String rlz)4033 private static Uri addRlzParameter(Uri uri, String rlz) { 4034 if (rlz.isEmpty()) { 4035 return uri; 4036 } 4037 return uri.buildUpon().appendQueryParameter("rlz", rlz).build(); 4038 } 4039 } 4040