Application Engines

Some of the key applications in Series 60, such as Contacts and Calendar, have server-based engines that provide APIs to allow other applications to access their data. In the majority of cases such applications will also provide switchable views to provide direct functionality to other applications. If a view suitable for your needs is not available or you simply want to access some of the data held, then you can use the engine API to get access to the data you require.

Log Engine

The log engine records events such as telephone calls, faxes, SMS messages and emails in a database and is normally accessed by the log viewer application. The log engine API provides other applications with access to the log engine. Entries in the log engine can be read, filtered, added and deleted. It is also possible for new event types to be added.

Reading Log Entries

The log engine is implemented as a server. In order to read entries, you need to make a connection to the engine using CLogClient , which allows views of events in the log engine to be created. Creation of a view is required to filter events in the log, to navigate through the list of events and to access an event at a particular position in the log.

The API provides three classes to generate views on the log engine events. Most useful are CLogViewEvent , which generates a filtered view of events, and CLogViewRecent , which gives a view containing recent events in the event log.

To use CLogViewEvent , a filter must be created using CLogFilter to specify which events should appear in the view. Each property of the filter has a getter and setter function ”the most important filter property being the event type. You can set a filter's event type using CLogFilter::SetEventType(TUid aType) , where aType is the UID associated with the event type of interest. Commonly used UID s include KLogCallEventTypeUid for voice calls and KLogShortMessageEventTypeUid for SMSs. To find a filter's event type use CLogFilter::EventType() .

A filter can contain a contact, a number, a remote party, the duration, a duration type and a direction. A string such as "incoming" or "outgoing" is used to express the direction of an event, and this can be found by passing one of the resource IDs: R_LOG_DIR_IN , R_LOG_DIR_OUT , R_LOG_DIR_IN_ALT , R_LOG_DIR_OUT_ALT , R_LOG_DIR_FETCHED or R_LOG_DIR_MISSED to CLogClient::GetString() .

 TBuf<64> directionStr; iLogClient.GetString(directionStr, R_LOG_DIR_IN); ilogEvent->SetDirection(directionStr); 

