Writing a UPS Dialog Creator

Introduction

Dialog creators are ECom plug-ins that device creators can write to generate the dialogs containing prompts for phone users.

The plug-in has an API that consists of two asynchronous functions:

  • The PrepareDialog() function. This function is called first by the UPS server. It enables the dialog creator to query other system servers such as AppArc or the SIS registry to retrieve additional information to display in the dialog. For example it might query the SIS registry using the client application’s secure id.

  • The DisplayDialog() function. This function is called second by the UPS server. It displays the prompt, most commonly through the notification framework. This function also returns the option selected by the user and, if applicable, the fingerprint for a new decision record.

The UPS displays only one prompt at a time so it is possible for there to be a delay between calling the function to prepare the dialog and the function to display the dialog. It is also possible for other dialogs to be displayed between the dialog being prepared and its being displayed.

Both PrepareDialog() and DisplayDialog() are asynchronous and must support cancellation through CActive::Cancel. If either function is cancelled, the dialog creator instance is destroyed without calling further methods.

The work split between PrepareDialog() and DisplayDialog() described above is a recommendation, and some of the functionality could be implemented directly in the notifier implementation.

In the example given in this document, the functionality for dialogs is implemented separately from the functionality for policy evaluators. This allows multiple policy evaluators to share common UI code. However, it is possible to deliver policy evaluator and dialog creator plug-ins in the same DLL.

Procedure

Dialog creators implement the CDialogCreator interface. This section shows how to implement the functions that prepare and display the user prompt.

  1. Prepare a dialog using the PrepareDialog() function.

  2. Display the dialog using the DisplayDialog() function.

The Dialog Creator plug-in creates a dialog prompt in cases where no previous decision is found in the decision database.

Preparing the dialog

The parameters to PrepareDialog() are mostly const pointers and references to the data that has already been generated by the UPS or policy evaluator.

The following table describes the parameters for the PrepareDialog() function:

Parameter Description

aRequest

Data that the system server provides to the UPS, including the ClientSid, ServerSid and ServiceId.

aPolicy

Details of the policy for the service.

aFingerprints

A pointer to the fingerprints array generated by the policy evaluator. (If the user selects "Always" or “Never”, the dialog creator returns a pointer to an existing fingerprint object instead of re-generating the fingerprint.)

aClientEntity

Details of client entity (will be null if the client process is not an execution host or the policy evaluator does not support client entities).

aEvalPrivateData

Opaque data, which can be any other information for use in the fingerprint or to be displayed in the dialog.

aStatus

The request status used to contain completion information for the function.

Note:
The UPS does not allow any responses to be returned to the system server except those defined in the UPS policy file. If the dialog creator returns an option that was not specified in the policy, the request is rejected and EUpsDecNo is returned.

Your implementation can assume that the pointers remain valid until after DisplayDialog() has been called.

The PrepareDialog() function sets values.

The DoPrepareDialogL() function prepares the data for the prompt. The DoPrepareDialogL() function is called through a switch statement, which can be seen in the full code sample at the end of this.

Displaying the dialog

The DisplayDialog() function displays the dialog created by PrepareDialog(). On completion, it sets the option selected by the user and the fingerprint value if “Always” or “Never” was selected. Usually, dialogs are displayed using the notifier framework, but device creators are free to use other mechanisms.

On completion iOptionSelected must be set to the option selected by the user: Yes, No, Session, Always or Never.

  • If the user selects “Always” or “Never”, iFingerprint must be set to point to the fingerprint to use for the new decision record. Usually, this would just point to one of the fingerprints passed to PrepareDialog(), but generating a new fingerprint is also permitted.

  • If the decision record already exists and DisplayDialog() modifies the value of aEvaluatorInfo, the record is updated with the new value.

In the example at the end of this section, the DisplayDialog() function sets values. The DoDisplayDialogL() function calls the notifier framework to display the dialogs. The DoDisplayDialogL() function is called through a switch statement, which can be seen in the full code sample at the end of this section:

