/****************************************************************************
**
** This file is part of the Qt Extended Opensource Package.
**
** Copyright (C) 2009 Trolltech ASA.
**
** Contact: Qt Extended Information (info@qtextended.org)
**
** This file may be used under the terms of the GNU General Public License
** version 2.0 as published by the Free Software Foundation and appearing
** in the file LICENSE.GPL included in the packaging of this file.
**
** Please review the following information to ensure GNU General Public
** Licensing requirements will be met:
**     http://www.fsf.org/licensing/licenses/info/GPLv2.html.
**
**
****************************************************************************/

#include "qsimcommand.h"
#include "qatutils.h"
#include "qsmsmessage.h"
#include "qgsmcodec.h"
#include <qvarlengtharray.h>

static QString textToHtml(const QString& text, const QByteArray& attrs);

class QSimMenuItemPrivate
{
public:
    QSimMenuItemPrivate()
    {
        identifier = 0;
        iconId = 0;
        hasHelp = false;
        iconSelfExplanatory = false;
        nextAction = 0;
    }

    uint identifier;
    QString label;
    QByteArray labelAttribute;
    uint iconId;
    bool hasHelp;
    bool iconSelfExplanatory;
    uint nextAction;
};


class QSimCommandPrivate
{
public:
    QSimCommandPrivate()
    {
        ref = 1;
        commandNumber = 1;
        type = QSimCommand::NoCommand;
        sourceDevice = QSimCommand::SIM;
        destinationDevice = QSimCommand::ME;
        flags = 0;
        minimumLength = 0;
        maximumLength = 255;
        callClass = QSimCommand::Voice;
        tone = QSimCommand::ToneNone;
        duration = 0;
        defaultItem = 0;
        iconId = 0;
        otherIconId = 0;
        timerId = 0;
        device = -1;
        qualifier = 0;
        bufferSize = 0;
        dataLength = 0;
    }
    QSimCommandPrivate( QSimCommandPrivate *other )
    {
        ref = 1;
        commandNumber = other->commandNumber;
        type = other->type;
        sourceDevice = other->sourceDevice;
        destinationDevice = other->destinationDevice;
        flags = other->flags;
        text = other->text;
        textAttribute = other->textAttribute;
        otherText = other->otherText;
        otherTextAttribute = other->otherTextAttribute;
        defaultText = other->defaultText;
        minimumLength = other->minimumLength;
        maximumLength = other->maximumLength;
        number = other->number;
        subAddress = other->subAddress;
        callClass = other->callClass;
        tone = other->tone;
        duration = other->duration;
        title = other->title;
        titleAttribute = other->titleAttribute;
        defaultItem = other->defaultItem;
        menuItems = other->menuItems;
        url = other->url;
        language = other->language;
        iconId = other->iconId;
        otherIconId = other->otherIconId;
        timerId = other->timerId;
        device = other->device;
        qualifier = other->qualifier;
        extensionData = other->extensionData;
        bearerDesc = other->bearerDesc;
        bufferSize = other->bufferSize;
        dataLength = other->dataLength;
        apn = other->apn;
        uti = other->uti;
        userLogin = other->userLogin;
        userPassword = other->userPassword;
        destAddress = other->destAddress;
        localAddress = other->localAddress;
    }

    bool flag( int bit ) const
    {
        return ( ( flags & bit ) != 0 );
    }

    void setFlag( int bit, bool value )
    {
        if ( value )
            flags |= bit;
        else
            flags &= ~bit;
    }

    bool qualifierBit( int bit ) const
    {
        return ( ( qualifier & bit ) != 0 );
    }

    void setQualifierBit( int bit, bool value )
    {
        if ( value )
            qualifier |= bit;
        else
            qualifier &= ~bit;
    }

    #define SC_ImmediateResponse    (1<<0)
    #define SC_IconSelfExplanatory  (1<<1)
    #define SC_IconSelfExplanatory2 (1<<2)
    #define SC_SuppressUserFeedback (1<<3)

    QAtomicInt ref;
    int commandNumber;
    QSimCommand::Type type;
    QSimCommand::Device sourceDevice;
    QSimCommand::Device destinationDevice;
    int flags;
    QString text;
    QByteArray textAttribute;
    QString otherText;
    QByteArray otherTextAttribute;
    QString defaultText;
    uint minimumLength;
    uint maximumLength;
    QString number;
    QString subAddress;
    QSimCommand::CallClass callClass;
    QSimCommand::Tone tone;
    uint duration;
    QString title;
    QByteArray titleAttribute;
    uint defaultItem;
    QList<QSimMenuItem> menuItems;
    QString url;
    QString language;
    uint iconId;
    uint otherIconId;
    uint timerId;
    int device;
    int qualifier;
    QByteArray extensionData;
    QByteArray bearerDesc;
    ushort bufferSize;
    uint dataLength;
    QByteArray apn;
    QByteArray uti;
    QString userLogin;
    QString userPassword;
    QByteArray destAddress;
    QByteArray localAddress;
};


/*!
    \class QSimMenuItem
    \inpublicgroup QtTelephonyModule

    \brief The QSimMenuItem class provides information about a menu item within a SIM toolkit application

    \ingroup telephony
    \sa QSimCommand, QSimToolkit
*/

/*!
    Construct a new menu item with default parameters.
*/
QSimMenuItem::QSimMenuItem()
{
    d = new QSimMenuItemPrivate();
}


/*!
    Construct a new menu item as a copy of \a value.
*/
QSimMenuItem::QSimMenuItem( const QSimMenuItem& value )
{
    d = new QSimMenuItemPrivate();
    d->identifier = value.d->identifier;
    d->label = value.d->label;
    d->labelAttribute = value.d->labelAttribute;
    d->iconId = value.d->iconId;
    d->hasHelp = value.d->hasHelp;
    d->iconSelfExplanatory = value.d->iconSelfExplanatory;
    d->nextAction = value.d->nextAction;
}


/*!
    Destruct a menu item.
*/
QSimMenuItem::~QSimMenuItem()
{
    delete d;
}


/*!
    Returns the menu item's numeric identifier.

    \sa setIdentifier()
*/
uint QSimMenuItem::identifier() const
{
    return d->identifier;
}


/*!
    Sets the menu item's numeric identifier to \a value.

    \sa identifier()
*/
void QSimMenuItem::setIdentifier( uint value )
{
    d->identifier = value;
}


/*!
    Returns the menu item's label.

    \sa setLabel()
*/
QString QSimMenuItem::label() const
{
    return d->label;
}


/*!
    Sets the menu item's label to \a value.

    \sa label()
*/
void QSimMenuItem::setLabel( const QString& value )
{
    d->label = value;
}


/*!
    Returns the menu item's text attributes for formatting label().
    Returns an empty QByteArray if the label() is not formatted.

    \sa setLabelAttribute(), label(), labelHtml()
    \since 4.4
*/
QByteArray QSimMenuItem::labelAttribute() const
{
    return d->labelAttribute;
}


/*!
    Sets the menu item's text attributes for formatting label() to \a value.
    If \a value is an empty QByteArray, then label() is not formatted.

    \sa labelAttribute(), label(), labelHtml()
    \since 4.4
*/
void QSimMenuItem::setLabelAttribute( const QByteArray& value )
{
    d->labelAttribute = value;
}


/*!
    Returns the menu item's label() as a HTML string.  If the menu
    item has labelAttribute(), then they will be used to format
    the label() appropriately.

    \sa label(), labelAttribute()
    \since 4.4
*/
QString QSimMenuItem::labelHtml() const
{
    return textToHtml( d->label, d->labelAttribute );
}


/*!
    Returns true if this menu item has help available; otherwise returns false.

    \sa setHasHelp()
*/
bool QSimMenuItem::hasHelp() const
{
    return d->hasHelp;
}


/*!
    Sets the flag that indicates if this menu item has help
    available to \a value.

    \sa hasHelp()
*/
void QSimMenuItem::setHasHelp( bool value )
{
    d->hasHelp = value;
}


/*!
    Returns the icon identifier associated with this menu item.
    Returns zero if there is no icon.

    \sa setIconId()
*/
uint QSimMenuItem::iconId() const
{
    return d->iconId;
}


/*!
    Sets the icon identifier associated with this menu item to \a value.

    \sa iconId()
*/
void QSimMenuItem::setIconId( uint value )
{
    d->iconId = value;
}


/*!
    Returns true if the icon specified by iconId() is self-explanatory
    without the display of label().  If this function returns false,
    then label() should be displayed next to the icon.  If iconId()
    returns zero, then iconSelfExplanatory() should be ignored.

    \sa setIconSelfExplanatory()
*/
bool QSimMenuItem::iconSelfExplanatory() const
{
    return d->iconSelfExplanatory;
}


/*!
    Sets the self-explanatory flag to \a value.

    \sa iconSelfExplanatory()
*/
void QSimMenuItem::setIconSelfExplanatory( bool value )
{
    d->iconSelfExplanatory = value;
}


/*!
    Returns the next action indicator for this menu item.  Next action indicators
    have the same values as command types from the QSimCommand::Type list.
    The default value is zero, indicating no next action indicator for this menu item.

    \sa setNextAction()
*/
uint QSimMenuItem::nextAction() const
{
    return d->nextAction;
}


/*!
    Sets the next action indicator for this menu item to \a value.  Next action
    indicators have the same values as command types from the QSimCommand::Type list.
    The value zero indicates that there is no next action indicator for this menu item.

    \sa nextAction()
*/
void QSimMenuItem::setNextAction( uint value )
{
    d->nextAction = value;
}


/*!
    Make a copy of \a value.
*/
QSimMenuItem& QSimMenuItem::operator=( const QSimMenuItem & value )
{
    d->identifier = value.d->identifier;
    d->label = value.d->label;
    d->labelAttribute = value.d->labelAttribute;
    d->iconId = value.d->iconId;
    d->hasHelp = value.d->hasHelp;
    d->iconSelfExplanatory = value.d->iconSelfExplanatory;
    d->nextAction = value.d->nextAction;
    return *this;
}

/*!
    \class QSimCommand
    \inpublicgroup QtTelephonyModule

    \brief The QSimCommand class specifies the details of a SIM toolkit command message.

    Applications that run within a SIM send commands to the host program
    to interact with the user.  These commands might entail choosing an
    item from a menu, asking if it is OK to dial a phone number, asking
    for a line of input, displaying text messages, etc.

    The QSimCommand class encapsulates a single SIM toolkit command,
    containing all of the information about it.  QSimCommand objects
    are delivered to the host program by way of the QSimToolkit::command() signal.

    In Qtopia, the host program is \c simapp.

    \ingroup telephony
    \sa QSimToolkit, QSimMenuItem, QSimTerminalResponse
*/

/*!
    \enum QSimCommand::Type
    This enum defines the type of a SIM toolkit message within a
    QSimCommand object.

    \value NoCommand No command type currently set.
    \value Timeout Indicate that an operation has timed out.
    \value Refresh Notification of a SIM refresh.
    \value MoreTime The SIM toolkit application is asking for more time.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value PollInterval The SIM toolkit application is setting the polling interval
           for new commands.  This command type is handled automatically by the
           modem or the modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value PollingOff This SIM toolkit application is asking for polling to be turned off.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value SetupEventList Set up the list of events to be reported to the
           SIM toolkit application.
    \value SetupCall Set up a phone call to a specific number.
    \value SendSS Notification that the application is sending a SS message.
    \value SendSMS Notification that the application is sending a SMS message.
    \value SendUSSD Notification that the application is sending a USSD message.
    \value SendDTMF Notification that the application is sending a DTMF tone.
    \value LaunchBrowser Launch a Web browser on a URL.
    \value PlayTone Play a tone to the user.
    \value DisplayText Display a text message.
    \value GetInkey Get a single key of input.
    \value GetInput Get a line of input.
    \value SelectItem Process a submenu within a SIM toolkit application.
    \value SetupMenu Process the main menu of a SIM toolkit application.
    \value ProvideLocalInformation The SIM toolkit application is asking for local
           information to be supplied.  This command type is handled automatically
           by the modem or the modem vendor plugin, so client applications will
           not see this command type via QSimToolkit::command().
    \value TimerManagement The SIM toolkit application is activating or deactivating
           timers.  This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value SetupIdleModeText Set the text to be displayed when the phone is idle.
    \value PerformCardAPDU Send an APDU to the additional card reader.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value PowerOnCard Power on an additional card reader.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value PowerOffCard Power off an additional card reader.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value GetReaderStatus Get the status of an additional card reader.
           This command type is handled automatically by the modem or the
           modem vendor plugin, so client applications will not see this
           command type via QSimToolkit::command().
    \value RunATCommand Run an AT command against the modem
    \value LanguageNotification Inform the user about the language the SIM
           toolkit application will be displaying messages in.
    \value OpenChannel Open a data channel.
    \value CloseChannel Close a data channel.
    \value ReceiveData Receive data on a data channel.
    \value SendData Send data on a data channel.
    \value GetChannelStatus Get the current status of a data channel.
    \value ServiceSearch Search for the availability of a service in the
           environment of the terminal.  Since Qtopia 4.4.  Compliant
           with ETSI TS 102 223.
    \value GetServiceInformation Get the complete service record related
           to a service.  Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value DeclareService Declare a service that is provided by the SIM.
           Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value SetFrames Divide the screen into multiple, scrollable rectangular
           regions in order to present multiple information at once.
           Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value GetFramesStatus Get the status on the frames supported by the SIM.
           Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value RetrieveMultimediaMessage Retrieve a multimedia message from
           the network.  Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value SubmitMultimediaMessage Submit a multimedia message to the network.
           Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value DisplayMultimediaMessage Display a multimedia message.
           Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value EndSession Indicate that the SIM toolkit session has ended
    \value SetupMainMenu Alias for SetupMenu, provided for backwards compatiblity.
    \value SetupSubMenu Alias for SelectItem, provided for backwards compatiblity.
*/

