javamanager/javainstaller/installerui/javasrc/com/nokia/mj/impl/installer/ui/eswt2/InstallerUiEswt.java
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 00:10:53 +0300
changeset 79 2f468c1958d0
parent 76 4ad59aaee882
permissions -rw-r--r--
Revision: v2.2.15 Kit: 201039

/*
* Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/


package com.nokia.mj.impl.installer.ui.eswt2;

import com.nokia.mj.impl.fileutils.FileUtility;
import com.nokia.mj.impl.installer.ui.ApplicationInfo;
import com.nokia.mj.impl.installer.ui.DownloadInfo;
import com.nokia.mj.impl.installer.ui.InstallerUi;
import com.nokia.mj.impl.installer.ui.InstallerUiListener;
import com.nokia.mj.impl.installer.ui.InstallInfo;
import com.nokia.mj.impl.installer.ui.LaunchAppInfo;
import com.nokia.mj.impl.installer.ui.PermissionInfo;
import com.nokia.mj.impl.installer.ui.UninstallInfo;
import com.nokia.mj.impl.rt.ui.ConfirmData;
import com.nokia.mj.impl.rt.ui.RuntimeUi;
import com.nokia.mj.impl.rt.ui.RuntimeUiFactory;
import com.nokia.mj.impl.utils.ResourceUtil;
import com.nokia.mj.impl.utils.StartUpTrace;
import com.nokia.mj.impl.utils.exception.InstallerExceptionBase;

import java.io.InputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;

import org.eclipse.ercp.swt.midp.UIThreadSupport;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.extension.DisplayExtension;
import org.eclipse.swt.internal.qt.BaseCSSEngine;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

/**
 * JavaInstaller eSWT UI.
 */
public class InstallerUiEswt extends InstallerUi
{
    /** Disable UI temporarily. */
    private static final boolean DISABLE_UI =
        (System.getProperty("com.nokia.mj.impl.installer.ui.disableui")
         == null? false: true);
    /** Default shell style. */
    private static final int SHELL_STYLE =
        SWT.BORDER | SWT.APPLICATION_MODAL | SWT.ON_TOP;

    private BaseCSSEngine iCssEngine = null;
    private Shell iParent = null;
    private Shell iDialog = null;
    private ProgressView iProgressView = null;
    private ProgressView iPreparingInstallationView = null;
    private ProgressView iDlProgressView = null;
    private ProgressView iOcspProgressView = null;
    private InstallConfirmationView iInstallConfirmationView = null;
    private PermissionConfirmationView iPermissionConfirmationView = null;
    private UsernamePasswordView iUsernamePasswordView = null;
    private LaunchAppQueryView iLaunchAppQueryView = null;
    private ErrorView iErrorView = null;
    private ErrorDetailsView iErrorDetailsView = null;
    private RuntimeConfirmationView iRuntimeConfirmationView = null;
    /** Synchronization object for waiting for the UI initialization. */
    private Object iInitWaitObject = new Object();
    /** Synchronization object for waiting for the UI termination. */
    private Object iExitWaitObject = new Object();
    /**
     * Synchronization object for synchronizing progress updates
     * and confirmation dialogs. Used to guard disabling and
     * getting iDisplayProgress variable.
     */
    private Object iProgressSyncObject = new Object();
    /** Flag telling if UI main thread exists. */
    private boolean iUiThreadExists = false;
    /** Name of the application to be installed. */
    private String iAppName = null;
    /** Security icon.  */
    private Image iSecurityIcon = null;
    /** Flag telling if progress bar should be displayed. */
    private boolean iDisplayProgress = false;
    /** Flag telling if the first progress bar update has been traced. */
    private boolean iProgressBarUpdateTraced = false;

    /** Certificate details view, owned by the view where it was opened. */
    private CertificateDetailsView iCertificateDetailsView = null;

    /** Hashtable for storing the loaded icons. */
    private static Hashtable iImageTable = null;
    /** Best size for application icon. */
    private static Point iBestImageSize = null;

    /** Default shell bounds. */
    private Rectangle iDefaultShellBounds = null;
    private Rectangle iDefaultShellClientBounds = null;

    /** Bold font for views. */
    private Font iBoldFont = null;

    /** Currently active view. */
    private ViewBase iActiveView = null;

    /**
     * Constructor.
     */
    public InstallerUiEswt()
    {
        super();
    }

    /**
     * Initialise InstallerUi after construction.
     */
    public void init(int aMode, InstallerUiListener aListener)
    {
        super.init(aMode, aListener);
        StartUpTrace.doTrace("InstallerUiEswt init");
        InstallerRuntimeUi.init(this);
        // Create a hashtable for icons.
        iImageTable = new Hashtable();
        // Create a new thread to be the UI main thread.
        UIThreadSupport.startInUIThread(new Runnable()
        {
            public void run()
            {
                uiMain();
            }
        });
        // To wait InstallerUi to be ready before installer main thread
        // continues, uncomment the following line.
        //waitForUi();
    }

