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