/*!
    \enum QSimCommand::Disposition
    This enum defines the disposition of other calls when a \c SetupCall
    command is executed by the SIM toolkit application.

    \value IfNoOtherCalls Perform the setup only if there are no other calls.
    \value PutOnHold Put other calls on hold when the new call is setup.
    \value Disconnect Disconnect other calls when the new call is setup.
*/

/*!
    \enum QSimCommand::CallClass
    This enum defines the class of call that is being created by a
    \c SetupCall command.

    \value Voice Voice call
    \value Data Data call
    \value Fax Fax call
*/

/*!
    \enum QSimCommand::Tone
    This enum defines the tones that can be played by a \c PlayTone command.

    \value ToneNone No tone specified.
    \value ToneDial Currently dialing
    \value ToneBusy Called party is busy
    \value ToneCongestion Network is congested
    \value ToneRadioAck Radio acknowledged
    \value ToneDropped Connection has been dropped
    \value ToneError An error occurred
    \value ToneCallWaiting An incoming call is waiting
    \value ToneRinging Ring to indicate and incoming call
    \value ToneGeneralBeep A general beep tone
    \value TonePositiveBeep A beep indicating a positive outcome
    \value ToneNegativeBeep A beep indicating a negative outcome

*/

/*!
    \enum QSimCommand::RefreshType
    This enum defines the type of refresh that was performed by the SIM.

    \value InitAndFullFileChange SIM initialization and full file
           change notification.
    \value FileChange File change notification.
    \value InitAndFileChange SIM initialization and file change notification.
    \value Initialization SIM initialization.
    \value Reset SIM reset.
    \value NaaApplicationReset NAA application reset.  This is only applicable
           for a 3G platform.  Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
    \value NaaSessionReset NAA session reset.  This is only applicable
           for a 3G platform.  Since Qtopia 4.4.  Compliant with ETSI TS 102 223.
*/

/*!
    \enum QSimCommand::Event
    This enum defines the types of events that the SIM expects to be reported.

    \value NoEvent No event type specified.
    \value IdleScreen Report an event when the system is idle.
    \value UserActivity Report an event when there is user activity.
    \value Both Report for both system idle and user activity.
    \value Cancel Cancel event reporting.
*/

/*!
    \enum QSimCommand::BrowserLaunchMode
    This enum defines the launch mode to use when displaying web pages in the browser.

    \value IfNotAlreadyLaunched Launch browser, if not already launched.
    \value UseExisting Use the existing browser, but not if it is running
           a secured session.
    \value CloseExistingAndLaunch Close the existing browser session
           and launch a new browser session.
*/

/*!
    \enum QSimCommand::MenuPresentation
    This enum defines the type of presentation to use for SIM toolkit menus.

    \value AnyPresentation The user interface may use any presentation it wishes.
    \value DataValuesPresentation The user interface should present the menu as
           a choice of several data values.
    \value NavigationOptionsPresentation The user interface should present the menu
           as a choice of several navigation options.
*/

/*!
    \enum QSimCommand::Device
    This enum defines the source or destination device for a SIM command, according
    to 3GPP TS 11.14, section 12.7.
    
    \value Keypad Keypad device
    \value Display Display device
    \value Earpiece Earpiece device
    \value CardReader0 Additional card reader 0
    \value CardReader1 Additional card reader 1
    \value CardReader2 Additional card reader 2
    \value CardReader3 Additional card reader 3
    \value CardReader4 Additional card reader 4
    \value CardReader5 Additional card reader 5
    \value CardReader6 Additional card reader 6
    \value CardReader7 Additional card reader 7
    \value Channel1 Data channel 1
    \value Channel2 Data channel 2
    \value Channel3 Data channel 3
    \value Channel4 Data channel 4
    \value Channel5 Data channel 5
    \value Channel6 Data channel 6
    \value Channel7 Data channel 7
    \value SIM SIM device
    \value ME ME (mobile equipment) device
    \value Network Source or destination is the network
*/

/*!
    Construct a new SIM toolkit command object with default parameters.
*/
QSimCommand::QSimCommand()
{
    d = new QSimCommandPrivate();
}


/*!
    Construct a new SIM toolkit command object as a copy of \a value.
*/
QSimCommand::QSimCommand( const QSimCommand& value )
{
    d = value.d;
    d->ref.ref();
}


/*!
    Destruct a SIM toolkit command object.
*/
QSimCommand::~QSimCommand()
{
    if ( !d->ref.deref() )
        delete d;
}


/*!
    Returns the SIM command number.  The default value is 1.  The command number
    may be something other than 1 if more than one SIM command is being processed
    simultaneously, but this is a fairly rare occurence.  The main use for
    command numbers is to match commands and responses.

    Applies to: all commands

    \sa setCommandNumber()
*/
int QSimCommand::commandNumber() const
{
    return d->commandNumber;
}


/*!
    Sets the SIM command number to \a value.  The usual value for the command
    number is 1.  The command number may be set to something other than 1 if more
    than one SIM command is being processed simultaneously, but this is a fairly
    rare occurence.  The main use for command numbers is to match commands and responses.

    Applies to: all commands

    \sa commandNumber()
*/
void QSimCommand::setCommandNumber( int value )
{
    dwrite()->commandNumber = value;
}


/*!
    Returns the type of this command.  The default value is \c NoCommand.

    Applies to: all commands.

    \sa setType()
*/
QSimCommand::Type QSimCommand::type() const
{
    return d->type;
}


/*!
    Sets the type of this command to \a value.

    Applies to: all commands.

    \sa type()
*/
void QSimCommand::setType( QSimCommand::Type value )
{
    dwrite()->type = value;
}


/*!
    Returns the source device that originated the command.  The default value is \c SIM.

    Applies to: all commands.

    \sa setSourceDevice()
*/
QSimCommand::Device QSimCommand::sourceDevice() const
{
    return d->sourceDevice;
}


/*!
    Sets the source device that originated the command to \a value.  Usually this
    is \c SIM.

    Applies to: all commands.

    \sa sourceDevice()
*/
void QSimCommand::setSourceDevice( QSimCommand::Device value )
{
    dwrite()->sourceDevice = value;
}


/*!
    Returns the destination device for the command.  The default value is \c ME.

    Applies to: all commands.

    \sa setDestinationDevice()
*/
QSimCommand::Device QSimCommand::destinationDevice() const
{
    return d->destinationDevice;
}


/*!
    Sets the destination device for the command to \a value.

    Applies to: all commands.

    \sa destinationDevice()
*/
void QSimCommand::setDestinationDevice( QSimCommand::Device value )
{
    dwrite()->destinationDevice = value;
}


/*!
    Returns true if there is help available for this command; otherwise returns false.
    The help flag is the most significant bit of the qualifier() byte.

    Applies to: \c SetupMenu, \c GetInkey, \c GetInput, \c SelectItem.

    \sa setHasHelp()
*/
bool QSimCommand::hasHelp() const
{
    if ( d->type == QSimCommand::GetInkey ||
         d->type == QSimCommand::GetInput ||
         d->type == QSimCommand::SelectItem ||
         d->type == QSimCommand::SetupMenu )
        return d->qualifierBit( 0x80 );
    else
        return false;
}


/*!
    Sets the flag that determines if there is help available
    for this command to \a value.  The help flag is the most significant bit
    of the qualifier() byte.

    Applies to: \c SetupMenu, \c GetInkey, \c GetInput, \c SelectItem.

    \sa hasHelp()
*/
void QSimCommand::setHasHelp( bool value )
{
    if ( d->type == QSimCommand::GetInkey ||
         d->type == QSimCommand::GetInput ||
         d->type == QSimCommand::SelectItem ||
         d->type == QSimCommand::SetupMenu )
        dwrite()->setQualifierBit( 0x80, value );
}


/*!
    Returns the text to be displayed as either a message, a prompt, or an SMS.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c SendSS, \c SendSMS,
    \c SendUSSD, \c PlayTone, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage, \c RunATCommand,
    \c SendDTMF, \c LaunchBrowser, \c SetupIdleModeText, \c SetupCall.

    \sa setText()
*/
QString QSimCommand::text() const
{
    return d->text;
}


/*!
    Sets the text to be displayed by this command to \a value.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c SendSS, \c SendSMS,
    \c SendUSSD, \c PlayTone, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage, \c RunATCommand,
    \c SendDTMF, \c LaunchBrowser, \c SetupIdleModeText, \c SetupCall.

    \sa text()
*/
void QSimCommand::setText( const QString& value )
{
    dwrite()->text = value;
}


/*!
    Returns the text attributes to be applied to text() for formatting.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c SendSS, \c SendSMS,
    \c SendUSSD, \c PlayTone, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage.

    \sa setTextAttribute(), text()
    \since 4.4
*/
QByteArray QSimCommand::textAttribute() const
{
    return d->textAttribute;
}


/*!
    Sets the text attributes to be applied to text() for formatting to \a value.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c SendSS, \c SendSMS,
    \c SendUSSD, \c PlayTone, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage.

    \sa textAttribute(), text(), textHtml()
    \since 4.4
*/
void QSimCommand::setTextAttribute( const QByteArray& value )
{
    dwrite()->textAttribute = value;
}


/*!
    Returns text() formatted as HTML according to the attributes
    in textAttribute().

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c SendSS, \c SendSMS,
    \c SendUSSD, \c PlayTone, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage.

    \sa text(), textAttribute()
    \since 4.4
*/
QString QSimCommand::textHtml() const
{
    return textToHtml( d->text, d->textAttribute );
}


/*!
    Returns true if user feedback should be suppressed.  The default value is false.

    This option controls what should happen when text() is an empty string for
    the \c SendSS, \c SendSMS, \c SendUSSD, and \c PlayTone commands.  If
    suppressUserFeedback() returns true, then the command should be performed
    without any notification to the user.  If suppressUserFeedback() is false,
    then generic feedback should be provided to the user to indicate that a
    command is being performed.

    Applies to: \c SendSS, \c SendSMS, \c SendUSSD, \c PlayTone.

    \sa setSuppressUserFeedback(), text()
*/
bool QSimCommand::suppressUserFeedback() const
{
    return d->flag( SC_SuppressUserFeedback );
}


/*!
    Sets the user feedback suppression flag to \a value.

    This option controls what should happen when text() is an empty string for
    the \c SendSS, \c SendSMS, \c SendUSSD, and \c PlayTone commands.  If
    \a value is true, then the command should be performed without any notification
    to the user.  If \a value is false, then generic feedback should be provided to
    the user to indicate that a command is being performed.

    Applies to: \c SendSS, \c SendSMS, \c SendUSSD, \c PlayTone.

    \sa suppressUserFeedback(), text()
*/
void QSimCommand::setSuppressUserFeedback( bool value )
{
    dwrite()->setFlag( SC_SuppressUserFeedback, value );
}


/*!
    Returns the other text to be displayed as a message.  This is typically used
    with \c SetupCall commands to specify the text to be displayed during the call setup
    phase, when text() is used for the user confirmation phase.

    Applies to: \c SetupCall

    \sa setOtherText()
*/
QString QSimCommand::otherText() const
{
    return d->otherText;
}


/*!
    Sets the other text to be displayed to \a value.  This is typically used
    with \c SetupCall commands to specify the text to be displayed during the call setup
    phase, when text() is used for the user confirmation phase.

    Applies to: \c SetupCall

    \sa otherText()
*/
void QSimCommand::setOtherText( const QString& value )
{
    dwrite()->otherText = value;
}


/*!
    Returns the text attributes to use to format otherText().

    Applies to: \c SetupCall

    \sa setOtherTextAttribute(), otherText(), otherTextHtml()
    \since 4.4
*/
QByteArray QSimCommand::otherTextAttribute() const
{
    return d->otherTextAttribute;
}


/*!
    Sets the text attributes to use to format otherText() to \a value.

    Applies to: \c SetupCall

    \sa otherTextAttribute(), otherText(), otherTextHtml()
    \since 4.4
*/
void QSimCommand::setOtherTextAttribute( const QByteArray& value )
{
    dwrite()->otherTextAttribute = value;
}


/*!
    Returns otherText() formatted as HTML according to the attributes
    in otherTextAttribute().

    Applies to: \c SetupCall

    \sa otherText(), otherTextAttribute()
    \since 4.4
*/
QString QSimCommand::otherTextHtml() const
{
    return textToHtml( d->otherText, d->otherTextAttribute );
}


/*!
    Returns the default text for \c GetInput commands.

    Applies to: \c GetInput

    \sa setDefaultText()
*/
QString QSimCommand::defaultText() const
{
    return d->defaultText;
}


/*!
    Sets the default text for \c GetInput commands to \a value.

    Applies to: \c GetInput

    \sa defaultText()
*/
void QSimCommand::setDefaultText( const QString& value )
{
    dwrite()->defaultText = value;
}


