com.msopentech.applicationgateway.EnterpriseBrowserActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.msopentech.applicationgateway.EnterpriseBrowserActivity.java

Source

/*
 *  Copyright (c) Microsoft Open Technologies
 *  All rights reserved. 
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0  
 *  THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 *  LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
 *  MERCHANTABLITY OR NON-INFRINGEMENT. 
 *  See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */

package com.msopentech.applicationgateway;

import java.io.InputStream;
import java.util.Vector;

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse;

import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.webkit.CookieManager;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;

import com.msopentech.applicationgateway.adapters.UrlAutoCompleteAdapter;
import com.msopentech.applicationgateway.connection.OnOperationExecutionListener;
import com.msopentech.applicationgateway.connection.Router;
import com.msopentech.applicationgateway.data.ConnectionTraits;
import com.msopentech.applicationgateway.data.TabInfo;
import com.msopentech.applicationgateway.data.URLInfo;
import com.msopentech.applicationgateway.preferences.AuthPreferences;
import com.msopentech.applicationgateway.preferences.PersistenceManager;
import com.msopentech.applicationgateway.preferences.PersistenceManager.PersistenceObserver;
import com.msopentech.applicationgateway.utils.Utility;

/**
 * Implements main activity containing enterprise browser.
 */
public class EnterpriseBrowserActivity extends Activity implements PersistenceObserver {
    /**
     * Default "http" prefix.
     */
    static final String HTTP_TOKEN_ATTRIBUTE = "http";

    /**
     * Default "https" prefix.
     */
    static final String HTTPS_TOKEN_ATTRIBUTE = "https";

    /**
     * Default "http://" protocol prefix.
     */
    static final String HTTP_PREFIX_ATTRIBUTE = "http://";

    /**
     * Default "https://" protocol prefix.
     */
    static final String HTTPS_PREFIX_ATTRIBUTE = "https://";

    /**
     * Host prefix for cloud server. 
     */
    public static final String DEFAULT_CLOUD_CONNECTION_HOST_PREFIX = HTTPS_PREFIX_ATTRIBUTE
            + "appgateway.windows.net/";

    /**
     * Host prefix for cloud server.
     */
    public static String CLOUD_CONNECTION_HOST_PREFIX = DEFAULT_CLOUD_CONNECTION_HOST_PREFIX;

    /**
     * URL postfix to be appended {@link #CLOUD_CONNECTION_HOST_PREFIX} to 'cloudify' URLs via cloud proxy.
     */
    static final String CLOUD_CONNECTION_HOST_BROWSER_POSTFIX = "connect/browser/";

    /**
     * URL postfix to be appended {@link #CLOUD_CONNECTION_HOST_PREFIX} to SMART'cloudify' URLs via cloud proxy.
     */
    static final String CLOUD_CONNECTION_HOST_SMARTBROWSER_POSTFIX = "connect/smartbrowser/";

    /**
     * URL to be combined with user URL to connect via cloud proxy.
     */
    static String CLOUD_BROWSER_URL = CLOUD_CONNECTION_HOST_PREFIX + CLOUD_CONNECTION_HOST_BROWSER_POSTFIX;

    /**
     * Attribute to check for when asserting session validity. Session is espired when it is present in the URL.
     */
    static final String SESSION_EXPIRED_ATTRIBUTE = "session-expired/?orig_url=";

    /**
     * Agents activity ID.
     */
    public static final int ACTIVITY_AGENTS = 1;

    /**
     * Bookmarks activity ID.
     */
    public static final int ACTIVITY_BOOKMARKS_AND_HISTORY = 2;

    /**
     * Sign-In activity ID.
     */
    public static final int ACTIVITY_SIGN_IN = 3;

    /**
     * Advanced Router Settings activity ID.
     */
    public static final int ACTIVITY_ADVANCED_ROUTER_SETTINGS = 4;

    /**
     * ClientStatusAndDiagnostics activity ID.
     */
    public static final int ACTIVITY_CLIENT_STATUS_AND_DIAGNOSTICS = 5;

    /**
     * Extras key to get connection traits.
     */
    public static String EXTRAS_TRAITS_KEY = "connection_traits";

    /**
     * Extras key to get session ID.
     */
    public static String EXTRAS_URL_KEY = "browse_to_url";

    /**
     * Extras key to browse to router system page.
     */
    public static String EXTRAS_BROWSE_TO_ROUTER_SYSTEM_PAGE_KEY = "browse_to_router_system_page";

    /**
     * Extras key to get agent name.
     */
    public static String EXTRAS_SMART_BROWSER_ON = "smart_browser_on";

    /**
     * Extras key to clear cookies.
     */
    public static String EXTRAS_CLEAR_COOKIES_ON = "clear_cookies_on";

    /**
     * Sign in indicator.
     */
    private boolean mIsSigninRequired = true;

    /**
     * Boolean to indicate if we are using smart browser.
     */
    private boolean mUseSmartBrowser = false;

    /**
     * Current connection traits.
     */
    public static ConnectionTraits mTraits;

    /**
     * User-entered original URI
     */
    private String mUserOriginalURI;

    /**
     * Stored edit view for the user's URL.
     */
    private AutoCompleteTextView mUrlEditTextView;

    /**
     * Stored back button view.
     */
    private ImageButton mBackButtonView;

    /**
     * Stored forward button view.
     */
    private ImageButton mForwardButtonView;

    /**
     * Stored reload button view.
     */
    private ImageButton mReloadButtonView;

    /**
     * Stored status button.
     */
    private ImageButton mStatusButtonView;
    /**
     * Stored WebView.
     */
    private WebView mActiveWebView;

    /**
     * Stored progress bar view.
     */
    private ProgressBar mProgressBarView;

    /**
     * Stored add tab button.
     */
    private ImageButton mAddButtonView;

    /**
     * A web view client shared among all tabs.
     */
    private MyWebViewClient mWebViewClient;

    /**
     * A stored instance of {@linkplain CustomTabHost} to interact with tabs.
     */
    private CustomTabHost mCustomTabHost;

