I del 1 tittade vi på hur man kom igång med Symbian utveckling för Sony Ericssons telefoner, t ex M600. I detta nummer tar vi upp ett mera omfattande, mer realistiskt och eventuellt användbart exempel. All källkod finns på mobil.se, av utrymmesskäl kan vi inte få med allt tidningen.
Tanken är att göra en enkel applikation som efter ett avslutat telefonsamtal ger användaren möjlighet att skriva en notering om samtalet som sedan lagras i "anteckningsfältet" för den man just pratat med. En förutsättning är då förstås att den man pratat med finns lagrad som en kontakt i telefonen.
Komma igång
Vi börjar på samma sätt som i förra numret, vi skapar ett skal för en applikation m h a Carbides wizard. Starta Carbide.c++ och välj File | New | C++ Application for UIQ Project i menyn. Ge ditt projekt ett namn, t ex ''SamtalsNotering". Välj template "UIQ 3.x GUI Application". Gå sedan igenom resten av guiden, man behöver inte mata in något mer eller ändra något av de förvalda alternativen. Detta exempel är lämpligast att bygga direkt för telefonen och testar på den.
Allmänt om aktiva objekt
Symbian-applikationer använder sig normalt bara av en tråd. För att kunna göra flera saker "samtidigt" så använder man sig istället av något som kallas aktiva objekt. I praktiken så innebär det att man definierar och implementerar egna klasser som ärver från klassen CActive.
Ofta har då en applikation flera olika aktiva objekt som väntar på att olika händelser skall inträffa och när händelsen inträffar tar man hand om den i klassens RunL() metod.
I detta sammanhang pratar man också om asynkrona metoder. I olika APIer finns det ofta både synkrona och asynkrona varianter av en funktion som gör samma sak. Den synkrona varianten kommer att blockera applikationens tråd till dess att funktionen är klar. Tar funktionen lång tid att utföra så kommer applikationen inte vara mottaglig för input från användaren under tiden och det är inte alls bra. En asynkron metod sätter bara igång funktionen och kodraden efter exekveras omgående. Asynkrona metoder anropar man oftast från ett aktivt objekt. När sedan funktionen är klar anropas det aktiva objektets RunL() metod. Under tiden har applikationen kunnat göra andra saker, lyssna på användarinput och andra aktiva objekt kan ha körts.
CMyActive klassen
Vi börjar att utöka vår applikation med en ny klass CMyActive.
Vi lägger därför till filerna:
\inc\CMyActive.hklass definition
\src\CMyActive.cppImplementation av klassens metoder
Header-filen ser initialt ut så här:
KOD START
#ifndef CMYACTIVE_H_
#define CMYACTIVE_H_
#include
//Aktivt objekt som prenumerarar på samtalsinformation
class CMyActive: public CActive
{
public:
static CMyActive* NewL();
~CMyActive();
void Start();
protected:
//Implementerar metoder definierade i basklassen CActive
void RunL();
void DoCancel();
TInt RunError(TInt aError);
private:
CMyActive();
void ConstructL();
private:
enum TState
{
EStart,
EWaitingForCall,
ECallInProgress,
EFindingContact,
EUpdatingContact
};
TState iState; //Tillstånd
};
#endif /*CMYACTIVE_H_*/
KOD SLUT
Notera hur klassen ärver från CActive och att man måste definiera metoderna RunL och DoCancel. Ofta är ett aktivt objekt en tillståndsmaskin, det är därför vi har definierat medlemsvariablen iState som kan anta ett antal olika värden.
Hur en första implementation i CMyActive.cpp ser ut kan du se i exempel1.txt
NewL() metoden är Symbians sätt att skapa objekt, så kallad tvåfas-konstruktion. Detta är intimt förknippat med några andra grundbultar i Symbian-programmering nämligen Cleanup-stacken och TRAP/Leave. Dessa tekniker används för att hantera möjliga felsituationer speciellt då att det är slut på internminne (RAM) i telefonen och programmet inte kan allokera mer minne. Allt detta beskrivs mycket detaljerat i SDK dokumentation och annan Symbian dokumentation så vi går inte in i mer detalj här.
Hålla reda på telefonsamtals tillstånd och hämta information om samtal
Vi vill veta när ett telefonsamtal kopplas upp och när det sedan avslutas. För detta kommer vi använda klassen CTelephony så vi måste inkludera:
KOD START
#include //CTelephony med mera
KOD SLUT
I CMyActive klassen definierar vi också en medlemsvariabel som är en pekare till ett CTelephony objekt plus några andra medlemsvariabler som används för att hämta ut information om samtalet:
KOD START
CTelephony* iTelephony;
CTelephony::TCallStatusV1 iLineStatus;
CTelephony::TCallStatusV1Pckg iLineStatusPckg;
KOD SLUT
I CMyActive::ConstructL() så skapar vi vårt CTelephony objekt:
KOD START
void CMyActive::ConstructL()
{
//Skapa objekt för telefoni
iTelephony = CTelephony::NewL();
:
KOD SLUT
iLineStatus/iLineStatusPckg variablerna måste initieras.
KOD START
CMyActive::CMyActive():CActive(EPriorityStandard),
iLineStatusPckg(iLineStatus)
{
CActiveScheduler::Add(this); //Måste göras för varje aktivt objekt
}
KOD SLUT
Kom även ihåg att ta bort detta objekt i klassens destruktor annars läcker vi minne.
KOD START
CMyActive::~CMyActive()
{
Cancel();
//Ta bort alla objekt vi skapade i konstruktor
delete iTelephony;
}
KOD SLUT
Nu kommer vi till det aktiva objektets kärna, ett asynkront anrop (prenumeration) och RunL() metoden där vi hanterar att händelsen har inträffat.
KOD START
void CMyActive::Start()
{
//Lyssna/prenumerera på samtalshändelser, ändringar av s k "call state"
iTelephony->NotifyChange(iStatus, CTelephony::EVoiceLineStatusChange, iLineStatusPckg);
SetActive();
iState = EWaitingForCall; //Nytt tillstånd, vi väntar på att ett samtal kopplas upp
}
KOD SLUT
Här anropar vi NotifyChange och definierar att vi vill bli informerade när "voice line status" ändras. Telefonlinjens tillstånd kommer vi att kunna läsa ut ur variabeln iLineStatusPckg som vi skickar med funktionsanropet. Notera att asynkrona metoder alltid har ett argument av typen TRequestStatus. Alla aktiva objekt har en medlemsvariabel, iStatus av denna typ, och denna skickar vi med i anropet.
RunL metoden blir ganska omfattande så det finns inte utrymme att ha med all kod i tidningen utan vi hänvisar till hemsidan.
I RunL kommer vi att hamna varje gång samtalet ändrar tillstånd. Tillståndet förändras typiskt så här för ett inkommande samtal: Ringing - Answering - Connected - (Disconnecting) - Idle.
När telefonlinjens status ändras till Connected kommer vi att lagra undan starttid. Dessutom kommer vi använda metoden CTelephony::GetCallInfo för att hämta ut:
-Inkommande eller utgående samtal
-Numret på den man pratar med om det finns tillgängligt.
KOD START
if (iLineStatus.iStatus == CTelephony::EStatusConnected)
{
//Ett samtal har kopplats upp
TTime startTime;
startTime.HomeTime(); //Nu
iSamtalsInfo.iStart = startTime; //lagra undan start-tid
iState = ECallInProgress; //Nytt tillstånd
callSelectionV1.iSelect = CTelephony::EActiveCall;
//Hämta information om detta samtal
TInt err = iTelephony->GetCallInfo(callSelectionV1Pckg, callInfoPkg, remotePartyInfoPkg);
if (err == KErrNone)
{
CTelephony::TTelNumber number = remotePartyInfo.iRemoteNumber.iTelNumber;
CTelephony::TTelNumber dialledNumber = callInfo.iDialledParty.iTelNumber;
iSamtalsInfo.iDirection = remotePartyInfo.iDirection;
if (remotePartyInfo.iDirection == CTelephony::EMobileTerminated)
iSamtalsInfo.iTelNumber = number; //Incoming call
else
iSamtalsInfo.iTelNumber = dialledNumber; //Outgoing call
:
KOD SLUT
När status sedan ändras till EStatusIdle så räknar vi ut samtalets längd.
All information vi får fram lagrar vi en medlemsvariabel iSamtalsInfo av typen TSamtalsInfo som vi själva definierat i klassen CMyActive
KOD START
//Struktur där vi lagrar information om ett samtal
class TSamtalsInfo
{
public:
TTime iStart;//Starttid
TTimeIntervalSeconds iDuration;//Samtalets längd
CTelephony::TTelNumber iTelNumber;//Telefonnummer
CTelephony::TCallDirection iDirection;//Inkommnade/Utgående samtal
TContactItemId iCntId;//id på matchande kontakt
};
KOD SLUT
KOD START
TSamtalsInfo iSamtalsInfo;
KOD SLUT
Andra tillståndsförändringar ignorerar vi, då anropar vi bara NotifyChange() igen så vi kommer att bli anropade när nästa tillståndsförändring sker.
KOD START
iTelephony->NotifyChange(iStatus, CTelephony::EVoiceLineStatusChange, iLineStatusPckg);
SetActive();
KOD SLUT
När man använder en ny klass som CTelephony i sin applikation så kommer man få länkningsfel om man inte pekar ut motsvarande bibliotek (lib) i sitt projekt.
DUMP1
Man kan lägga till bibliotek i projektets properties.
Gör Add, och lägg till följande fil:
DUMP2
Notera att när man jobbar i emulatorn så lägger man till motsvarande *.lib fil som ligger under \epoc32\release\winscw\udeb.
I och med att vi använder CTelephony::GetCallInfo så måste vi ge applikationen en capability, ReadUserData. Även detta görs i Properties.
Sök i kontaktdatabasen
När ett samtal är avslutat så har vi samlat på oss information om starttid, samtalets längd och förhoppningsvis telefonnummer för den vi pratat med. Numret använder vi nu för att göra en sökning i kontaktdatabasen.
Vi börjar med att utöka CMyActive klassen med följande medlemsvariabler och de inkludera motsvarande header-filer.
KOD START
CContactDatabase* iContactsDb;
CContactItemFieldDef* iCntFieldDef;
CIdleFinder* iFinder; //Används för sökning i kontakter
KOD SLUT
Sedan skapar vi några av dessa i klassens konstruktor.
KOD START
void CMyActive::ConstructL()
{
//Skapa objekt för telefoni och kontakt-hantering
iTelephony = CTelephony::NewL();
iContactsDb = CContactDatabase::OpenL();
iCntFieldDef = new(ELeave) CContactItemFieldDef();
iCntFieldDef->AppendL(KUidContactFieldPhoneNumber); //search only tel numbers
iState = EStart;
}
KOD SLUT
Vi kommer att använda metoden FindAsyncL() i CContactDatabase.
KOD START
CIdleFinder *FindAsyncL(const TDesC &aText, const CContactItemFieldDef *aFieldDef, MIdleFindObserver *aObserver);
KOD SLUT
aText är den sträng(telefonnummer) vi letar efter, aFieldDef är de fält som man vill leta i, vi har definierad att bara leta i fält som lagrar telefonnummer. Det här asynkrona anropet har inget TRequestStatus argument, utan använder sig av en annan teknik. Här skall man skicka med en pekare till ett objekt som man vill skall bli anropat med resultatet av sökningen.
I detta fall vill vi att vår CMyActive klass skall vara denna observer. Därför låter vi CMyActive ärva även från MIdleFindObserver.
KOD START
class CMyActive: public CActive, public MIdleFindObserver
KOD SLUT
Att ärva från denna klass (eller så kallade mixin interface) innebär att vi måste implementera metoden IdleFindCallback() i CMyActive. Det är genom denna callback funktion vi kommer att få veta hur sökningen fortlöper.
Innan vi drar igång sökningen så tar vi och manipulerar det telefonnummer vi vill söka efter. Vi behåller bara de 8 sista siffrorna, detta för att undvika problem med hur nummer lagras i kontakter, t ex med +46, mellanslag, inledande nollor.
KOD START
//Använd bara 8 sista siffrorna vid sökning, bli av med +46, 0, etc. 7 annat vanligt //värde också,
CTelephony::TTelNumber strippedNumber = iSamtalsInfo.iTelNumber.Right(8);
// Dra igång asynkron sökning i kontakt-databasen,
// IdleFindCallBack kommer rapportera hur sökningen går!
iFinder = iContactsDb->FindAsyncL(strippedNumber, iCntFieldDef, this);
KOD SLUT
this = pekare till sig själv, dvs vi talar om för kontaktdatabasen att det är vårt CMyActive-objekt där det finns en IdleFindCallback metod som skall anropas för att rapportera hur sökningen går.
Hur vår implementation av IdleFindCallback ser initialt ut kan du se i exempel2.txt.
IdleFindCallback kommer anropas ett flertal gånger men vi väntar tills vi får veta att sökningen är klar. Om det finns ett eller flera träffar så lagrar vi undan id för den första träffen.
Ytterligare en observer
Nu har vi all information inklusive kontaktid. Nu vill vi låta användaren mata in en notering och lagra den tillsammans med annan information om samtalet i motsvarande kontakt. Innan vi visar en dialog vill vi att vår applikation får fokus. Detta är lättast att implementera i AppUi-klassen och därför vill vi att CMyActive skall anropa AppUi-klassen. Detta löser vi med att göra en egen observer-klass. Lägg till följande i början av CMyActive.h
KOD START
// Mixin-klass (motsvarande Javas interface)
// HandleContactLookupComplete metod måste implementeras i // ärvd klass
class MMyObserver
{
public:
virtual void HandleContactLookupComplete(const TSamtalsInfo& aSamtalsInfo)=0; //Pure virtual metod
protected:
virtual ~MMyObserver() {}
};
KOD SLUT
Sedan lägger vi till en medlemsvariabel i CMyActive.
KOD START
MMyObserver* iObserver;
KOD SLUT
Och ändrar vårt interface till CMyActive något.
KOD START
static CMyActive* NewL(MMyObserver* aObserver);
KOD SLUT
KOD START
CMyActive(MMyObserver* aObserver);
KOD SLUT
KOD START
CMyActive::CMyActive(MMyObserver* aObserver):CActive(EPriorityStandard),
iObserver(aObserver), iLineStatusPckg(iLineStatus)
{
KOD SLUT
Detta innebär också att konstruktionen av vårt CMyActive objekt måste ändras.
KOD START
iTeleMonitor = CMyActive::NewL(this);
KOD SLUT
I vår AppUi-klass så ärver vi nu även från MMyObserver och implementerar callback-funktionen HandleContactLookupComplete.
KOD START
class CSamtalsNoteringAppUi : public CQikAppUi, public MMyObserver
{
public:
// from CQikAppUi
void ConstructL();
~CSamtalsNoteringAppUi();
void HandleContactLookupComplete(const TSamtalsInfo& aSamtalsInfo);
KOD SLUT
Implementationen av callback-funktionen ser ut så här. Vi kommer att återkomma till iDialogHandler.
KODSTART
void CSamtalsNoteringAppUi::HandleContactLookupComplete(const TSamtalsInfo& aSamtalsInfo)
{
//Aktivera vår egen applikations vy så vår app får fokus:
TVwsViewId viewId(KUidSamtalsNoteringApp,KUidSamtalsNoteringView);
TRAPD(err, ActivateViewL(viewId));
iDialogHandler->Start(aSamtalsInfo);
//Börja lyssna efter nästa samtal.
iTeleMonitor->Start();
}
KOD SLUT
Till slut måste vi se till att callback-funktionen anropas från CMyActive klassen efter att vår sökning lyckats.
KOD START
if (idArray->Count() >= 1)
{
//1 eller flera träffar
iSamtalsInfo.iCntId = (*idArray)[0];
//Anropa vår observer = AppUi-objektet
iObserver->HandleContactLookupComplete(iSamtalsInfo);}
KOD SLUT
En enkel (asynkron) dialog
Vi definierar ytterligare en klass som är ett aktivt objekt. Denna klass följer samma mönster som CMyActive. Hur den ser ut kan du se i exempel3.txt
I Start metoden kommer vi att dra igång en dialogbox som inte blockerar vårt program. När användaren stänger dialogen med någon knapp så kommer vår RunL metod att anropas.
KOD START
void CAsyncDialogHandler::Start(const TSamtalsInfo& aSamtalsInfo)
{
//Lagra undan samtalsinfo i objektets medlemsvariabel.
iSamtalsInfo = aSamtalsInfo;
if (!IsActive())
//Om dialogen fortfarande visas för föregående samtal, så struntar vi att ta hand om //nästa samtal....kan göras bättre såklart
{
iStatus = KRequestPending; //Workaround pga ofullkomlighet i UIQ3 Simple dialog
iDialog = new (ELeave) CQikSimpleDialog(); //Skapa dialog objekt
iDialog->PrepareL(R_DLG_OK_CANCEL); //Läs in layout från resursfil
iDialog->RunL(iStatus); //Kör/visa dialogen, metoden returnerar omedelbart
SetActive();
}
}
KOD SLUT
Dialogens utseende definieras som en struktur, R_DLG_OK_CANCEL, i resursfilen SamtalsNotering.rss, se källkoden på vår hemsida, i filen exempel 4
Uppdatera information i kontaktdatabasen
Metoden CreateCallLogTextL skapar en textsträng som innehåller tidpunkt för samtalet, samtalets längd, inkommande eller utgående samtal och den text som användaren matat in i dialogboxen. UpdateContactNotesFieldL tar sedan texten och lägger in den under rätt kontakt. Se exempel5.txt
I och med att vi ändrar data i kontakter så måste vi ge vår applikation ytterligare en capability, WriteUserData.