/*!
    Returns true if a DisplayText command should attempt to display
    the text as high priority (true) or normal priority (false).
    The default value is false.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage.

    \sa setHighPriority()
*/
bool QSimCommand::highPriority() const
{
    if ( d->type == QSimCommand::DisplayText ||
         d->type == QSimCommand::DisplayMultimediaMessage)
        return d->qualifierBit( 0x01 );
    else
        return false;
}


/*!
    Sets the high priority text display flag to \a value.  If type() is not \c DisplayText
    or \c DisplayMultimediaMessage, then the request is ignored.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage.

    \sa highPriority()
*/
void QSimCommand::setHighPriority( bool value )
{
    if ( d->type == QSimCommand::DisplayText ||
         d->type == QSimCommand::DisplayMultimediaMessage)
        dwrite()->setQualifierBit( 0x01, value );
}


/*!
    Returns true if the text should be automatically cleared after
    a small delay (usually 3 seconds); otherwise returns false.  If this is false, then
    the client must call QSimToolkit::clearText() to move on to
    the next SIM application state.  The default value is true.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage.

    Note: in Qtopia 4.3 and newer, the default value is true.  If Qtopia 4.2 and older,
    the default value was false.  The change was due to the introduction of the
    qualifier() value.

    \sa setClearAfterDelay()
*/
bool QSimCommand::clearAfterDelay() const
{
    if ( d->type == QSimCommand::DisplayText ||
         d->type == QSimCommand::DisplayMultimediaMessage)
        return !d->qualifierBit( 0x80 );
    else
        return true;
}


/*!
    Sets the clear after delay flag for text display to \a value.  If type() is not
    \c DisplayText or \c DisplayMultimediaMessage, the request is ignored.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage.

    \sa clearAfterDelay()
*/
void QSimCommand::setClearAfterDelay( bool value )
{
    if ( d->type == QSimCommand::DisplayText ||
         d->type == QSimCommand::DisplayMultimediaMessage)
        dwrite()->setQualifierBit( 0x80, !value );
}


/*!
    Returns true if a \c DisplayText or \c DisplayMultimediaMessage command
    should result in an immediate response to the SIM without asking the
    user to confirm the text first; or false if the user should confirm,
    or the response should be sent after the usual delay if clearAfterDelay()
    is set.  The default value is false.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage

    \sa setImmediateResponse(), clearAfterDelay()
*/
bool QSimCommand::immediateResponse() const
{
    return d->flag( SC_ImmediateResponse );
}


/*!
    Sets the immediate response flag to \a value.  If \a value is true, then the
    \c DisplayText or \c DisplayMultimediaMessage command should result in an
    immediate response to the SIM without asking the user to confirm the text first.
    If \a value is false, the user should confirm, or the response should be sent
    after the usual delay if clearAfterDelay() is set.

    Applies to: \c DisplayText, \c DisplayMultimediaMessage

    \sa immediateResponse(), clearAfterDelay()
*/
void QSimCommand::setImmediateResponse( bool value )
{
    dwrite()->setFlag( SC_ImmediateResponse, value );
}


/*!
    Returns true if the \c GetInkey and \c GetInput commands should return UCS2
    input strings to the SIM; false if the commands should return strings in the
    default SMS alphabet instead.  The default value is false.

    This setting is ignored if either wantYesNo() returns true, and this setting
    always overrides packedInput().

    Applies to: \c GetInkey, \c GetInput

    \sa setUcs2Input(), wantYesNo(), packedInput()
*/
bool QSimCommand::ucs2Input() const
{
    if ( d->type == QSimCommand::GetInkey || d->type == QSimCommand::GetInput )
        return d->qualifierBit( 0x02 );
    else
        return false;
}


/*!
    Sets the UCS2 input flag to \a value.  If \a value is true, then the
    \c GetInkey and \c GetInput commands should send UCS2 input strings to the SIM.
    If \a value is false, then the commands should send strings in the
    default SMS alphabet instead.

    This setting is ignored if either wantYesNo() returns true, and this setting
    always overrides packedInput().

    \sa ucs2Input(), wantYesNo(), packedInput()
*/
void QSimCommand::setUcs2Input( bool value )
{
    if ( d->type == QSimCommand::GetInkey || d->type == QSimCommand::GetInput )
        dwrite()->setQualifierBit( 0x02, value );
}


/*!
    Returns true if the \c GetInput command should return packed
    7-bit input strings to the SIM; false if the commands should return strings in the
    unpacked 8-bit alphabet instead.  The default value is false.

    This setting is ignored if either wantYesNo() or ucs2Input() returns true.

    Applies to: \c GetInput

    \sa setPackedInput(), wantYesNo(), ucs2Input()
*/
bool QSimCommand::packedInput() const
{
    if ( d->type == QSimCommand::GetInput )
        return d->qualifierBit( 0x08 );
    else
        return false;
}


/*!
    Sets the packed input flag to \a value.  If \a value is true, then the
    \c GetInput command should send packed 7-bit input strings to the SIM.
    If \a value is false, then the commands should send strings in the
    unpacked 8-bit alphabet instead.

    This setting is ignored if either wantYesNo() or ucs2Input() returns true,
    of if type() is not \c GetInput.

    Applies to: \c GetInput

    \sa packedInput(), wantYesNo(), ucs2Input()
*/
void QSimCommand::setPackedInput( bool value )
{
    if ( d->type == QSimCommand::GetInput )
        dwrite()->setQualifierBit( 0x08, value );
}


/*!
    Returns true if \c GetInkey or \c GetInput wants input that consists
    only of digits (true), or any character combination (false).
    The allowable digits are 0-9, #, *, and +.  The default value is true.

    Applies to: \c GetInkey, \c GetInput.

    Note: in Qtopia 4.3 and newer, the default value is true.  If Qtopia 4.2 and older,
    the default value was false.  The change was due to the introduction of the
    qualifier() value.

    \sa setWantDigits()
*/
bool QSimCommand::wantDigits() const
{
    if ( d->type == QSimCommand::GetInkey || d->type == QSimCommand::GetInput )
        return !d->qualifierBit( 0x01 );
    else
        return true;
}


/*!
    Sets the flag that determines if \c GetInkey or \c GetInput wants
    input that consists only of digits (true), or any character
    combination (false), to \a value.

    Applies to: \c GetInkey, \c GetInput.

    \sa wantDigits()
*/
void QSimCommand::setWantDigits( bool value )
{
    if ( d->type == QSimCommand::GetInkey || d->type == QSimCommand::GetInput )
        dwrite()->setQualifierBit( 0x01, !value );
}


/*!
    Returns true if \c GetInkey wants input that consists of a \c Yes or
    \c No answer.  The default value is false.

    Applies to: \c GetInkey

    \sa setWantYesNo()
*/
bool QSimCommand::wantYesNo() const
{
    if ( d->type == QSimCommand::GetInkey )
        return d->qualifierBit( 0x04 );
    else
        return false;
}


/*!
    Sets the flag that determines if \c GetInkey wants input that
    consists of a \c Yes or \c No answer, to \a value.

    Applies to: \c GetInkey

    \sa wantYesNo()
*/
void QSimCommand::setWantYesNo( bool value )
{
    if ( d->type == QSimCommand::GetInkey )
        dwrite()->setQualifierBit( 0x04, value );
}


/*!
    Returns true if \c GetInkey wants that the response is immediately sent
    after key press.  The default value is false.

    Applies to: \c GetInkey

    \sa setWantImmediateResponse()
*/
bool QSimCommand::wantImmediateResponse() const
{
    if ( d->type == QSimCommand::GetInkey )
        return d->qualifierBit( 0x08 );
    else
        return false;
}

/*!
    Sets the flag that determines if \c GetInkey wants that
    the response is immediately sent after key press.

    Applies to: \c GetInkey

    \sa wantImmediateResponse()
*/
void QSimCommand::setWantImmediateResponse( bool value )
{
    if ( d->type == QSimCommand::GetInkey )
        dwrite()->setQualifierBit( 0x08, value );
}

/*!
    Returns the minimum text length for input.  The default value is 0.

    Applies to: \c GetInput.

    \sa setMinimumLength()
*/
uint QSimCommand::minimumLength() const
{
    return d->minimumLength;
}


/*!
    Sets the minimum text length for input to \a value.

    Applies to: \c GetInput.

    \sa minimumLength()
*/
void QSimCommand::setMinimumLength( uint value )
{
    dwrite()->minimumLength = value;
}


/*!
    Returns the maximum text length for input.  The default value is 255, which
    indicates that the text is unlimited in length.

    Applies to: \c GetInput.

    \sa setMaximumLength()
*/
uint QSimCommand::maximumLength() const
{
    return d->maximumLength;
}


/*!
    Sets the maximum text length for input to \a value.

    Applies to: \c GetInput.

    \sa maximumLength()
*/
void QSimCommand::setMaximumLength( uint value )
{
    dwrite()->maximumLength = value;
}


/*!
    Returns true if input should be echoed; otherwise returns false.
    The default value is true.

    Applies to: \c GetInput.

    Note: in Qtopia 4.3 and newer, the default value is true.  If Qtopia 4.2 and older,
    the default value was false.  The change was due to the introduction of the
    qualifier() value.

    \sa setEcho()
*/
bool QSimCommand::echo() const
{
    if ( d->type == QSimCommand::GetInput )
        return !d->qualifierBit( 0x04 );
    else
        return true;
}


/*!
    Sets the flag that determines if input should be echoed to \a value.

    Applies to: \c GetInput.

    \sa echo()
*/
void QSimCommand::setEcho( bool value )
{
    if ( d->type == QSimCommand::GetInput )
        dwrite()->setQualifierBit( 0x04, !value );
}


/*!
    Returns the disposition of other calls when a call setup occurs.
    The default value is \c IfNoOtherCalls.

    Applies to: \c SetupCall.

    \sa setDisposition()
*/
QSimCommand::Disposition QSimCommand::disposition() const
{
    if ( d->type == QSimCommand::SetupCall )
        return (QSimCommand::Disposition)(d->qualifier >> 1);
    else
        return IfNoOtherCalls;
}


/*!
    Sets the disposition of other calls when a call setup occurs to \a value.

    Applies to: \c SetupCall.

    \sa disposition()
*/
void QSimCommand::setDisposition( QSimCommand::Disposition value )
{
    if ( d->type == QSimCommand::SetupCall )
        dwrite()->qualifier = (((int)value) << 1) | (d->qualifier & 0x01);
}


/*!
    Returns true if with-redial modifier is set for call setup disposition();
    otherwise returns false.  The default value is false.

    Applies to: \c SetupCall.

    \sa setWithRedial()
*/
bool QSimCommand::withRedial() const
{
    if ( d->type == QSimCommand::SetupCall )
        return d->qualifierBit( 0x01 );
    else
        return false;
}

/*!
    Sets the with-redial modifier for call setup disposition to \a value.

    Applies to: \c SetupCall.

    \sa withRedial()
*/
void QSimCommand::setWithRedial( bool value )
{
    if ( d->type == QSimCommand::SetupCall )
        dwrite()->setQualifierBit( 0x01, value );
}

/*!
    Returns the phone number for \c SetupCall, address for \c SendSMS, the
    supplementary service string for \c SendSS, the unstructured supplementary
    service string for \c SendUSSD, or the sequence of DTMF digits for \c SendDTMF.

    Applies to: \c SetupCall, \c SendSMS, \c SendSS, \c SendUSSD, \c SendDTMF.

    \sa setNumber()
*/
QString QSimCommand::number() const
{
    return d->number;
}


/*!
    Sets the phone number for \c SetupCall, address for \c SendSMS, the
    supplementary service string for \c SendSS, unstructured supplementary service
    string for \c SendUSSD, to \a value, or the sequence of DTMF digits for \c SendDTMF.

    Applies to: \c SetupCall, \c SendSMS, \c SendSS, \c SendUSSD, \c SendDTMF.

    \sa number()
*/
void QSimCommand::setNumber( const QString& value )
{
    dwrite()->number = value;
}


/*!
    Returns the sub-address (e.g. extension) for a call setup.

    Applies to: \c SetupCall.

    \sa setSubAddress()
*/
QString QSimCommand::subAddress() const
{
    return d->subAddress;
}


/*!
    Sets the sub-address (e.g. extension) for a call setup to \a value.

    Applies to: \c SetupCall.

    \sa subAddress()
*/
void QSimCommand::setSubAddress( const QString& value )
{
    dwrite()->subAddress = value;
}


/*!
    Returns the class of call to be setup (Voice, Data, or Fax).
    The default value is \c Voice.

    Applies to: \c SetupCall.

    \sa setCallClass()
*/
QSimCommand::CallClass QSimCommand::callClass() const
{
    return d->callClass;
}


/*!
    Sets the class of call to be setup (Voice, Data, or Fax) to \a value.

    Applies to: \c SetupCall.

    \sa callClass()
*/
void QSimCommand::setCallClass( QSimCommand::CallClass value )
{
    dwrite()->callClass = value;
}


/*!
    Returns the tone to be played by a \c PlayTone command.  The default
    value is \c ToneNone.

    Applies to: \c PlayTone

    \sa setTone(), duration()
*/
QSimCommand::Tone QSimCommand::tone() const
{
    return d->tone;
}