    /**
     * Adapter for autocompletion.
     */
    private UrlAutoCompleteAdapter mAutoCompleteAdapter;

    /**
     * Flag to track if session recovery is in progress.
     */
    boolean isInSessionExpiryRecovery = false;

    /**
    * Non-cloudified URL that was originally requested to be loaded by the user.
     */
    String mOriginalUrl = null;

    /**
    * Custom web client to handle page loading requests.
    */
    private class MyWebViewClient extends WebViewClient {

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            try {
                Log.d(EnterpriseBrowserActivity.class.getSimpleName(), "onPageStarted() for url = " + url);

                if (mOriginalUrl == null) {
                    mOriginalUrl = convertCloudUrlToNormal(url);
                }

                //Handle session expiry.
                if (url.contains(SESSION_EXPIRED_ATTRIBUTE)) {
                    mActiveWebView.stopLoading();
                    if (isInSessionExpiryRecovery) {
                        return;
                    }
                    isInSessionExpiryRecovery = true;

                    // Listener for the 2nd attempt when full authentication is required.
                    final OnOperationExecutionListener authListener = new OnOperationExecutionListener() {
                        @Override
                        public void onExecutionComplete(int operation, final Object[] result) {
                            try {
                                ConnectionTraits traits = (ConnectionTraits) result[0];
                                if (!traits.isError()) {
                                    // Updating connection traits after full auth is successful.
                                    mTraits = traits;
                                    // Switch to agent specific stored data.
                                    PersistenceManager.initialize(mTraits.agent.getAgentId());
                                    isInSessionExpiryRecovery = false;
                                    // Continue url load process started by the user.
                                    goToUrl(mOriginalUrl);
                                } else {
                                    mIsSigninRequired = true;
                                    showSignIn(getResources().getString(R.string.sign_in_session_expiry_alert_text),
                                            false);
                                }
                            } catch (final Exception e) {
                            }
                        }

                        @Override
                        public void onBeforeExecution(int operation) {
                        }
                    };
                    // Listener for the 1st attempt when only session recovery is required.
                    OnOperationExecutionListener sessionRecoveryListener = new OnOperationExecutionListener() {
                        @Override
                        public void onExecutionComplete(int operation, final Object[] result) {
                            try {
                                ConnectionTraits traits = (ConnectionTraits) result[1];
                                if (!traits.isError()) {
                                    // Updating session after successful session request.
                                    mTraits.setSession(traits.sessionID);
                                    isInSessionExpiryRecovery = false;
                                    goToUrl(mOriginalUrl);
                                } else {
                                    // Starting attempt 2 - full authentication.
                                    Router.performRequest(Router.ACTION_AUTHENTICATE,
                                            new Object[] { AuthPreferences.loadCredentials() }, authListener,
                                            EnterpriseBrowserActivity.this);
                                }
                            } catch (final Exception e) {
                            }
                        }

                        @Override
                        public void onBeforeExecution(int operation) {
                        }
                    };
                    // Starting attempt 1 - session recovery.
                    Router.performRequest(Router.ACTION_OBTAIN_SESSION, new Object[] { mTraits },
                            sessionRecoveryListener, EnterpriseBrowserActivity.this);
                    return;
                }

                String displayUrl = EnterpriseBrowserActivity.convertCloudUrlToNormal(url);

                TabInfo tab = mCustomTabHost.getTabInfoForWebView(view);
                if (tab == null) {
                    return;
                }

                tab.setPageLoadingInProgress(true);

                //We can change the toolbar here only if the current tab is active.
                if (mCustomTabHost.isTabActive(tab)) {
                    String displayedUrl = mUrlEditTextView.getText().toString();
                    if (!displayedUrl.contentEquals(displayUrl)) {
                        mUrlEditTextView.setText(displayUrl);
                    }
                    mUrlEditTextView.setEnabled(false);
                    mForwardButtonView.setEnabled(false);
                    mBackButtonView.setEnabled(false);
                    mReloadButtonView.setImageResource(R.drawable.cancel);
                    mReloadButtonView.setEnabled(true);
                }
            } catch (final Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".onPageStarted(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            try {
                Log.d(EnterpriseBrowserActivity.class.getSimpleName(), "onPageFinished() for url = " + url);

                TabInfo tab = mCustomTabHost.getTabInfoForWebView(view);

                //The tab must have been closed while loading a page and is dead now. Do nothing in this case.
                if (tab == null) {
                    return;
                }

                tab.setPageLoadingInProgress(false);

                //Update the label of the tab.
                tab.getLabel().setText(view.getTitle());

                //We can change the toolbar here only if the current tab is active.
                if (mCustomTabHost.isTabActive(tab)) {
                    mForwardButtonView.setEnabled(mActiveWebView.canGoForward());
                    mBackButtonView.setEnabled(mActiveWebView.canGoBack());
                    mUrlEditTextView.setEnabled(true);
                    mReloadButtonView.setImageResource(R.drawable.reload_button);
                }

                String realUrl = convertCloudUrlToNormal(url);
                PersistenceManager.addRecord(PersistenceManager.ContentType.HISTORY,
                        new URLInfo(realUrl, view.getTitle()));
                mProgressBarView.setProgress(0);
            } catch (final Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".onPageFinished(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }
        }

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            if (!mUseSmartBrowser) {
                return null;
            } else if (url.startsWith(CLOUD_BROWSER_URL)) {
                return null;
            } else if (url.contentEquals(CLOUD_CONNECTION_HOST_PREFIX + "system")) {
                return null;
            } else {
                try {
                    HttpClient agentsClient = new DefaultHttpClient();
                    String fixupUrl = EnterpriseBrowserActivity.convertNormalUrlToCloud(url, mTraits.sessionID,
                            mUserOriginalURI);
                    HttpGet agentsRequest = new HttpGet(fixupUrl);
                    BasicHttpResponse response = (BasicHttpResponse) agentsClient.execute(agentsRequest);

                    StatusLine status = response.getStatusLine();
                    if (status.getStatusCode() != 200 || response.getFirstHeader("Content-Type") == null
                            || response.getFirstHeader("Transfer-Encoding") == null) {
                        return null;
                    }

                    HttpEntity entity = response.getEntity();
                    if (entity == null) {
                        return null;
                    }

                    InputStream responseStream = entity.getContent();
                    if (responseStream == null) {
                        return null;
                    }

                    return new WebResourceResponse(response.getFirstHeader("Content-Type").getValue().toString(),
                            response.getFirstHeader("Transfer-Encoding").getValue().toString(), responseStream);

                } catch (Exception e) {
                    Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                            + ".onPageFinished(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
                    return null;
                }
            }
        }
    }

    /**
     * This delegate function is called each time a new item is added into the merged storage of PersistenceManager.
     */
    public void onSuggestionsListChanged() {
        mAutoCompleteAdapter.notifyDataSetChanged();
    }

    /**
     * Converts regular/normal URL to a 'cloudified' one i.e. with cloud prefix and session ID. Converts only non-cloudified URLs and only
     * if we have active session ID, otherwise returns incoming URL without changes.
     * 
     * @param url URL to be converted.
     * @param sessionID ID of the current active session.
     * @param originalUrl Initial URL.
     * 
     * @return URL converted to a cloud format. Returns incoming URL without changes if it already has cloud prefix or if session ID equals
     *         <code>null</code> or if exception is caught.
     */
    private static String convertNormalUrlToCloud(String url, String sessionID, String originalUrl) {
        try {
            if (!url.startsWith(CLOUD_BROWSER_URL) && sessionID != null) {
                boolean http = true;

                if (url.startsWith(CLOUD_CONNECTION_HOST_PREFIX)) { // Special case for JS and other possible returned URLs which we need to fixup
                    url = url.substring(CLOUD_CONNECTION_HOST_PREFIX.length());
                    if (!originalUrl.endsWith("/"))
                        url = originalUrl + '/' + url;
                    else
                        url = originalUrl + url;
                }

                if (url.startsWith(HTTPS_PREFIX_ATTRIBUTE)) {
                    http = false;
                }

                if (url.startsWith(HTTPS_PREFIX_ATTRIBUTE)) {
                    url = url.substring(HTTPS_PREFIX_ATTRIBUTE.length());
                } else if (url.startsWith(HTTP_PREFIX_ATTRIBUTE)) {
                    url = url.substring(HTTP_PREFIX_ATTRIBUTE.length());
                }

                if (http) {
                    url = CLOUD_BROWSER_URL + sessionID + "/" + HTTP_TOKEN_ATTRIBUTE + "/" + url;
                } else {
                    url = CLOUD_BROWSER_URL + sessionID + "/" + HTTPS_TOKEN_ATTRIBUTE + "/" + url;
                }
            }
            return url;
        } catch (final Exception e) {
            Log.d(EnterpriseBrowserActivity.class.getSimpleName() + ".convertNormalUrlToCloud()", "Failed.");
        }
        return url;
    }

    /**
     * Takes a cloud URL with {@linkplain #CLOUD_BROWSER_URL} prefix and converts it info a regular URL.
     * 
     * @param url URL to convert.
     * @return Regular URL. Returns String provided as an argument if exception occurs. 
     */
    private static String convertCloudUrlToNormal(String url) {
        try {
            if (!url.startsWith(CLOUD_BROWSER_URL)) {
                return url;
            }

            int charactersToCutOff = CLOUD_BROWSER_URL.length() + 1;

            String str = url.substring(charactersToCutOff);

            int realAddressIndex = str.indexOf('/');
            if (realAddressIndex == -1) {
                return url;
            }

            realAddressIndex++;

            if (realAddressIndex > str.length())
                return null;

            str = str.substring(realAddressIndex);

            return str.replaceFirst("/", "://");
        } catch (final Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".convertCloudUrlToNormal(): Failed. " + e.toString(), ApplicationGateway.getAppContext());
        }

        return url;
    }

    /**
     * Opens provided URL within {@linkplain WebView}
     * 
     * @param normalUrl URL to be opened.
     * @return <code>True</code> if URL can be safely opened i.e. if we have a valid session ID, <code>false</code> otherwise (redirects
     *         user to perform sign-in operation).
     */
    private boolean goToUrl(String normalUrl) {
        Log.d(EnterpriseBrowserActivity.class.getSimpleName(), "goToUrl() for url = " + normalUrl);
        if (mTraits == null || mTraits.sessionID == null) {
            mIsSigninRequired = true;
            //TODO:
            showSignIn(null, false);
            return false;
        } else {
            String fixupUrl = EnterpriseBrowserActivity.convertNormalUrlToCloud(normalUrl, mTraits.sessionID,
                    mUserOriginalURI);
            mActiveWebView.loadUrl(fixupUrl);
            return true;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        try {
            super.onCreate(savedInstanceState);
            this.requestWindowFeature(Window.FEATURE_NO_TITLE);

            setContentView(R.layout.main);

            String preferredRouter = AuthPreferences.loadPreferredRouter();
            if (preferredRouter != null && !preferredRouter.isEmpty()) {
                CLOUD_CONNECTION_HOST_PREFIX = preferredRouter;
            }

            Boolean useSmartBrowser = AuthPreferences.loadUseSmartBrowser();
            if (useSmartBrowser) {
                CLOUD_BROWSER_URL = CLOUD_CONNECTION_HOST_PREFIX + CLOUD_CONNECTION_HOST_SMARTBROWSER_POSTFIX;
            } else {
                CLOUD_BROWSER_URL = CLOUD_CONNECTION_HOST_PREFIX + CLOUD_CONNECTION_HOST_BROWSER_POSTFIX;
            }

            mWebViewClient = new MyWebViewClient();

            mBackButtonView = (ImageButton) findViewById(R.id.main_back);
            mForwardButtonView = (ImageButton) findViewById(R.id.main_go);
            mAddButtonView = (ImageButton) findViewById(R.id.main_add_tab);
            mStatusButtonView = (ImageButton) findViewById(R.id.main_status);
            mProgressBarView = (ProgressBar) findViewById(R.id.main_progress_bar);
            mReloadButtonView = (ImageButton) findViewById(R.id.main_reload);
            mUrlEditTextView = (AutoCompleteTextView) findViewById(R.id.main_url);

            // The following is present to fix a bug found on devices with hard keyboards,
            // not soft keyboards.  Hard keyboards have physical keys versus a soft
            // keyboard which uses a displayed keyboard.  With hard keyboards and EditText boxes 
            // on activities with TabHosts, entering certain keys on the hard keyboard 
            // causes the focus to shift from an EditText to another item on the activity.
            // This manifested itself when running the browser on an oDriod device and no
            // text could be entered into mUrlEditTextView.  
            //
            // The following work-around appears to solve this problem (found on forums) 
            // without causing any regressions.
            View.OnTouchListener focusHandler = new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    v.requestFocusFromTouch();
                    return false;
                }
            };
            mUrlEditTextView.setOnTouchListener(focusHandler);

            mCustomTabHost = new CustomTabHost();

            PersistenceManager.registerObserver(this);

            mAutoCompleteAdapter = new UrlAutoCompleteAdapter(this, android.R.layout.simple_list_item_1,
                    PersistenceManager.getAllMergedRecords(), mCustomTabHost);
            mUrlEditTextView.setAdapter(mAutoCompleteAdapter);

            View.OnClickListener listener = new View.OnClickListener() {
                public void onClick(View view) {
                    switch (view.getId()) {
                    case R.id.main_go: {
                        if (mActiveWebView.canGoForward())
                            mActiveWebView.goForward();
                        break;
                    }
                    case R.id.main_back: {
                        if (mActiveWebView.canGoBack())
                            mActiveWebView.goBack();
                        break;
                    }
                    case R.id.main_reload: {
                        TabInfo tab = mCustomTabHost.getTabInfoForWebView(mActiveWebView);
                        if (tab.isPageLoadingInProgress()) {
                            mActiveWebView.stopLoading();
                        } else {
                            String url = mActiveWebView.getUrl();
                            if (url != null && !mIsSigninRequired) {
                                //The user could have got a new session. 
                                //We can neither clear the current url in WebView, 
                                //nor use its reload() function since the url inside it is "cloudified" using the previous SessionID.
                                //However, we can safely extract it from the WebView, normalize it, and then "cloudify" it with the right SessionID.
                                //This implementation allows us to keep the reload button always enabled during the lifetime of a tab after the first URL has been entered.
                                goToUrl(convertCloudUrlToNormal(url));
                            }
                        }
                        break;
                    }
                    case R.id.main_add_tab: {
                        mCustomTabHost.createNewTab();
                        break;
                    }
                    }
                }
            };

            mAddButtonView.setOnClickListener(listener);
            mForwardButtonView.setOnClickListener(listener);
            mBackButtonView.setOnClickListener(listener);
            mReloadButtonView.setOnClickListener(listener);

            mForwardButtonView.setEnabled(false);
            mBackButtonView.setEnabled(false);

            mUrlEditTextView.setOnKeyListener(new View.OnKeyListener() {
                public boolean onKey(View view, int keyCode, KeyEvent event) {
                    if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                        if (mUrlEditTextView.isPopupShowing()) {
                            mUrlEditTextView.dismissDropDown();
                        }

                        String uri = mUrlEditTextView.getText().toString();

                        if (!uri.startsWith(HTTP_TOKEN_ATTRIBUTE) && !uri.startsWith(HTTPS_TOKEN_ATTRIBUTE)) {
                            uri = HTTP_PREFIX_ATTRIBUTE + uri;
                            mUrlEditTextView.setText(uri);
                        }
                        mUserOriginalURI = uri;
                        if (goToUrl(mUserOriginalURI))
                            return true;
                    }
                    return false;
                }
            });

            mTraits = null;
            showSignIn(null, false);
        } catch (final Exception e) {
            Utility.showAlertDialog(
                    EnterpriseBrowserActivity.class.getSimpleName() + ".onCreate(): Failed. " + e.toString(),
                    EnterpriseBrowserActivity.this);
        }
    }

    public void showSettingsPopup(View v) {
        try {
            PopupMenu popup = new PopupMenu(this, v);
            MenuInflater inflater = popup.getMenuInflater();
            popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                public boolean onMenuItemClick(MenuItem item) {
                    switch (item.getItemId()) {
                    case R.id.menu_connection: {
                        showAgentsList();
                        return true;
                    }
                    case R.id.menu_sign_in_or_out: {
                        if (mIsSigninRequired) {
                            // Menu item in sign in mode.
                            //TODO: 
                            showSignIn(null, true);
                            return true;
                        }

                        // Clean up the session.
                        mTraits.sessionID = null;
                        mTraits.token = null;
                        mTraits.agent = null;

                        // Indicate the condition.
                        mIsSigninRequired = true;

                        //Remove all tabs except one.
                        mCustomTabHost.resetTabs();

                        mUrlEditTextView.setText("");

                        mProgressBarView.setProgress(0);

                        // Disable the reload button and set the right image for it.
                        mReloadButtonView.setEnabled(false);
                        mReloadButtonView.setImageResource(R.drawable.reload_button);

                        mStatusButtonView.setImageResource(R.drawable.connection_red);

                        // Since it's the user's intention to sign out, his
                        // history must be dropped.
                        PersistenceManager.dropContent(PersistenceManager.ContentType.HISTORY);

                        return true;
                    }
                    // Removed from RELEASE version. Should be active for development ONLY.                       
                    //                        case R.id.menu_advanced_router_settings: {
                    //                            showAdvancedRouterSettings(null);
                    //                        }
                    default: {
                        return false;
                    }
                    }
                }
            });
            inflater.inflate(R.menu.settings_menu, popup.getMenu());

            if (mIsSigninRequired) {
                MenuItem signItem = popup.getMenu().findItem(R.id.menu_sign_in_or_out);
                signItem.setTitle(getResources().getString(R.string.browser_menu_item_sign_in));
            }

            // Assumes that agent always has a display name. If display name is not present agent is considered to be not connected.
            MenuItem agentItem = popup.getMenu().findItem(R.id.menu_connection);
            String status = getResources().getString(R.string.browser_menu_item_choose_connection_not_connected);
            if (mTraits != null && mTraits.agent != null && !TextUtils.isEmpty(mTraits.agent.getDisplayName())) {
                status = mTraits.agent.getDisplayName();
            }
            Spannable span = new SpannableString(
                    getResources().getString(R.string.browser_menu_item_choose_connection, status));
            span.setSpan(new RelativeSizeSpan(0.8f), 18, span.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            span.setSpan(new ForegroundColorSpan(Color.GRAY), 18, span.length(),
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            agentItem.setTitle(span);

            popup.show();
        } catch (final Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".showSettingsPopup(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Invokes bookmarks and history activity.
     * 
     * @param v View this method is attached to.
     */
    public void onShowBookmarks(View v) {
        try {
            showBookmarksList();
        } catch (final Exception e) {
            Utility.showAlertDialog(
                    EnterpriseBrowserActivity.class.getSimpleName() + ".onShowBookmarks(): Failed. " + e.toString(),
                    EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Shows dialog enabling user to bookmark current (by default) or any other page.
     * 
     * @param v View this method is attached to.
     */
    public void showAddBookmarkDialog(View v) {
        try {
            final Dialog dialog = new Dialog(this);
            dialog.setContentView(R.layout.create_bookmark_dialog);
            dialog.setTitle(getString(R.string.bookmark_dialog_title));
            dialog.setCanceledOnTouchOutside(false);

            TextView cancelButton = (TextView) dialog.findViewById(R.id.create_bookmark_cancel);
            TextView saveButton = (TextView) dialog.findViewById(R.id.create_bookmark_ok);

            OnClickListener listener = new OnClickListener() {
                public void onClick(View v) {
                    switch (v.getId()) {
                    case R.id.create_bookmark_cancel: {
                        dialog.dismiss();
                        break;
                    }
                    case R.id.create_bookmark_ok: {
                        String name = ((EditText) dialog.findViewById(R.id.create_bookmark_name)).getText()
                                .toString();
                        String address = ((EditText) dialog.findViewById(R.id.create_bookmark_url)).getText()
                                .toString();

                        if (null != name && !(name.contentEquals("")) && null != address
                                && !(address.contentEquals(""))) {
                            PersistenceManager.addRecord(PersistenceManager.ContentType.BOOKMARKS,
                                    new URLInfo(address, name));
                        }

                        dialog.dismiss();
                        break;
                    }
                    }
                }
            };

            cancelButton.setOnClickListener(listener);
            saveButton.setOnClickListener(listener);

            EditText name = (EditText) dialog.findViewById(R.id.create_bookmark_name);
            EditText address = (EditText) dialog.findViewById(R.id.create_bookmark_url);

            name.setText(mActiveWebView.getTitle());
            address.setText(mUrlEditTextView.getText());

            dialog.show();
        } catch (final Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".showAddBookmarkDialog(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Opens bookmarks & history activity.
     */
    public void showBookmarksList() {
        try {
            Intent intent = new Intent(this, BookmarksActivity.class);
            startActivityForResult(intent, ACTIVITY_BOOKMARKS_AND_HISTORY);
        } catch (Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".showBookmarksList(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Opens proxy agents list. Redirects user to Sign In activity in case current token is invalid (equals <code>null</code>).
     */
    public void showAgentsList() {
        try {
            if (mTraits == null || mTraits.token == null) {
                showSignIn(getResources().getString(R.string.sign_in_connection_choose_alert_text), false);
            } else {
                // We need agent and token from traits.
                Intent intent = new Intent(this, AgentsActivity.class)
                        .putExtra(EnterpriseBrowserActivity.EXTRAS_TRAITS_KEY, mTraits);
                startActivityForResult(intent, ACTIVITY_AGENTS);
            }
        } catch (Exception e) {
            Utility.showAlertDialog(
                    EnterpriseBrowserActivity.class.getSimpleName() + ".showAgentsList(): Failed. " + e.toString(),
                    EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Opens Advanced Router Settings activity.
     */
    public void showAdvancedRouterSettings(String message) {
        try {
            Intent intent = new Intent(this, AdvancedRouterSettingsActivity.class);
            startActivityForResult(intent, ACTIVITY_ADVANCED_ROUTER_SETTINGS);
        } catch (Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".showAdvancedRouterSettings(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Opens Sign In activity.
     * 
     * @param message Optional message (can be <code>null</code>) to be displayed within Sign In activity to explain user the reason why it
     *            was opened.
     * @param uiPrompt Flag defining if opening sign in page was a user action otr not.
     */
    public void showSignIn(String message, boolean uiPrompt) {
        try {
            Intent intent = new Intent(this, SignInActivity.class);
            if (message != null)
                intent.putExtra(SignInActivity.ALERT_MESSAGE_KEY, message);
            intent.putExtra(SignInActivity.UI_PROMPT, Boolean.toString(uiPrompt));
            startActivityForResult(intent, ACTIVITY_SIGN_IN);
        } catch (Exception e) {
            Utility.showAlertDialog(
                    EnterpriseBrowserActivity.class.getSimpleName() + ".showSignInList(): Failed. " + e.toString(),
                    EnterpriseBrowserActivity.this);
        }
    }

    /**
     * Opens status and diagnostics activity.
     */
    public void showClientStatusAndDiagnostics(View v) {
        try {
            Intent intent = new Intent(this, ClientStatusAndDiagnosticsActivity.class);
            startActivityForResult(intent, ACTIVITY_CLIENT_STATUS_AND_DIAGNOSTICS);
        } catch (Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".showClientStatusAndDiagnostics(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        try {
            super.onActivityResult(requestCode, resultCode, data);

            // The data argument can be null if the user simply exited the
            // activity by clicking back or cancel.
            if (data == null) {
                return;
            }

            Bundle extras = data.getExtras();

            if (extras == null) {
                return;
            }

            switch (requestCode) {
            case ACTIVITY_SIGN_IN: {
                if (resultCode != RESULT_CANCELED) {
                    mCustomTabHost.clearAllHistory();

                    mTraits = (ConnectionTraits) data
                            .getSerializableExtra(EnterpriseBrowserActivity.EXTRAS_TRAITS_KEY);

                    if (mTraits.sessionID == null && mTraits.token == null && mTraits.agent.getAgentId() == null) {
                        // Authentication failed. Have to make user make another attempt while informing of error reason.
                        showSignIn(null, true);
                    } else {
                        mStatusButtonView.setImageResource(R.drawable.connection_green);

                        if (mIsSigninRequired) {
                            mIsSigninRequired = false;
                        }
                        // Switch to agent specific stored data.
                        PersistenceManager.initialize(mTraits.agent.getAgentId());
                    }
                } else {
                    if (mTraits == null || mTraits.isError()) {
                        // Will get here if error was received on signIn screen and user pressed 'cancel' there.
                        mIsSigninRequired = true;
                        if (mTraits != null) {
                            mTraits.sessionID = null;
                            mTraits.token = null;
                        }
                    }
                }
                break;
            }

            case ACTIVITY_ADVANCED_ROUTER_SETTINGS: {
                String url = data.getStringExtra(CLOUD_CONNECTION_HOST_PREFIX);
                Boolean useSmartBrowser = data.getBooleanExtra(EXTRAS_SMART_BROWSER_ON, false);

                if (!CLOUD_CONNECTION_HOST_PREFIX.contentEquals(url) || mUseSmartBrowser != useSmartBrowser) {
                    CLOUD_CONNECTION_HOST_PREFIX = url;
                    CLOUD_BROWSER_URL = CLOUD_CONNECTION_HOST_PREFIX;

                    mUseSmartBrowser = useSmartBrowser;
                    AuthPreferences.storeUseSmartBrowser(mUseSmartBrowser);

                    if (useSmartBrowser) {
                        CLOUD_BROWSER_URL = CLOUD_BROWSER_URL + CLOUD_CONNECTION_HOST_SMARTBROWSER_POSTFIX;
                    } else {
                        CLOUD_BROWSER_URL = CLOUD_BROWSER_URL + CLOUD_CONNECTION_HOST_BROWSER_POSTFIX;
                    }

                    AuthPreferences.storePreferredRouter(url);
                    mStatusButtonView.setImageResource(R.drawable.connection_red);
                    mCustomTabHost.clearAllHistory();
                    showSignIn(null, false);
                }

                Boolean clearCookies = data.getBooleanExtra(EXTRAS_CLEAR_COOKIES_ON, false);
                if (clearCookies) {
                    CookieManager.getInstance().removeAllCookie();
                }

                break;
            }

            case ACTIVITY_BOOKMARKS_AND_HISTORY: {
                String url = data.getStringExtra(EXTRAS_URL_KEY);
                if (!TextUtils.isEmpty(url)) {
                    goToUrl(url);
                }
                break;
            }

            case ACTIVITY_AGENTS: {
                // Since the agent has changed, we can no longer use the old
                // history in WebViewClient with the old SessionID.
                mCustomTabHost.clearAllHistory();
                mReloadButtonView.setEnabled(false);

                ConnectionTraits traits = (ConnectionTraits) data.getSerializableExtra(EXTRAS_TRAITS_KEY);

                if (traits != null && traits.sessionID != null) {
                    mTraits.sessionID = traits.sessionID;
                    mStatusButtonView.setImageResource(R.drawable.connection_green);
                    // Should never be null
                    if (traits.agent != null) {
                        if (traits.agent.getAgentId() != null) {
                            mTraits.agent.setAgentId(traits.agent.getAgentId());
                            // Switch to agent specific stored data.
                            PersistenceManager.dropContent(PersistenceManager.ContentType.HISTORY);
                            PersistenceManager.initialize(mTraits.agent.getAgentId());
                        }
                        if (traits.agent.getDisplayName() != null) {
                            mTraits.agent.setDisplayName(traits.agent.getDisplayName());
                        }
                    }
                }
                break;
            }

            case ACTIVITY_CLIENT_STATUS_AND_DIAGNOSTICS: {
                String browse = data.getStringExtra(EXTRAS_BROWSE_TO_ROUTER_SYSTEM_PAGE_KEY);
                if (!(browse == null || browse.isEmpty()))
                    mActiveWebView.loadUrl(CLOUD_CONNECTION_HOST_PREFIX + "system");

                break;
            }
            }
        } catch (Exception e) {
            Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                    + ".onActivityResult(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        try {
            if ((keyCode == KeyEvent.KEYCODE_BACK)) {
                if (mActiveWebView.canGoBack()) {
                    mActiveWebView.goBack();
                    return true;
                }
            }
        } catch (Exception e) {
            Utility.showAlertDialog(
                    EnterpriseBrowserActivity.class.getSimpleName() + ".onKeyDown(): Failed. " + e.toString(),
                    EnterpriseBrowserActivity.this);
        }
        return super.onKeyDown(keyCode, event);
    }

    /**
     * Implements a wrapper for tabs.
     */
    class CustomTabHost implements Runnable, UrlAutoCompleteAdapter.IPublishResultsNotifiable {
        /**
         * The progress bar updater, shared among the tabs.
         */
        private ProgressUpdater mProgressBar;

        /**
         * A new tab label.
         */
        private static final String NEW_TAB_LABEL = "New tab";

        /**
         * Since we can't delete a single tab from TabHost, we need this vector to keep track of all TabSpecs.
         */
        private Vector<TabInfo> mTabStorage;

        /**
         * Stored TabHost instance.
         */
        private TabHost mTabHost;

        /**
         * A factory object for creating new tabs' content.
         */
        private TabHost.TabContentFactory mTabFactory;

        /**
         * A stored HorizontalScrollView.
         */
        private HorizontalScrollView mTabScrollView;

        /**
         * This method is used to asynchronously scroll the tab toolbar right when a new tab is created.
         */
        public void run() {
            mTabScrollView.fullScroll(ScrollView.FOCUS_RIGHT);
        }

        /**
         * A class constructor.
         */
        public CustomTabHost() {
            try {
                mTabFactory = new TabHost.TabContentFactory() {
                    public View createTabContent(String tag) {
                        return mActiveWebView;
                    }
                };
                mProgressBar = new ProgressUpdater();

                mTabScrollView = (HorizontalScrollView) findViewById(R.id.scroll);
                mTabHost = (TabHost) findViewById(android.R.id.tabhost);
                mTabHost.setup();

                OnTabChangeListener listener = new OnTabChangeListener() {
                    public void onTabChanged(String tabId) {
                        mActiveWebView = (WebView) mTabHost.getCurrentView();

                        //Set the right condition for the back and forward buttons.
                        mForwardButtonView.setEnabled(mActiveWebView.canGoForward());
                        mBackButtonView.setEnabled(mActiveWebView.canGoBack());

                        TabInfo tab = getTabInfoForWebView(mActiveWebView);
                        updateTabsUI();

                        //Change the state of the progress bar.
                        mProgressBarView.setProgress(tab.getProgress());

                        if (tab.isPageLoadingInProgress()) {
                            mUrlEditTextView.setEnabled(false);
                            mReloadButtonView.setImageResource(R.drawable.cancel);
                            mReloadButtonView.setEnabled(true);
                        } else {
                            mUrlEditTextView.setEnabled(true);
                            mReloadButtonView.setImageResource(R.drawable.reload_button);

                            if (mActiveWebView.getUrl() != null) { //If getUrl is null, then the user hasn't entered any url for this tab yet.
                                mReloadButtonView.setEnabled(true);
                            } else {
                                mReloadButtonView.setEnabled(false);
                            }
                        }

                        String pageUrl = mActiveWebView.getUrl();
                        if (pageUrl != null) {
                            mUrlEditTextView.setText(convertCloudUrlToNormal(pageUrl));
                        } else {
                            mUrlEditTextView.setText("");
                        }
                    }
                };

                mTabHost.setOnTabChangedListener(listener);
                mTabStorage = new Vector<TabInfo>();
                createNewTab();
            } catch (Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".listener.onTabChanged(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }
        }

        /**
         * Creates a new tab and makes it active.
         */
        private void createNewTab() {
            try {
                TabSpec tab = mTabHost.newTabSpec(NEW_TAB_LABEL);

                LayoutInflater mInflater = LayoutInflater.from(EnterpriseBrowserActivity.this);
                mActiveWebView = (WebView) mInflater.inflate(R.layout.web_view, null);

                mActiveWebView.setWebChromeClient(mProgressBar);
                mActiveWebView.setWebViewClient(mWebViewClient);
                mActiveWebView.getSettings().setJavaScriptEnabled(true);
                mActiveWebView.getSettings().setBuiltInZoomControls(true);
                mActiveWebView.getSettings().setSupportZoom(true);
                mActiveWebView.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH);
                mActiveWebView.getSettings().setUseWideViewPort(true);

                tab.setContent(mTabFactory);

                View tabIndicator = LayoutInflater.from(EnterpriseBrowserActivity.this)
                        .inflate(R.layout.tab_widget_layout, mTabHost.getTabWidget(), false);
                TextView title = (TextView) tabIndicator.findViewById(R.id.tab_title);
                title.setText(NEW_TAB_LABEL);

                ImageButton closeTabButton = (ImageButton) tabIndicator.findViewById(R.id.close_tab_button);
                closeTabButton.setOnClickListener(mTabDeletionListener);

                tab.setIndicator(tabIndicator);

                TabInfo tabInfo = new TabInfo(tab, closeTabButton, mActiveWebView, title,
                        (ImageView) tabIndicator.findViewById(R.id.tab_left_edge),
                        (ImageView) tabIndicator.findViewById(R.id.tab_right_edge));

                if (mTabStorage.isEmpty()) { //If we created the first tab the cross must be gone.
                    tabInfo.getCloseButton().setVisibility(View.GONE);
                } else if (mTabStorage.size() == 1) { //We must return the cross for the first tab, if we're creating the second one now.
                    mTabStorage.elementAt(0).getCloseButton().setVisibility(View.VISIBLE);
                }

                mTabStorage.add(tabInfo);

                int newTabIndex = mTabHost.getTabWidget().getTabCount();
                mTabHost.addTab(tab);
                mTabHost.setCurrentTab(newTabIndex);

                mUrlEditTextView.setText("");
                //Scroll right the tab toolbar.
                mTabScrollView.post(this);
            } catch (final Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".createNewTab(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }
        }

        private void updateTabsUI() {
            TabInfo tab = getTabInfoForWebView(mActiveWebView);
            //Display tab edges correctly.
            int tabIndex = mTabHost.getCurrentTab();
            int tabNumber = mTabStorage.size();
            TabInfo element;
            for (int i = 0; i < tabNumber; i++) {
                element = mTabStorage.elementAt(i);
                if (element == tab) {
                    element.getLeftEdge().setImageResource(R.drawable.tab_active_left);
                    element.getRightEdge().setImageResource(R.drawable.tab_active_right);
                } else {
                    if (i < tabIndex) {
                        element.getLeftEdge().setImageResource(R.drawable.tab_inactive_left);
                        element.getRightEdge().setImageResource(R.drawable.tab_inactive_filling);
                    } else {
                        element.getLeftEdge().setImageResource(R.drawable.tab_inactive_filling);
                        element.getRightEdge().setImageResource(R.drawable.tab_inactive_right);
                    }
                }
            }
        }

        /**
         * A listener that handles tab closing. It implements the only safe way to delete a tab.
         */
        private OnClickListener mTabDeletionListener = new OnClickListener() {
            public void onClick(View view) {
                try {
                    if (mTabStorage.size() == 1) { //There is only one tab, user can't close it.
                        return;
                    }

                    int activeTabIndex = mTabHost.getCurrentTab();
                    mTabHost.clearAllTabs();
                    TabInfo tabInfo;
                    int elementToDeleteIndex = -1;
                    for (int i = 0; i < mTabStorage.size(); i++) {
                        tabInfo = mTabStorage.elementAt(i);
                        if (tabInfo.getCloseButton() != view) {
                            mTabHost.addTab(tabInfo.getTabSpec());
                        } else {
                            elementToDeleteIndex = i;
                        }
                    }

                    if (elementToDeleteIndex == -1)
                        return;

                    //Halt any activity in the webview in case it is.
                    tabInfo = mTabStorage.elementAt(elementToDeleteIndex);
                    if (tabInfo.isPageLoadingInProgress())
                        tabInfo.getWebView().stopLoading();

                    //Now it is safe to remove the corresponding tabInfo from the storage.
                    mTabStorage.remove(elementToDeleteIndex);

                    //If there is only one tab left, we don't need to set the right active tab, but 'close' button must be made invisible.
                    if (mTabStorage.size() == 1) {
                        mTabStorage.elementAt(0).getCloseButton().setVisibility(View.GONE);
                        return;
                    }

                    //Since we know that indices in the vector correspond to the right indices in the TabHost, we can calculate the position of the active tab.
                    if (activeTabIndex < elementToDeleteIndex) { //The deleted tab followed the active tab.
                        mTabHost.setCurrentTab(activeTabIndex);
                    } else {
                        if (activeTabIndex == elementToDeleteIndex) { //The deleted tab was active.
                            //If the deleted tab was not the first one, no correction is required.
                            if (activeTabIndex != 0)
                                activeTabIndex--;
                            mTabHost.setCurrentTab(activeTabIndex);
                        } else {
                            mTabHost.setCurrentTab(--activeTabIndex); //The deleted tab preceded the active one. The index must be adjusted.
                        }
                    }
                } catch (final Exception e) {
                    Utility.showAlertDialog(
                            EnterpriseBrowserActivity.class.getSimpleName()
                                    + ".tabDeletionListener.onClick(): Failed. " + e.toString(),
                            EnterpriseBrowserActivity.this);
                }
            }
        };

        /**
         * Stops all tabs and brings the activity to its initial state.
         */
        private void resetTabs() {
            try {
                for (int i = 0; i < mTabStorage.size(); i++) {
                    TabInfo tab = mTabStorage.elementAt(i);
                    tab.setPageLoadingInProgress(false);
                    tab.getWebView().stopLoading();
                }

                mTabHost.clearAllTabs();
                mTabStorage.clear();
                createNewTab();
            } catch (final Exception e) {
                Utility.showAlertDialog(
                        EnterpriseBrowserActivity.class.getSimpleName() + ".resetTabs(): Failed. " + e.toString(),
                        EnterpriseBrowserActivity.this);
            }
        }

        /**
         * Returns TabInfo object for the specified WebView.
         * 
         * @param webView {@linkplain WebView} instance to be associated with requested {@linkplain TabInfo} data object.
         * 
         * @return TabInfo object for the specified view.
         */
        private TabInfo getTabInfoForWebView(WebView webView) {
            try {
                for (int i = 0; i != mTabStorage.size(); i++) {
                    TabInfo tab = mTabStorage.elementAt(i);
                    if (tab.getWebView() == webView) {
                        return tab;
                    }
                }
            } catch (final Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".getTabInfoForWebView(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }

            return null;
        }

        /**
         * Helps to identify whether the given TabInfo object belongs to the active tab.
         * 
         * @param tab Recipient data object.
         * 
         * @return True if the object belongs to the active tab, false otherwise.
         */
        boolean isTabActive(final TabInfo tab) {
            if (tab == mTabStorage.elementAt(mTabHost.getCurrentTab())) {
                return true;
            } else {
                return false;
            }
        }

        /**
         * Cleans {@linkplain WebView} history in all tabs. Disables 'back' and 'forward' toolbar buttons.
         */
        public void clearAllHistory() {
            try {
                for (int i = 0; i != mTabStorage.size(); i++) {
                    mTabStorage.elementAt(i).getWebView().clearCache(true);
                    mTabStorage.elementAt(i).getWebView().clearHistory();
                }

                mForwardButtonView.setEnabled(false);
                mBackButtonView.setEnabled(false);
            } catch (final Exception e) {
                Utility.showAlertDialog(EnterpriseBrowserActivity.class.getSimpleName()
                        + ".clearAllHistory(): Failed. " + e.toString(), EnterpriseBrowserActivity.this);
            }
        }

        @Override
        public boolean shouldPublishResults() {
            return !mTabStorage.elementAt(mTabHost.getCurrentTab()).isPageLoadingInProgress();
        }

        /**
         * Simple page loading progress handler.
         */
        private class ProgressUpdater extends WebChromeClient {
            public void onProgressChanged(WebView view, int progress) {
                try {
                    if (progress == 100) {
                        if (!isInSessionExpiryRecovery) {
                            mOriginalUrl = null;
                        }
                        progress = 0;
                    }

                    TabInfo tab = getTabInfoForWebView(view);

                    // With the use of smartbrowser and shouldInterceptRequest, this is occassionally called after the initial page load
                    // finishes. This is an unfortunate hack.
                    if (!mUrlEditTextView.isEnabled()) {
                        tab.setProgress(progress);

                        if (isTabActive(tab)) {
                            mUrlEditTextView.dismissDropDown();
                            mProgressBarView.setProgress(progress);
                        }
                    }
                } catch (final Exception e) {
                    // Log.d() used intentionally to prevent ubiquitous dialogs in case of error.
                    Log.d(ProgressUpdater.class.getSimpleName(), ".onProgressChanged(): Failed. " + e.toString());
                }
            }
        }
    }
}