////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2010 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.components
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.SoftKeyboardEvent;
import flash.events.SoftKeyboardTrigger;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.utils.Timer;

import mx.core.EventPriority;
import mx.core.FlexGlobals;
import mx.core.FlexVersion;
import mx.core.mx_internal;
import mx.effects.IEffect;
import mx.effects.Parallel;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import mx.events.SandboxMouseEvent;
import mx.managers.PopUpManager;
import mx.managers.SystemManager;
import mx.styles.StyleProtoChain;

import spark.effects.Move;
import spark.effects.Resize;
import spark.effects.animation.Animation;
import spark.effects.easing.IEaser;
import spark.effects.easing.Power;
import spark.events.PopUpEvent;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched by the container when it's opened and ready for user interaction.
 *
 *  <p>This event is dispatched when the container switches from the
 *  <code>closed</code> to <code>normal</code> skin state and the transition
 *  to that state completes.</p>
 *
 *  <p>Note: As of Flex 4.6, SkinnablePopUp container inherits its styles
 *  from the stage and not its owner.</p>
 *
 *  @eventType spark.events.PopUpEvent.OPEN
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
[Event(name="open", type="spark.events.PopUpEvent")]

/**
 *  Dispatched by the container when it's closed.
 *
 *  <p>This event is dispatched when the container switches from the
 *  <code>normal</code> to <code>closed</code> skin state and
 *  the transition to that state completes.</p>
 *
 *  <p>The event provides a mechanism to pass commit information from
 *  the container to an event listener.
 *  One typical usage scenario is building a multiple-choice dialog with a
 *  cancel button.
 *  When a valid option is selected, you close the pop up
 *  with a call to the <code>close()</code> method, passing
 *  <code>true</code> to the <code>commit</code> parameter and optionally passing in
 *  any relevant data.
 *  When the SkinnablePopUpContainer has completed closing,
 *  it dispatches this event.
 *  Then, in the event listener, you can check the <code>commit</code>
 *  property and perform the appropriate action.  </p>
 *
 *  @eventType spark.events.PopUpEvent.CLOSE
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
[Event(name="close", type="spark.events.PopUpEvent")]

//--------------------------------------
//  Styles
//--------------------------------------

/**
 *  Duration of the soft keyboard move and resize effect in milliseconds.
 *
 *  @default 150
 *
 *  @langversion 3.0
 *  @playerversion Flash 11
 *  @playerversion AIR 3.1
 *  @productversion Flex 4.6
 */
[Style(name="softKeyboardEffectDuration", type="Number", format="Time", inherit="no", minValue="0.0")]

//--------------------------------------
//  States
//--------------------------------------