/*!
    Sets the tone to be played by a \c PlayTone command to \a value.

    Applies to: \c PlayTone

    \sa tone(), duration()
*/
void QSimCommand::setTone( QSimCommand::Tone value )
{
    dwrite()->tone = value;
}


/*!
    Returns the number of milliseconds to play a tone.  The default value is zero,
    indicating that the default duration for the tone should be used.

    Applies to: \c PlayTone.

    Note: this function is obsoleted by duration(), and will return the same value
    as duration().

    \sa duration(), setToneTime(), tone()
*/
uint QSimCommand::toneTime() const
{
    return d->duration;
}


/*!
    Sets the number of milliseconds to play a tone to \a value.

    Applies to: \c PlayTone.

    Note: this function is obsoleted by setDuration().

    \sa setDuration(), toneTime(), tone()
*/
void QSimCommand::setToneTime( uint value )
{
    dwrite()->duration = value;
}


/*!
    Returns the number of milliseconds to play a tone or the poll interval.
    The default value is zero, indicating that the default duration should be used.

    Applies to: \c PlayTone, \c PollInterval.

    \sa setDuration(), tone()
*/
uint QSimCommand::duration() const
{
    return d->duration;
}

/*!
    Sets the duration of a poll interval or to play a tone to \a value milliseconds.

    Applies to: \c PlayTone, \c PollInterval.

    \sa duration(), tone()
*/
void QSimCommand::setDuration( uint value )
{
    dwrite()->duration = value;
}

/*!
    Returns true if icons for menu items should be displayed on the device's soft keys
    if the number of icons is less than or equal to the number of soft keys.  If there are
    more icons than soft keys, then regular menu selection should be used.  Returns false
    for regular menu selection.  The default value is false.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa setSoftKeysPreferred(), menuPresentation()
*/
bool QSimCommand::softKeysPreferred() const
{
    if ( d->type == QSimCommand::SetupMenu )
        return d->qualifierBit( 0x01 );
    else if ( d->type == QSimCommand::SelectItem )
        return d->qualifierBit( 0x04 );
    else
        return false;
}

/*!
    Sets the soft key preferred option to \a value.  If \a value is true then the
    icons for menu items should be displayed on the device's soft keys if the number
    of icons is less than or equal to the number of soft keys.  If there are more
    icons than soft keys, then regular menu selection should be used.  If \a value
    is false, then regular menu selection is always used.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa softKeysPreferred(), menuPresentation()
*/
void QSimCommand::setSoftKeysPreferred( bool value )
{
    if ( d->type == QSimCommand::SetupMenu )
        dwrite()->setQualifierBit( 0x01, value );
    else if ( d->type == QSimCommand::SelectItem )
        dwrite()->setQualifierBit( 0x04, value );
}

/*!
    Returns the menu presentation type to use for \c SelectItem commands.
    The default value is \c AnyPresentation.

    Applies to: \c SelectItem.

    \sa setMenuPresentation(), softKeysPreferred()
*/
QSimCommand::MenuPresentation QSimCommand::menuPresentation() const
{
    if ( d->type == QSimCommand::SelectItem && d->qualifierBit( 0x01 ) != 0 ) {
        if ( d->qualifierBit( 0x02 ) != 0 )
            return NavigationOptionsPresentation;
        else
            return DataValuesPresentation;
    } else {
        return AnyPresentation;
    }
}

/*!
    Sets the menu presentation type to use for \c SelectItem commands
    to \a value.

    Applies to: \c SelectItem.

    \sa menuPresentation(), softKeysPreferred()
*/
void QSimCommand::setMenuPresentation( QSimCommand::MenuPresentation value )
{
    if ( d->type == QSimCommand::SelectItem ) {
        if ( value == NavigationOptionsPresentation ) {
            dwrite()->setQualifierBit( 0x01, true );
            dwrite()->setQualifierBit( 0x02, true );
        } else if ( value == DataValuesPresentation ) {
            dwrite()->setQualifierBit( 0x01, true );
            dwrite()->setQualifierBit( 0x02, false );
        } else {
            dwrite()->setQualifierBit( 0x01, false );
            dwrite()->setQualifierBit( 0x02, false );
        }
    }
}

/*!
    Returns the title to be displayed on a menu.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa setTitle()
*/
QString QSimCommand::title() const
{
    return d->title;
}


/*!
    Sets the title to be displayed on a menu to \a value.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa title()
*/
void QSimCommand::setTitle( const QString& value )
{
    dwrite()->title = value;
}


/*!
    Returns the text attribute to use to format title().

    Applies to: \c SetupMenu, \c SelectItem.

    \sa setTitleAttribute(), title(), titleHtml()
    \since 4.4
*/
QByteArray QSimCommand::titleAttribute() const
{
    return d->titleAttribute;
}


/*!
    Sets the text attribute to use to format title() to \a value.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa titleAttribute(), title(), titleHtml()
    \since 4.4
*/
void QSimCommand::setTitleAttribute( const QByteArray& value )
{
    dwrite()->titleAttribute = value;
}


/*!
    Returns title() formatted as HTML according to the attributes
    in titleAttribute().

    Applies to: \c SetupMenu, \c SelectItem.

    \sa title(), titleAttribute()
    \since 4.4
*/
QString QSimCommand::titleHtml() const
{
    return textToHtml( d->title, d->titleAttribute );
}


/*!
    Returns the index of the default item in the menu, or zero if no default.

    Applies to: \c SelectItem.

    \sa setDefaultItem()
*/
uint QSimCommand::defaultItem() const
{
    return d->defaultItem;
}


/*!
    Sets the index of the default item in the menu, or zero if no default,
    to \a value.

    Applies to: \c SelectItem.

    \sa defaultItem()
*/
void QSimCommand::setDefaultItem( uint value )
{
    dwrite()->defaultItem = value;
}


/*!
    Returns the list of menu items in the menu.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa setMenuItems()
*/
QList<QSimMenuItem> QSimCommand::menuItems() const
{
    return d->menuItems;
}


/*!
    Sets the list of menu items in the menu to \a value.

    Applies to: \c SetupMenu, \c SelectItem.

    \sa menuItems()
*/
void QSimCommand::setMenuItems( const QList<QSimMenuItem>& value )
{
    dwrite()->menuItems = value;
}


/*!
    Returns the type of refresh to that was performed by the SIM.
    The default value is InitAndFullFileChange.

    Applies to: \c Refresh.

    \sa setRefreshType()
*/
QSimCommand::RefreshType QSimCommand::refreshType() const
{
    if ( d->type == QSimCommand::Refresh )
        return (QSimCommand::RefreshType)(d->qualifier);
    else
        return InitAndFullFileChange;
}


/*!
    Sets the type of refresh to that was performed by the SIM to \a value.
    The request is ignored if type() is not \c Refresh.

    Applies to: \c Refresh.

    \sa refreshType()
*/
void QSimCommand::setRefreshType( QSimCommand::RefreshType value )
{
    if ( d->type == QSimCommand::Refresh )
        setQualifier( (int)value );
}


/*!
    Returns the events to be reported.

    Applies to: \c SetupEventList.

    Note: this function can only return information about \c IdleScreen and \c UserActivity
    events.  To access information about all SIM toolkit events in the command,
    use extensionField() with a tag value of 0x99.

    \sa setEvents()
*/
QSimCommand::Event QSimCommand::events() const
{
    if ( d->type != QSimCommand::SetupEventList )
        return NoEvent;
    if ( d->extensionData.isEmpty() )
        return NoEvent;
    QByteArray evdata = extensionField( 0x99 );
    if ( evdata.isEmpty() ) {
        return Cancel;      // Field is present, but empty, which means "Cancel".
    } else if ( evdata.contains( (char)0x05 ) ) {
        if ( evdata.contains( (char)0x04 ) )
            return Both;
        else
            return IdleScreen;
    } else if ( evdata.contains( (char)0x04 ) ) {
        return UserActivity;
    } else {
        return NoEvent;
    }
}


/*!
    Sets the events to be reported to \a value.

    Applies to: \c SetupEventList.

    Note: this function can only set information about \c IdleScreen and \c UserActivity
    events.  To set information about other SIM toolkit events, use setExtensionField()
    with a tag value of 0x99.

    \sa events()
*/
void QSimCommand::setEvents( QSimCommand::Event value )
{
    if ( d->type == QSimCommand::SetupEventList ) {
        dwrite()->extensionData = QByteArray();
        switch ( value ) {

            case NoEvent:       break;

            case IdleScreen:
            {
                d->extensionData += (char)0x99;
                d->extensionData += (char)0x01;
                d->extensionData += (char)0x05;
            }
            break;

            case UserActivity:
            {
                d->extensionData += (char)0x99;
                d->extensionData += (char)0x01;
                d->extensionData += (char)0x04;
            }
            break;

            case Both:
            {
                d->extensionData += (char)0x99;
                d->extensionData += (char)0x02;
                d->extensionData += (char)0x04;
                d->extensionData += (char)0x05;
            }
            break;

            case Cancel:
            {
                d->extensionData += (char)0x99;
                d->extensionData += (char)0x00;
            }
            break;
        }
    }
}


/*!
    Returns the browser launch mode.  The default value is IfNotAlreadyLaunched.

    Applies to: \c LaunchBrowser.

    \sa setBrowserLaunchMode()
*/
QSimCommand::BrowserLaunchMode QSimCommand::browserLaunchMode() const
{
    if ( d->type == QSimCommand::LaunchBrowser )
        return (QSimCommand::BrowserLaunchMode)(d->qualifier);
    else
        return IfNotAlreadyLaunched;
}

/*!
    Sets the browser launch mode to \a value.

    Applies to: \c LaunchBrowser.

    \sa browserLaunchMode()
*/
void QSimCommand::setBrowserLaunchMode( QSimCommand::BrowserLaunchMode value )
{
    if ( d->type == QSimCommand::LaunchBrowser )
        setQualifier( (int)value );
}

/*!
    Returns the URL to launch in the browser.

    Applies to: \c LaunchBrowser.

    \sa setUrl()
*/
QString QSimCommand::url() const
{
    return d->url;
}

/*!
    Sets the URL to launch in the browser to \a value.

    Applies to: \c LaunchBrowser.

    \sa url()
*/
void QSimCommand::setUrl( const QString& value )
{
    dwrite()->url = value;
}

/*!
    Returns the language that will be used for any text string within
    proactive commands or envelope command responses.

    Applies to: \c LanguageNotification.

    \sa setLanguage()
*/
QString QSimCommand::language() const
{
    return d->language;
}

/*!
    Sets the language that will be used for any text string within
    proactive commands or envelope command responses to \a value.

    Applies to: \c LanguageNotification.

    \sa language()
*/
void QSimCommand::setLanguage( const QString& value )
{
    dwrite()->language = value;
}

/*!
    Returns the icon identifier associated with this command.
    Returns zero if there is no icon.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c PlayTone, \c SelectItem,
    \c SendSMS, \c SendSS, \c SetupCall, \c SetupMenu, \c LaunchBrowser,
    \c SetupIdleModeText, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage

    \sa setIconId()
*/
uint QSimCommand::iconId() const
{
    return d->iconId;
}

/*!
    Sets the icon identifier associated with this menu item to \a value.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c PlayTone, \c SelectItem,
    \c SendSMS, \c SendSS, \c SetupCall, \c SetupMenu, \c LaunchBrowser,
    \c SetupIdleModeText, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage

    \sa iconId()
*/
void QSimCommand::setIconId( uint value )
{
    dwrite()->iconId = value;
}

/*!
    Returns true if the icon specified by iconId() is self-explanatory
    without the display of text().  If this function returns false,
    then text() should be displayed next to the icon.  If iconId()
    returns zero, then iconSelfExplanatory() should be ignored.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c PlayTone, \c SelectItem,
    \c SendSMS, \c SendSS, \c SetupCall, \c SetupMenu, \c LaunchBrowser,
    \c SetupIdleModeText, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage

    \sa setIconSelfExplanatory()
*/
bool QSimCommand::iconSelfExplanatory() const
{
    return d->flag( SC_IconSelfExplanatory );
}

/*!
    Sets the self-explanatory flag to \a value.

    Applies to: \c DisplayText, \c GetInkey, \c GetInput, \c PlayTone, \c SelectItem,
    \c SendSMS, \c SendSS, \c SetupCall, \c SetupMenu, \c LaunchBrowser,
    \c SetupIdleModeText, \c ServiceSearch, \c GetServiceInformation,
    \c RetrieveMultimediaMessage, \c SubmitMultimediaMessage

    \sa iconSelfExplanatory()
*/
void QSimCommand::setIconSelfExplanatory( bool value )
{
    dwrite()->setFlag( SC_IconSelfExplanatory, value );
}

/*!
    Returns the other icon identifier associated with this command.
    Returns zero if there is no icon.  This is typically used
    with \c SetupCall commands to specify the icon to be displayed during the call setup
    phase, when iconId() is used for the user confirmation phase.

    Applies to: \c SetupCall

    \sa setOtherIconId()
*/
uint QSimCommand::otherIconId() const
{
    return d->otherIconId;
}


/*!
    Sets the other icon identifier associated with this menu item to \a value.
    This is typically used with \c SetupCall commands to specify the icon to be
    displayed during the call setup phase, when iconId() is used for the user
    confirmation phase.

    Applies to: \c SetupCall

    \sa otherIconId()
*/
void QSimCommand::setOtherIconId( uint value )
{
    dwrite()->otherIconId = value;
}