    /**
     * This method is executed in UI main thread.
     */
    private void uiMain()
    {
        log("uiMain: thread started");
        iUiThreadExists = true;
        try
        {
            // Create the necessary views.
            DisplayExtension display = new DisplayExtension();
            StartUpTrace.doTrace("InstallerUiEswt display created");
            display.setAppName(""); // Remove display title.
            iCssEngine = new BaseCSSEngine(display);
            iParent = new Shell(display);
            iDialog = new Shell(iParent, SHELL_STYLE);
            iDefaultShellBounds = iDialog.internal_getDefaultBounds();
            iDefaultShellClientBounds = iDialog.getClientArea();
            iBoldFont = getBoldFont();
            StartUpTrace.doTrace("InstallerUiEswt shell created");
            iProgressView = new ProgressView(this, iDialog, getTitle(), false, true);
            //createPreparingInstallationView();

            iParent.addControlListener(new CListener(this));
            log("InstallerUiEswt CListener added");

            display.addListener(SWT.Dispose, new Listener()
            {
                public void handleEvent(Event aEvent)
                {
                    log("Dispose event for display");
                    // Prevent UI from being automatically disposed.
                    //aEvent.doit = false;
                }
            });

            // Initialize best image size.
            iBestImageSize = new Point(
                DisplayExtension.getBestImageWidth(DisplayExtension.LIST_ELEMENT),
                DisplayExtension.getBestImageHeight(DisplayExtension.LIST_ELEMENT));
            log("Best image size: " + iBestImageSize);

            synchronized (iInitWaitObject)
            {
                // Notify that UI is now ready.
                iInitWaitObject.notify();
            }
            StartUpTrace.doTrace("InstallerUiEswt ready");

            // If there has been ui update requests, update the ui right
            // away.
            if (iMode == MODE_INSTALL && getOcspIndicator())
            {
                setOcspIndicator(getOcspIndicator());
            }
            else if (iMode == MODE_APP_CONVERSION &&
                     iAppConversionTotal > 0)
            {
                updateAppConversionIndicator(
                    iAppConversionCurrent, iAppConversionTotal);
            }

            // UI event loop must be executed in UI main thread,
            // which is the thread where Display is created.
            while (isUiReady())
            {
                if (!display.readAndDispatch())
                {
                    display.sleep();
                }
            }
            disposeResources();
            display.dispose();
            log("uiMain: display disposed");
            synchronized (iExitWaitObject)
            {
                // Notify that UI main thread has been terminated.
                iExitWaitObject.notify();
            }
        }
        catch (Throwable t)
        {
            logError("Exception in uiMain", t);
            // Release wait object in case someone is waiting for them.
            synchronized (iInitWaitObject)
            {
                iUiThreadExists = false;
                iInitWaitObject.notify();
            }
            synchronized (iExitWaitObject)
            {
                iExitWaitObject.notify();
            }
        }
        iUiThreadExists = false;
        log("uiMain: thread ended");
    }

    /**
     * Cancels all confirmation views that are currently displayed.
     */
    public void cancelConfirmations()
    {
        super.cancelConfirmations();
        if (iPreparingInstallationView != null &&
            !iPreparingInstallationView.isDisposed())
        {
            iPreparingInstallationView.dispose();
            iPreparingInstallationView = null;
        }
        if (iCertificateDetailsView != null)
        {
            iCertificateDetailsView.confirmCancel();
        }
        if (iInstallConfirmationView != null)
        {
            iInstallConfirmationView.confirmCancel();
        }
        if (iPermissionConfirmationView != null)
        {
            iPermissionConfirmationView.confirmCancel();
        }
        if (iUsernamePasswordView != null)
        {
            iUsernamePasswordView.confirmCancel();
        }
        if (iLaunchAppQueryView != null)
        {
            iLaunchAppQueryView.confirmCancel();
        }
        if (iErrorView != null)
        {
            iErrorView.confirmCancel();
        }
        if (iErrorDetailsView != null)
        {
            iErrorDetailsView.confirmCancel();
        }
        if (iRuntimeConfirmationView != null)
        {
            iRuntimeConfirmationView.confirmCancel();
        }
        // Remove download progress bar if it visible.
        if (iDlProgressView != null && !iDlProgressView.isDisposed())
        {
            iDlProgressView.dispose();
            iDlProgressView = null;
        }
    }

