palm-os-sdk/PalmOne/Samples/TickStock/Src/TicksTock.c

952 lines
25 KiB
C

/******************************************************************************
* Copyright (c) 2006 Palm, Inc. or its subsidiaries.
* All rights reserved.
*****************************************************************************/
/**
* @file TicksTock.c
* @brief Source code for the TickStock sample application.
*
* TickStock is a sample application that demonstrates:
*
* - mainly, how to detect and handle incoming call interruption
* - how to detect if device is using EvDO connection
* - downloading data while keeping UI responsive
* - implementing [background] HTTP request as a state machine
*
* The application connects to Yahoo! Finance website and download
* the HTTP content until it finds the latest price of the queried
* stock.
*
* Based on BGStockTicker sample code.
*/
#include <PalmOS.h>
#include <PalmOSGlue.h>
#include <HsNav.h>
#include <HsExt.h>
#include <HsPhone.h>
#include "TicksTock.h"
#include "TicksTock_Rsc.h"
/*********************************************************************
* Entry Points
*********************************************************************/
#define kAppGlobalFtrID 0x00 // store globals as feature memory
#define kUpdateSuspendedFtrID 0x02 // store suspended state as feature
#define kDisplayLatestPriceUpdateCode 0xFF // custom form redraw code
/*********************************************************************
* Global variables
*********************************************************************/
// Different types of data services
const Char services[6][6] = {"None", "CSD", "1xRTT", "EVDO", "GPRS", "EDGE"};
/*********************************************************************
* Internal Constants
*********************************************************************/
/* Define the minimum OS version we support */
#define ourMinVersion sysMakeROMVersion(3,0,0,sysROMStageDevelopment,0)
#define kPalmOS20Version sysMakeROMVersion(2,0,0,sysROMStageDevelopment,0)
/*********************************************************************
* Internal Functions
*********************************************************************/
static void DisplayProgressRect(Char *progressStr, UInt16 count)
{
RectangleType rect = {{42, 120}, {76, 20}};
Char str[256] = "";
WinEraseRectangleFrame(roundFrame, &rect);
WinEraseRectangle(&rect, 0);
// StrPrintF(str, "%s %d", progressStr, count); // Print with retry count
StrPrintF(str, "%s", progressStr);
WinPaintRectangleFrame(roundFrame, &rect);
WinPaintChars(str, StrLen(str), 52, 124);
return;
}
static void DisplayNetworkStatus()
{
UInt16 phnRefNum = sysInvalidRefNum;
PhnDataSessionStateType state;
RectangleType rect = {{133, 1}, {27, 12}};
Err error = errNone;
UInt32 deviceID = 0;
// Only implemented starting from Treo 700p
FtrGet(sysFtrCreator, sysFtrNumOEMDeviceID, &deviceID);
if (deviceID != 'D052' /* Check device id in palmOneResources.h */)
{
// device is not running on a Treo 700p
return;
}
HsGetPhoneLibrary(&phnRefNum);
PhnLibOpen(phnRefNum);
// Must fill out the version
MemSet(&state, sizeof(PhnDataSessionStateType), 0);
state.version = PhnDataSessionStateTypeVersion;
error = PhnLibGetDataSessionInfo(phnRefNum, &state);
PhnLibClose(phnRefNum);
if (error)
{
WinEraseRectangle(&rect, 0);
WinDrawChars("Unknown", StrLen("Unknown"), 133, 1);
}
else if (state.sessionStatus != phnPacketDataSessionNone)
{
WinEraseRectangle(&rect, 0);
WinDrawChars(services[state.sessionType], StrLen(services[state.sessionType]), 133, 1);
}
return;
}
// Look for "Last Trade: " string and parse out the bold HTML tag.
// The price should follow after.
static Boolean ParseHTTPContent(MyAppGlobalType *globals)
{
Char *pStart = NULL;
Char *pEnd = NULL;
Char *ptr = NULL;
ptr = StrStr(globals->recvBuf.ptr, "Last Trade:");
if(!ptr) return false;
ptr = StrStr(ptr, "<b>");
ptr += 3; // move pointer to the end of <b>
pStart = ptr; // point to start of price string
pEnd = StrStr(ptr, "</b>");
if(!pEnd) return false;
*pEnd = 0; // assign \0 to the end of price string
StrCopy(globals->data[globals->company].price, pStart);
return true;
}
#pragma mark -
static Err UpdateGlobals(MyAppGlobalType *globals)
{
MemPtr pData = NULL;
Err error = errNone;
error = FtrGet(appFileCreator, kAppGlobalFtrID, (UInt32 *)&pData);
if (error || !pData)
{
error = FtrPtrNew(appFileCreator, kAppGlobalFtrID, sizeof(MyAppGlobalType), &pData);
}
if (pData)
DmWrite(pData, 0, globals, sizeof(MyAppGlobalType));
return error;
}
static Err GetGlobals(MyAppGlobalType *globals)
{
Err error = errNone;
MemPtr pData = NULL;
MyAppGlobalType newGlobals;
MemSet(&newGlobals, sizeof(MyAppGlobalType), 0);
error = FtrGet(appFileCreator, kAppGlobalFtrID, (UInt32 *)&pData);
if (error || !pData)
{
error = FtrPtrNew(appFileCreator, kAppGlobalFtrID, sizeof(MyAppGlobalType), &pData);
if (pData)
{
DmWrite(pData, 0, &newGlobals, sizeof(MyAppGlobalType));
*globals = newGlobals;
}
}
else
{
if (!globals) ErrFatalDisplay("No container for global data");
if (!pData) ErrFatalDisplay("pData = NULL");
MemMove(globals, pData, sizeof(MyAppGlobalType));
}
return error;
}
static Err SetFieldTextFromStr(FieldPtr field, Char *s, Boolean redraw)
{
MemHandle h;
h = FldGetTextHandle(field);
if(h)
{
Err err;
FldSetTextHandle(field, NULL);
err = MemHandleResize(h, StrLen(s)+1);
if(err!=errNone)
{
FldSetTextHandle(field, h);
return err;
}
}
else
{
h = MemHandleNew(StrLen(s)+1);
if(!h)
return memErrNotEnoughSpace;
}
StrCopy((Char *)MemHandleLock(h), s);
MemHandleUnlock(h);
FldSetTextHandle(field, h);
if(redraw)
FldDrawField(field);
return errNone;
}
/*
* FUNCTION: GetObjectPtr
*
* DESCRIPTION:
*
* This routine returns a pointer to an object in the current form.
*
* PARAMETERS:
*
* formId
* id of the form to display
*
* RETURNED:
* address of object as a void pointer
*/
static void * GetObjectPtr(UInt16 objectID)
{
FormType * frmP;
frmP = FrmGetActiveForm();
return FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, objectID));
}
#pragma mark -
/***********************************************************************
*
* FUNCTION: PrvCommonInitGauge
*
* DESCRIPTION: Call this function on form open to initialize the radio and
* battery status gadgets.
* .
*
* PARAMETERS: frm
*
* RETURNED: nothing
*
*
* REVISION HISTORY:
* Name Date Description
* ---- ---- -----------
* vmk 1/30/02 Created -- based on David Matiskella's code
* in Soho.
*
***********************************************************************/
static void PrvCommonInitGauge( FormPtr frm )
{
UInt32 hsStatusVersion;
if (FtrGet (hsFtrCreator, hsFtrIDStatusGadgetRev, &hsStatusVersion) == 0)
{
HsStatusSetGadgetType (frm, NetworkBatteryGadget, hsStatusGadgetBattery);
HsStatusSetGadgetType (frm, NetworkSignalGadget, hsStatusGadgetSignal);
}
} // CommonInitGauge
/*
* FUNCTION: MainFormInit
*
* DESCRIPTION: This routine initializes the MainForm form.
*
* PARAMETERS:
*
* frm
* pointer to the MainForm form.
*/
static void MainFormInit(FormType *frmP)
{
ControlType *pCheckbox = NULL;
MyAppGlobalType globals;
GetGlobals(&globals);
pCheckbox = GetObjectPtr(MainAutoUpdateCheckbox);
CtlSetValue(pCheckbox, globals.prefs.auto_update);
return;
}
/*
* FUNCTION: MainFormDoCommand
*
* DESCRIPTION: This routine performs the menu command specified.
*
* PARAMETERS:
*
* command
* menu item id
*/
static Boolean MainFormDoCommand(UInt16 command)
{
Boolean handled = false;
UInt16 cardNo;
LocalID dbID;
DmSearchStateType searchState;
DmGetNextDatabaseByTypeCreator(true, &searchState, sysFileTApplication,
appFileCreator, true, &cardNo, &dbID);
switch (command)
{
case OptionsAboutTicksTock:
{
MenuEraseStatus(0);
HsAboutHandspringApp(cardNo, dbID, "2007", "Palm DTS Team");
handled = true;
break;
}
}
return handled;
}
/*
* FUNCTION: MainFormHandleEvent
*
* DESCRIPTION:
*
* This routine is the event handler for the "MainForm" of this
* application.
*
* PARAMETERS:
*
* eventP
* a pointer to an EventType structure
*
* RETURNED:
* true if the event was handled and should not be passed to
* FrmHandleEvent
*/
static Boolean MainFormHandleEvent(EventType * eventP)
{
Boolean handled = false;
FormType * frmP;
MyAppGlobalType globals;
GetGlobals(&globals);
switch (eventP->eType)
{
// Process HTTP request and response during nilEvent
case nilEvent:
return HTTPHandleEvent();
case menuEvent:
return MainFormDoCommand(eventP->data.menu.itemID);
case frmOpenEvent:
frmP = FrmGetActiveForm();
FrmSetFocus(frmP, noFocus);
FrmSetFocus(frmP, FrmGetObjectIndex(frmP, MainRefreshButton));
FrmDrawForm(frmP);
MainFormInit(frmP);
// Initialize the HTTP state to:
// - No state
// - Retry count is 0
// - First company stock to query is 'Palm'
globals.HTTPState = HTTPStateNone;
globals.stateCount = 0;
globals.company = Palm;
PrvCommonInitGauge(frmP);
handled = true;
break;
case frmUpdateEvent:
if (eventP->data.frmUpdate.formID == MainForm &&
eventP->data.frmUpdate.updateCode == kDisplayLatestPriceUpdateCode)
{
FieldType *pPalmFld = GetObjectPtr(MainPalmField);
FieldType *pYhooFld = GetObjectPtr(MainYhooField);
FieldType *pGoogFld = GetObjectPtr(MainGoogField);
FieldType *pMsftFld = GetObjectPtr(MainMsftField);
FieldType *pIntcFld = GetObjectPtr(MainIntcField);
frmP = FrmGetActiveForm();
FrmDrawForm(frmP);
DisplayNetworkStatus();
SetFieldTextFromStr(pPalmFld, globals.data[Palm].price, true);
SetFieldTextFromStr(pYhooFld, globals.data[Yahoo].price, true);
SetFieldTextFromStr(pGoogFld, globals.data[Google].price, true);
SetFieldTextFromStr(pMsftFld, globals.data[Microsoft].price, true);
SetFieldTextFromStr(pIntcFld, globals.data[Intel].price, true);
handled = true;
}
break;
case ctlSelectEvent:
{
if (eventP->data.ctlSelect.controlID == MainRefreshButton)
{
if (globals.HTTPState == HTTPStateNone)
{
// Change HTTP state to Init to start initialization,
// after which the HTTP request is made
globals.HTTPState = HTTPStateInit;
globals.stateCount = 0;
globals.company = Palm;
handled = true;
}
}
else if (eventP->data.ctlSelect.controlID == MainAutoUpdateCheckbox)
{
globals.prefs.auto_update =
(CtlGetValue(eventP->data.ctlSelect.pControl))?true:false;
handled = true;
}
break;
}
case appStopEvent:
// If application is quitting, and state machine is still running,
// close down and clean up as necessary
if (globals.HTTPState != HTTPStateNone)
{
if (globals.netLib.socket) CloseSocket(globals.netLib.refNum, globals.netLib.socket);
if (globals.netLib.refNum) CloseNetLib(globals.netLib.refNum);
}
break;
default:
break;
}
if (handled)
UpdateGlobals(&globals);
return handled;
}
#pragma mark -
Boolean HTTPHandleEvent()
{
Boolean handled = true;
Err error = errNone;
MyAppGlobalType globals;
Boolean done = false;
UInt32 suspended = 0;
HTTPStateType state;
GetGlobals(&globals);
state = globals.HTTPState;
// Check whether the state is currently suspended.
FtrGet(appFileCreator, kUpdateSuspendedFtrID, &suspended);
if (suspended)
{
state = HTTPStateSuspended;
}
else
globals.stateCount++;
switch (state)
{
case HTTPStateInit:
// Load NetLib and create socket. If success, clear retry count and
// proceed to connect
globals.netLib.loaded = InitNetLib(&globals.netLib.refNum, &error);
if (!error)
{
DisplayNetworkStatus();
error = OpenSocket(globals.netLib.refNum, &globals.netLib.socket, globals.prefs.blocking);
if (!error)
{
globals.HTTPState = HTTPStateConnect;
globals.stateCount = 0;
}
}
break;
case HTTPStateConnect:
// Connect to Yahoo! Finance website. If connected, clear retry count and
// proceed to send request
DisplayProgressRect("Connecting", globals.stateCount);
error = ConnectSocket(globals.netLib.refNum, globals.netLib.socket, QUOTE_SERVER);
if (!error)
{
globals.HTTPState = HTTPStateSendRequest;
globals.stateCount = 0;
}
break;
case HTTPStateSendRequest:
{
// Send request string to webserver. When done, clear retry count and
// proceed to receive response
const Char symbols[5][5] = {"PALM", "YHOO", "GOOG", "MSFT", "INTC"};
Char data[64] = "";
UInt16 size = 0;
DisplayProgressRect("Sending", globals.stateCount);
StrPrintF(data, QUOTE_CMD, symbols[globals.company]);
size = StrLen(data);
done = SendData(globals.netLib.refNum, globals.netLib.socket, data, &size, &error);
if (done)
{
globals.HTTPState = HTTPStateRecvHeader;
globals.stateCount = 0;
}
break;
}
case HTTPStateRecvHeader:
// Don't care about this for now; initiate download by de-initializing
// the receive buffer so that it can be renewed later
globals.recvBuf.ptr = NULL;
globals.recvBuf.offset = 0;
globals.recvBuf.size = 0;
globals.HTTPState = HTTPStateRecvContent;
globals.stateCount = 0;
break;
case HTTPStateRecvContent:
{
// Download and wait for data until one of the following is true:
// - If buffer full, reset buffer and ask for more data
// - If connection is closed remotely, parse data and move to close state
// - If received data contains what we need, parse data and move to close state
Boolean complete = false;
DisplayProgressRect("Receiving", globals.stateCount);
done = RecvData(globals.netLib.refNum, globals.netLib.socket,
(void **)&globals.recvBuf.ptr, &globals.recvBuf.offset, &globals.recvBuf.size, &error);
// Peek into buffer and check if it already contains the price data
complete = ParseHTTPContent(&globals);
if (complete || done)
{
globals.HTTPState = HTTPStateClose;
globals.stateCount = 0;
}
// Clean up if buffer is full or if request is complete
if (complete || done || globals.recvBuf.size == 0)
{
MemPtrFree(globals.recvBuf.ptr);
globals.recvBuf.ptr = NULL;
}
break;
}
case HTTPStateClose:
// Close socket and check if this is the last company stock
// that we want to query. If not, open socket and connect again.
// Otherwise, clean up and close NetLib and display the latest prices
DisplayProgressRect("Closing", globals.stateCount);
CloseSocket(globals.netLib.refNum, globals.netLib.socket);
if ((globals.company + 1) == CompanyCount)
{
CloseNetLib(globals.netLib.refNum);
globals.HTTPState = HTTPStateDone;
globals.stateCount = 0;
}
else
{
// Get latest stock price for the next company
globals.company++;
error = OpenSocket(globals.netLib.refNum, &globals.netLib.socket, globals.prefs.blocking);
if (!error)
{
globals.HTTPState = HTTPStateConnect;
globals.stateCount = 0;
}
else
{
globals.HTTPState = HTTPStateError;
globals.stateCount = 0;
globals.error = error;
}
}
break;
case HTTPStateDone:
// At this point, the latest prices of all companies have
// been received
FrmUpdateForm(MainForm, kDisplayLatestPriceUpdateCode);
globals.HTTPState = HTTPStateNone;
globals.stateCount = 0;
break;
case HTTPStateError:
{
Char errStr[8] = "";
StrPrintF(errStr, "0x%x", globals.error);
FrmCustomAlert(ErrorAlert, "Error code: ", errStr, 0);
globals.HTTPState = HTTPStateNone;
globals.stateCount = 0;
break;
}
case HTTPStateSuspended:
// If connection is suspended, clean up and close socket and NetLib.
// It is up to applications how this should be handled. If the server is
// capable of maintaining state of data transfer, then instead of cleaning up
// application should save the state somewhere here.
//
// Please note that no data can be sent or received once the phone has switched to
// data call.
DisplayProgressRect("Cleaning up", globals.stateCount);
if (globals.netLib.socket) CloseSocket(globals.netLib.refNum, globals.netLib.socket);
if (globals.netLib.refNum) CloseNetLib(globals.netLib.refNum);
globals.HTTPState = HTTPStateNone;
globals.stateCount = 0;
break;
default:
handled = false;
break;
}
if (handled)
{
UpdateGlobals(&globals);
}
return handled;
}
void PhoneHandleEvent(PhnEventType *pEvent)
{
UInt32 suspended = 0;
switch(pEvent->eventType)
{
case phnEvtStartIncomingCall:
// Set HTTP state to be suspended because there is an incoming call
FtrSet(appFileCreator, kUpdateSuspendedFtrID, 0xFFFF);
DisplayProgressRect("Incoming call!", 0);
// One last call to HTTPHandleEvent() for clean-up
// Next event in the event queue will be the appStopEvent
// to switch to the phone app
HTTPHandleEvent();
break;
case phnEvtDisconnectConf:
case phnEvtDisconnectInd:
// If call is disconnected, and the app is in suspended mode, re-launch
// and retry/continue download
FtrGet(appFileCreator, kUpdateSuspendedFtrID, &suspended);
if (suspended)
{
UInt16 cardNo = 0;
LocalID dbID = 0;
FtrSet(appFileCreator, kUpdateSuspendedFtrID, (UInt32) 0);
SysCurAppDatabase(&cardNo, &dbID);
SysUIAppSwitch(cardNo, dbID, sysAppLaunchCmdNormalLaunch, NULL);
}
break;
}
return;
}
#pragma mark -
/*
* FUNCTION: AppHandleEvent
*
* DESCRIPTION:
*
* This routine loads form resources and set the event handler for
* the form loaded.
*
* PARAMETERS:
*
* event
* a pointer to an EventType structure
*
* RETURNED:
* true if the event was handled and should not be passed
* to a higher level handler.
*/
static Boolean AppHandleEvent(EventType * eventP)
{
UInt16 formId;
FormType * frmP;
if (eventP->eType == frmLoadEvent)
{
/* Load the form resource. */
formId = eventP->data.frmLoad.formID;
frmP = FrmInitForm(formId);
FrmSetActiveForm(frmP);
/*
* Set the event handler for the form. The handler of the
* currently active form is called by FrmHandleEvent each
* time is receives an event.
*/
switch (formId)
{
case MainForm:
FrmSetEventHandler(frmP, MainFormHandleEvent);
break;
}
return true;
}
return false;
}
/*
* FUNCTION: AppEventLoop
*
* DESCRIPTION: This routine is the event loop for the application.
*/
static void AppEventLoop(void)
{
UInt16 error;
EventType event;
do
{
//Slow down! So that data transfer is more interruptible
//by phone call. This is used for DEMO purposes only!
//Process network event every 3 secs
EvtGetEvent(&event, SysTicksPerSecond() * 3);
if (! SysHandleEvent(&event))
{
if (! MenuHandleEvent(0, &event, &error))
{
if (! AppHandleEvent(&event))
{
FrmDispatchEvent(&event);
}
}
}
} while (event.eType != appStopEvent);
}
/*
* FUNCTION: AppStart
*
* DESCRIPTION: Get the current application's preferences.
*
* RETURNED:
* errNone - if nothing went wrong
*/
static Err AppStart(void)
{
MyAppGlobalType globals;
UInt16 prefsSize;
UInt16 phnRefNum = 0;
Err err = errNone;
GetGlobals(&globals);
/* Read the saved preferences / saved-state information. */
prefsSize = sizeof(globals.prefs);
if (PrefGetAppPreferences(
appFileCreator, appPrefID, &globals.prefs, &prefsSize, true) ==
noPreferenceFound)
{
/* no prefs; initialize pref struct with default values */
globals.prefs.blocking = false;
globals.prefs.timeout = -1;
globals.prefs.auto_update = false;
}
// Register for phone events:
err=HsGetPhoneLibrary(&phnRefNum);
PhnLibOpen(phnRefNum);
PhnLibRegister(phnRefNum, appFileCreator, phnServiceAll);
PhnLibClose(phnRefNum);
UpdateGlobals(&globals);
return err;
}
/*
* FUNCTION: AppStop
*
* DESCRIPTION: Save the current state of the application.
*/
static void AppStop(void)
{
MyAppGlobalType globals;
GetGlobals(&globals);
/*
* Write the saved preferences / saved-state information. This
* data will be saved during a HotSync backup.
*/
PrefSetAppPreferences(
appFileCreator, appPrefID, appPrefVersionNum,
&globals.prefs, sizeof(globals.prefs), true);
/* Close all the open forms. */
FrmCloseAllForms();
UpdateGlobals(&globals);
}
#pragma mark -
/*
* FUNCTION: RomVersionCompatible
*
* DESCRIPTION:
*
* This routine checks that a ROM version is meet your minimum
* requirement.
*
* PARAMETERS:
*
* requiredVersion
* minimum rom version required
* (see sysFtrNumROMVersion in SystemMgr.h for format)
*
* launchFlags
* flags that indicate if the application UI is initialized
* These flags are one of the parameters to your app's PilotMain
*
* RETURNED:
* error code or zero if ROM version is compatible
*/
static Err RomVersionCompatible(UInt32 requiredVersion, UInt16 launchFlags)
{
UInt32 romVersion;
/* See if we're on in minimum required version of the ROM or later. */
FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
if (romVersion < requiredVersion)
{
if ((launchFlags &
(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) ==
(sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
{
FrmAlert (RomIncompatibleAlert);
/* Palm OS versions before 2.0 will continuously relaunch this
* app unless we switch to another safe one. */
if (romVersion < kPalmOS20Version)
{
AppLaunchWithCommand(
sysFileCDefaultApp,
sysAppLaunchCmdNormalLaunch, NULL);
}
}
return sysErrRomIncompatible;
}
return errNone;
}
/*
* FUNCTION: PilotMain
*
* DESCRIPTION: This is the main entry point for the application.
*
* PARAMETERS:
*
* cmd
* word value specifying the launch code.
*
* cmdPB
* pointer to a structure that is associated with the launch code
*
* launchFlags
* word value providing extra information about the launch.
*
* RETURNED:
* Result of launch, errNone if all went OK
*/
UInt32 PilotMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
Err error;
error = RomVersionCompatible (ourMinVersion, launchFlags);
if (error) return (error);
switch (cmd)
{
case sysAppLaunchCmdNormalLaunch:
error = AppStart();
if (error)
return error;
/*
* start application by opening the main form
* and then entering the main event loop
*/
FrmGotoForm(MainForm);
AppEventLoop();
AppStop();
break;
case phnLibLaunchCmdEvent:
PhoneHandleEvent((PhnEventType *)cmdPBP);
break;
}
return errNone;
}