/*!
    Returns true if the icon specified by otherIconId() is self-explanatory
    without the display of otherText().  If this function returns false,
    then otherText() should be displayed next to the icon.  If otherIconId()
    returns zero, then otherIconSelfExplanatory() should be ignored.

    Applies to: \c SetupCall

    \sa setOtherIconSelfExplanatory()
*/
bool QSimCommand::otherIconSelfExplanatory() const
{
    return d->flag( SC_IconSelfExplanatory2 );
}


/*!
    Sets the self-explanatory flag for the icon specified by otherIconId to \a value.

    Applies to: \c SetupCall

    \sa otherIconSelfExplanatory()
*/
void QSimCommand::setOtherIconSelfExplanatory( bool value )
{
    dwrite()->setFlag( SC_IconSelfExplanatory2, value );
}


/*!
    Returns true if SMS packing should be performed when sending an SMS message
    for the \c SendSMS command; or false if packing is not required.  The default
    value is false.

    Many modems or modem vendor plugins will consume this value and remove it
    from the SIM command before it reaches the client application layer.  Client
    applications should not rely upon this value being present.

    Applies to: \c SendSMS

    \sa setSmsPacking()
*/
bool QSimCommand::smsPacking() const
{
    if ( d->type == QSimCommand::SendSMS )
        return d->qualifierBit( 0x01 );
    else
        return false;
}

/*!
    Sets the SMS packing flag to \a value.  If \a value is true, then SMS packing
    should be performed when sending an SMS message for the \c SendSMS command.
    If \a value is false, then no packing is required.

    Many modems or modem vendor plugins will consume this value and remove it
    from the SIM command before it reaches the client application layer.  Client
    applications should not rely upon this value being present.

    Applies to: \c SendSMS

    \sa smsPacking()
*/
void QSimCommand::setSmsPacking( bool value )
{
    if ( d->type == QSimCommand::SendSMS )
        dwrite()->setQualifierBit( 0x01, value );
}

/*!
    Returns the number of the timer to which a Timer Management command is
    addressed.

    Applies to: \c TimerManagement

    \sa setTimerId()
*/
int QSimCommand::timerId() const
{
    return d->timerId;
}

/*!
    Sets the identifier of the timer to which the Timer Management command
    is being sent.  \a id should be between 1 and 8 to address one of the
    eight timers available.

    Applies to: \c TimerManagement

    \sa timerId()
*/
void QSimCommand::setTimerId( int id )
{
    dwrite()->timerId = id;
}

ushort QSimCommand::bufferSize() const
{
    return d->bufferSize;
}

void QSimCommand::setBufferSize( ushort value )
{
    dwrite()->bufferSize = value;
}

uint QSimCommand::dataLength() const
{
    return d->dataLength;
}

void QSimCommand::setDataLength( uint value )
{
    dwrite()->dataLength = value;
}

QString QSimCommand::userLogin() const
{
    return d->userLogin;
}

void QSimCommand::setUserLogin( const QString& value )
{
    dwrite()->userLogin = value;
}

QString QSimCommand::userPassword() const
{
    return d->userPassword;
}

void QSimCommand::setUserPassword( const QString& value )
{
    dwrite()->userPassword = value;
}

QByteArray QSimCommand::uti() const
{
    return d->uti;
}

void QSimCommand::setUti( const QByteArray& value )
{
    dwrite()->uti = value;
}

QByteArray QSimCommand::bearerDesc() const
{
    return d->bearerDesc;
}

void QSimCommand::setBearerDesc( const QByteArray& value )
{
    dwrite()->bearerDesc = value;
}

QByteArray QSimCommand::localAddress() const
{
    return d->localAddress;
}

void QSimCommand::setLocalAddress( const QByteArray& value )
{
    dwrite()->localAddress = value;
}

QByteArray QSimCommand::destAddress() const
{
    return d->destAddress;
}

void QSimCommand::setDestAddress( const QByteArray& value )
{
    dwrite()->destAddress = value;
}

QByteArray QSimCommand::apn() const
{
    return d->apn;
}

void QSimCommand::setApn( const QByteArray& value )
{
    dwrite()->apn = value;
}

/*!
    Copy the QSimCommand object \a value.
*/
QSimCommand& QSimCommand::operator=( const QSimCommand & value )
{
    if ( d == value.d )
        return *this;

    if ( !d->ref.deref() )
        delete d;

    d = value.d;
    d->ref.ref();

    return *this;
}

// Read BER tag and length information from a QByteArray.
// We also need to export this to qsimterminalresponse.cpp and qsimenvelope.cpp.
void _qtopiaphone_readBer( const QByteArray& binary, uint& posn, uint& tag, uint& length )
{
    if ( posn < (uint)binary.size() ) {
        tag = (uint)(binary[posn] & 0xFF);
        ++posn;
    } else {
        tag = (uint)(-1);
    }
    if ( posn < (uint)binary.size() ) {
        length = (uint)(binary[posn] & 0xFF);
        ++posn;
        if ( length == 0x81 ) {
            // Two-byte length value.
            if ( posn < (uint)binary.size() ) {
                length = (uint)(binary[posn] & 0xFF);
                ++posn;
            } else {
                length = 0;
            }
        } else if ( length == 0x82 ) {
            // Three-byte length value.
            if ( ( posn + 1 ) < (uint)binary.size() ) {
                length = (((uint)(binary[posn] & 0xFF)) << 8) |
                         (uint)(binary[posn + 1] & 0xFF);
                posn += 2;
            } else {
                length = 0;
                posn = (uint)binary.size();
            }
        }
    } else {
        length = 0;
    }
}
#define readBer _qtopiaphone_readBer

// Decode an EFADN string from a BER field.  GSM 11.11, section 10.5.1.
// Exported to qsimcontrolevent.cpp.
QString _qtopiaphone_decodeEFADN( const QByteArray& binary, uint posn, uint length )
{
    uint num, page, ch;
    QString temp;
    QTextCodec *codec = QAtUtils::codec( "gsm" );
    if ( length == 0 )
        return QString("");
    else if ( binary[posn] == (char)0x80 ) {
        // UCS-2 coding.
        ++posn;
        --length;
        while ( length >= 2 ) {
            ch = (((uint)((binary[posn]) & 0xFF)) << 8) |
                  ((uint)(binary[posn + 1] & 0xFF));
            if ( ch != 0xFFFF )
                temp += (QChar)ch;
            posn += 2;
            length -= 2;
        }
        return temp;
    } else if ( binary[posn] == (char)0x81 ) {
        // 8-bit half page index coding.
        if ( length < 3 )
            return QString("");
        num = ((uint)(binary[posn + 1] & 0xFF));
        page = ((uint)(binary[posn + 2] & 0xFF)) << 7;
        posn += 3;
        length -= 3;
    } else if ( binary[posn] == (char)0x82 ) {
        // 16-bit half page index coding.
        if ( length < 4 )
            return QString("");
        num = ((uint)(binary[posn + 1] & 0xFF));
        page = (((uint)(binary[posn + 2] & 0xFF)) << 8) |
                ((uint)(binary[posn + 3] & 0xFF));
        posn += 4;
        length -= 4;
    } else {
        // 7-bit GSM default alphabet coding.
        while ( length > 0 && binary[posn + length - 1] == (char)0xFF ) {
            // Strip 0xFF bytes from the end of the string first.
            --length;
        }
        return codec->toUnicode( binary.data() + posn, (int)length );
    }
    while ( num > 0 && length > 0 ) {
        ch = ((uint)(binary[posn] & 0xFF));
        if ( ch < 0x80 ) {
            temp += QGsmCodec::singleToUnicode( (char)ch );
        } else {
            temp += (QChar)( page + (ch & 0x7F) );
        }
        ++posn;
        --length;
        --num;
    }
    return temp;
}
#define decodeEFADN _qtopiaphone_decodeEFADN

// Decode an EFADN number from a BER field.  GSM 11.11, section 10.5.1.
static QString decodeEFADNNumber( const QByteArray& binary,
                                  uint posn, uint length )
{
    static char const digits[] = "0123456789*#pDE";
    QString temp;
    uint ch;
    while ( length > 0 ) {
        ch = ((uint)(binary[posn] & 0xFF));
        if ( ( ch & 0x0F ) == 0x0F )
            break;
        temp += (QChar)( digits[ch & 0x0F] );
        if ( ( ch & 0xF0 ) == 0xF0 )
            break;
        temp += (QChar)( digits[(ch >> 4) & 0x0F] );
        ++posn;
        --length;
    }
    return temp;
}

// Write an EFADN number to a BER field.
static void writeEFADNNumber( QByteArray& data, const QString& number, int tag = 0x86,
                              int localTag = 129 )
{
    QString num = QAtUtils::stripNumber( number );
    if ( tag == 0xAC ) {
        // DTMF strings don't have a type prefix.
        data += (char)tag;
        data += (char)((num.length() + 1) / 2);
    } else if ( num.startsWith( QChar('+') ) ) {
        num = num.mid(1);
        data += (char)tag;
        data += (char)(1 + ((num.length() + 1) / 2));
        data += (char)145;
    } else {
        data += (char)tag;
        data += (char)(1 + ((num.length() + 1) / 2));
        data += (char)localTag;
    }
    int byte = 0;
    int nibble = 0;
    for ( int index = 0; index < num.length(); ++index ) {
        int ch = num[index].unicode();
        switch ( ch ) {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':   ch -= '0'; break;

            case '*':           ch = 10; break;
            case '#':           ch = 11; break;
            case 'p': case 'P': case ',':
                                ch = 12; break;
            case 'D': case 'd': ch = 13; break;
            case 'E': case 'e': ch = 14; break;

            default:            ch = 10; break;
        }
        if ( nibble ) {
            data += (char)(byte | (ch << 4));
            nibble = 0;
        } else {
            byte = ch;
            nibble = 1;
        }
    }
    if ( nibble ) {
        data += (char)(byte | 0xF0);
    }
}

// Decode a coded string, GSM 11.14, section 12.15.
QString _qtopiaphone_decodeCodedString( const QByteArray& binary, uint posn, uint length )
{
    QString temp;
    uint ch, byte, bitCount;
    if ( length == 0 )
        return QString("");
    ch = (uint)(binary[posn++] & 0x0C);
    --length;
    if ( ch == QSMS_DefaultAlphabet ) {
        ch = 0;
        bitCount = 0;
        while ( length > 0 ) {
            byte = binary[posn++];
            --length;
            for ( int i = 0; i < 8; i++ ) {
                // test the bit and shift it down again, as i doesn't mark
                // where we are in the current char, but bitCount does
                ch |= ( ( byte & (1 << i) ) >> i) << bitCount;
                bitCount++;
                if ( bitCount == 7 ) {
                    bitCount = 0;
                    temp += QGsmCodec::singleToUnicode( (char)ch );
                    ch = 0;
                }
            }
        }
        if ( bitCount == 0 && temp.length() > 0 &&
             temp[temp.length() - 1] == (char)0x0D ) {
            // CR used as a padding character in the last byte - strip it.
            temp = temp.left( temp.length() - 1 );
        }
        return temp;
    } else if ( ch == QSMS_UCS2Alphabet ) {
        while ( length >= 2 ) {
            ch = (((uint)((binary[posn]) & 0xFF)) << 8) |
                  ((uint)(binary[posn + 1] & 0xFF));
            temp += (QChar)ch;
            posn += 2;
            length -= 2;
        }
        return temp;
    } else {
        return decodeEFADN( binary, posn, length );
    }
}
#define decodeCodedString _qtopiaphone_decodeCodedString

// Decode a subaddress, GSM 11.14, section 12.3.  The actual encoding
// is specified in GSM 24.008, Annex E.
static QString decodeSubAddress( const QByteArray& pdu, uint posn, uint length )
{
    // Verify that this looks like a BCD-encoded IA5 NSAP subaddress.
    if ( length < 2 || pdu[posn] != (char)0x80 || pdu[posn + 1] != (char)0x50 )
        return QString();
    QString result;
    posn += 2;
    length -= 2;
    while ( length > 0 ) {
        int bcd = (pdu[posn++] & 0xFF);
        result += QChar( (bcd >> 4) * 10 + (bcd & 0x0F) + 32 );
        --length;
    }
    return result;
}