    /**
     * Confirm installation. UI should display an installation
     * confirmation dialog to the user. UI must update
     * aInstallInfo basing on user selections.
     * This method blocks until user has answered to the dialog.
     *
     * @param aInstallInfo installation information
     * @return true if user has accepted installation, false otherwise
     */
    public boolean confirm(InstallInfo aInstallInfo)
    {
        super.confirm(aInstallInfo);

        waitForUi();
        if (!isUiReady())
        {
            // If UI is not ready by the time confirmation is requested,
            // throw an exception.
            throw new RuntimeException("JavaInstallerUi not ready");
        }

        if (iPreparingInstallationView != null &&
            !iPreparingInstallationView.isDisposed())
        {
            iPreparingInstallationView.setVisible(false);
            iPreparingInstallationView.dispose();
            iPreparingInstallationView = null;
        }

        boolean result = true;
        if (result)
        {
            StartUpTrace.doTrace("InstallerUiEswt confirm");
            if (iInstallConfirmationView == null)
            {
                final Display display = iParent.getDisplay();
                final InstallerUiEswt self = this;
                display.syncExec(new Runnable()
                {
                    public void run()
                    {
                        iInstallConfirmationView =
                            new InstallConfirmationView(self, iDialog);
                    }
                });
            }
            result = iInstallConfirmationView.confirm(aInstallInfo);
            iInstallConfirmationView.dispose();
            iInstallConfirmationView = null;
        }
        if (result)
        {
            iAppName = aInstallInfo.getName();
            if (iUsernamePasswordView != null)
            {
                // UsernamePasswordView blocks prompting until
                // app name is set so that username/password
                // prompt will not be displayed if the user
                // does not confirm installation.
                iUsernamePasswordView.setAppName(iAppName);
            }
            else
            {
                // UsernamePasswordView is not being displayed,
                // let's allow progress to be displayed.
                iDisplayProgress = true;
                // If download progress view is in use,
                // display it to user only after confirmation.
                if (iDlProgressView != null && !iDlProgressView.isVisible())
                {
                    iDlProgressView.setVisible(true);
                }
            }
        }
        else
        {
            // The install confirmation has been rejected,
            // nothing to display anymore.
            iParent.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    iParent.dispose();
                }
            });
        }
        log("Installation confirmation returns " + result);
        return result;
    }

    /**
     * Confirm permissions. UI should display a permission
     * confirmation dialog to the user.
     * This method blocks until user has answered to the dialog.
     *
     * @param aPermissionInfo permission information
     * @return true if user has accepted permissions, false otherwise
     */
    public boolean confirmPermissions(PermissionInfo aPermissionInfo)
    {
        super.confirmPermissions(aPermissionInfo);

        waitForUi();
        if (!isUiReady())
        {
            aPermissionInfo.setPermissionAllowed(false);
            return true;
        }

        // Ensure that UI is visible when this prompt is displayed.
        unhide();

        synchronized (iProgressSyncObject)
        {
            // Do not display progress bar during dialog.
            iDisplayProgress = false;
        }
        if (iPermissionConfirmationView == null)
        {
            final Display display = iParent.getDisplay();
            final InstallerUiEswt self = this;
            display.syncExec(new Runnable()
            {
                public void run()
                {
                    iPermissionConfirmationView =
                        new PermissionConfirmationView(self, iDialog);
                }
            });
        }
        boolean result = iPermissionConfirmationView.confirm(
                             iInstallInfo, aPermissionInfo);
        iPermissionConfirmationView.dispose();
        iPermissionConfirmationView = null;
        iDisplayProgress = true;
        iProgressView.setVisible(true);
        log("Permission confirmation returns " + result +
            ", user selection " + aPermissionInfo.isPermissionAllowed());
        return result;
    }

    /**
     * Confirm uninstallation. UI should display an uninstallation
     * confirmation dialog to the user.
     * This method blocks until user has answered to the dialog.
     *
     * @param aUninstallInfo uninstallation information
     * @return true if user has accepted uninstallation, false otherwise
     */
    public boolean confirm(UninstallInfo aUninstallInfo)
    {
        return super.confirm(aUninstallInfo);
    }

    /**
     * This method is used to notify UI that installation or
     * uninstallation has started. Upon this call UI could
     * for example display progress bar.
     * This method must return quickly.
     */
    public void started()
    {
        super.started();
    }

    /**
     * This method is used to notify UI that installation or
     * uninstallation has progressed. Upon this call UI can
     * update progress bar.
     * This method must return quickly.
     *
     * @param aProgress progress value between 0 and 100
     */
    public void updateProgress(int aProgress)
    {
        super.updateProgress(aProgress);
        if (DISABLE_UI) return; // Disable UI temporarily.
        if (!isUiReady())
        {
            return;
        }
        // UI is created asynchronously, so it might be that
        // UI was not yet ready when started() was called.
        // Ensure that iProgressView has been opened before
        // updating it.
        synchronized (iProgressSyncObject)
        {
            if (iDlProgressView != null && iDlProgressView.isVisible())
            {
                // If download progress is being displayed,
                // do not display installation progress.
                return;
            }
            if (iDisplayProgress && !iProgressView.isVisible() &&
                iCertificateDetailsView == null)
            {
                // Re-create iProgressView here so that it gets
                // application info that was set when confirm()
                // was called.
                final InstallerUiEswt self = this;
                iParent.getDisplay().syncExec(new Runnable()
                {
                    public void run()
                    {
                        iProgressView = new ProgressView(
                            self, iDialog, getTitle(), false, true);
                    }
                });
                iProgressView.setVisible(true);
            }
            if (iDisplayProgress && !iProgressBarUpdateTraced)
            {
                StartUpTrace.doTrace(
                    "InstallerUiEswt progress " + aProgress + " %");
                iProgressBarUpdateTraced = true;
            }
        }
        iProgressView.updateProgress(aProgress);

    }

    /**
     * This method is used to notify UI that installation or
     * uninstallation has ended. Upon this call UI can
     * stop displaying progress bar.
     * This method must return quickly.
     */
    public void ended()
    {
        super.ended();
        if (DISABLE_UI) return; // Disable UI temporarily.
        if (!isUiReady())
        {
            return;
        }
        Display display = iParent.getDisplay();
        display.syncExec(new Runnable()
        {
            public void run()
            {
                iParent.dispose();
            }
        });
        log("ended: parent disposed");
        // Let's wait for ui to be properly terminated before returning.
        synchronized (iExitWaitObject)
        {
            try
            {
                if (!display.isDisposed())
                {
                    iExitWaitObject.wait();
                }
            }
            catch (InterruptedException ie)
            {
                // Ignore silently.
            }
        }
        log("ended returns");
    }

    /**
     * This method is used to notify UI that a download has started.
     * Upon this call UI can for example prepare to display a download
     * progress bar.
     * NOTE: When this method is called, the totalSize in DownlodInfo
     * is not yet initialized.
     * This method must return quickly.
     *
     * @param aDownloadInfo information about download
     */
    public void started(DownloadInfo aDownloadInfo)
    {
        super.started(aDownloadInfo);
        if (DISABLE_UI) return; // Disable UI temporarily.
        if (!isUiReady())
        {
            return;
        }
        // Ensure that download progress bar is displayed and
        // updated to zero progress.
        long oldCurrentSize = aDownloadInfo.getCurrentSize();
        long oldTotalSize = aDownloadInfo.getTotalSize();
        aDownloadInfo.setCurrentSize(0);
        aDownloadInfo.setTotalSize(100);
        updateProgress(aDownloadInfo);
        aDownloadInfo.setCurrentSize(oldCurrentSize);
        aDownloadInfo.setTotalSize(oldTotalSize);
    }

    /**
     * This method is used to notify UI that a download
     * has progressed. Upon this call UI can
     * update download progress bar.
     * This method must return quickly.
     *
     * @param aDownloadInfo information about download
     */
    public void updateProgress(DownloadInfo aDownloadInfo)
    {
        super.updateProgress(aDownloadInfo);
        if (DISABLE_UI) return; // Disable UI temporarily.
        if (!isUiReady())
        {
            return;
        }

        if (iDlProgressView == null)
        {
            final boolean indeterminate =
                (aDownloadInfo.getTotalSize() <= 0);
            final InstallerUiEswt self = this;
            iParent.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    iDlProgressView = new ProgressView(
                        self, iDialog,
                        InstallerUiTexts.get(InstallerUiTexts.DOWNLOADING),
                        indeterminate, true);
                }
            });
        }

        synchronized (iProgressSyncObject)
        {
            if (iDisplayProgress && !iDlProgressView.isVisible() &&
                iCertificateDetailsView == null)
            {
                iDlProgressView.setVisible(true);
            }
        }
        if (aDownloadInfo.getTotalSize() > 0)
        {
            int progress = (int)((aDownloadInfo.getCurrentSize()*100)/
                                 aDownloadInfo.getTotalSize());
            iDlProgressView.updateProgress(progress);
        }
    }

    /**
     * This method is used to notify UI that a download
     * has ended. Upon this call UI can stop
     * displaying download progress bar.
     * This method must return quickly.
     *
     * @param aDownloadInfo information about download
     */
    public void ended(DownloadInfo aDownloadInfo)
    {
        super.ended(aDownloadInfo);
        if (DISABLE_UI) return; // Disable UI temporarily.
        if (!isUiReady())
        {
            return;
        }
        if (iDlProgressView != null && !iDlProgressView.isDisposed())
        {
            iDlProgressView.dispose();
            iDlProgressView = null;
        }
        synchronized (iProgressSyncObject)
        {
            if (iDisplayProgress && !iProgressView.isVisible() &&
                iCertificateDetailsView == null)
            {
                iProgressView.setVisible(true);
            }
        }
    }

    /**
     * Set OCSP indicator on or off.
     *
     * @param aOn true when OCSP is started, false when OCSP is stopped
     */
    public void setOcspIndicator(boolean aOn)
    {
        super.setOcspIndicator(aOn);
        if (DISABLE_UI) return; // Disable UI temporarily.
        waitForUi();
        if (!isUiReady())
        {
            log("UI not ready, could not set OCSP indicator to " + aOn);
            return;
        }
        if (aOn)
        {
            if (iOcspProgressView == null)
            {
                final InstallerUiEswt self = this;
                iParent.getDisplay().syncExec(new Runnable()
                {
                    public void run()
                    {
                        iOcspProgressView = new ProgressView(
                            self, iDialog,
                            InstallerUiTexts.get(InstallerUiTexts.OCSP_CHECK_PROGRESS),
                            true, true);
                    }
                });
            }
            if (iOcspProgressView != null)
            {
                if (!iOcspProgressView.isVisible() &&
                    iCertificateDetailsView == null)
                {
                    iOcspProgressView.setVisible(true);
                }
            }
        }
        else
        {
            if (iOcspProgressView != null)
            {
                if (iOcspProgressView.isVisible())
                {
                    iOcspProgressView.setVisible(false);
                }
                iOcspProgressView.dispose();
                iOcspProgressView = null;
            }
        }
    }

    /**
     * Update application conversion indicator.
     *
     * @param aCurrent Index of the current application (1..total).
     * @param aTotal Total number of applications. Setting aTotal to
     * zero indicates that application conversion has been completed
     * and ui can be disposed.
     */
    public void updateAppConversionIndicator(int aCurrent, int aTotal)
    {
        super.updateAppConversionIndicator(aCurrent, aTotal);
        if (!isUiReady())
        {
            log("UI not ready, could not update app conversion indicator");
            return;
        }
        int progress = 101;
        if (aTotal > 0)
        {
            progress = (aCurrent*100)/aTotal;
        }
        if (progress <= 100)
        {
            iProgressView.setText(getTitle());
            iProgressView.updateProgress(progress);
            if (!iProgressView.isVisible())
            {
                iProgressView.removeCancelCommand();
                iProgressView.setVisible(true);
            }
            iParent.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    // Ensure that conversion indicator
                    // is drawn to the foreground.
                    iParent.setMaximized(true);
                }
            });
        }
        else
        {
            iProgressView.updateProgress(100);
            // Call ended() method which will dispose the ui.
            ended();
        }
    }

    /**
     * Notify user that an error has occurred.
     *
     * @param aInstallerException exception indicating the error reason
     */
    public void error(InstallerExceptionBase aInstallerException)
    {
        super.error(aInstallerException);
        if (DISABLE_UI) return; // Disable UI temporarily.

        waitForUi();
        waitForCertificateDetailsView();
        if (!isUiReady() || iHidden || iConfirmationsCanceled) {
            return;
        }

        // Use ErrorView to display error message.
        if (iErrorView == null)
        {
            final Display display = iParent.getDisplay();
            final InstallerUiEswt self = this;
            display.syncExec(new Runnable()
            {
                public void run()
                {
                    iErrorView = new ErrorView(self, iDialog);
                }
            });
        }
        boolean result = iErrorView.error(aInstallerException);
        iErrorView.dispose();
        iErrorView = null;

        if (result)
        {
            // Display error details.
            if (iErrorDetailsView == null)
            {
                final Display display = iParent.getDisplay();
                final InstallerUiEswt self = this;
                display.syncExec(new Runnable()
                {
                    public void run()
                    {
                        iErrorDetailsView = new ErrorDetailsView(self, iDialog);
                    }
                });
            }
            result = iErrorDetailsView.error(aInstallerException);
            iErrorDetailsView.dispose();
            iErrorDetailsView = null;
        }
    }

    /**
     * Seeks confirmation from the user.
     *
     * @param aAppName     the name of the application on behalf of which the
     *                     confirmation is requested
     * @param aConfirmData the data to be confirmed. Unless the user has
     *                     canceled the confirmation, this data will be filled
     *                     in with user's answer upon return
     * @return             true if the user has answered, false if the user has
     *                     canceled the confirmation
     */
    public boolean confirm(String aAppName, ConfirmData aConfirmData)
    {
        if (DISABLE_UI) return true; // Disable UI temporarily.
        waitForUi();
        if (!isUiReady()) {
            return true;
        }
        waitForCertificateDetailsView();
        if (iConfirmationsCanceled)
        {
            return false;
        }

        // Ensure that UI is visible when this prompt is displayed.
        unhide();

        if (iRuntimeConfirmationView == null)
        {
            final Display display = iParent.getDisplay();
            final InstallerUiEswt self = this;
            final String appName = aAppName;
            final ConfirmData confirmData = aConfirmData;
            display.syncExec(new Runnable()
            {
                public void run()
                {
                    iRuntimeConfirmationView = new RuntimeConfirmationView(
                        self, iDialog, appName, confirmData);
                }
            });
        }
        boolean result = iRuntimeConfirmationView.confirm();
        iRuntimeConfirmationView.dispose();
        iRuntimeConfirmationView = null;
        log("Runtime confirmation returns " + result);
        return result;
    }

    /**
     * Ask username and password for http authentication.
     * This method blocks until user has answered to the dialog.
     *
     * @param aUrl url for which authentication is required
     * @return Array of two strings, the first being username
     * and second being password. If username and password
     * cannot be obtained, this method returns null.
     */
    public String[] getUsernamePassword(String aUrl)
    {
        if (DISABLE_UI) return new String[] { "", "" }; // Disable UI temporarily.
        waitForUi();
        waitForCertificateDetailsView();
        if (!isUiReady() || iConfirmationsCanceled)
        {
            return null;
        }

        // Ensure that UI is visible when this prompt is displayed.
        unhide();

        synchronized (iProgressSyncObject)
        {
            // Do not display progress bar during dialog.
            iDisplayProgress = false;
        }
        if (iUsernamePasswordView == null)
        {
            final Display display = iParent.getDisplay();
            final InstallerUiEswt self = this;
            display.syncExec(new Runnable()
            {
                public void run()
                {
                    iUsernamePasswordView =
                        new UsernamePasswordView(self, iDialog);
                }
            });
        }
        iUsernamePasswordView.setAppName(iAppName);
        final String[] usernamePassword =
            iUsernamePasswordView.getUsernamePassword(aUrl);
        iUsernamePasswordView.dispose();
        iUsernamePasswordView = null;
        iDisplayProgress = true;

        if (usernamePassword != null)
        {
            log("Username: " + usernamePassword[0]);
            // Do not write confidential information (password) to log.
            //log("Password: " + usernamePassword[1]);
        }
        else
        {
            log("No username and password provided");
        }
        return usernamePassword;
    }

    /**
     * Asks from the user if the installed application should be launched.
     *
     * @param aLaunchAppInfo Information about application that can
     * be launched. Unless the user cancels the query,
     * this data will be filled in with user's answer upon return.
     * @return true if the user has chosen to launch the application,
     * false if the user has canceled the query
     */
    public boolean launchAppQuery(LaunchAppInfo aLaunchAppInfo)
    {
        if (DISABLE_UI) return false; // Disable UI temporarily.
        waitForUi();
        waitForCertificateDetailsView();
        if (!isUiReady() || iConfirmationsCanceled ||
            iHidden || getInstallInfo() == null)
        {
            // Either UI is not yet ready, user has cancelled
            // installation or UI is hidden; in all these cases
            // do nothing.
            return false;
        }

        if (iLaunchAppQueryView == null)
        {
            final Display display = iParent.getDisplay();
            final InstallerUiEswt self = this;
            display.syncExec(new Runnable()
            {
                public void run()
                {
                    iLaunchAppQueryView = new LaunchAppQueryView(self, iDialog);
                }
            });
        }
        boolean result = iLaunchAppQueryView.launchAppQuery(aLaunchAppInfo);
        iLaunchAppQueryView.dispose();
        iLaunchAppQueryView = null;
        if (!result)
        {
            iParent.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    iParent.dispose();
                }
            });
        }
        log("LaunchAppQuery returns " + result + " for " + aLaunchAppInfo);
        return result;
    }

    /**
     * Executes given Runnable synchronously in the UI thread.
     */
    public void syncExec(Runnable aRunnable)
    {
        if (!iParent.getDisplay().isDisposed())
        {
            iParent.getDisplay().syncExec(aRunnable);
        }
    }

    /**
     * Hides or unhides InstallerUi.
     */
    public void hide(boolean aHide)
    {
        iHidden = aHide;
        if (iDialog != null)
        {
            iDialog.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    iDialog.setMinimized(iHidden);
                }
            });
        }
        super.hide(aHide);
    }

    /**
     * Unhides the UI if it has been hidden.
     */
    protected void unhide()
    {
        if (iHidden)
        {
            hide(false);
        }
    }

    /**
     * Sets flag telling if certificate details view is open.
     */
    protected void setCertificateDetailsView(CertificateDetailsView aView)
    {
        if (iCertificateDetailsView != null && aView == null)
        {
            // Certificate details view has been closed,
            // notify possible waiters.
            synchronized (iCertificateDetailsView)
            {
                iCertificateDetailsView.notify();
            }
        }
        iCertificateDetailsView = aView;
    }

    /**
     * Waits until certificate details view is closed.
     */
    protected void waitForCertificateDetailsView()
    {
        if (iCertificateDetailsView != null)
        {
            // If certificate details view is open, wait until
            // user closes it.
            synchronized (iCertificateDetailsView)
            {
                try
                {
                    iCertificateDetailsView.wait();
                }
                catch (InterruptedException ie)
                {
                }
            }
        }
    }

    /**
     * Returns string title basing on mode of this InstallerUi.
     */
    protected String getTitle()
    {
        String result = super.getTitle();
        if (isUiReady())
        {
            if (iMode == MODE_INSTALL)
            {
                result = InstallerUiTexts.get(InstallerUiTexts.INSTALLING);
            }
            else if (iMode == MODE_UNINSTALL)
            {
                result = InstallerUiTexts.get("Uninstalling");
            }
            else if (iMode == MODE_APP_CONVERSION)
            {
                result = InstallerUiTexts.get(
                    "Converting data for application " +
                    iAppConversionCurrent + "/" + iAppConversionTotal);
            }
        }
        return result;
    }

    /**
     * Returns icon for indicating identified or unidentified application.
     *
     * @param aDisplay display to which the icon will be added
     * @param aIdentified if true returns icon for identified application,
     * otherwise returns icon for unidentified application
     * @return icon for identified or unidentified application, or null
     * if icon cannot be loaded
     */
    protected Image getSecurityIcon(Display aDisplay, boolean aIdentified)
    {
        if (iSecurityIcon != null)
        {
            return iSecurityIcon;
        }
        String iconFilename = "java_3_untrusted.png";
        if (aIdentified)
        {
            iconFilename = "java_3_trusted.png";
        }
        try
        {
            String resourceDir = ResourceUtil.getResourceDir(0);
            for (int i = 1; iSecurityIcon == null && resourceDir != null; i++)
            {
                iSecurityIcon = loadImage(
                    aDisplay, resourceDir + iconFilename, false);
                resourceDir = ResourceUtil.getResourceDir(i);
            }
        }
        catch (Throwable t)
        {
            log("Can not load security icon: " + t);
        }
        return iSecurityIcon;
    }

    /**
     * Loads image from specified InputStream. This method scales the image
     * to optimum size.
     *
     * @param aDisplay display to which the icon will be added
     * @param aInputStream InputStream where the image is loaded
     * @param aImageName name of the image to be loaded
     * @return image from InputStream or null if image cannot be loaded
     */
    protected static Image loadImage(
        Display aDisplay, InputStream aInputStream, String aImageName)
    {
        return loadImage(aDisplay, aInputStream, aImageName, true);
    }

    /**
     * Loads image from specified file.
     *
     * @param aDisplay display to which the icon will be added
     * @param aImageFilename name of the image file to be loaded
     * @param aScaleImage flag telling if the loaded image should be scaled
     * @return image from file or null if image cannot be loaded
     */
    private static Image loadImage(
        Display aDisplay, String aImageFilename, boolean aScaleImage)
    {
        Image result = null;
        InputStream imageInputStream = null;
        try
        {
            FileUtility imageFile = new FileUtility(aImageFilename);
            if (imageFile.exists())
            {
                imageInputStream = imageFile.openInputStream();
                result = loadImage(aDisplay, imageInputStream,
                                   aImageFilename, aScaleImage);
            }
        }
        catch (IOException ioe)
        {
            log("Can not get InputStream for " + aImageFilename + ": " + ioe);
        }
        finally
        {
            if (imageInputStream != null)
            {
                try
                {
                    imageInputStream.close();
                    imageInputStream = null;
                }
                catch (IOException ioe)
                {
                    logError("Closing image InputStream failed", ioe);
                }
            }
        }
        return result;
    }

    /**
     * Loads image from specified InputStream.
     *
     * @param aDisplay display to which the icon will be added
     * @param aInputStream InputStream where the image is loaded
     * @param aImageName name of the image to be loaded
     * @param aScaleImage flag telling if the loaded image should be scaled
     * @return image from InputStream or null if image cannot be loaded
     */
    private static Image loadImage(
        Display aDisplay, InputStream aInputStream,
        String aImageName, boolean aScaleImage)
    {
        Image result = null;
        if (aImageName != null)
        {
            result = (Image)iImageTable.get(aImageName);
        }
        if (result != null)
        {
            log("Using already loaded image " + aImageName);
            return result;
        }
        try
        {
            long startTime = System.currentTimeMillis();
            aDisplay.setData("org.eclipse.swt.internal.image.loadSize",
                             iBestImageSize);
            Image image = new Image(aDisplay, aInputStream);
            if (aScaleImage)
            {
                ImageData imageData = image.getImageData();
                if (iBestImageSize.x != imageData.width ||
                        iBestImageSize.y != imageData.height)
                {
                    Point oldSize =
                        new Point(imageData.width, imageData.height);
                    imageData = imageData.scaledTo(iBestImageSize.x, iBestImageSize.y);
                    log("Image " + aImageName + " scaled from " +
                        oldSize.x + "x" + oldSize.y + " to " +
                        iBestImageSize.x + "x" + iBestImageSize.y);
                    image = new Image(aDisplay, imageData);
                }
            }
            result = image;
            long endTime = System.currentTimeMillis();
            log("Loaded image " + aImageName + " (load time " +
                (endTime - startTime) + " ms)");
            iImageTable.put(aImageName, result);
        }
        catch (Throwable t)
        {
            log("Can not load image " + aImageName + ": " + t);
            //logError("Exception while loading image " + aImageName, t);
        }
        return result;
    }

    /** Returns true if UI has been created and can be used. */
    protected boolean isUiReady()
    {
        if (iProgressView == null)
        {
            log("ui not ready, iProgressView is null");
            return false;
        }
        if (iProgressView.isDisposed())
        {
            log("ui not ready, iProgressView is disposed");
            return false;
        }
        return true;
    }

    /**
     * Blocks until UI has became ready.
     */
    private void waitForUi()
    {
        synchronized (iInitWaitObject)
        {
            try
            {
                if (iUiThreadExists && iProgressView == null)
                {
                    iInitWaitObject.wait();
                }
            }
            catch (InterruptedException ie)
            {
                // Ignore silently.
            }
        }
    }

    /**
     * Called when screen orientation changes.
     */
    private void screenOrientationChanged()
    {
        iDefaultShellBounds = iDialog.internal_getDefaultBounds();

        if (iActiveView != null)
        {
            iActiveView.screenOrientationChanged();
        }
    }

    private static class CListener implements ControlListener
    {
        private InstallerUiEswt iInstallerUi = null;

        CListener(InstallerUiEswt aInstallerUi)
        {
            iInstallerUi = aInstallerUi;
        }

        public void controlMoved(ControlEvent aEvent)
        {
        }

        public void controlResized(ControlEvent aEvent)
        {
            iInstallerUi.screenOrientationChanged();
        }
    }

    /**
     * Creates preparing installation view.
     */
    private void createPreparingInstallationView()
    {
        if (iPreparingInstallationView == null)
        {
            final InstallerUiEswt self = this;
            iParent.getDisplay().syncExec(new Runnable()
            {
                public void run()
                {
                    iPreparingInstallationView = new ProgressView(
                        self, iDialog, "Preparing installation", true, false);
                    iPreparingInstallationView.setVisible(true);
                }
            });
        }
    }

    Rectangle getDefaultShellBounds()
    {
        return iDefaultShellBounds;
    }

    Rectangle getDefaultShellClientBounds()
    {
        return iDefaultShellClientBounds;
    }

    /** Get bold font for the current view. */
    Font getBoldFont()
    {
        if (iBoldFont == null)
        {
            FontData[] fontDatas = iParent.getFont().getFontData();
            FontData[] boldFontDatas = new FontData[fontDatas.length];
            for (int i = 0; i < boldFontDatas.length; i++)
            {
                boldFontDatas[i] = new FontData
                (fontDatas[i].getName(), fontDatas[i].getHeight()+1, SWT.BOLD);
            }
            iBoldFont = new Font(iParent.getDisplay(), boldFontDatas);
        }
        return iBoldFont;
    }

    void setActiveView(ViewBase aView)
    {
        if (iActiveView != null && iActiveView != aView &&
                !iActiveView.isDisposed())
        {
            iActiveView.setVisible(false);
        }
        iActiveView = aView;
    }

    ViewBase getActiveView()
    {
        return iActiveView;
    }

    /**
     * Loads JavaInstaller UI stylesheet.
     */
    void loadCss()
    {
        String cssFilename = "javaapplicationinstaller.css";
        String cssPath = null;
        try
        {
            if (iCssEngine != null)
            {
                boolean loaded = false;
                String resourceDir = ResourceUtil.getResourceDir(0);
                for (int i = 1; !loaded && resourceDir != null; i++)
                {
                    cssPath = resourceDir + cssFilename;
                    FileUtility cssFile = new FileUtility(cssPath);
                    if (cssFile.exists())
                    {
                        iCssEngine.loadCSS(cssPath);
                        log("CSS loaded from " + cssPath);
                        break;
                    }
                    resourceDir = ResourceUtil.getResourceDir(i);
                }
            }
        }
        catch (Throwable t)
        {
            logError("Loading CSS from " + cssPath + " failed", t);
        }
    }

    private void disposeResources() {
        if (iBoldFont != null && !iBoldFont.isDisposed())
        {
            iBoldFont.dispose();
        }
        if (iSecurityIcon != null && !iSecurityIcon.isDisposed())
        {
            iSecurityIcon.dispose();
        }
        Enumeration e = iImageTable.elements();
        while (e.hasMoreElements())
        {
            Image img = (Image)e.nextElement();
            if (img != null && !img.isDisposed())
            {
                img.dispose();
            }
        }
    }
}