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