/*!
    Decode a SIM command from its raw binary \a pdu form, as according
    to 3GPP TS 11.14.  The binary data is assumed to be in BER form,
    starting with the command tag.  If the data starts with a
    "Proactive SIM" BER command wrapper, it will be skipped.
    Returns the decoded SIM command.

    \sa toPdu()
*/
QSimCommand QSimCommand::fromPdu( const QByteArray& pdu )
{
    QSimCommand sc;
    uint posn = 0;
    uint newPosn;
    uint tag, length;
    QList<QSimMenuItem> items;
    bool seenText = false;
    bool seenTextAttribute = false;
    bool seenIcon = false;

    // Read the first BER blob, which should be the command details.
    readBer( pdu, posn, tag, length );
    if ( tag == 0xD0 ) {
        // There appears to be a "Proactive SIM" wrapper on the front
        // of the command details.  Skip over it.
        readBer( pdu, posn, tag, length );
    }
    if ( (tag & 0x7F) != 0x01 ) {
        // This doesn't appear to be a SIM command at all.
        return sc;
    }

    // Process the contents of the SIM command, tag by tag.
    for (;;) {
        if ( ( posn + length ) > (uint)pdu.size() ) {
            break;
        }
        newPosn = posn + length;
        switch ( tag & 0x7F ) {

            case 0x01:
            {
                // Main command details blob, GSM 11.14, section 12.6.
                if ( length < 3 )
                    break;
                sc.setCommandNumber( pdu[posn] & 0xFF );
                sc.setType( (QSimCommand::Type)(pdu[posn + 1] & 0xFF) );
                sc.setQualifier( pdu[posn + 2] & 0xFF );
            }
            break;

            case 0x02:
            {
                // Device identities, GSM 11.14, section 12.7.
                if ( length >= 2 ) {
                    sc.setSourceDevice( (QSimCommand::Device)( pdu[posn] & 0xFF ) );
                    sc.setDestinationDevice( (QSimCommand::Device)( pdu[posn + 1] & 0xFF ) );
                }
            }
            break;

            case 0x04:
            {
                // Duration for a PlayTone or PollInterval command, GSM 11.14, section 12.8.
                if ( sc.type() == QSimCommand::OpenChannel ) {
                    // This is actually part of the network setup parameters,
                    // so add it to the extension data.
                    sc.addExtensionField( tag, pdu.mid( posn, length ) );
                    break;
                }
                if ( length < 2 )
                    break;
                uint multiplier = 60000;            // Minutes.
                if ( pdu[posn] == 0x01 )         // Seconds.
                    multiplier = 1000;
                else if ( pdu[posn] == 0x02 )    // Tenths of a second.
                    multiplier = 100;
                sc.setDuration( multiplier * ( pdu[posn + 1] & 0xFF ) );
            }
            break;

            case 0x05:
            {
                // Title to display with a menu, or alpha identifier for other commands.
                // GSM 11.14, section 12.2.
                if ( sc.type() == SetupMenu || sc.type() == SelectItem ) {
                    sc.setTitle( decodeEFADN( pdu, posn, length ) );
                } else if ( seenText ) {
                    sc.setOtherText( decodeEFADN( pdu, posn, length ) );
                } else {
                    sc.setText( decodeEFADN( pdu, posn, length ) );
                    seenText = true;
                    if ( length == 0 )
                        sc.setSuppressUserFeedback( true );
                }
            }
            break;

            case 0x06:
            case 0x09:
            {
                // Address for SetupCall and SendSMS, GSM 11.14, section 12.1.
                // SS string for SendSS command, GSM 11.14, section 12.14.
                if ( length < 1 )
                    break;
                if ( pdu[posn] == (char)145 ) {
                    sc.setNumber
                        ( "+" + decodeEFADNNumber
                                    ( pdu, posn + 1, length - 1 ) );
                } else {
                    sc.setNumber( decodeEFADNNumber
                                    ( pdu, posn + 1, length - 1 ) );
                }
            }
            break;

            case 0x08:
            {
                // Sub-address for SetupCall, GSM 11.14, section 12.3.
                sc.setSubAddress( decodeSubAddress( pdu, posn, length ) );
            }
            break;

            case 0x0D:
            {
                // Text string for command, GSM 11.14, section 12.15.
                if ( sc.type() == QSimCommand::LaunchBrowser ||
                     sc.type() == QSimCommand::OpenChannel ) {
                    // This is actually part of the network setup parameters,
                    // so add it to the extension data.
                    sc.addExtensionField( tag, pdu.mid( posn, length ) );
                } else {
                    sc.setText( decodeCodedString( pdu, posn, length ) );
                }
            }
            break;

            case 0x0A:
            {
                // USSD string, GSM 11.14, section 12.17.
                sc.setNumber( decodeCodedString( pdu, posn, length ) );
            }
            break;

            case 0x11:
            {
                // Response length, GSM 11.14, section 12.11.
                if ( length >= 2 ) {
                    sc.setMinimumLength( pdu[posn] & 0xFF );
                    sc.setMaximumLength( pdu[posn + 1] & 0xFF );
                }
            }
            break;

            case 0x17:
            {
                // Default text string for GET INPUT command, GSM 11.14, section 12.23.
                sc.setDefaultText( decodeCodedString( pdu, posn, length ) );
            }
            break;

            case 0x18:
            {
                // Next action indicator list, GSM 11.14, section 12.24.
                int index = 0;
                while ( length > 0 && index < items.size() ) {
                    uint action = (uint)(pdu[posn++] & 0xFF);
                    items[index].setNextAction( action );
                    --length;
                    ++index;
                }
            }
            break;

            case 0x0E:
            {
                // Tone code for a PlayTone command, GSM 11.14, section 12.16.
                if ( length > 0 )
                    sc.setTone( (QSimCommand::Tone)(pdu[posn] & 0xFF) );
            }
            break;

            case 0x0F:
            {
                // Menu item for command, GSM 11.14 section, 12.9.
                if ( !length )
                    break;      // Zero-length items indicate empty menu.
                QSimMenuItem item;
                if ( length > 0 ) {
                    item.setIdentifier( (uint)(pdu[posn] & 0xFF) );
                    ++posn;
                    --length;
                }
                item.setLabel( decodeEFADN( pdu, posn, length ) );
                item.setHasHelp( sc.hasHelp() );
                items.append( item );
            }
            break;

            case 0x10:
            {
                // Default menu item identifier, GSM 11.14, section 12.10.
                if ( length > 0 )
                    sc.setDefaultItem( (uint)(pdu[posn] & 0xFF) );
            }
            break;

            case 0x1E:
            {
                // Icon identifier, GSM 11.14, section 12.31.
                if ( length >= 2 ) {
                    if ( seenIcon ) {
                        sc.setOtherIconSelfExplanatory( (pdu[posn] & 0x01) == 0 );
                        sc.setOtherIconId( (uint)(pdu[posn + 1] & 0xFF) );
                    } else {
                        sc.setIconSelfExplanatory( (pdu[posn] & 0x01) == 0 );
                        sc.setIconId( (uint)(pdu[posn + 1] & 0xFF) );
                        seenIcon = true;
                    }
                }
            }
            break;

            case 0x1F:
            {
                // Item icon identifier list, GSM 11.14, section 12.32.
                int index = 0;
                bool selfExplanatory = true;
                if ( length > 0 ) {
                    if ( (pdu[posn] & 0x01) != 0 )
                        selfExplanatory = false;
                    ++posn;
                    --length;
                }
                while ( length > 0 && index < items.size() ) {
                    uint iconId = (uint)(pdu[posn++] & 0xFF);
                    items[index].setIconId( iconId );
                    items[index].setIconSelfExplanatory( selfExplanatory );
                    --length;
                    ++index;
                }
            }
            break;

            case 0x24:
            {
                // Timer identifier.
                sc.setTimerId( pdu[posn] );
            }
            break;

            case 0x25:
            {
                // Timer value.
                int secs = 0;
                secs += ((unsigned char) pdu[posn + 0] >> 4) * 3600;
                secs += ((unsigned char) pdu[posn + 0] & 15) * 36000;
                secs += ((unsigned char) pdu[posn + 1] >> 4) * 60;
                secs += ((unsigned char) pdu[posn + 1] & 15) * 600;
                secs += ((unsigned char) pdu[posn + 2] >> 4) * 1;
                secs += ((unsigned char) pdu[posn + 2] & 15) * 10;
                sc.setDuration( secs * 1000 );
            }
            break;

            case 0x2B:
            {
                // Immediate response block.
                sc.setImmediateResponse(true);
            }
            break;

            case 0x2C:
            {
                // DTMF digits for SendDTMF command, GSM 11.14, section 12.44.
                sc.setNumber( decodeEFADNNumber( pdu, posn, length ) );
            }
            break;

            case 0x31:
            {
                // URL for LaunchBrowser command, GSM 11.14, section 12.48.
                sc.setUrl( decodeEFADN( pdu, posn, length ) );
            }
            break;

            case 0x50:
            {
                // Text attribute, ETSI TS 102 223, section 8.72.
                if ( sc.type() == SetupMenu || sc.type() == SelectItem ) {
                    sc.setTitleAttribute( pdu.mid( posn, length ) );
                } else if ( seenTextAttribute ) {
                    sc.setOtherTextAttribute( pdu.mid( posn, length ) );
                } else {
                    sc.setTextAttribute( pdu.mid( posn, length ) );
                    seenTextAttribute = true;
                }
            }
            break;

            case 0x51:
            {
                // Item text attribute list, ETSI TS 102 223, section 8.73.
                int index = 0;
                while ( length >= 4 && index < items.size() ) {
                    items[index].setLabelAttribute( pdu.mid( posn, 4 ) );
                    length -= 4;
                    posn += 4;
                    ++index;
                }
            }
            break;

            default:
            {
                // Don't know what this is, so add it as an extension field.
                sc.addExtensionField( tag, pdu.mid( posn, length ) );
            }
            break;
        }
        posn = newPosn;
        if ( posn >= (uint)pdu.size() ) {
            break;
        }
        readBer( pdu, posn, tag, length );
    }

    sc.setMenuItems( items );
    return sc;
}

// Write a BER length value.  Exported to qsimterminalresponse.cpp and qsimenvelope.cpp.
void _qtopiaphone_writeBerLength( QByteArray& binary, int length )
{
    int size = binary.size();
    if ( length < 128 ) {
        binary.resize( size + 1 );
        binary[size] = (char)length;
    } else if ( length < 256 ) {
        binary.resize( size + 2 );
        binary[size] = (char)0x81;
        binary[size + 1] = (char)length;
    } else {
        binary.resize( size + 3 );
        binary[size] = (char)0x82;
        binary[size + 1] = (char)( length >> 8 );
        binary[size + 2] = (char)length;
    }
}
#define writeBerLength _qtopiaphone_writeBerLength

// Write a string, encoded in the best possible data coding scheme.
// We also need to export this to qsimterminalresponse.cpp.
void _qtopiaphone_writeTextString( QByteArray& binary, const QString& str,
                                   QSimCommand::ToPduOptions options, int tag = 0x8D );
void _qtopiaphone_writeTextString( QByteArray& binary, const QString& str,
                                   QSimCommand::ToPduOptions options, int tag )
{
    if ( str.isNull() ) {
        binary += (char)tag;
        binary += (char)0x00;
        return;
    }
    if ( str.isEmpty() && ( options & QSimCommand::EncodeEmptyStrings ) == 0 ) {
        // Special form for empty strings.
        binary += (char)tag;
        binary += (char)0x01;
        binary += (char)0x00;
        return;
    }
    int schemeMask = ((tag & 0xFF00) >> 8);     // For USSD string output.
    if ( ( options & QSimCommand::UCS2Strings ) == 0 ) {
        QTextCodec *gsm = QAtUtils::codec( "gsm-noloss" );
        if ( gsm->canEncode( str ) ) {
            if ( ( options & QSimCommand::PackedStrings ) != 0 ) {
                // Use the packed 7-bit GSM alphabet to encode the string.
                QByteArray packed;
                int bitCount = 0;
                int bits = 0;
                for ( int u = 0; u < str.length(); u++ ) {
                    unsigned short c = QGsmCodec::twoByteFromUnicode( str[u].unicode() );
                    if ( c >= 256 ) {
                        // Encode a two-byte sequence.
                        for ( int i = 0; i < 7; i++ ) {
                            if ( bitCount == 8 ) {
                                bitCount = 0;
                                packed += (char)bits;
                                bits = 0;
                            }
                            if ( ((1 << (i+8)) & c) != 0 )
                                bits |= (1 << bitCount);
                            ++bitCount;
                        }
                    }
                    for ( int i = 0; i < 7; i++ ) {
                        if ( bitCount == 8 ) {
                            bitCount = 0;
                            packed += (char)bits;
                            bits = 0;
                        }
                        if ( ((1 << i) & c) != 0 )
                            bits |= (1 << bitCount);
                        ++bitCount;
                    }
                }
                if ( bitCount != 0 ) {
                    packed += (char)bits;
                }
                binary += (char)tag;
                writeBerLength( binary, packed.length() + 1 );
                binary += (char)(QSMS_DefaultAlphabet | schemeMask);
                binary += packed;
            } else {
                // Use the unpacked 8-bit GSM alphabet to encode the string.
                QByteArray unpacked = gsm->fromUnicode( str );
                binary += (char)tag;
                writeBerLength( binary, unpacked.length() + 1 );
                binary += (char)(QSMS_8BitAlphabet | schemeMask);
                binary += unpacked;
            }
            return;
        }
    }

    // If we get here, we must use UCS-2 to encode the text string.
    QByteArray ucs2;
    const ushort *data = str.utf16();
    int len = str.length();
    while ( len-- > 0 ) {
        ushort ch = *data++;
        ucs2 += (char)(ch >> 8);
        ucs2 += (char)ch;
    }
    binary += (char)tag;
    writeBerLength( binary, ucs2.length() + 1 );
    binary += (char)(QSMS_UCS2Alphabet | schemeMask);
    binary += ucs2;
}
#define writeTextString _qtopiaphone_writeTextString

// Write an EFADN string as a BER field.  GSM 11.11, section 10.5.1.
// Exported to qsimcontrolevent.cpp.
void _qtopiaphone_writeEFADN( QByteArray& binary, const QString& str,
                              QSimCommand::ToPduOptions options, int tag = 0x85 );