Example

The following code shows an example of a full implementation of the dialog creator file:

#include "refdialogcreator.h"
#include <ecom/implementationproxy.h>
#include <apaid.h>
#include <apgcli.h>
#include <ups/promptrequest.h>
#include <swi/sisregistrypackage.h>
#include <swi/sisregistrysession.h>
#include <scs/nullstream.h>
#include <s32mem.h>

static const TUint KRefDialogCreatorImplementationId = 0x10283694;

static const TUint KRefNotifierImplementationId = 0x1028369B;

CDialogCreator* CRefDialogCreator::CreateDialogCreatorL()
/**
Factory method that instantiates a new dialog creator EComplug-in.

@return A pointer to the new reference dialog creator object.
*/
    {
    CRefDialogCreator* self = new (ELeave)CRefDialogCreator();
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

static const TImplementationProxy ImplementationTable[] = 
    {
    IMPLEMENTATION_PROXY_ENTRY(KRefDialogCreatorImplementationId, CRefDialogCreator::CreateDialogCreatorL)
    };

EXPORT_C const TImplementationProxy* ImplementationGroupProxy(TInt& aTableCount)
/**
Standard EComfactory
*/
    {
    aTableCount = sizeof(ImplementationTable) / sizeof(TImplementationProxy);
    return ImplementationTable;
    }    
    

CRefDialogCreator::CRefDialogCreator() 
/**
Constructor
*/
    : CDialogCreator(), iPromptResult(), iPromptResultPckg(iPromptResult), iState(EIdle)
    {
    CActiveScheduler::Add(this);    
    }
    
CRefDialogCreator::~CRefDialogCreator()
/**
Destructor
*/
    {
    Deque();
    delete iPromptData;
    iPromptDataDes.Close();
    iNotifier.Close();
    }

void CRefDialogCreator::ConstructL()
/**
Second phase constructor
*/
    {
    User::LeaveIfError(iNotifier.Connect());
    }

void CRefDialogCreator::DoCancel()
    {
    if (iState == EProcessResult)
        {
        iNotifier.CancelNotifier(TUid::Uid(KRefNotifierImplementationId));
        }
    if (iClientStatus)
        {
        User::RequestComplete(iClientStatus, KErrCancel);
        }
    }
    
TInt CRefDialogCreator::RunError(TInt aError)
    {
    if (iClientStatus)
        {
        User::RequestComplete(iClientStatus, aError);
        }
    return KErrNone;
    }

void CRefDialogCreator::RunL()
    {
    User::LeaveIfError(iStatus.Int());
    switch (iState)
        {
        case EPrepareDialog:
            DoPrepareDialogL();
            break;
        case EDisplayDialog:
            DoDisplayDialogL();
            break;
        case EProcessResult:
            DoProcessResultL();
            break;
        default:
            ASSERT(EFalse);            
        }
    }
    
void CRefDialogCreator::DoPrepareDialogL()
    {
    iPromptData = CPromptData::NewL();
    
    // Only one state at the moment but more should be
    // added for long running operators e.g. querying the SIS registry
    // or resolving the client entity.
    ResolveClientNameL(iRequest->ClientSid());
    
    // Get the vendor name for the client process
    ResolveVendorNameL(iRequest->ClientVid());
    
    // Server / Service localized names generated in notifier plug-in. 
    iPromptData->iServerSid = iRequest->ServerSid();
    iPromptData->iServiceId = iRequest->ServiceId();
    
    // Different dialog text is displayed depending on whether the client application
    // is signed.
    // N.B. Protected SID is assumed to be signed or included at ROM build.
    if (iRequest->IsClientSidProtected()) iPromptData->iFlags |= ETrustedClient;
    
    // Use the options specified by the policy
    iPromptData->iOptions = iPolicy->Options();
    
    // Add the descriptions of the fingerprints. This could be used
    // to allow the user to grant access to all destinations 
    // or a single destination.
    TInt count = iFingerprints->Count();
    for (TInt i = 0; i < count; ++i)
        {
        HBufC* description = (*iFingerprints)[i]->Description().AllocLC();
        iPromptData->iDescriptions.AppendL(description);
        CleanupStack::Pop(description);
        }
    
    User::RequestComplete(iClientStatus, KErrNone);
    // DisplayDialog is invoked by the UPS, this just verifies 
    // that PrepareDialog was called first.
    iState = EDisplayDialog;
    }
    
void CRefDialogCreator::DoDisplayDialogL()
/**
Uses the notifier framework to display the dialog.
*/
    {
    // Externalize the prompt data to a descriptor
    RNullWriteStream ns;
    ns << *iPromptData;
    ns.CommitL();
    iPromptDataDes.CreateL(ns.BytesWritten());
    RDesWriteStream ws;    
    ws.Open(iPromptDataDes);
    ws << *iPromptData;
    ws.CommitL();    
    iNotifier.StartNotifierAndGetResponse(iStatus, TUid::Uid(KRefNotifierImplementationId),
        iPromptDataDes, iPromptResultPckg);
    SetActive();
    iState = EProcessResult;
    }
    
void CRefDialogCreator::DoProcessResultL()
/**
Processes the result returned by the notifier.
*/
    {
    if (iPromptResult.iSelected == CPolicy::EAlways ||
        iPromptResult.iSelected == CPolicy::ENever)
        {
        // The Always or Never option was selected so return the fingerprint 
        // for the new decision record.
        // 
        // In this implementation a copy of the original fingerprint is returned. However,
        // it is permitted to return a different fingerprint e.g. a modifier description.        
        if (iPromptResult.iDestination >= 0 && iPromptResult.iDestination < iFingerprints->Count())        
            {
            *iFingerprint = (*iFingerprints)[iPromptResult.iDestination];
            }
        else
            {
            ASSERT(EFalse);    // should never happen, unless the notifier has errors.
            }
        }            
    *iOptionSelected =    iPromptResult.iSelected;
    iState = EIdle;
    User::RequestComplete(iClientStatus, KErrNone);        
    }

void CRefDialogCreator::ResolveVendorNameL(const TVendorId& aVid)
/**
Looks up the localized vendor name for the client process and writes
this to iPromptData->iVendorName.

Typically, this would be resolved from the SIS registry or a lookup table.

@param aVid    The vendor id of the client process.
*/
    {
    if (iPromptData->iVendorName.Length() != 0)
        {
        // already obtained vendor name from SIS registry
        return;
        }
        
    if (aVid.iId == 0x70000001)
        {
        _LIT(KSymbian, "XYZ Vendor");
        iPromptData->iVendorName.Create(KSymbian);
        }
    else 
        {
        _LIT(KUnknown, "Unknown vendor");
        iPromptData->iVendorName.Create(KUnknown);
        }
    }
    
void CRefDialogCreator::ResolveClientNameL(const TSecureId& aSid)
/**
Generates a human readable name for the client process. In order of 
preference the following data is returned

- The AppArc caption name.
- The localized package name that owns this SID.
- A value from a lookup table.
- The filename for the client process executable.

@param aSid    The secure id of the client process.
*/
    {
    TBool found = EFalse;
    
    // Although the client name from AppArc takes precedance the SIS
    // registry is always invoked in order to retrieve the vendor name
    found |= ResolveClientNameFromSisRegistryL(aSid);
    found |= ResolveClientNameFromAppArcL(aSid);
            
    // A lookup that maps secure-ids to application names could
    // be used here.

    // Fall back to the filename of the client process
    // The original thread may have exited so the process handle is used instead.
    // because the client-side object e.g. RSocket may be shared between threads.

    // If the process has exited then it's o.k. to leave.
    if (! found)
        {            
        RProcess clientProcess;
        User::LeaveIfError(clientProcess.Open(iRequest->ClientProcessId()));
        CleanupClosePushL(clientProcess);
        iPromptData->iClientName.Create(clientProcess.FileName());        
        CleanupStack::PopAndDestroy(&clientProcess); 
        }
    }

TBool CRefDialogCreator::ResolveClientNameFromAppArcL(const TSecureId& aSid)
/**
Gets the caption name for the application from AppArc (if available).

@param    aSid    The secure id of the client process.
@return            ETrue if a match was found in apparc; otherwise, EFalse is returned.
*/
    {
    TBool found(EFalse);
        
    RApaLsSession apa;
    CleanupClosePushL(apa);    
    TInt err = apa.Connect();
    if (err == KErrNone)
        {        
        TApaAppInfo* info = new(ELeave) TApaAppInfo();
        CleanupStack::PushL(info);
        
        err = apa.GetAppInfo(*info, TUid::Uid(aSid));
        if (err == KErrNone)
            {
            iPromptData->iClientName.Close();
            iPromptData->iClientName.Create(info->iCaption);
            found = ETrue;
            }
        else if (err != KErrNotFound)
            {
            User::Leave(err);
            }    
        CleanupStack::PopAndDestroy(info); 
        }
    else if (err != KErrNotFound)
        {
        // If the connection to apparc failed with KErrNotFound
        // then the error is ignored becase we assume the dialog
        // creator was invoked from text-shell
        User::Leave(err);
        }
    CleanupStack::PopAndDestroy(&apa);
    return found;
    }
    
TBool CRefDialogCreator::ResolveClientNameFromSisRegistryL(const TSecureId& aSid)
/**
Retrieves the client and vendor information from the SIS registry.
@param aSid        The secure-id of the client application to lookup in the registry.
@return            ETrue, if the lookup was successful; otherwise, EFalse is returned.
*/
    {
    TBool found(EFalse);
    Swi::RSisRegistrySession r;
    User::LeaveIfError(r.Connect());
    CleanupClosePushL(r);
    
    Swi::CSisRegistryPackage* p(0);
    TRAPD(err, p = r.SidToPackageL(TUid::Uid(aSid.iId)));
    if (err == KErrNone)
        {
        iPromptData->iClientName.Create(p->Name());
        iPromptData->iVendorName.Create(p->Vendor());
        found = ETrue;
        delete p;
        }
    CleanupStack::PopAndDestroy(&r);
    return found;
    }

// From CDialogCreator
void CRefDialogCreator::PrepareDialog(
    const UserPromptService::CPromptRequest& aRequest, const CPolicy& aPolicy,            
    const RPointerArray<CFingerprint>& aFingerprints, const CClientEntity* aClientEntity,
    const TAny* aEvalPrivateData, TRequestStatus& aStatus)
    {
    aStatus = KRequestPending;
    iClientStatus = &aStatus;
    
    iRequest = &aRequest;
    iPolicy = &aPolicy;
    iFingerprints = &aFingerprints;
    iEvalPrivateData = aEvalPrivateData;
    (void) aClientEntity;

    // Kick off dialog creator state machine
    iState = EPrepareDialog;
    iStatus = KRequestPending;
    TRequestStatus* status = &iStatus;
    SetActive();
    User::RequestComplete(status, KErrNone);
    }
    
void CRefDialogCreator::DisplayDialog(CPolicy::TOptions& aOptions, const CFingerprint*& aFingerprint,
        TUint& aEvaluatorInfo, TRequestStatus& aStatus)
    {    
    aStatus = KRequestPending;
    iClientStatus = &aStatus;
    
    iOptionSelected = &aOptions;
    iFingerprint = &aFingerprint;
    aFingerprint = 0;
    iEvaluatorInfo = &aEvaluatorInfo;
    iClientStatus = &aStatus;
    
    // Start state machine
    ASSERT(iState == EDisplayDialog); // PrepareDialog should have been called first
    iStatus = KRequestPending;
    TRequestStatus* status = &iStatus;
    SetActive();
    User::RequestComplete(status, KErrNone);
    }