Once the filter has been created, it can be employed to generate a view of the events in the log engine using CLogViewEvent::SetFilterL(const CLogFilter& aFilter, TRequestStatus& aStatus) . Note that this function is asynchronous, as creating the view can take some time. If there are no events in the view, the function returns EFalse , if there are events, the asynchronous request is issued and the function returns ETRue .

 TBool eventsInView = iLogView->SetFilterL(*iLogFilter, iStatus); ... if (eventsInView) { // if there are events in the filtered view // the asynchronous request has been issued SetActive(); } else { ... } 

CLogViewEvent is derived from CLogView , which provides the following functions to navigate the events in a view: FirstL(TRequestStatus& aStatus) , LastL(TRequestStatus& aStatus) , NextL(TRequestStatus& aStatus) and PreviousL(TRequestStatus& aStatus) .

 if (iLogView->NextL(iStatus)) { // if there is another entry, issue the request // to move to the next entry. SetActive(); } 

The class CLogEvent encapsulates the details of an event in the log engine. Each event is given a unique ID when it is added, and it can be accessed using CLogEvent::Id() . Each CLogEvent attribute has a getter and setter function.

 CLogEvent* logEvent = CLogEvent::NewL(); logEvent->SetEventType(KLogCallEventTypeUid); logEvent->SetDurationType(KLogDurationValid); ... logEvent->SetDirection(directionStr); ... logEvent->SetDuration(duration); ... logEvent->SetNumber(number); ... logEvent->SetContact(contactId); ... logEvent->SetTime(time); void CCallArray::AddEntryL(const CLogEvent& aEvent) { ... TTimeIntervalSeconds timeInSeconds = aEvent.Duration(); ... entryParameters.iDuration = timeInSeconds.Int(); entryParameters.iContactId = aEvent.Contact(); const TDesC& number = aEvent.Number(); ... } 

It is possible to add your own event type(s) to the log engine. A new event type is defined with CLogEventType, then registered with the log engine using CLogClient::AddEventType() . In addition to the standard information stored for an event, CLogEvent provides the means to store and link your own data. Each event can have a link associated with it using CLogEvent::SetLink() and can be accessed using CLogEvent::Link() . The link can be used to relate the event to an entity in another application, such as the message ID for emails. An event can also have data associated with it; the data is stored as a heap descriptor, allocated by CLogEvent::SetDataL() and accessed using CLogEvent::Data() .

To add an event to the log engine, create a CLogEvent object and add it using CLogClient::AddEvent(CLogEvent& aEvent, TRequestStatus& aStatus). Ensure the CLogEvent object remains in existence and valid until the request completes.

 CLogEvent* logEvent = CLogEvent::NewL(); logEvent->SetEventType(KLogCallEventTypeUid); logEvent->SetDurationType(KLogDurationValid); ... iEventArray.Append(logEvent); ... // Issue a request to add the next event to the log iLogClient.AddEvent(*(iEventArray[iEntryAdded]), iStatus); SetActive(); 

To delete an event from the log engine, use CLogClient::DeleteEvent(TLogId aId, TRequestStatus& aStatus).

Camera APIs

Series 60 1.x and 2.x provides support for devices with a built-in camera, with access to the camera hardware being controlled by the Camera Server. Only one client may use the camera hardware at any given time, so if you try to connect to the Camera Server when it is already in use, an error value is returned. The Series 60 1.x Camera Server is fairly simple, it supports taking pictures of two fixed types: either 24-bit 640 by 480 pixel images or 12-bit 160 by 120 pixel images ”Series 60 2.x expands on the options available. The simplicity of the API has not proven an obstacle to the development of a large range of applications that use the Camera Server, such as video recording. The accompanying example applications, FilmReel and FilmReel2 for Series 60 2.x, take a sequence of low-resolution pictures, which are aggregated in a larger image that is saved to the Photo Album.

Using Camera APIs with an Emulator

To make the Camera Server functional on a Series 60 1.x emulator, you need to copy two .mbm files into the root of the emulator's c: drive. A .zip archive containing suitable files is available from http://www.forum.nokia.com, or as part of the online materials for this book, as described in the Preface.

Two image files ( valo_vga.mbm and valo_qqvga.mbm ) are provided to emulate high-resolution and low-resolution images from the camera. Copy both files to the root of the emulator's c: drive ”this is located in one of the following directories in the root of your SDK, as appropriate to your IDE and SDK configuration:

For \Epoc32\wins\c\

”for Visual C++

For \Epoc32\winsb\c\

”for C++Builder

For \Epoc32\winscw\c\

”for CodeWarrior



Instead of real images, a static image from one of the .mbm files will be returned to the application using the Camera Server, when running on an emulator. Note that for the example application, FilmReel , to work correctly on the emulator, it is essential that the image files are in the correct location.

For Series 60 2.x SDKs, the Camera Server captures an image of a small box, which slowly moves across the screen, as can be seen in the standard Camera application's viewfinder. The two images for Series 60 1.x SDKs are not required when using the APIs on a Series 60 2.x SDK. Instead, the camera API captures a gray image with the targeting square in the position at the time of image capture. The image is then available to the Media Gallery application.

Camera Server

Before you can take a picture with the camera, you need to make a connection to the Camera Server and then turn the camera on. The code used below is taken from the file CameraManager.cpp .

The client-side handle to the Camera Server is of type RCameraServ , and connecting to it is performed by simply calling Connect() :

 User ::LeaveIfError(iCameraServer.Connect()); 

Note that this code will leave if you cannot access the camera hardware. This might be due to another application already using it, or even to the absence of camera hardware on the device.


You then need to turn the camera on. The Camera Server provides an asynchronous function, TurnCameraOn() , as turning the camera on can take quite some time. Usually a request to turn on the camera would be encapsulated within an active object:

 iCameraServer.TurnCameraOn(iStatus); SetActive(); 

When the camera is no longer required, it can be turned off using TurnCameraOff() and the connection to the Camera Server terminated using Close() :

 iCameraServer.TurnCameraOff(); iCameraServer.Close(); 

Note that the camera will also be turned off automatically after a few minutes of inactivity. There will be no notification of this behavior, so if the camera has been turned off and you try to take a picture, then an error is returned in the trequestStatus of the calling GetImage() function, as described next. You must turn the camera back on before trying to retake the picture.

Taking a Picture

The main point of using a camera is to take pictures; this is achieved using the asynchronous function GetImage() :

 void GetImage(TRequestStatus& iStatus, CFbsBitmap& aBitmap); 

In order to use GetImage() , you need to pass in references to a trequestStatus and a bitmap object. Do not worry about setting the correct width, height, or bit depth for the bitmap object, as the Camera Server does this automatically. Usually this request to take a picture would be encapsulated within an active object:

 iCameraServer.GetImage(iStatus, aBitmap); SetActive(); 

When the request completes, the value of iStatus will indicate if a picture was successfully taken.

Settings

Two camera settings can be changed: the image quality and the lighting conditions. Starting with the latter, the Camera Server has two lighting states: normal and night. The night setting should be used when the overall lighting is dim or dark, but it can also sometimes give better results in bright conditions. To change the lighting settings use the logically named SetLightingConditions() , which takes a parameter of either RCameraServ::ELightingNormal or RCameraServ::ELightingNight .

 iCameraServer.SetLightingConditions(RCameraServ::ELightingNormal); 

The other setting you can change is the image quality. The camera API supports two image-quality settings: high and low. To change the image quality use SetImageQuality() with a value of either RCameraServ::EQualityHigh or RCameraServ::EQualityLow . High-quality images are 640 by 480 pixels with 16 million colors, and low-quality images are 160 by 120 pixels with 4096 colors.

 iCameraServer.SetImageQuality(RCameraServ::EQualityHigh); 

A high-quality image is 32 times the size of a low-quality image, and this might be an issue, especially if numerous images are being stored.

The speed with which pictures are taken depends on the image quality and the lighting conditions, with low-quality images being significantly quicker to take than high-quality images. To implement a viewfinder, create a timed loop to take and display pictures using the low-quality image mode. This will give less image flicker than the high-quality image mode.


Camera API Changes in Series 60 2.x

The Camera APIs have changed between Series 60 1.x and 2.x. To illustrate the use of the Series 60 2.x Camera APIs, an example application called FilmReel2 is provided. The main differences between this and the Series 60 1.x example application, FilmReel , are in the CFilmReel2Container class. FilmReel2 uses the Series 60 2.x CCamera class instead of CCameraManager ”the CActive -derived class that encapsulates the Series 60 1.x Camera API, RCameraServ , as used in the FilmReel application.

CCamera provides an interface that an application can use to interact, and to acquire images from the camera. In FilmReel , an Active Object-derived class was used to handle the asynchronous events. However, Series 60 2.x provides a mixin class MCameraObserver , and this is registered with CCamera to notify the application of various key events. You must provide implementations of the pure virtual methods declared by MCameraObserver . An appropriate method will be called when one of the specified events occurs ”for example, when the camera is ready, when an image has been taken, or when an error has occurred.

To capture an image, you need to have created an instance of CCamera ”this is achieved by calling the static NewL() function:

 iCamera = CCamera::NewL(aObserver, 0); 

Once you have created an instance of the camera, the camera device must be reserved. To achieve this, call the Reserve() method:

 iCamera->Reserve(); 

The Reserve() method performs an asynchronous function call, and once the camera has been reserved, it will call the MCameraObserver::ReserveComplete() method. If the camera device was reserved successfully, the error code returned is KErrNone .

After successfully reserving the camera, the next step is to power it on, by calling the PowerOn() method. The PowerOn() method performs an asynchronous action, and once it has completed it calls the MCameraObserver::PowerOnComplete() callback method.

 iCamera->PowerOn(); 

After the camera has been powered on successfully, and before an image can be captured, the image format and size must be specified. The formats supported are available from TCameraInfo::iImageFrameFormatsSupported , and the sizes supported are available from CCamera::EnumerateCaptureSizes() . To set the format and size the client calls CCamera::PrepareImageCaptureL() , specifying the image format and size. This must be called at least once before taking an image.

 iCamera->PrepareImageCaptureL(CCamera::EFormatFbsBitmapColor4K, 1); 

Once the camera has been set up, the client application can call CCamera::CaptureImage() to capture an image. If an image has been captured, it will call MCameraObserver::ImageReady() . This will return the image if successful. Note, the image format and size vary, depending on the camera hardware used.

Once the application has finished using the camera, it should be released, and this is achieved by calling CCamera::Release ().

The CCamera interface also offers the following configuration features:

  • Flash mode.

  • Zoom settings for both optical and digital.

  • Brightness.

  • Contrast.

  • Exposure.

The availability of these features will depend on the hardware capabilities on the device.

Note also that FilmReel2 switches to the main view of the Media Gallery application, as this has replaced Photo Album/Images in Series 60 2.x.

At the time of writing, when building for target, it was necessary to build for thumb , since no ECam.lib was available for armi . Hence the .pkg file supplied with the example project (to build the .sis installation package file) specifies that the target build location for the executable files is thumb . Also, the Platform UID in the .pkg file is set to 2.0 to ensure the application is not installed on a device based on an earlier version of Series 60.

Phonebook Engine

The phonebook engine (aka Contacts) API allows access to its database of contacts and provides classes that enable the Phonebook application's standard dialogs to be used, from within your own application.

The phonebook API is a Series 60 implementation of the Symbian OS Contacts engine API. Much of the Contacts engine API is duplicated by the phonebook API, and it should be used only when the phonebook API is not sufficient.

Creating and Accessing Phonebook Items

The phonebook engine is implemented by CPbkContactEngine . If a default database exists, CPbkContactEngine::NewL() connects to the database; otherwise it creates it. An overloaded version of this function exists for opening a database other than the default.

 iPbkContactEngine = CPbkContactEngine::NewL(); 

Each entry in the phonebook database is made up of a number of fields that can be added or removed from an entry. Each field stores a single value that can be text, binary, or a date and time. CPbkFieldInfo objects contain information about the field types, and TPbkContactItemField objects contain the actual field data in an entry.

The fields supported by the phonebook database can be found using CPbkContactEngine::FieldsInfo() , which returns an array of CPbkFieldInfo objects.

 const CPbkFieldsInfo& fieldsInfo = iPbkContactEngine->FieldsInfo(); TInt numberOfFields = fieldsInfo.Count(); for (TInt i = 0; i < numberOfFields; i++) { CPbkFieldInfo* fieldInfo = fieldsInfo[i]; ... } 

CPbkFieldInfo provides the means to discover the attributes of a field, such as whether it contains a telephone number or text.

 CPbkFieldInfo* fieldInfo = fieldsInfo[i]; if (fieldInfo->IsPhoneNumberField()) ... 

Each entry in the phonebook database is given a unique ID ( TContactItemId ), used when specifying entries. To access the fields of any contact entry you need to open it for reading or writing. To open for reading use CPbkContactEngine::ReadContactL() , which takes a TContactItemId and returns a CPbkContactItem* for reading.

To modify a contact entry, open the entry for writing, make the changes and then commit them to the phonebook database. To open for writing use CPbkContactEngine::OpenContactL() , which takes a TContactItemId and returns a CPbkContactItem* for editing. Once the modifications have been made, commit them using CPbkContactEngine::CommitContactL() , taking the CPbkContactItem to be committed.

The class CPbkContactItem represents an individual entry in the phonebook database. It provides access and search functions for the array of TPbkContactItemField that it owns, as well as utility functions to obtain the email, telephone, SMS and MMS details for an entry. The title of an entry is found using CPbkContactItem::GetContactTitleL() . The title field mapping varies according to which fields contain data. For example, if no first name is defined but a last name is, the last name is returned as the title. Similarly, if no first or last name is defined, the business name is returned as the title.

 // open contact for reading CPbkContactItem* contact = iPbkContactEngine->ReadContactLC(aContactId); // get the contacts name HBufC* name = contact->GetContactTitleL(); 

You can search for a particular field in a contact ( TPbkContactItemField ) by field ID ( TPbkFieldId ) or by field definition ( CPbkFieldInfo ). To search by ID use CPbkContactItem::FindField() , which returns a TPbkContactItemField based upon the field ID passed as a parameter. The list of field Ids can be found in PbkFields.hrh .

 TPbkContactItemField* phoneNumber = contactItem->FindField(EPbkFieldIdPhoneNumberGeneral); 

Other useful functions include CPbkContactItem::FindNextFieldWithText() and CPbkContactItem::FindNextFieldWithPhoneNumber() , which find the next entry containing text and the next phone number, respectively.

A TPbkContactItemField field can be one of four types, the two important ones being text and date/time. There is no numeric field data type ”telephone numbers and so on, are stored in text fields. As well as the data, the field owns a content type describing the type of data stored, a label describing the field for the user, and attribute flags.

The content type can be discovered with TPbkContactItemField::StorageType() . If the data is text, there are several ways of accessing it. TPbkContactItemField::Text() returns a TPtr to the text stored in a field, while TPbkContactItemField::GetTextL() fills the descriptor passed in with the text stored in the field. To add or edit existing text use TPbkContactItemField::TextStorage() , which returns a pointer to the field's text storage (a CContactTextField object). Using CContactTextField::SetTextL() , you can then change the text data stored by the field.

 // find the phonenumber field of the contact, and add // aCallInfo's telephone number TPbkContactItemField* phoneNumber = contactItem->FindField(EPbkFieldIdPhoneNumberGeneral); phoneNumber->TextStorage()->SetTextL(aCallInfo.iTelephoneNumber); 

Similarly, if the data stored in the field is a date/time, TPbkContactItem::Time() returns the data as a TTime . If the data is to be edited, TPbkContactItem::DateTimeStorage() returns a pointer to the field's date storage (a CContactDateField object). To set the time for this field use CContactDateField::SetTime() , which takes a TTime parameter.

To add a new field to an entry, create a CPbkFieldInfo to describe the field you wish to add, then use CPbkContactItem::AddFieldL() to add the field to the entry. Alternatively use CPbkContactItem::AddOrReturnUnusedFieldL() , which finds an unused field or creates a new one based upon the CPbkFieldInfo parameter passed to the function.

To add a new entry to the phonebook database, create a CPbkContactItem . The simplest way to do this is by using CPbkContactEngine::CreateEmptyContactL() , which returns a CPbkContactItem with default fields.

 CPbkContactItem* contactItem = iPbkContactEngine->CreateEmptyContactL(); 

Alternatively you could use the CPbkContactItem NewL() or NewLC() methods, which construct a wrapper around, and take ownership of, a CContactItem that has been constructed using the contact model API.

[View full width]
 static CPbkContactItem* NewL(CContactItem* aItem, const CPbkFieldsInfo& aFieldsInfo,  MPbkContactNameFormat& aNameFormat); 

A reference to the phonebook engine's MPbkContactNameFormat can be obtained using CPbkContactEngine::ContactNameFormat() .

Once the CPbkContactItem has been constructed, add it to the phonebook database using CPbkContactEngine::AddNewContactL() .

 TContactItemId contactId = iPbkContactEngine>AddNewContactL(*contactItem); 

To make a copy of an entry, CPbkContactEngine::DuplicateContactL() takes the ID of the entry to be copied and returns the ID of the new entry.

To delete a single entry use CPbkContactEngine::DeleteContactL() , which takes the ID of the entry to be deleted. If more than one entry is to be deleted, use CPbkContactEngine::DeleteContactsOnBackgroundL() , which takes an array of IDs in the form of a CContactIdArray . This is an asynchronous function, so, in most cases, it will be preferable to other nonasynchronous functions for deleting multiple entries.

Receiving Notification

It is possible to ask the phonebook engine to notify an application when changes are made to the phonebook database. This can be important when dealing with long-running applications, where changes can be made to the database when the application is in the background. CPbkContactChangeNotifier manages the task of registering an observer with the phonebook engine. When a CPbkContactChangeNotifier object is constructed, it registers the observer as an observer of the relevant phonebook engine, and on destruction removes the observer from the phonebook engine's list of observers.

CPbkContactChangeNotifier::NewL() can be used to register an observer with the phonebook engine, taking as parameters the engine to be observed and the observer to be called when the engine changes.

Alternatively, CPbkContactEngine::CreateContactChangeNotifierL() can be used, taking as a parameter the observer to be called and returning a CPbkContactChangeNotifier .

In order for a class to be used as a phonebook engine observer, it needs to inherit from MPbkContactDbObserver , which contains two functions: DatabaseEventHandledL() and HandleDatabaseEventL() , both of which are passed a TContactDbObserverEvent . An observer of the engine is notified of any change event twice; HandleDatabaseEventL() is called first by CPbkContactEngine for all of its observers, then CPbkContactEngine calls DatabaseEventHandledL() for each of the observers to ensure that they have all been notified of the change event.

The default implementation of DataBaseEventHandled() does nothing; otherwise the observer class will receive two notifications of every phonebook engine event. The TContactDbObserverEvent passed to DatabaseEventHandledL() and HandleDatabaseEventL() contains the type of event that has occurred and the ID of the phonebook database entry affected. The list of event types can be found in cntdbobs.h . Commonly reported events types include: EContactDbObserverEventContactChanged , EContactDbObserverEventContactDeleted , EContactDbObserverEventContactAdded and EContactDbObserverEventSpeedDialsChanged .

CPbkContactEngine functions such as CPbkContactEngine::AddNewContactL() take a second parameter, TBool aImmediateNotify , which by default is false. If true, the observers are notified immediately of the event (for example, a contact has been added); however, the observers will be informed of the event twice.

Phonebook Searching

To find a particular entry in the database, the phonebook engine API provides the means to iterate though all the entries or search directly. To iterate through the phonebook database use CPbkContactIter .

An iterator can be constructed using CPbkContactIter::NewL() , CPbkContactIter::NewLC() or CPbkContactEngine::CreateContactIteratorLC() . To iterate through the entries use CPbkContactIter::FirstL() to move to the first entry and CPbkContactIter::NextL() to move to the next. Both functions return the ID of the entry the iterator has been advanced to, or KNullContactId if none are found. A pointer to the current phonebook database entry is obtained using CPbkContactIter::CurrentL() , or a copy of the current entry is obtained using CPbkContactIter::GetCurrentL() .

When searching the phonebook database for a telephone number, use CPbkContactEngine::MatchPhoneNumberL() rather than iterating through all of the entries. CPbkContactEngine::MatchPhoneNumberL() searches for the telephone number passed in as a descriptor (the second parameter passed is the number of characters to use in the search), returning an array of IDs of matching entries in the form of a CContactIdArray .

To search for text other than a telephone number, you can search synchronously or asynchronously. To search synchronously, CPbkContactEngine::FindLC() scans the phonebook database for the text and returns a CContactIdArray containing the IDs of matching entries. It is possible to pass a CPbkFieldIdArray of field IDs to be searched, but in most cases FindLC() searches other fields as well as those specified, so the matched entries need to be checked. As searching for text can take some time, especially if there are many entries in the phonebook, searching asynchronously is generally a better choice.

To search the phonebook asynchronously use CPbkIdleFinder . To obtain a CPbkIdleFinder call CPbkContactEngine::FindAsyncL() , the default implementation takes a descriptor parameter with the text to be searched for. FindAsyncL() creates a CPbkFindlerIdle object that searches asynchronously for the text in the phonebook database. To check if CPbkIdleFinder has finished searching, MIdleFindObserver , the third parameter of CPbkContactEngine::FindAsyncL() , allows the user of CPbkIdlefinder to monitor the search progress. MIdleFindObserver::IdleFindCallBack() is called for every 16 items searched, and also at the end of the search. This callback greatly assists in implementing a progress dialog or search status update. Discovering if CPbkIdlefinder has finished is achieved by calling CPbkIdleFinder::IsComplete() . When the search is complete, the array of IDs of matching entries is obtained using CPbkIdleFinder::TakeContactIds() .

Speed Dialing and Common Dialogs

The phonebook engine manages the mapping of speed dial numbers to telephone numbers within the phonebook database. CPbkContactEngine provides four functions to manage speed dial mappings.

[View full width]
 void SetFieldAsSpeedDialL(CPbkContactItem& aItem, TInt aFieldIndex, TInt  aSpeedDialPosition) // Sets the requested field of the contact as a speed dial number. TContactItemId GetSpeedDialFieldL(TInt aSpeedDialPosition, TDes& aPhoneNumber) const //  Gets the number mapped to the requested speed dial location. void RemoveSpeedDialFieldL(TContactItemId aContactId, TInt aSpeedDialPosition) // Removes  the speed dial mapping from a contact. TBool IsSpeedDialAssigned(const CPbkContactItem& aItem, TInt aFieldIndex) const //  Returns if an item's field is assigned to a speed dial mapping. 

CSpdiaControl is a utility class that provides services to both the Speed Dial and Phonebook applications for getting and setting a speed dial number configuration. Use it in your applications to provide a dialog for assigning a speed dial position to a telephone number.

 // Launch the speed dial dialog to add a speed dial link // to the selected contact CSpdiaControl* speedDialControl = CSpdiaControl::NewL(*iPbkContactEngine); return speedDialControl->ExecuteLD(aId, (*iFieldInfoArray)[0]-> FieldId()); 

As well as the speed dial selection dialog, the phonebook API offers other common dialogs accessible to external applications, such as CPbkPhoneNumberSelect to select a phone number, CPbkContactEditorDlg to edit a contact, and CPbkDataSaveAppUi to implement "Save to phonebook" menu functionality.

 void CPhoneBookEngine::DynInitMenuPaneL(TInt aResourceId, CEikMenuPane* aMenuPane) { iPbkDataSaveAppUi->DynInitMenuPaneL(aResourceId, aMenuPane); if (aResourceId == R_CALLVIEW_MENU_PANE) { // Adds Phonebook data save menu items // to the menu pane iPbkDataSaveAppUi->AddMenuItemsL(aMenuPane, ECallSummaryAddContactDB); } } TBool CPhoneBookEngine::HandlePhoneBookCommandL(TInt aCommand, const TDesC& aNumber) { return iPbkDataSaveAppUi->HandleCommandL(aCommand, *iFieldInfoArray, aNumber); } 

Compact Business Cards and vCards

With the CBCardEngine class, the phonebook API supports the transmission of vCard and compact business cards to and from the phonebook database, by infrared, SMS and so on.

A CBCardEngine object is created using CBCardEngine::NewL(CPbkContactEngine* aEngine) , where aEngine is the open phonebook engine object for import or export. The CBCardEngine uses RReadStream and RWriteStream for the transfer of vCard data to and from the phonebook database. This allows the streams to be stored in a file or a stream buffer hosted by a descriptor.

To read a vCard record from a stream into the phonebook database, pass a reference to an open CPbkContactItem and the stream to read the vCard from to the function CBCardEngine::ImportBusinessCardL(CPbkContactItem& aDestItem, RReadStream& aSourceStream) . It is up to you to commit any changes to the phonebook database.

To write a vCard record into a stream, use CBCardEngine::ExportBusinessCardL(RWriteStream& aDestStream, CPbkContactItem& aSourceItem) , where aSourceItem is the contact entry you wish to export to the stream.

Calendar Engine Access

The Calendar (aka Agenda) engine/model API provides access to the time-management data used by the Calendar and To-do applications. The model allows entries to be added, edited and deleted. You can also use it to access details associated with entries, such as alarm and synchronization information.

The API is very large, and only a brief glimpse of its power and versatility can be given here; however, its use is extensively covered in the SDK documentation ”see the Symbian API Guide, Application Engines and API Reference sections.

Agenda Models and Concepts

There are four types of agenda entry, each with different properties:

  • Events ( CAgnEvent ) ” are "all-day" entries that may have a display time, but do not have an actual start time. Events may have multiple day durations using the optional end date information.

  • Anniversaries ( CAgnAnniv ) ” are events that can occur only once a year, such as birthdays. Information about the base year can be stored.

  • Appointments ( CAgnAppt ) ” have a start time and date, and an end time and date.

  • To-dos ( CAgnToDo ) ” represent a task to be carried out and may have a display time and date, and either a due or crossed-out date to indicate when the task is due by or when it was performed. A to-do also contains details about the to-do list it belongs to and its priority.

The models for accessing the agenda data are arranged in a three-layer hierarchy:

  • Entry model ( CAgnEntryModel ) ” is the base model. An entry object represents each entry, with all repetitions stored in the one object.

  • Indexed model ( CAgnIndexedModel ) ” extends the entry model, indexing the data to allow filtering before entries are read.

  • Instance model ( CAgnModel ) ” extends the indexed model and is the most suitable for applications with a user interface, as separate objects are used to represent each instance of a repeating object. This will be the focus of the discussion.

Agenda Server

Before creating an instance of one of the model classes, you need to create an instance of the Agenda Server client, RAgendaServ , using RAgendaServ::NewL() . Then connect it to the server by calling RAgendaServ::Connect() . This will start the server if it is not already running.

Series 60 applications should always access the Agenda model in Client mode. "Normal" mode is obsolete and does not allow concurrent access to agenda files. Beware of using methods specifically overloaded for "normal" mode.


Once an instance of a model class has been created using its NewL() function, call CAgnEntryModel::SetServer() to associate the Agenda Server with the model. Most calls to the Agenda Server are made indirectly via a model class. When SetServer() is called, the model's mode is automatically set to client, but this can also be explicitly set using CAgnEntryModel::SetMode() .

 iAgendaServer = RAgendaServ::NewL(); iAgendaServer->Connect(); iModel = CAgnModel::NewL(this); iModel->SetServer(iAgendaServer); iModel->SetMode(CAgnEntryModel::EClient); 

Having created an instance of the model class and set the associated server, it is necessary to open the file containing the agenda data. This is achieved using the model class's OpenL() function, which takes the name of the file containing the agenda data with the default times to display for the different types of entry. The CAgnIndexedModel and CAgnModel versions of OpenL() are further overloaded to take a MAgnProgressCallBack observer, which reports the progress of the load.

 iModel->OpenL(*agendaFile, KDefaultTimeForEvents, KDefaultTimeForAnnivs, KDefaultTimeForDayNote, &iObserver, EFalse); 

Notification of Changes and Progress Monitoring

Some functions of the agenda model can block the model while a long-running task completes. When the model is created, it is possible to specify an observer derived from MAgnModelStateCallBack , which provides a function StateCallBack() , and called with EBlocked when the model enters a blocked state, and EOk when it leaves the blocked state.

 class CAlarmOrganiserEngineBase: public CBase, public MAgnModelStateCallBack { ... private: // from MAgnModelStateCallBack void StateCallBack(CAgnEntryModel::TState aState); ... }; void CAlarmOrganiserEngineBase::ConstructL() { ... iModel = CAgnModel::NewL(this); ... } 

The state of the model can be found at other times by using CAgnEntryModel::State() , which returns the current state of the model.

To provide information on the progress of agenda operations that may take an extended time to complete ”for example, merging and tidying agenda files ”the file operation functions are overloaded to take an observer derived from MAgnProgressCallBack supplying two functions: Progress() and Completed() . These functions are called by the model to keep the user informed of the current state of the operation. Progress() supplies the percentage of the operation that has completed, Completed() when the operation has fully completed, rather than Progress() being called at 100 percent. The frequency with which Progress() is called is set by the callback frequency parameter when calling the function that issues the long-running request.

[View full width]
 iModel->OpenL(*agendaFile, KDefaultTimeForEvents, KDefaultTimeForAnnivs,  KDefaultTimeForDayNote, &iObserver, EFalse, EOpenCallBackHigh); 

Alternatively, you could wait for the file to be loaded by using the function RAgendaServ::WaitUntilLoaded() ; however, this is a synchronous operation and will freeze the UI, making the machine unresponsive , so it is not recommended.

Reading Agenda Entries

Reading entries depends on whether the instance or the entry model is being used. To get entries from the instance model, you can use one of the CAgnModel::PopulateXXXInstanceListL() functions, which return a list of IDs matching the criteria given in the arguments to the function. This can include a filter.

[View full width]
 void PopulateDayInstanceListL(CAgnDayList<TAgnInstanceId>* aList, const TAgnFilter&  aFilter, const TTime& aTodaysDate) const; void PopulateDayDateTimeInstanceListL(CAgnDayDateTimeInstanceList* aList, const TAgnFilter&  aFilter, const TTime& aTodaysDate) const; void PopulateMonthInstanceListL(CAgnMonthInstanceList* aList,const TAgnFilter& aFilter  ,const TTime& aTodaysDate) const; void PopulateTodoInstanceListL(CAgnTodoInstanceList* aList,const TTime& aTodaysDate) const; void PopulateSymbolInstanceListL(CAgnSymbolList* aList,const TAgnFilter& aFilter, const  TTime& aTodaysDate) const; 

The CAgnXXXList classes, such as CAgnDayDateTimeInstanceList , are wrapper classes around an array of IDs. Having obtained a list of IDs of interest, you can get the instance from its ID using CAgnModel::FetchInstanceLC() , which returns a pointer to a CAgnEntry .

 CAgnEntry* entry = iModel->FetchInstanceLC((*dayList)[ii - 1]); 

When the instance is retrieved, its type is unknown. The base class for the agenda entry classes, CAgnEntry , provides the means to discover the type of an entry enabling you to cast it to the appropriate class. Calling CAgnEntry::Type() , will return a value of EAnniv , EAppt , EEvent or ETodo . You can then cast the instance to the correct class using CAgnEntry::CastToAnniv() , CAgnEntry::CastToAppt() , CAgnEntry::CastToEvent() or CAgnEntry::CastToToDo() , respectively.

 CAgnEntry* entry = iArray[aIndexOfAlarm]; switch (entry->Type()) { case CAgnEntry::EAnniv: case CAgnEntry::EAppt: case CAgnEntry::EEvent: { ... } case CAgnEntry::ETodo: { CAgnTodo* todo = entry->CastToTodo(); ... } ... } 

Entries in the agenda model can also contain text that is stored in rich text format. The text associated with an entry can be accessed using CAgnEntry::RichTextL() , which returns a pointer to a rich text object.

 TBuf<256> text; entry->RichTextL()->Extract(text, 0, entry->RichTextL()->DocumentLength()); 

As well as rich text, an entry can contain other information such as repeat details, category information, location, meeting attendees and so on. Each entry data item can be accessed through a range of specifically named methods provided by CAgnEntry ”see the SDK documentation for further details.

Searching Instances and Filtering

TAgnFilter can be passed to the "populate list" functions to filter the IDs returned. There are also more specialized filters derived from TAgnFilter , such as TAgnsrvTidyFilter , used when tidying an agenda file ”see the SDK documentation for further details.

After creating a TAgnFilter , you need to set the details of the filter. For example, to create a filter that filters out the instances which do not have an alarm set, use TAgnFilter::SetIncludeAlarmedOnly() .

 TAgnFilter filter; filter.SetIncludeAlarmedOnly(iAlarmed); 

Filters can also include or exclude: to-dos, anniversaries and events, timed or untimed appointments, repeating entries, and crossed-out entries. It is possible to extract information about a filter; for example, TAgnFilter::AreAlarmedOnlyIncluded() returns whether the filter is restricted to alarmed entries or not.

As well as being able to populate lists with instances meeting certain criteria, it is possible to search for instances, or instances with a particular property. The agenda model API offers several functions to help with this. One option is to iterate though the entries using an iterator created by RAgendaServ::CreateDateIterator() or RAgendaServ::CreateEntryIterator() . CAgnModel also offers an implementation with CAgnModel::NextDayWithInstance() and CAgnModel::PreviousDayWithInstance() , which find the next or previous day that has an instance in the agenda model. This can then be used within one of the list-populating functions to find the relevant instances.

 TTime day; ... day = iModel->NextDayWithInstance(day, filter, day); 

To search for instances matching a search string use CAgnModel::FindNextInstanceL() or CAgnModel::FindPreviousInstanceL() . Once a match is made, a search should be made of the rest of the instances for that day, and a CAgnDayList is returned containing any matches.

Adding, Editing and Deleting Entries

To add a new entry to the model, create it with CAgnAppt::NewL() , CAgnEvent::NewL() , CAgnAnniv::NewL() , CAgnToDo::NewL() , or the corresponding NewLC() function. To make the entry repeating, create an instance of CAgnRptDef , defining the required repeating pattern, and pass it to CAgnEntry::SetRptDefL() which sets the repeat details of the entry. Once you have constructed and set the contents of the entry, then add the entry to the model using CAgnModel::AddEntryL() , which takes the entry and returns an entry ID. If the entry is a to-do, a second parameter must be passed indicating the position in the to-do list the entry will occupy.

CAgnEntryModel and CAgnIndexedModel are concerned primarily with entries in the agenda file, while the CAgnModel is concerned with instances rather than entries. Therefore, if you are using CAgnModel , use UpdateInstanceL() , DeleteInstanceL() and FetchInstanceL() , rather than UpdateEntryL() , DeleteEntryL() and FetchEntryL() . Always use CAgnModel::AddEntryL() to add a new entry to an agenda model, as it does not have an instance date at the point that it is added.

If the entry is nonrepeating, then there are no added complications of dealing with an instance rather than with an entry. After getting the entry to edit using CAgnModel::FetchInstanceLC() , update the entry in the model using CAgnModel::UpdateInstanceL() , which returns the ID of the entry.

 CAgnEntry* entry = iModel->FetchInstanceLC((*dayList)[ii - 1]); ... iModel->UpdateInstanceL(entry); 

If the entry is repeating, then as well as passing the entry, you need to pass an enumeration parameter, TAgnWhichInstances , which can be ECurrentInstance , EAllInstances , ECurrentAndFutureInstances , or ECurrentAndPastInstances and describes whether you are changing all, or a subset, of the instances.

CAgnModel::UpdateInstancesL() defaults to EAllInstances , where the changes are made to all instances of the entries. Otherwise, as you are in effect trying to split the entry by defining instances that are distinct in properties, a new entry is created. Choosing ECurrentInstance results in the creation of a new nonrepeating entry, while choosing either ECurrentAndFutureInstances or ECurrentAndPastInstances results in a new repeating entry.

To delete an instance use CAgnModel::DeleteInstanceL() , which takes either a pointer to the entry, or the ID of the instance. If the instance is not repeating, then it is deleted. If the entry is repeating, a TAgnWhichInstances enumeration parameter is passed, which defaults to EAllInstances . This deletes the entry and all instances. If ECurrentInstance is used, the current instance is deleted, but before this can happen, the current instance needs to be made a repeat exception. A repeat exception is a date that would normally be included in an entry's repeat date, but is excluded; for example, an entry is repeated every Monday, except dates which are public holidays. Then the public holidays are exceptions. A repeat exception is created using CAgnRptDef::AddExceptionL() , which takes a TAgnException , which is a wrapper class around the date which is to be the exception .

Calendar Alarms

A Calendar (aka Agenda) event can have an associated alarm, which is handled by the alarm server. In order for alarms set in the Calendar (Agenda) file to be handled by the alarm server, they must be added to the alarm queue.

To create an association with the alarm server, create a CAgnAlarm object, which represents the interface between the agenda model and the alarm server. A connection to the alarm server is made during construction of the CAgnAlarm object. Once created, register the CAgnAlarm object with the model using CAgnEntryModel::RegisterAlarm() . Once registered, whenever an alarmed entry is added to the file, the model queues the next few outstanding alarms with the alarm server:

 // Create a CAgnAlarm and associated it with the model iAgnAlarm = CAgnAlarm::NewL(iModel); iModel->RegisterAlarm(iAgnAlarm); 

Before closing the agenda file that contains the current model, you need to call CAgnAlarm::OrphanAlarm() . This allows the alarms to be processed after the session is closed:

 // Orphan the alarm, before we close the file iAgnAlarm->OrphanAlarm(); delete iAgnAlarm; 

To set an alarm for an entry use CAgnBasicEntry::SetAlarm() , setting the number of days before the start date that the warning alarm is to be sounded, and the time the alarm is to be sounded. The time the alarm is to sound is expressed as the number of minutes after midnight.

 TTime time = entry->InstanceStartDate(); TInt hours = time.DateTime(). Hour (); TInt minutes = time.DateTime().Minute(); entry->SetAlarm(0, minutes + 60 * hours); 

For to-do entries, the alarm is set relative to the due date. For the alarm to be relative to the start date, call CAgnToDo::SetAlarmFromStartDate() after setting the alarm:

 CAgnTodo* todo = entry->CastToTodo(); todo->SetAlarm(0, 0); todo->SetAlarmFromStartDate(); ... iModel->UpdateInstanceL(entry); 

To remove an alarm from an entry, call CAgnEntry::ClearAlarm() :

 entry->ClearAlarm(); iModel->UpdateInstanceL(entry); 

If you need to inquire about the details of any alarm associated with an agenda entry, there are methods to return this data. The method names are fairly self-explanatory. To find the number of days prior to the date of the agenda entry, use CAgnBasicEntry::AlarmDaysWarning() , followed by CAgnBasicEntry::AlarmTime() to find the time of the alarm. If you are only enquiring as to whether the entry has an alarm, use CAgnBasicEntry::HasAlarm() .

 if (aEntry->HasAlarm()) { ... } 

Photo Album Engine

The photo album engine provides an API whose purpose is to assist in the management of image files. As well as providing utility functions, it provides a variety of common dialogs and controls that can be used in your own applications.

Note that in Series 60 2.x the Photo Album (aka Images) application has been replaced by the Media Gallery application. As a result, some of the photo album classes and methods are deprecated for use in Series 60 2.x applications. Search for "Deprecated List" in the Series 60 2.x SDK documentation for specific details.

Accessing Photo Album Folders

The majority of the photo album API is concerned with the management of images within the photo album folders, the settings for which can be found via the static TPAlbSettings functions. These return the settings for the various image folders.

 IMPORT_C static HBufC* RootImageFolderLC(); ... IMPORT_C static TInt SetDefaultImageFolderL(TDesC& aPath); 

Two classes enable transfer of images between an application and the image folders: CPAlbImageUtil moves images to the image folders and provides the image-saving API for the photo album. CPAlbImageFactory retrieves bitmaps from the image folders.

To place an image in the image folders, CPAlbImageUtil::MoveImageL() will move the image, and CPAlbImageUtil::CopyImageL() will copy the image, leaving the original in place. Both functions take a TBool parameter, aReplace , which will replace an existing file of the same name in the image folders if true. If aReplace is false, then CPAlbImgaUtil will generate a unique name for the image file based upon its original name.

To generate a thumbnail of the image being moved or copied, you can use an overloaded version of CPAlbImageUtil::MoveImageL() or CPAlbImageUtil::CopyImageL() that takes a reference to a CFbsBitmap , which will be filled with a thumbnail of the image being transferred.

 iImageUtil = CPAlbImageUtil NewL(); iImageUtil->MoveImageL(aSourceFile, ETrue, aThumbBitmap); 

The API also enables folder management tasks such as deleting and renaming image files using CPAlbImageUtil::DeleteImageL() and CPAlbImageUtil::RenameImageL(). Both take a CPAlbImageData object that encapsulates image information, providing member functions to access the name of the file containing the image, the path to the file and inquiring whether a thumbnail image exists.

After moving or copying an image file, you can get a pointer to the CPAlbImageData describing the image using CPAlbImageUtil::ImageData() . A pointer to the CPAlbImageData object is used to synchronize an image file and its thumbnail representation using CPAlbImageUtil::SynchronizeL() . The synchronization process completes by generating a thumbnail for the image file if one does not already exist, and if the thumbnail exists but not the image file, then the thumbnail is deleted.

CPAlbImageFactory allows retrieval of both the original image and its thumbnail from the image folders. As the creation of a bitmap from the image stored in the image folders can take some time, CPAlbImageFactory provides asynchronous functions to generate the bitmaps, with the assistance of the mixin class MPAlbImageFactoryObserver . Its single function, MPTfoCreateComplete(CPAlbImageFactory* aObj, TInt aError, CFbsBitmap* aBitmap) , is called on completion of an asynchronous request for a bitmap with aObj , a pointer to the CPAlbImageFactory which made the request, and aBitmap , the requested bitmap. The ownership of the returned bitmap is passed to the observing class. To get a bitmap from an image in the image folders use CPAlbImageFactory::GetImageAsync() , which takes either the full path filename of the image, or a CPAlbImageData :

[View full width]
 void CImageObserverClass::GetImage(TFileName aFilename) { iImageFactory = CPAlbImageFactory::NewL(this); iImageFactory->GetImageAsync(aFileName); } void CImageObserverClass::MPTfoCreateComplete(CPAlbImageFactory* aObj, TInt aError,  CFbsBitmap* aBitmap) { if (aError == KErrNone) { iBitmap = aBitmap; ... } ... } 

To fetch a thumbnail use a CPAlbImageData object to specify the thumbnail required and call CPAlbImageFactory::GetThumbnailAsync(CPAlbImageData) . If the thumbnail does not exist, it is created and stored in the thumbnail directory. You can also generate thumbnails with the synchronous functions CPAlbImageFactory::CreateThumbFromBitmapL() and CPAlbImageFactory::CreateThumbnailFromBitmapL() .

Using Photo Album UI Components

In Series 60 1.x the Photo Album application provides several UI components that might be useful in your applications, such as selection dialogs and an image viewer control. However, for Series 60 2.x applications see the note earlier under Photo Album Engine about deprecated classes and methods.

The selection dialogs allow applications to present the user with a familiar interface for the selection of images. There are two selection dialogs, CPAlbImageFetchPopupList and CPAlbPictureFetchPopupList , for selecting images and pictures, respectively. Pictures essentially differ from images in their folder location and the filename extension used. For a picture file to be compatible with photo album it must have a filename extension of .ota , and the target folder is always the "Pictures" folder inside photo album.

To present the user with an image selection dialog, construct the CPAlbImageFetchPopupList dialog, passing a pointer to a CPAlbImageData object, which, when the dialog returns, will contain the details of the image selected:

 CPAlbImageData* data = CPAlbImageData::NewL(); CleanupStack::PushL(data); TBool result(CPAlbPictureFetchPopupList::RunL(data)); ... CleanupStack::PopAndDestroy(data); 

To display an image in your own application, you normally need to load the image, convert it into a CFbsBitmap , then draw the bitmap onto the screen, as described in Chapter 11. The photo album API, however, provides CPAlbImageViewerBasic that hides these steps.

To use a CPAlbImageViewerBasic control to display an image, create the control and then call CPAlbImageViewerBasic::LoadImageL() , which takes the name of the image file to load and the color depth of the image as parameters. As well as displaying the image, CPAlbImageViewerBasic can be used to rotate, pan and zoom the image:

 #include <AknUtils.h> // Needed for CompleteWithAppPath() // Note no drive letter given _LIT(KFileName, "\Images\mini.jpg"); ... CPAlbImageViewerBasic* iViewer = CPAlbImageViewerBasic::NewL(this, Rect()); TFilename filename = KFileName; // filename = c:\Images\mini.jpg CompleteWithAppPath(filename); iViewer->LoadImageL(filename, EColor4K); 

Note the use of CompleteWithAppPath() here to complete the full filename ”the drive letter and any missing path is provided from the applications installation location. Take care here, CompleteWithAppPath() may return an error code if there is a problem, but the filename will not be changed if an error occurred.



Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net