void _qtopiaphone_writeEFADN( QByteArray& binary, const QString& str,
                              QSimCommand::ToPduOptions options, int tag )
{
    // Determine if we can encode the string in the unpacked GSM alphabet encoding.
    if ( ( options & QSimCommand::UCS2Strings ) == 0 ) {
        QTextCodec *gsm = QAtUtils::codec( "gsm-noloss" );
        if ( gsm->canEncode( str ) ) {
            QByteArray unpacked = gsm->fromUnicode( str );
            if ( tag != -1 ) {
                binary += (char)tag;
                writeBerLength( binary, unpacked.length() );
            }
            binary += unpacked;
            return;
        }
    }

    // If we get here, we must use UCS-2 to encode the text string.
    QByteArray ucs2;
    const ushort *data = str.utf16();
    int len = str.length();
    while ( len-- > 0 ) {
        ushort ch = *data++;
        ucs2 += (char)(ch >> 8);
        ucs2 += (char)ch;
    }
    if ( tag != -1 ) {
        binary += (char)tag;
        writeBerLength( binary, ucs2.length() + 1 );
    }
    binary += (char)0x80;
    binary += ucs2;
}
#define writeEFADN _qtopiaphone_writeEFADN

// Write icon details.
static void writeIcon( QByteArray& binary, uint iconId, bool selfExplanatory, bool mandatory )
{
    if ( iconId ) {
        binary += (char)( mandatory ? 0x9E : 0x1E );
        binary += (char)0x02;
        binary += (char)( selfExplanatory ? 0 : 1 );
        binary += (char)iconId;
    }
}

// Write duration information.  Needs to be exported to qsimterminalresponse.cpp.
void _qtopiaphone_writeDuration( QByteArray& data, uint time )
{
    if ( time != 0 ) {
        data += (char)0x84;
        data += (char)0x02;
        if ( ( time % 1000 ) != 0 && time <= 25500 ) {
            // Encode in tenths of a second.
            data += (char)0x02;
            data += (char)(time / 100);
        } else if ( ( time % 60000 ) != 0 && time <= 255000 ) {
            // Encode in seconds.
            data += (char)0x01;
            data += (char)(time / 1000);
        } else if ( time <= 255 * 6000 ) {
            // Encode in minutes.
            data += (char)0x00;
            data += (char)(time / 60000);
        } else {
            // Encode maximum time value.
            data += (char)0x00;
            data += (char)0xFF;
        }
    }
}
#define writeDuration _qtopiaphone_writeDuration

// Write a subaddress, GSM 11.14, section 12.3.  The actual encoding
// is specified in GSM 24.008, Annex E.
static void writeSubAddress( QByteArray& data, const QString& value )
{
    data += (char)0x88;
    writeBerLength( data, value.length() + 2 );
    data += (char)0x80;     // NSAP address type
    data += (char)0x50;     // NSAP address is BCD-encoded IA5 characters
    for ( const QChar &ch : value ) {
        int val = ch.unicode() - 32;
        if ( val > 127 )
            val = 0;
        data += (char)(((val / 10) << 4) + (val % 10));
    }
}

// Extract an extension field named "tag" from "extData" and write it to "data".
// The field will be removed from "extData".  Returns false if no such field.
// Exported to qsimcontrolevent.cpp.
bool _qtopiaphone_extractAndWriteExtField( QByteArray& data, QByteArray& extData, int tag )
{
    uint startposn;
    uint posn = 0;
    uint currentTag, length;
    while ( posn < (uint)( extData.length() ) ) {
        startposn = posn;
        readBer( extData, posn, currentTag, length );
        if ( ( currentTag & 0x7F ) == (uint)( tag & 0x7F ) ) {
            data += extData.mid( startposn, (posn + length) - startposn );
            extData = extData.left( startposn ) + extData.mid( posn + length );
            return true;
        }
        posn += length;
    }
    return false;
}
#define extractAndWriteExtField _qtopiaphone_extractAndWriteExtField

// Write a text attribute field.
static void writeTextAttribute( QByteArray& data, const QByteArray& attr )
{
    if ( !attr.isEmpty() ) {
        data += (char)0xD0;
        writeBerLength( data, attr.length() );
        data += attr;
    }
}

// Write a Timer Id field.
void _qtopiaphone_writeTimerId( QByteArray& data, uint timerId )
{
    data += 0xa4;
    data += 0x01;
    data += (char) timerId;
}
#define writeTimerId _qtopiaphone_writeTimerId

// Write a Timer Value field.
void _qtopiaphone_writeTimerValue( QByteArray& data, uint value )
{
    if (!value)
        return;

    data += 0xa5;
    data += 0x03;
#define TO_BCD(bin) (char) ((((bin) / 10) & 0xf) | (((bin) % 10) << 4))
    data += TO_BCD(value / 3600);
    data += TO_BCD((value / 60) % 60);
    data += TO_BCD(value % 60);
#undef TO_BCD
}
#define writeTimerValue _qtopiaphone_writeTimerValue

// Write a Bearer description field.
static void writeBearerDesc( QByteArray& data, const QByteArray& bearerDesc )
{
    if ( !bearerDesc.isEmpty() ) {
        data += (char)0x35;
        writeBerLength( data, bearerDesc.length() );
        data += bearerDesc;
    }
}

// Write a Network Access Name field as specified in TS 23.003
static void writeApn( QByteArray& data, const QByteArray& apn )
{
    if ( !apn.isEmpty() ) {
        data += (char)0x47;
        writeBerLength( data, apn.length() );
        data += apn;
    }
}

// Write a Buffer size field.
static void writeBufferSize( QByteArray& data, ushort size )
{
    if ( size ) {
        data += 0x39;
        data += 0x02;
        data += (char)(size >> 8);
        data += (char)size;
    }
}

// Write a data length field.
static void writeDataLength( QByteArray& data, uint value )
{
    data += 0x37;
    data += 0x01;
    data += (char)value;
}

// Write a UICC/terminal interface transport level field
static void writeUti( QByteArray& data, const QByteArray& uti )
{
    if ( !uti.isEmpty() ) {
        data += (char)0x3C;
        writeBerLength( data, uti.length() );
        data += uti;
    }
}

// Write the Address information field
static void writeOtherAddress( QByteArray& data, const QByteArray& otherAddress )
{
    if ( !otherAddress.isEmpty() ) {
        data += (char)0x3E;
        writeBerLength( data, otherAddress.length() );
        data += otherAddress;
    }
}
/*!
    \enum QSimCommand::ToPduOptions
    This enum defines additional options to use when encoding SIM commands with QSimCommand::toPdu().

    \value NoPduOptions No additional options.
    \value NoBerWrapper Do not include the outermost BER wrapper around the command parameters.
    \value PackedStrings Encode text strings in the packed 7-bit GSM alphabet instead
                         of the unpacked 8-bit GSM alphabet.  If a text string cannot
                         be encoded with the GSM alphabet, UCS-2 will be used.
    \value UCS2Strings Encode text strings in the UCS-2 alphabet, even if it could
                       otherwise be encoded in the packed 7-bit or unpacked 8-bit
                       GSM alphabet.
    \value EncodeEmptyStrings Encode empty strings in the actual character set, instead
                       of using the shorthand encoding.
*/

/*!
    Encode a SIM command into its raw binary PDU form, as described
    in 3GPP TS 11.14.  The resulting PDU form may be modified by the
    supplied \a options.

    \sa fromPdu()
*/
QByteArray QSimCommand::toPdu( QSimCommand::ToPduOptions options ) const
{
    QByteArray data;

    // Output the command details.
    QSimCommand::Type cmd = type();
    if ( cmd == NoCommand || cmd == Timeout )
        return QByteArray();
    int qualifier = d->qualifier;
    if ( cmd == GetInkey && wantYesNo() ) {
        // Make sure the "wantDigits" bit is unset if using Yes/No.
        qualifier &= ~0x01;
    }
    data += (char)0x81;
    data += (char)0x03;
    data += (char)commandNumber();
    data += (char)cmd;
    data += (char)qualifier;

    // Output the device identities (SIM to Display/Keypad/Earpiece/etc).
    data += (char)0x82;
    data += (char)0x02;
    data += (char)sourceDevice();
    data += (char)destinationDevice();

    // If user feedback suppression is on, then encode empty strings.
    if ( suppressUserFeedback() )
        options = (QSimCommand::ToPduOptions)( options | EncodeEmptyStrings );

    // Add the command parameters.
    QByteArray extData = extensionData();
    switch ( type() ) {
        case DisplayText:
        {
            writeTextString( data, text(), options );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            if ( immediateResponse() ) {
                data += (char)0xAB;
                data += (char)0x00;
            }
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case GetInkey:
        {
            writeTextString( data, text(), options );
            writeIcon( data, iconId(), iconSelfExplanatory(), false );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case GetInput:
        {
            writeTextString( data, text(), options );
            if ( minimumLength() != 0 || maximumLength() != 255 ) {
                data += (char)0x91;
                data += (char)0x02;
                data += (char)minimumLength();
                data += (char)maximumLength();
            }
            if ( !defaultText().isEmpty() ) {
                writeTextString( data, defaultText(), QSimCommand::NoPduOptions, 0x17 );
            }
            writeIcon( data, iconId(), iconSelfExplanatory(), false );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case PlayTone:
        {
            if ( !text().isEmpty() )
                writeEFADN( data, text(), options );
            if ( tone() != ToneNone ) {
                data += (char)0x8E;
                data += (char)0x01;
                data += (char)tone();
            }
            writeDuration( data, duration() );
            writeIcon( data, iconId(), iconSelfExplanatory(), false );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case PollInterval:
        {
            writeDuration( data, duration() );
        }
        break;

        case SetupMenu:
        case SelectItem:
        {
            if ( type() == SetupMenu || !title().isEmpty() )
                writeEFADN( data, title(), options );
            bool hasIcons = false;
            int attrLen = 0;
            bool hasNextActions = false;
            bool selfExplanatory = false;
            if ( menuItems().size() == 0 ) {
                data += (char)0x8F;
                data += (char)0x00;
            }
            for ( QSimMenuItem &item : menuItems() ) {
                QByteArray contents;
                contents += (char)item.identifier();
                writeEFADN( contents, item.label(), options, -1 );
                data += (char)0x8F;
                writeBerLength( data, contents.size() );
                data += contents;
                if ( item.iconId() != 0 )
                    hasIcons = true;
                if ( item.nextAction() != 0 )
                    hasNextActions = true;
                if ( item.iconSelfExplanatory() )
                    selfExplanatory = true;
                attrLen += item.labelAttribute().size();
            }
            if ( hasNextActions ) {
                data += (char)0x18;
                data += (char)menuItems().size();
                for ( const QSimMenuItem &item : menuItems() )
                    data += (char)item.nextAction();
            }
            if ( type() == SelectItem && defaultItem() != 0 ) {
                data += (char)0x90;
                data += (char)0x01;
                data += (char)defaultItem();
            }
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            if ( hasIcons ) {
                data += (char)0x9F;
                data += (char)(menuItems().size() + 1);
                data += (char)(selfExplanatory ? 0x00 : 0x01);
                for ( const QSimMenuItem &item : menuItems() )
                    data += (char)item.iconId();
            }
            writeTextAttribute( data, titleAttribute() );
            if ( attrLen != 0 ) {
                data += (char)0xD1;
                writeBerLength( data, attrLen );
                for ( QSimMenuItem &item : menuItems() ) {
                    data += item.labelAttribute();
                }
            }
        }
        break;

        case SendSMS:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options );
            if ( !number().isEmpty() )
                writeEFADNNumber( data, number() );
            extractAndWriteExtField( data, extData, 0x8B );
            writeIcon( data, iconId(), iconSelfExplanatory(), iconSelfExplanatory() );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SendSS:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options );
            writeEFADNNumber( data, number(), 0x89, 255 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SendUSSD:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), QSimCommand::NoPduOptions );
            if ( ( options & QSimCommand::PackedStrings ) != 0 )
                writeTextString( data, number(), options, 0xF08A );
            else if ( ( options & QSimCommand::UCS2Strings ) != 0 )
                writeTextString( data, number(), options, 0x488A );
            else // 8-Bit USSD
                writeTextString( data, number(), options, 0x448A );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SetupCall:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), QSimCommand::NoPduOptions );
            writeEFADNNumber( data, number() );
            extractAndWriteExtField( data, extData, 0x87 );
            if ( !subAddress().isEmpty() )
                writeSubAddress( data, subAddress() );
            writeDuration( data, duration() );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            if ( !otherText().isEmpty() )
                writeEFADN( data, otherText(), QSimCommand::NoPduOptions );
            writeIcon( data, otherIconId(), otherIconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
            writeTextAttribute( data, otherTextAttribute() );
        }
        break;

        case TimerManagement:
        {
            writeTimerId( data, timerId() );
            writeTimerValue( data, duration() / 1000 );
        }
        break;

        case SetupIdleModeText:
        {
            writeTextString( data, text(), options );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case RunATCommand:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), QSimCommand::NoPduOptions );
            extractAndWriteExtField( data, extData, 0xA8 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SendDTMF:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options );
            writeEFADNNumber( data, number(), 0xAC );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case LaunchBrowser:
        {
            extractAndWriteExtField( data, extData, 0x30 ); // Browser id
            writeEFADN( data, url(), QSimCommand::NoPduOptions, 0x31 );
            data += extData;        // Add all network setup parameters before the text.
            extData = QByteArray();
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), false );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case OpenChannel:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeBearerDesc( data, bearerDesc() );
            writeBufferSize( data, bufferSize() );
            writeApn( data, apn() );
            writeOtherAddress( data, localAddress() );
            if ( !userLogin().isEmpty() )
                writeTextString( data, userLogin(), QSimCommand::NoPduOptions, 0x0D );
            if ( !userPassword().isEmpty() )
                writeTextString( data, userPassword(), QSimCommand::NoPduOptions, 0x0D );
            writeUti( data, uti() );
            writeOtherAddress( data, destAddress() );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case ReceiveData:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeDataLength( data, dataLength() );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SendData:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            data += extData;
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case CloseChannel:
        case GetChannelStatus:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case ServiceSearch:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            extractAndWriteExtField( data, extData, 0xC3 ); // Service search
            extractAndWriteExtField( data, extData, 0xC2 ); // Device filter
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case GetServiceInformation:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            extractAndWriteExtField( data, extData, 0xC4 ); // Attribute information
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case RetrieveMultimediaMessage:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            extractAndWriteExtField( data, extData, 0xEA ); // Multimedia message reference
            extractAndWriteExtField( data, extData, 0x92 ); // MMS reception file
            extractAndWriteExtField( data, extData, 0xEE ); // Content identifier
            extractAndWriteExtField( data, extData, 0xEB ); // Message identifier
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case SubmitMultimediaMessage:
        {
            if ( !text().isEmpty() || ( options & QSimCommand::EncodeEmptyStrings ) != 0 )
                writeEFADN( data, text(), options, 0x05 );
            writeIcon( data, iconId(), iconSelfExplanatory(), true );
            extractAndWriteExtField( data, extData, 0x92 ); // MMS submission file
            extractAndWriteExtField( data, extData, 0xEB ); // Message identifier
            writeTextAttribute( data, textAttribute() );
        }
        break;

        case DisplayMultimediaMessage:
        {
            extractAndWriteExtField( data, extData, 0x92 ); // MMS submission file
            extractAndWriteExtField( data, extData, 0xEB ); // Message identifier
            if ( immediateResponse() ) {
                data += (char)0xAB;
                data += (char)0x00;
            }
        }
        break;

        case LanguageNotification:
        {
            if ( !language().isEmpty() && language().length() == 2 ) {
                data += (char)0xAD;
                data += (char)0x02;
                data += language().toUtf8();
            }
        }
        break;

        default: break;
    }

    // Add any remaining extension data that is specified.
    data += extData;

    // Add the "Proactive SIM" BER command wrapper if required.
    if ( ( options & NoBerWrapper ) == 0 ) {
        QByteArray ber;
        ber += (char)0xD0;
        writeBerLength( ber, data.size() );
        ber += data;
        return ber;
    } else {
        return data;
    }
}