/**
 *  The closed state.
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
[SkinState("closed")]

/**
 *  The SkinnablePopUpContainer class is a SkinnableContainer that functions as a pop-up.
 *  One typical use for a SkinnablePopUpContainer container is to open a simple window
 *  in an application, such as an alert window, to indicate that the user must perform some action.
 *
 *  <p>You do not create a SkinnablePopUpContainer container as part of the normal layout
 *  of its parent container.
 *  Instead, it appears as a pop-up window on top of its parent.
 *  Therefore, you do not create it directly in the MXML code of your application.</p>
 *
 *  <p>Instead, you create is as an MXML component, often in a separate MXML file.
 *  To show the component create an instance of the MXML component, and
 *  then call the <code>open()</code> method.
 *  You can also set the size and position of the component when you open it.</p>
 *
 *  <p>To close the component, call the <code>close()</code> method.
 *  If the pop-up needs to return data to a handler, you can add an event listener for
 *  the <code>PopUp.CLOSE</code> event, and specify the returned data in
 *  the <code>close()</code> method.</p>
 *
 *  <p>The SkinnablePopUpContainer is initially in its <code>closed</code> skin state.
 *  When it opens, it adds itself as a pop-up to the PopUpManager,
 *  and transition to the <code>normal</code> skin state.
 *  To define open and close animations, use a custom skin with transitions between
 *  the <code>closed</code> and <code>normal</code> skin states.</p>
 *
 *  <p>The SkinnablePopUpContainer container has the following default characteristics:</p>
 *     <table class="innertable">
 *     <tr><th>Characteristic</th><th>Description</th></tr>
 *     <tr><td>Default size</td><td>Large enough to display its children</td></tr>
 *     <tr><td>Minimum size</td><td>0 pixels</td></tr>
 *     <tr><td>Maximum size</td><td>10000 pixels wide and 10000 pixels high</td></tr>
 *     <tr><td>Default skin class</td><td>spark.skins.spark.SkinnablePopUpContainerSkin</td></tr>
 *     </table>
 *
 *  @mxml <p>The <code>&lt;s:SkinnablePopUpContainer&gt;</code> tag inherits all of the tag
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;s:SkinnablePopUpContainer
 *    <strong>Events</strong>
 *    close="<i>No default</i>"
 *    open="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *
 *  @see spark.skins.spark.SkinnablePopUpContainerSkin
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
public class SkinnablePopUpContainer extends SkinnableContainer
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function SkinnablePopUpContainer()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  Storage for the close event while waiting for the close transition to
     *  complete before dispatching it.
     *
     *  @private
     */
    private var closeEvent:PopUpEvent;

    /**
     *  Track whether the container is added to the PopUpManager.
     *
     *  @private
     */
    private var addedToPopUpManager:Boolean = false;
    
    //--------------------------------------------------------------------------
    //
    //  Soft Keyboard Effect Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  The current soft keyboard effect instance. This field is only set in
     *  cases where the position and size are not snapped during (a) initial
     *  activation and (b) deactivation.
     */
    private var softKeyboardEffect:IEffect;

    /**
     *  @private
     *  Original pop-up y-position.
     */
    private var softKeyboardEffectCachedYPosition:Number;

    /**
     *  @private
     *  Indicates a soft keyboard event was received but the effect is delayed
     *  while waiting for a mouseDown and mouseUp event sequence.
     */
    private var softKeyboardEffectPendingEventType:String = null;

    /**
     *  @private
     *  Number of milliseconds to wait for a mouseDown and mouseUp event
     *  sequence before playing the deactivate effect.
     */
    mx_internal var softKeyboardEffectPendingEffectDelay:Number = 100;

    /**
     *  @private
     *  Timer used for awaiting a mouseDown event after a pending soft keyboard
     *  effect.
     */
    private var softKeyboardEffectPendingEventTimer:Timer;

    /**
     *  @private
     *  Flag when orientationChanging is dispatched and orientationChange has
     *  not been received.
     */
    private var softKeyboardEffectOrientationChanging:Boolean = false;

    /**
     *  @private
     *  Flag when orientation change handlers are installed to suppress
     *  excess soft keyboard effects during orientation change on iOS.
     */
    private var softKeyboardEffectOrientationHandlerAdded:Boolean = false;
    
    /**
     *  @private
     *  Flag when mouse and orientation listeners are installed before
     *  the soft keyboard activate effect is played.
     */
    private var softKeyboardStateChangeListenersInstalled:Boolean;
    
    /**
     *  @private
     *  Flag when resize listers are installed after ACTIVATE and uninstalled
     *  just before DEACTIVATE.
     */
    private var resizeListenerInstalled:Boolean = false;

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  isOpen
    //----------------------------------

    /**
     *  Storage for the isOpen property.
     *
     *  @private
     */
    private var _isOpen:Boolean = false;

    [Inspectable(category="General", defaultValue="false")]

    /**
     *  Contains <code>true</code> when the container is open and is currently showing as a pop-up.
     *
     *  @see #open
     *  @see #close
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get isOpen():Boolean
    {
        return _isOpen;
    }

    /**
     *  Updates the isOpen flag to be reflected in the skin state
     *  without actually popping up the container through the PopUpManager.
     *
     *  @private
     */
    mx_internal function setIsOpen(value:Boolean):void
    {
        // NOTE: DesignView relies on this API, consult tooling before making changes.
        _isOpen = value;
        invalidateSkinState();
    }

    //----------------------------------
    //  resizeForSoftKeyboard
    //----------------------------------

    private var _resizeForSoftKeyboard:Boolean = true;

    /**
     *  Enables resizing the pop-up when the soft keyboard
     *  on a mobile device is active.
     *
     *  @default true
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    public function get resizeForSoftKeyboard():Boolean
    {
        return _resizeForSoftKeyboard;
    }

    /**
     *  @private
     */
    public function set resizeForSoftKeyboard(value:Boolean):void
    {
        if (_resizeForSoftKeyboard == value)
            return;

        _resizeForSoftKeyboard = value;
    }

    //----------------------------------
    //  moveForSoftKeyboard
    //----------------------------------

    private var _moveForSoftKeyboard:Boolean = true;

    /**
     *  Enables moving the pop-up when the soft keyboard
     *  on a mobile device is active.
     *
     *  @default true
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    public function get moveForSoftKeyboard():Boolean
    {
        return _moveForSoftKeyboard;
    }

    /**
     *  @private
     */
    public function set moveForSoftKeyboard(value:Boolean):void
    {
        if (_moveForSoftKeyboard == value)
            return;

        _moveForSoftKeyboard = value;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Opens the container as a pop-up, and switches the skin state from
     *  <code>closed</code> to <code>normal</code>.
     *  After and transitions finish playing, it dispatches  the
     *  <code>FlexEvent.OPEN</code> event.
     *
     *  @param owner The owner of the container.
     *  The popup appears over this component. The owner must not be descendant
     *  of this container.
     *
     *  @param modal Whether the container should be modal.
     *  A modal container takes all keyboard and mouse input until it is closed.
     *  A nonmodal container allows other components to accept input while the pop-up window is open.
     *
     *  @see #close
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function open(owner:DisplayObjectContainer, modal:Boolean = false):void
    {
        if (isOpen)
            return;

        closeEvent = null; // Clear any pending close event
        this.owner = owner;

        // We track whether we've been added to the PopUpManager to handle the
        // scenario of state transition interrupton. For example we may be playing
        // a close transition and be interrupted with a call to open() in which
        // case we wouldn't have removed the container from the PopUpManager since
        // the close transition never reached its end.
        if (!addedToPopUpManager)
        {
            addedToPopUpManager = true;

            // This will create the skin and attach it
            PopUpManager.addPopUp(this, owner, modal);

            updatePopUpPosition();
        }

        // Change state *after* we pop up, as the skin needs to go be in the initial "closed"
        // state while being created above in order for transitions to detect state change and play.
        _isOpen = true;
        invalidateSkinState();
        if (skin)
            skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler);
        else
            stateChangeComplete_handler(null); // Call directly
    }

    /**
     *  Positions the pop-up after the pop-up is added to PopUpManager but
     *  before any state transitions occur. The base implementation of open()
     *  calls updatePopUpPosition() immediately after the pop-up is added.
     *
     *  This method may also be called at any time to update the pop-up's
     *  position. Pop-ups that are positioned relative to their owner should
     *  call this method after position or size changes occur on the owner or
     *  it's ancestors.
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    public function updatePopUpPosition():void
    {
        // subclasses will implement custom positioning
        // e.g. PopUpManager.centerPopUp()
    }

    /**
     *  Changes the current skin state to <code>closed</code>, waits until any state transitions
     *  finish playing, dispatches a <code>PopUpEvent.CLOSE</code> event,
     *  and then removes the container from the PopUpManager.
     *
     *  <p>Use the <code>close()</code> method of the SkinnablePopUpContainer container
     *  to pass data back to the main application from the pop up.
     *  One typical usage scenario is building a dialog with a cancel button.
     *  When a valid option is selected in the dialog box, you close the dialog
     *  with a call to the <code>close()</code> method, passing
     *  <code>true</code> to the <code>commit</code> parameter and optionally passing
     *  any relevant data.
     *  When the SkinnablePopUpContainer has completed closing,
     *  it dispatch the <code>close</code> event.
     *  In the event listener for the <code>close</code> event, you can check
     *  the <code>commit</code> parameter and perform the appropriate actions.  </p>
     *
     *  @param commit Specifies if the return data should be committed by the application.
     *  The value of this argument is written to the <code>commit</code> property of
     *  the <code>PopUpEvent</code> event object.
     *
     *  @param data Specifies any data returned to the application.
     *  The value of this argument is written to the <code>data</code> property of
     *  the <code>PopUpEvent</code> event object.
     *
     *  @see #open
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function close(commit:Boolean = false, data:* = undefined):void
    {
        if (!isOpen)
            return;

        // We will dispatch the event later, when the close transition is complete.
        closeEvent = new PopUpEvent(PopUpEvent.CLOSE, false, false, commit, data);

        // Change state
        _isOpen = false;
        invalidateSkinState();

        if (skin)
            skin.addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler);
        else
            stateChangeComplete_handler(null); // Call directly
    }

    /**
     *  Called by the soft keyboard <code>activate</code> and <code>deactive</code> event handlers,
     *  this method is responsible for creating the Spark effect played on the pop-up.
     *
     *  This method may be overridden by subclasses. By default, it
     *  creates a parellel move and resize effect on the pop-up.
     *
     *  @param yTo The new y-coordinate of the pop-up.
     *
     *  @param height The new height of the pop-up.
     *
     *  @return An IEffect instance serving as the move and/or resize transition
     *  for the pop-up. This effect is played after the soft keyboard is
     *  activated or deactivated.
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    protected function createSoftKeyboardEffect(yTo:Number, heightTo:Number):IEffect
    {
        var move:Move;
        var resize:Resize;
        var easer:IEaser = new Power(0, 5);

        if (yTo != this.y)
        {
            move = new Move();
            move.target = this;
            move.yTo = yTo;
            move.disableLayout = true;
            move.easer = easer;
        }

        if (heightTo != this.height)
        {
            resize = new Resize();
            resize.target = this;
            resize.heightTo = heightTo;
            resize.disableLayout = true;
            resize.easer = easer;
        }

        if (move && resize)
        {
            var parallel:Parallel = new Parallel();
            parallel.addChild(move);
            parallel.addChild(resize);

            return parallel;
        }
        else if (move || resize)
        {
            return (move) ? move : resize;
        }

        return null;
    }

    //--------------------------------------------------------------------------
    //
    //  mx_internal properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------------
    //  softKeyboardEffectCachedExplicitHeight
    //----------------------------------------
    
    /**
     *  @private
     */
    private var _softKeyboardEffectExplicitHeightFlag:Boolean = false;
    
    /**
     *  @private
     *  Flag when explicitHeight is set when the soft keyboard effect is
     *  active. Use this to distinguish explicitHeight changes due to the
     *  resizeForSoftKeyboard setting. When true, we prevent the original
     *  cached height from being modified.
     */
    mx_internal function get softKeyboardEffectExplicitHeightFlag():Boolean
    {
        return _softKeyboardEffectExplicitHeightFlag;
    }
    
    private function setSoftKeyboardEffectExplicitHeightFlag(value:Boolean):void
    {
        _softKeyboardEffectExplicitHeightFlag = value;
    }
    
    //----------------------------------------
    //  softKeyboardEffectCachedExplicitWidth
    //----------------------------------------
    
    /**
     *  @private
     */
    private var _softKeyboardEffectExplicitWidthFlag:Boolean = false;
    
    /**
     *  @private
     *  Flag when explicitWidth is set when the soft keyboard effect is
     *  active. Use this to distinguish explicitWidth changes due to the
     *  resizeForSoftKeyboard setting.
     */
    mx_internal function get softKeyboardEffectExplicitWidthFlag():Boolean
    {
        return _softKeyboardEffectExplicitWidthFlag;
    }
    
    private function setSoftKeyboardEffectExplicitWidthFlag(value:Boolean):void
    {
        _softKeyboardEffectExplicitWidthFlag = value;
    }
    
    //----------------------------------
    //  softKeyboardEffectCachedHeight
    //----------------------------------

    private var _softKeyboardEffectCachedHeight:Number;

    /**
     *  @private
     *  The original pop-up height to restore to when the soft keyboard is
     *  deactivated. If an explicitHeight was defined at activation, use it.
     *  If not, then use explicitMaxHeight or measuredHeight.
     */
    mx_internal function get softKeyboardEffectCachedHeight():Number
    {
        var heightTo:Number = _softKeyboardEffectCachedHeight;

        if (!softKeyboardEffectExplicitHeightFlag)
        {
            if (!isNaN(explicitMaxHeight) && (measuredHeight > explicitMaxHeight))
                heightTo = explicitMaxHeight;
            else
                heightTo = measuredHeight;
        }

        return heightTo;
    }

    /**
     *  @private
     */
    private function setSoftKeyboardEffectCachedHeight(value:Number):void
    {
        // Only allow changes to the cached height if it was not set explicitly
        // prior to and/or during the soft keyboard effect.
        if (!softKeyboardEffectExplicitHeightFlag)
            _softKeyboardEffectCachedHeight = value;
    }

    //----------------------------------
    //  isSoftKeyboardEffectActive
    //----------------------------------

    private var _isSoftKeyboardEffectActive:Boolean;

    /**
     *  @private
     *  Returns true if the soft keyboard is active and the pop-up is moved
     *  and/or resized.
     */
    mx_internal function get isSoftKeyboardEffectActive():Boolean
    {
        return _isSoftKeyboardEffectActive;
    }

    //----------------------------------
    //  marginTop
    //----------------------------------

    private var _marginTop:Number = 0;

    /**
     *  @private
     *  Defines a margin at the top of the screen where the pop-up cannot be
     *  resized or moved to.
     */
    mx_internal function get softKeyboardEffectMarginTop():Number
    {
        return _marginTop;
    }

    /**
     *  @private
     */
    mx_internal function set softKeyboardEffectMarginTop(value:Number):void
    {
        _marginTop = value;
    }

    //----------------------------------
    //  marginBottom
    //----------------------------------

    private var _marginBottom:Number = 0;

    /**
     *  @private
     *  Defines a margin at the bottom of the screen where the pop-up cannot be
     *  resized or moved to.
     */
    mx_internal function get softKeyboardEffectMarginBottom():Number
    {
        return _marginBottom;
    }

    /**
     *  @private
     */
    mx_internal function set softKeyboardEffectMarginBottom(value:Number):void
    {
        _marginBottom = value;
    }

    //----------------------------------
    //  isMouseDown
    //----------------------------------

    private var _isMouseDown:Boolean = false;

    /**
     *  @private
     */
    private function get isMouseDown():Boolean
    {
        return _isMouseDown;
    }

    /**
     *  @private
     */
    private function set isMouseDown(value:Boolean):void
    {
        _isMouseDown = value;

        // Attempt to play a pending effect
        playPendingEffect(true);
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Force callout inheritance chain to start at the style root.
     */
    override mx_internal function initProtoChain():void
    {
        // Maintain backwards compatibility of popup style inheritance
        if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_6)
            super.initProtoChain();
        else
            StyleProtoChain.initProtoChain(this, false);
    }

    /**
     *  @private
     */
    override protected function getCurrentSkinState():String
    {
        // The states are:
        // "normal"
        // "disabled"
        // "closed"

        var state:String = super.getCurrentSkinState();
        if (!isOpen)
            return state == "normal" ? "closed" : state;
        return state;
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Play the soft keyboard effect.
     */
    private function startEffect(event:Event):void
    {
        removeEventListener(Event.ENTER_FRAME, startEffect);

        // Abort the effect if the pop-up is closed or closing. The state 
        // transition handler will restore the original size of the pop-up.
        if (!isOpen || !softKeyboardEffect)
            return;

        // Clear the cached positions when the deactivate effect is complete.
        softKeyboardEffect.addEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup);
        softKeyboardEffect.addEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup);
        
        // Install mouse delay and orientation change listeners now.
        // explicitHeight listener is installed after the effect completes.
        if (isSoftKeyboardEffectActive)
            installSoftKeyboardStateChangeListeners();

        // Force the master clock of the animation engine to update its
        // current time so that the overhead of creating the effect is not
        // included in our animation interpolation. See SDK-27793
        Animation.pulse();
        softKeyboardEffect.play();
    }

    /**
     *  @private
     *
     *  Called when we have completed transitioning to opened/closed state.
     */
    private function stateChangeComplete_handler(event:Event):void
    {
        // We get called directly with null if there's no skin to listen to.
        if (event)
            event.target.removeEventListener(FlexEvent.STATE_CHANGE_COMPLETE, stateChangeComplete_handler);

        // Check for soft keyboard support
        var topLevelApp:Application = FlexGlobals.topLevelApplication as Application;
        var softKeyboardEffectEnabled:Boolean = (topLevelApp && Application.softKeyboardBehavior == "none");
        var smStage:Stage = systemManager.stage;

        if (isOpen)
        {
            dispatchEvent(new PopUpEvent(PopUpEvent.OPEN, false, false));

            if (softKeyboardEffectEnabled)
            {
                if (smStage)
                {
                    // Install soft keyboard event handling on the stage
                    smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE,
                        stage_softKeyboardActivateHandler, true, EventPriority.DEFAULT, true);
                    smStage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE,
                        stage_softKeyboardDeactivateHandler, true, EventPriority.DEFAULT, true);
                    
                    // Use lower priority listener to allow subclasses to act 
                    // on the resize event before the soft keyboard effect does.
                    systemManager.addEventListener(Event.RESIZE, systemManager_resizeHandler, false, EventPriority.EFFECT);

                    updateSoftKeyboardEffect(true);
                }
            }
        }
        else
        {
            // Dispatch the close event before removing from the PopUpManager.
            dispatchEvent(closeEvent);
            closeEvent = null;

            if (softKeyboardEffectEnabled && smStage)
            {
                // Uninstall soft keyboard event handling
                smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE,
                    stage_softKeyboardActivateHandler, true);
                smStage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE,
                    stage_softKeyboardDeactivateHandler, true);
                systemManager.removeEventListener(Event.RESIZE, systemManager_resizeHandler);
            }

            // We just finished closing, remove from the PopUpManager.
            PopUpManager.removePopUp(this);
            addedToPopUpManager = false;
            owner = null;

            // Position and size may be invalid if the close transition
            // completes before (a) deactivate is fired or (b) a pending
            // soft keyboard change is still queued. Update immediately.
            updateSoftKeyboardEffect(true);
        }
    }

    /**
     *  @private
     */
    private function stage_softKeyboardActivateHandler(event:SoftKeyboardEvent=null):void
    {
        var isFirstActivate:Boolean = false;

        // Reset state
        softKeyboardEffectPendingEventType = null;

        // Save the original y-position and height if this is the first
        // ACTIVATE event and an existing effect is not already in progress.
        if (!isSoftKeyboardEffectActive && !softKeyboardEffect)
            isFirstActivate = true;

        if (isFirstActivate)
        {
            // Play the activate effect with animation
            updateSoftKeyboardEffect(false);
        }
        else
        {
            // Keyboard height has changed. However, the effect can be delayed if
            // a mouseUp within the pop-up is pending. An additional activate can
            // occur while the keyboard is open to reflect size changes due to auto
            // correction or orientation changes.
            
            // As in the deactivate case, the move and resize effects should be
            // delayed until the user can complete any mouse interaction. This
            // allows the size and position of the pop-up to stay stable allowing
            // events like a button press to complete normally.
            
            if (isMouseDown)
                setPendingSoftKeyboardEvent(event);
            else
                updateSoftKeyboardEffect(true);
        }
    }
    
    /**
     *  @private
     */
    private function stage_softKeyboardDeactivateHandler(event:SoftKeyboardEvent=null):void
    {
        // If the effect is not active (no move or resize was needed), do 
        // nothing. If we're in the middle of an orientation change, also do 
        // nothing.
        if (!isSoftKeyboardEffectActive || softKeyboardEffectOrientationChanging)
            return;
        
        // Reset state
        softKeyboardEffectPendingEventType = null;
        
        if (event.triggerType == SoftKeyboardTrigger.USER_TRIGGERED)
        {
            // userTriggered indicates they keyboard was closed explicitly (soft
            // button on soft keyboard) or on Android, pressing the back button.
            // Play the deactivate effect immediately.
            updateSoftKeyboardEffect(false);
        }
        else // if (event.triggerType == SoftKeyboardTrigger.CONTENT_TRIGGERED)
        {
            // contentTriggered indicates focus was lost by tapping away from
            // StageText or a programmatic call. Unfortunately, this
            // distinction isn't entirely intuitive. We only care about delaying
            // the deactivate effect when due to a mouse event. Delaying the
            // effect allows the pop-up position and size to stay static until
            // any mouse interaction is complete (e.g. button click).
            // However, the softKeyboardDeactivate event is fired before
            // the mouseDown event:
            //   deactivate -> mouseDown -> mouseUp
            
            // The approach here is to assume that a mouseDown was the trigger
            // for the softKeyboardDeactivate event. Continue to delay the
            // deactivate effect until a mouseDown and mouseUp sequence is
            // received. In the event that the deactivation was due to a
            // programmatic call, we'll stop this process after a specified
            // delay time.
            
            // If, in the future, the event order changes to either:
            //   (a) mouseDown -> deactivate -> mouseUp
            //   (b) mouseDown -> mouseUp -> deactivate
            // this approach will still work for the button click use case.
            // Sequence (b) would simply fire a normal button click and have
            // the consequence of a delayed deactivate effect only.
            setPendingSoftKeyboardEvent(event);
        }
    }

    /**
     *  @private
     *  Disable soft keyboard deactivate when orientation is changing.
     */
    private function stage_orientationChangingHandler(event:Event):void
    {
        softKeyboardEffectOrientationChanging = true;
    }

    /**
     *  @private
     *  Re-enable soft keyboard deactivate effect when orietation change 
     *  completes.
     */
    private function stage_orientationChangeHandler(event:Event):void
    {
        softKeyboardEffectOrientationChanging = false;
    }
    
    /**
     *  @private
     *  Listens for mouse events while the soft keyboard effect is active.
     */
    private function mouseHandler(event:MouseEvent):void
    {
        isMouseDown = (event.type == MouseEvent.MOUSE_DOWN);
    }

    private function systemManager_mouseUpHandler(event:Event):void
    {
        isMouseDown = false;
    }

    /**
     *  @private
     *  This function is only called when the pendingEffectTimer completed and no
     *  mouseDown and mouseUp sequence was fired.
     */
    private function pendingEffectTimer_timerCompleteHandler(event:Event):void
    {
        playPendingEffect(false)
    }

    /**
     *  @private
     *  Play the effect when (a) not triggered by a mouse event or
     *  (b) triggered by a mouseUp event
     */
    private function playPendingEffect(isMouseEvent:Boolean):void
    {
        var isTimerRunning:Boolean = softKeyboardEffectPendingEventTimer && softKeyboardEffectPendingEventTimer.running;

        // Received a mouseDown event while the timer is still running. Stop
        // the timer and wait for the next mouseUp.
        var mouseDownDuringTimer:Boolean = isTimerRunning &&
            (isMouseEvent && isMouseDown);

        // Cleanup the timer if we're (a) waiting for the next mouseUp or
        // (b) this function was called for timerComplete
        if (softKeyboardEffectPendingEventTimer && (mouseDownDuringTimer || !isMouseEvent))
        {
            softKeyboardEffectPendingEventTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler);
            softKeyboardEffectPendingEventTimer.stop();
            softKeyboardEffectPendingEventTimer = null;

            // If we caught a mouse down during the timer, we wait for the next
            // mouseUp event. There is no effect to play. If this isn't a mouse
            // event and the timer completed, then fall through and play the
            // pending effect.
            if (mouseDownDuringTimer)
                return;
        }

        // Sanity check that a pendingEvent still exists and that the pop-up
        // is currently open. If we timed out or if we got a mouseUp, then
        // allow the pending effect to play.
        var canPlayEffect:Boolean = softKeyboardEffectPendingEventType && isOpen &&
            (!isMouseEvent || (isMouseEvent && !isMouseDown));

        // Effect isn't played when still waiting for a mouseUp
        if (canPlayEffect)
        {
            // Clear pending state
            var isActivate:Boolean = softKeyboardEffectPendingEventType == SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE;
            
            // Snap the position only for pending activate events
            updateSoftKeyboardEffect(isActivate);
        }
    }

    /**
     *  @private
     */
    private function softKeyboardEffectCleanup(event:EffectEvent):void
    {
        // Cleanup effect
        softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_END, softKeyboardEffectCleanup);
        softKeyboardEffect.removeEventListener(EffectEvent.EFFECT_STOP, softKeyboardEffectCleanup);
        softKeyboardEffect = null;
        
        if (isSoftKeyboardEffectActive)
        {
            installActiveResizeListener();
        }
        else
        {
            // Remove resize listeners
            uninstallActiveResizeListener();
            
            // If the deactivate effect is complete, uninstall listeners for 
            // mouse delay and orientation change listeners
            uninstallSoftKeyboardStateChangeListeners();
        }
    }

    /**
     *  @private
     *  Set flags when explicit size changes are detected.
     */
    private function resizeHandler(event:Event=null):void
    {
        setSoftKeyboardEffectExplicitWidthFlag(!isNaN(explicitWidth));
        setSoftKeyboardEffectExplicitHeightFlag(!isNaN(explicitHeight));
    }
    
    /**
     *  @private
     *  Update effect immediately after orientation change is
     *  followed by a system manager resize.
     */
    private function systemManager_resizeHandler(event:Event):void
    {
        // Guard against extraneous resizing during orientation changing.
        // See SDK-31860.
        if (!softKeyboardEffectOrientationChanging)
            updateSoftKeyboardEffect(true);
    }

    //--------------------------------------------------------------------------
    //
    //  Private Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Update the position and height for this pop-up for various state changes
     *  including: soft keyboard activation and deactivation, delayed soft
     *  keyboard events due to mouse and keyboard event sequence, and
     *  orientation change events.
     */
    private function updateSoftKeyboardEffect(snapPosition:Boolean):void
    {
        // Stop the current effect
        if (softKeyboardEffect && softKeyboardEffect.isPlaying)
            softKeyboardEffect.stop();
        
        // Uninstall resize listeners during the effect. Listeners are 
        // installed again after the effect is complete or immediately affter
        // the size and position are snapped.
        uninstallActiveResizeListener();
        
        var softKeyboardRect:Rectangle = systemManager.stage.softKeyboardRect;
        var isKeyboardOpen:Boolean = isOpen && (softKeyboardRect.height > 0);
        
        // Capture start values if not set
        if (isNaN(softKeyboardEffectCachedYPosition))
        {
            if (isKeyboardOpen)
            {
                // Save original y-position and height
                softKeyboardEffectCachedYPosition = this.y;
                setSoftKeyboardEffectCachedHeight(this.height);
                
                // Initialize explicit size flags
                resizeHandler();
            }
            else
            {
                // Keyboard is closed and we don't have any start values yet.
                // Nothing to do.
                return;
            }
        }

        var sandboxRoot:DisplayObject = systemManager.getSandboxRoot();

        var yToLocal:Number = softKeyboardEffectCachedYPosition;
        var heightToLocal:Number = softKeyboardEffectCachedHeight;

        // If the keyboard is active, check for overlap
        if (isKeyboardOpen)
        {
            var scaleFactor:Number = 1;

            if (systemManager as SystemManager)
                scaleFactor = SystemManager(systemManager).densityScale;

            // All calculations are done in stage coordinates and converted back to
            // application coordinates when playing effects. Also note that
            // softKeyboardRect is also in stage coordinates.
            var popUpY:Number = yToLocal * scaleFactor;
            var popUpHeight:Number = heightToLocal * scaleFactor;
            var overlapGlobal:Number = (popUpY + popUpHeight) - softKeyboardRect.y;

            var yToGlobal:Number = popUpY;
            var heightToGlobal:Number = popUpHeight;

            if (overlapGlobal > 0)
            {
                // shift y-position up to remove offset overlap
                if (moveForSoftKeyboard)
                    yToGlobal = Math.max((softKeyboardEffectMarginTop * scaleFactor), (popUpY - overlapGlobal));

                // adjust height based on new y-position
                if (resizeForSoftKeyboard)
                {
                    // compute new overlap
                    overlapGlobal = (yToGlobal + popUpHeight) - softKeyboardRect.y;

                    // adjust height if there is overlap
                    if (overlapGlobal > 0)
                        heightToGlobal = popUpHeight - overlapGlobal - (softKeyboardEffectMarginBottom * scaleFactor);
                }
            }
            
            if ((yToGlobal != popUpY) ||
                (heightToGlobal != popUpHeight))
            {
                // convert to application coordinates, move to pixel boundaries
                yToLocal = Math.floor(yToGlobal / scaleFactor);
                heightToLocal = Math.floor(heightToGlobal / scaleFactor);

                // preserve minimum height
                heightToLocal = Math.max(heightToLocal, getMinBoundsHeight());
            }
        }

        // Update state
        _isSoftKeyboardEffectActive = isKeyboardOpen;

        var duration:Number = getStyle("softKeyboardEffectDuration");
        
        // Only create an effect when not snapping and the duration is
        // non-negative. An effect will not be created by default if there is
        // no change.
        if (!snapPosition && (duration > 0))
            softKeyboardEffect = createSoftKeyboardEffect(yToLocal, heightToLocal);
        
        if (softKeyboardEffect)
        {
            softKeyboardEffect.duration = duration;

            // Wait a frame so that any queued work can be completed by the framework
            // and runtime before the effect starts.
            addEventListener(Event.ENTER_FRAME, startEffect);
        }
        else
        {
            // No effect, snap. Set position and size explicitly.
            this.y = yToLocal;

            if (isOpen)
            {
                this.height = heightToLocal;
                
                // Validate so that other listeners like Scroller get the 
                // updated dimensions.
                validateNow();

                if (isSoftKeyboardEffectActive)
                {
                    installActiveResizeListener();
                    installSoftKeyboardStateChangeListeners();
                }
            }
            else // if (!isOpen)
            {
                // Uninstall mouse delay and orientation change listeners
                uninstallSoftKeyboardStateChangeListeners();
                
                // Clear explicit size leftover from Resize
                softKeyboardEffectResetExplicitSize();
                
                // Clear start values
                softKeyboardEffectCachedYPosition = NaN;
                setSoftKeyboardEffectExplicitWidthFlag(false);
                setSoftKeyboardEffectExplicitHeightFlag(false);
                setSoftKeyboardEffectCachedHeight(NaN);
            }
        }
    }

    /**
     *  @private
     *  Start waiting for a (mouseDown, mouseUp) event sequence before effect
     *  plays.
     */
    private function setPendingSoftKeyboardEvent(event:SoftKeyboardEvent):void
    {
        softKeyboardEffectPendingEventType = event.type;

        // If the mouseDown event was already received, wait indefinitely
        // for mouseUp. If the soft keyboard event fired before mouseDown,
        // start a timer.
        if (!isMouseDown)
        {
            softKeyboardEffectPendingEventTimer = new Timer(softKeyboardEffectPendingEffectDelay, 1);
            softKeyboardEffectPendingEventTimer.addEventListener(TimerEvent.TIMER_COMPLETE, pendingEffectTimer_timerCompleteHandler);
            softKeyboardEffectPendingEventTimer.start();
        }
    }

    /**
     *  @private
     *  Listeners installed just before the soft keyboard effect makes changes
     *  to the original position and height.
     */
    private function installSoftKeyboardStateChangeListeners():void
    {
        if (!softKeyboardStateChangeListenersInstalled)
        {
            // Listen for mouseDown event on the pop-up and delay the soft
            // keyboard effect until mouseUp. This allows button click
            // events to complete normally before the button is re-positioned.
            // See SDK-31534.
            var sandboxRoot:DisplayObject = systemManager.getSandboxRoot();
            addEventListener(MouseEvent.MOUSE_DOWN, mouseHandler);

            // Listen for mouseUp events anywhere to play the effect. See SDK-31534.
            sandboxRoot.addEventListener(MouseEvent.MOUSE_UP,
                mouseHandler, true /* useCapture */);
            sandboxRoot.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE,
                systemManager_mouseUpHandler);

            // Listen for orientationChanging and orientationChange to suspend
            // pop-up changes until the orientation change is complete.
            // On iOS, the keyboard is deactivated after orientationChanging,
            // then immediately activated after orientationChange is complete.
            // On Android, the keyboard is deactivated after orientationChanging,
            // but not reactivated after orienationChange.
            // On QNX, the keyboard is not deactivated at all. The keyboard is
            // simply activated again after orientationChange.
            softKeyboardEffectOrientationChanging = false;
            
            var smStage:Stage = systemManager.stage;
            smStage.addEventListener("orientationChanging", stage_orientationChangingHandler);
            smStage.addEventListener("orientationChange", stage_orientationChangeHandler);

            softKeyboardStateChangeListenersInstalled = true;
        }
    }

    /**
     *  @private
     */
    private function uninstallSoftKeyboardStateChangeListeners():void
    {
        if (softKeyboardStateChangeListenersInstalled)
        {
            // Uninstall effect delay handling
            removeEventListener(MouseEvent.MOUSE_DOWN, mouseHandler);
            
            var sandboxRoot:DisplayObject = systemManager.getSandboxRoot();
            sandboxRoot.removeEventListener(MouseEvent.MOUSE_UP, mouseHandler, true /* useCapture */);
            sandboxRoot.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler);

            // Uninstall orientation change handling
            var smStage:Stage = systemManager.stage;
            smStage.removeEventListener("orientationChange", stage_orientationChangeHandler);
            smStage.removeEventListener("orientationChanging", stage_orientationChangeHandler);

            softKeyboardStateChangeListenersInstalled = false;
        }
    }
    
    /**
     *  @private
     *  Listeners installed during the phase after the initial activate effect
     *  is complete and before the deactive effect starts.
     */
    private function installActiveResizeListener():void
    {
        if (!resizeListenerInstalled)
        {
            // Flag size changes while the soft keyboard effect is active (but
            // not playing)
            addEventListener("explicitWidthChanged", resizeHandler);
            addEventListener("explicitHeightChanged", resizeHandler);
            resizeListenerInstalled = true;
        }
    }
    
    /**
     *  @private
     */
    private function uninstallActiveResizeListener():void
    {
        if (resizeListenerInstalled)
        {
            // Uninstall resize listener
            removeEventListener("explicitWidthChanged", resizeHandler);
            removeEventListener("explicitHeightChanged", resizeHandler);
            resizeListenerInstalled = false;
        }
    }
    
    /**
     *  @private
     *  Clear explicit size that remains after a Resize effect.
     */
    mx_internal function softKeyboardEffectResetExplicitSize():void
    {
        // Only remove and restore listeners if they are currently installed.
        var installed:Boolean = resizeListenerInstalled;
        
        if (installed)
            uninstallActiveResizeListener();
        
        // Remove explicit settings if due to Resize effect
        if (!softKeyboardEffectExplicitWidthFlag)
            explicitWidth = NaN;
        
        if (!softKeyboardEffectExplicitHeightFlag)
            explicitHeight = NaN;
        
        if (installed)
            installActiveResizeListener();
    }
}
}