/*!
    Returns the qualifier byte from the header of the SIM command.  The qualifier byte
    contains flags that modify the behavior of the command from its default.  The default
    value is zero.

    Other functions such as hasHelp(), wantDigits(), highPriority(), etc provide a
    more convenient method to access the bits within the qualifier byte.

    Applies to: all commands.

    \sa setQualifier()
*/
int QSimCommand::qualifier() const
{
    return d->qualifier;
}

/*!
    Sets the qualifier byte in the header of the SIM command to \a value.
    The qualifier byte contains flags that modify the behavior of the command
    from its default.

    Other functions such as setHasHelp(), setWantDigits(), setHighPriority(), etc provide a
    more convenient method to set the bits within the qualifier byte.

    Applies to: all commands.

    \sa qualifier()
*/
void QSimCommand::setQualifier( int value )
{
    d->qualifier = value;
}

/*!
    Returns the extension data for this SIM command.  The extension data consists of
    zero or more BER tag-length-value fields for fields that are not otherwise handled
    by some other method in this class.

    Applies to: all commands.

    \sa setExtensionData(), extensionField()
*/
QByteArray QSimCommand::extensionData() const
{
    return d->extensionData;
}

/*!
    Sets the extension data for this SIM command to \a value.  The extension
    data consists of zero or more BER tag-length-value fields for fields that are
    not otherwise handled by some other method in this class.

    Applies to: all commands.

    \sa extensionData(), addExtensionField()
*/
void QSimCommand::setExtensionData( QByteArray value )
{
    d->extensionData = value;
}

/*!
    Returns the contents of an extension field.  The \a tag is an 8-bit value,
    from 3GPP TS 11.14, that specifies the particular field the caller is
    interested in.  The most significant bit of \a tag is ignored when searching
    for the field, as it contains the "must comprehend" status from 3GPP TS 11.14,
    and is not important for locating the desired field.

    This is a simpler method than extensionData() for accessing values within
    the extension data.  As an example, the following code extracts the
    "SMS TPDU" information from a \c SendSMS SIM command:

    \code
    QSimCommand cmd;
    ...
    QByteArray tpdu = cmd.extensionField(0x8B);
    \endcode

    Applies to: all commands.

    \sa addExtensionField()
*/
QByteArray QSimCommand::extensionField( int tag ) const
{
    uint posn = 0;
    uint currentTag, length;
    while ( posn < (uint)( d->extensionData.length() ) ) {
        readBer( d->extensionData, posn, currentTag, length );
        if ( ( currentTag & 0x7F ) == (uint)( tag & 0x7F ) )
            return d->extensionData.mid( posn, length );
        posn += length;
    }
    return QByteArray();
}

/*!
    Adds an extension field to the data from extensionData().  The field will have
    the specified \a tag and contents given by \a value.

    Applies to: all commands.

    \sa extensionField()
*/
void QSimCommand::addExtensionField( int tag, const QByteArray& value )
{
    d->extensionData += (char)tag;
    writeBerLength( d->extensionData, value.size() );
    d->extensionData += value;
}

// Get a writable copy of the shared data storage.
QSimCommandPrivate *QSimCommand::dwrite()
{
    // If we are the only user of the private object, return it as-is.
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
    if ( d->ref.loadRelaxed() == 1 )
#else
    if ( d->ref.load() == 1 )
#endif
        return d;

    // Create a new private object and copy the current contents into it.
    QSimCommandPrivate *newd = new QSimCommandPrivate(d);
    if ( !d->ref.deref() )
        delete d;
    d = newd;
    return newd;
}

#define EMS_ALIGNMENT       0x03
#define EMS_LEFT            0x00
#define EMS_CENTER          0x01
#define EMS_RIGHT           0x02
#define EMS_NO_ALIGN        0x03
#define EMS_FONT_SIZE       0x0C
#define EMS_FONT_LARGE      0x04
#define EMS_FONT_SMALL      0x08
#define EMS_BOLD            0x10
#define EMS_ITALIC          0x20
#define EMS_UNDERLINE       0x40
#define EMS_STRIKE          0x80
#define EMS_NO_FORMAT       ((-1 << 8) | EMS_NO_ALIGN)

// Map an EMS color code into a HTML color value.
static QString mapEmsColor(int color)
{
    static const char * const colors[16] = {
        "#000000",      // Black
        "#808080",      // Dark Grey
        "#800000",      // Dark Red
        "#808000",      // Dark Yellow
        "#008000",      // Dark Green
        "#008080",      // Dark Cyan
        "#000080",      // Dark Blue
        "#800080",      // Dark Magenta
        "#C0C0C0",      // Grey
        "#FFFFFF",      // White
        "#FF0000",      // Bright Red
        "#FFFF00",      // Bright Yellow
        "#00FF00",      // Bright Green
        "#00FFFF",      // Bright Cyan
        "#0000FF",      // Bright Blue
        "#FF00FF"       // Bright Magenta
    };
    return QString::fromLatin1(colors[color & 0x0F]);
}

// Convert a text string into HTML, using the specified EMS attributes.
static QString textToHtml(const QString& text, const QByteArray& attrs)
{
    if (text.isEmpty())
        return text;

    // Scan the attribute array and build up the formatting
    // flags for every character in the text string.
    int len = text.length();
    QVarLengthArray<int> formats(len);
    for (int posn = 0; posn < len; ++posn) {
        formats[posn] = EMS_NO_FORMAT;
    }
    int bgcolor = -1;
    for (int index = 0; (index + 3) <= attrs.size(); index += 4) {
        // Extract the next formatting block.
        int start = attrs[index] & 0xFF;
        int end;
        if (attrs[index + 1])
            end = start + (attrs[index + 1] & 0xFF);
        else
            end = len;
        int format = attrs[index + 2] & 0xFF;
        int colors;
        if ((index + 4) <= attrs.size()) {    // This byte is optional.
            colors = attrs[index + 3] & 0xFF;
            if (bgcolor == -1)
                bgcolor = ((colors >> 4) & 0x0F);
        } else {
            colors = -1;
        }

        // Apply the formatting to all of the positions that need it.
        while (start < end && start < len) {
            formats[start] = (colors << 8) | format;
            ++start;
        }
    }

    // Convert the text string according to the instructions.
    QString result;
    int posn = 0;
    int format = EMS_NO_FORMAT;
    int newFormat;
    if (bgcolor != -1) {
        // Set the overall background color for the entire page based
        // on the background color for the first attribute block.
        // If there are no attribute blocks, or only one attribute block
        // with three bytes, then leave the background color as the
        // system default.
        result += QLatin1String("<body bgcolor=\"");
        result += mapEmsColor(bgcolor);
        result += QLatin1String("\">");
    }
    while (posn < len) {
        // Copy characters that have the same format as the previous one.
        while (posn < len && formats[posn] == format) {
            QChar ch = text[posn];
            if (ch.unicode() == '\n') {
                result += QLatin1String("<br>");
            } else if (ch.unicode() == '\r') {
                result += QLatin1String("<br>");
                if ((posn + 1) < len && text[posn + 1] == QChar('\n'))
                    ++posn;
            } else if (ch.unicode() == '<') {
                result += QLatin1String("&lt;");
            } else if (ch.unicode() == '>') {
                result += QLatin1String("&gt;");
            } else if (ch.unicode() == '&') {
                result += QLatin1String("&amp;");
            } else {
                result += ch;
            }
            ++posn;
        }

        // Adjust the format to match the new requirements.
        if (posn < len)
            newFormat = formats[posn];
        else
            newFormat = EMS_NO_FORMAT;
        int oldColor = (format >> 8);
        int newColor = (newFormat >> 8);
        if ((format & EMS_STRIKE) != 0)
            result += QLatin1String("</s>");
        if ((format & EMS_UNDERLINE) != 0)
            result += QLatin1String("</u>");
        if ((format & EMS_ITALIC) != 0)
            result += QLatin1String("</i>");
        if ((format & EMS_BOLD) != 0)
            result += QLatin1String("</b>");
        if ((format & EMS_FONT_SIZE) == EMS_FONT_SMALL)
            result += QLatin1String("</small>");
        else if ((format & EMS_FONT_SIZE) == EMS_FONT_LARGE)
            result += QLatin1String("</big>");
        if (oldColor != -1)
            result += QLatin1String("</font>");
        if ((format & EMS_ALIGNMENT) != (newFormat & EMS_ALIGNMENT)) {
            if ((format & EMS_ALIGNMENT) == EMS_LEFT)
                result += QLatin1String("</div>");
            else if ((format & EMS_ALIGNMENT) == EMS_RIGHT)
                result += QLatin1String("</div>");
            else if ((format & EMS_ALIGNMENT) == EMS_CENTER)
                result += QLatin1String("</center>");
            if ((newFormat & EMS_ALIGNMENT) == EMS_LEFT)
                result += QLatin1String("<div align=\"left\">");
            else if ((newFormat & EMS_ALIGNMENT) == EMS_RIGHT)
                result += QLatin1String("<div align=\"right\">");
            else if ((newFormat & EMS_ALIGNMENT) == EMS_CENTER)
                result += QLatin1String("<center>");
        }
        if (newColor != -1) {
            result += QLatin1String("<font");
            int back = ((newColor >> 4) & 0x0F);
            int fore = (newColor & 0x0F);
            if (back != bgcolor) {
                result += QLatin1String(" style=\"background-color: ");
                result += mapEmsColor(back);
                result += QLatin1String("\"");
            }
            result += QLatin1String(" color=\"");
            result += mapEmsColor(fore);
            result += QLatin1String("\">");
        }
        if ((newFormat & EMS_FONT_SIZE) == EMS_FONT_SMALL)
            result += QLatin1String("<small>");
        else if ((newFormat & EMS_FONT_SIZE) == EMS_FONT_LARGE)
            result += QLatin1String("<big>");
        if ((newFormat & EMS_BOLD) != 0)
            result += QLatin1String("<b>");
        if ((newFormat & EMS_ITALIC) != 0)
            result += QLatin1String("<i>");
        if ((newFormat & EMS_UNDERLINE) != 0)
            result += QLatin1String("<u>");
        if ((newFormat & EMS_STRIKE) != 0)
            result += QLatin1String("<s>");

        // Switch to the new format.
        format = newFormat;
    }
    if (bgcolor != -1) {
        // Terminate the outer background color declaration.
        result += QLatin1String("</body>");
    }

    return result;
}
