/** * \file SimpleSMS.c * * SimpleSMS * * This is the main source file for the Simple SMS App. This * code demonstrates how to write your own SMS application. The app * will register with the Phone Library, send a custom message, and * display a dialog for incoming messages. * * \license * * Copyright (c) 2002 Handspring Inc., All Rights Reserved * * $Id:$ * ***************************************************************/ #include #include #include #include #include "SimpleSMS.h" // ------------------------------------------------------------- // Constants // ------------------------------------------------------------- #define appFileCreator 'HSMS' #define appVersionNum 0x01 #define appPrefID 0x00 #define appPrefVersionNum 0x01 #define kMinVersionSupported 0x3523040 // ------------------------------------------------------------- // Structures // ------------------------------------------------------------- #define serviceCenterSize 40 #define addressSize 40 #define messageSize 160 typedef struct { char address[addressSize + 1]; char message[messageSize + 1]; } SimpleSMSPreferenceType; typedef struct NBSNotificationEventType PhnNBSNotificationEventType; // ------------------------------------------------------------- // Globals // ------------------------------------------------------------- static SimpleSMSPreferenceType smsPrefs; static Boolean smsLibLoaded; static UInt16 smsLibRef; // ------------------------------------------------------------- // Prototypes // ------------------------------------------------------------- static Err StartApplication (void); static void EventLoop (void); static void StopApplication (void); static Boolean SimpleSMSEvent (EventPtr event); static UInt32 HandlePhoneLibraryEvent (MemPtr cmdPBP, UInt16 launchFlags); static Err LoadPhoneLibrary (UInt16* libRefP, Boolean* libLoadedP); static Err SMSRegister (Boolean bReg); static void SendTheMessage (void); static void DisplayMessage (UInt32 messageID); static void TurnOnOptionLock (void); static void* GetObjectPtr (UInt16 objectID); static void CopyFromGlobalToFields (void); static UInt32 CopyFromFieldsToGlobal (void); static void HandleNotificationEvent ( MemPtr cmdPBP, UInt16 launchFlags); // ------------------------------------------------------------- // Functions // ------------------------------------------------------------- /****************************************************************/ /* PilotMain */ /** * Main entry point * * \param cmd IN Value specifying the launch code. * \param cmdPBP IN Pointer to a structure that is associated with the launch code. * \param launchFlags IN Value providing extra information about the launch. * * \retval UInt32 * ****************************************************************/ UInt32 PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) { UInt32 error; switch (cmd) { case sysAppLaunchCmdNormalLaunch: // normal application launch error = StartApplication (); if (error) return error; EventLoop (); StopApplication (); break; case phnLibLaunchCmdRegister: // Received when device is reset (or the library is loaded ?) // DbgBreak(); // SMSRegister (true); break; case 0x7000: // For Treo 300 workaround. Currently the launch code is different on Treo 300 than 180/270 case phnLibLaunchCmdEvent: // we have an event from the Phone library, so handle it error = HandlePhoneLibraryEvent (cmdPBP, launchFlags); if (error) return error; break; case sysAppLaunchCmdNotify: HandleNotificationEvent (cmdPBP, launchFlags); break; default: break; } return errNone; } /****************************************************************/ /* StartApplication */ /** * Application start * * \retval Err * ****************************************************************/ static Err StartApplication (void) { Err error = 0; char errMsg[10]; smsLibLoaded = false; smsLibRef = 0; #if 0 // This block is not really neccessary since we use the Handspring/Treo's phone libraries // whick guarantees the device is (should be) a Treo. UInt16 prefsSize; void* procP; UInt32 value; // Make sure that we are running on the Treo // Make sure that we are at the appropriate Palm OS version if (!FtrGet (hsFtrCreator, hsFtrIDVersion, &value)) { if (value < kMinVersionSupported) { FrmCustomAlert (resAlertPhoneLibrary, "PalmOS version too low", errMsg, NULL); return 1; } } else { FrmCustomAlert (resAlertPhoneLibrary, "No hsFtrIDVersion", errMsg, NULL); return 1; } // Make sure that we support the HsIndicator function call. // We use this test to make sure that we are on a Treo device. procP = HsGetTrapAddress (hsSelIndicator); if (procP && procP != HsGetTrapAddress(hsSelUnimplemented)) { // Read the saved preferences / saved-state information. prefsSize = sizeof(SimpleSMSPreferenceType); if (PrefGetAppPreferences (appFileCreator, appPrefID, &smsPrefs, &prefsSize, true) != noPreferenceFound) ; } else { // Display the error dialog and return with error FrmCustomAlert (resAlertPhoneLibrary, "No hsSelIndicator proc", errMsg, NULL); return 1; } #endif // load the Phone Library error = LoadPhoneLibrary (&smsLibRef, &smsLibLoaded); if (error) { StrIToA(errMsg, error); FrmCustomAlert (resAlertPhoneLibrary, "Can't Load the Phone Library", errMsg, NULL); return 1; } else { // We were able to load the library // Now, let's register with the SMS library error = SMSRegister (true); if (error) FrmAlert (resAlertCantRegisterWithSMSLibrary); } // verify the Phone is powered and SIM status is ready if ( PhnLibModulePowered (smsLibRef) == false) { FrmAlert (resAlertPhoneNotReady); if (smsLibLoaded) (void)PhnLibClose (smsLibRef); return 1; } // Check what device we are on: GSM or CDMA FrmGotoForm(resFormIDSimpleSMS); /* (void)HsAttrGet (hsAttrPhoneType, 0, &phoneType); if (phoneType == hsAttrPhoneTypeGSM) { FrmGotoForm (resFormIDSimpleSMS); } else if (phoneType == hsAttrPhoneTypeCDMA) { FrmGotoForm (resFormIDSimpleSMSNoSend); } else { // Default to the GSM (send/receive message) form. Pre-Treo 300 devices do not support this attribute. FrmGotoForm (resFormIDSimpleSMS); phoneType = hsAttrPhoneTypeGSM; } */ return error; } /****************************************************************/ /* EventLoop */ /** * Main event loop * * \retval none * ****************************************************************/ static void EventLoop(void) { UInt16 err; UInt16 formID; FormPtr formP; EventType event; do { EvtGetEvent (&event, sysTicksPerSecond/2); if (SysHandleEvent (&event)) continue; if (MenuHandleEvent ((void *)0, &event, &err)) continue; if (event.eType == frmLoadEvent) { formID = event.data.frmLoad.formID; formP = FrmInitForm (formID); FrmSetActiveForm (formP); switch (formID) { case resFormIDSimpleSMS: FrmSetEventHandler (formP, (FormEventHandlerPtr)SimpleSMSEvent); break; case resFormIDSimpleSMSNoSend: FrmSetEventHandler (formP, (FormEventHandlerPtr)SimpleSMSEvent); break; } } FrmDispatchEvent(&event); } while (event.eType != appStopEvent); } /****************************************************************/ /* StopApplication */ /** * Stop the application * * \retval none * ****************************************************************/ static void StopApplication (void) { //if (phoneType == hsAttrPhoneTypeGSM) //{ // CopyFromFieldsToGlobal(); //} // Write the saved preferences / saved-state information. This data // will be backed up during a HotSync. //PrefSetAppPreferences (appFileCreator, appPrefID, appPrefVersionNum, &smsPrefs, sizeof (smsPrefs), true); // Close all the open forms. FrmCloseAllForms (); // Release the SMS library if (smsLibLoaded) PhnLibClose(smsLibRef); } /****************************************************************/ /* SimpleSMSEvent */ /** * Event handler for the main form * * \param event IN Event to process * * \retval Boolean * ****************************************************************/ static Boolean SimpleSMSEvent (EventPtr eventP) { UInt16 err; UInt16 cardNo; FormPtr frmP; LocalID dbID; FieldPtr fieldP; DmSearchStateType searchState; Boolean handled = false; DmGetNextDatabaseByTypeCreator(true, &searchState, sysFileTApplication, appFileCreator, true, &cardNo, &dbID); switch (eventP->eType) { case menuEvent: switch (eventP->data.menu.itemID) { case resMenuItemAbout: MenuEraseStatus (0); HsAboutHandspringApp (cardNo, dbID, "2007", "palmOne DTS Team"); handled = true; break; case resMenuItemUnRegister: MenuEraseStatus (0); err = SMSRegister (false); if (err) FrmAlert (resAlertCantRegisterWithSMSLibrary); handled = true; break; } break; case frmOpenEvent: frmP = FrmGetActiveForm(); // Set the focus to the edit field FrmSetFocus(frmP, noFocus); FrmSetFocus(frmP, FrmGetObjectIndex(frmP, resButtonSend)); FrmDrawForm (frmP); if (FrmGetActiveFormID () == resFormIDSimpleSMS) { CopyFromGlobalToFields(); TurnOnOptionLock (); } else if (FrmGetActiveFormID () == resFormIDSimpleSMSNoSend) { } // Setup the battery and signal gadgets { UInt32 hsStatusVersion; if (FtrGet (hsFtrCreator, hsFtrIDStatusGadgetRev, &hsStatusVersion) == 0) { HsStatusSetGadgetType (frmP, resGadgetMainBatteryLevel, hsStatusGadgetBattery); HsStatusSetGadgetType (frmP, resGadgetSignalLevel, hsStatusGadgetSignal); } } handled = true; break; case ctlSelectEvent: switch (eventP->data.ctlSelect.controlID) { case resButtonSend: SendTheMessage(); break; default: break; } break; case fldEnterEvent: fieldP = (FieldType*)GetObjectPtr (eventP->data.fldEnter.fieldID); switch (eventP->data.fldEnter.fieldID) { case resFieldAddress: case resFieldServiceCenter: FldHandleEvent (fieldP, eventP); TurnOnOptionLock (); handled = true; break; case resFieldMessage: FldHandleEvent (fieldP, eventP); if (FldGetInsPtPosition(fieldP) == 0) HsGrfSetStateExt(false, false, false, true, false, true); handled = true; break; } break; default: break; } return handled; } /****************************************************************/ /* HandleNotificationEvent */ /** * This function handles all Phone specific events. * * \param cmdPBP IN Pointer to a structure that is associated with the launch code. * \param launchFlags IN Value providing extra information about the launch. * * \retval UInt32 - Result of launch * ****************************************************************/ static void HandleNotificationEvent ( MemPtr cmdPBP, UInt16 launchFlags) { SysNotifyParamType* notifyParam = (SysNotifyParamType*)cmdPBP ; launchFlags = launchFlags; // Prevent compiler error if (notifyParam->notifyType == phnNBSEvent) { PhnNBSNotificationEventType* NBSParamP = (PhnNBSNotificationEventType*) notifyParam->notifyDetailsP; // Display NBS info if (NBSParamP->binary) // NBS binary { ; } else // NBS text { char dstPort[20], srcPort[20]; // NBSParamP->refnum; // NBSParamP->maxnum; // NBSParamP->seqnum; // NBSParamP->srcPort; // NBSParamP->dstPort; // NBSParamP->senderP; // NBSParamP->datetime; StrIToA(dstPort, (long) NBSParamP->dstPort); StrIToA(srcPort, (long) NBSParamP->srcPort); StrCat(dstPort, "/"); StrCat(dstPort, srcPort); FrmCustomAlert(resAlertIncomingNBSMessage, NBSParamP->senderP, dstPort, NBSParamP->dataP); //Display like regular SMS. //DisplayMessage(NBSParamP->msgID); } notifyParam->handled = true; } } /****************************************************************/ /* HandlePhoneLibraryEvent */ /** * This function handles all Phone specific events. * * \param cmdPBP IN Pointer to a structure that is associated with the launch code. * \param launchFlags IN Value providing extra information about the launch. * * \retval UInt32 - Result of launch * ****************************************************************/ static UInt32 HandlePhoneLibraryEvent(MemPtr cmdPBP, UInt16 launchFlags) { PhnEventPtr eventP = (PhnEventPtr)cmdPBP; UInt32 flags; Err error = 0; UInt32 locked = 0; launchFlags = launchFlags; // Prevent compiler warning of unused var switch (eventP->eventType) { case phnEvtSegmentInd: case phnEvtMessageInd: // a message has been received DisplayMessage (eventP->data.params.id); // Tell the Phone Library that we handled the event and no // other app needs to know about this. eventP->acknowledge = true; HsAttrGet(hsAttrKeyboardLocked, 0, &locked); if (locked) HsAttrSet(hsAttrKeyboardLocked, 0, 0); break; case phnEvtMessageStat: // a message status has changed switch (eventP->data.params.newStatus) { case kSending: // this is an expected status, do nothing break; case kSent: // ok, did we successfully send the message? error = PhnLibGetFlags (smsLibRef, eventP->data.params.id, &flags); if (error) return error; if ((flags & kFailed) == 0) FrmAlert (resAlertSuccessfulTransfer); else FrmAlert (resAlertUnsuccessfulTransfer); break; default: break; } eventP->acknowledge = true; break; default: break; } return error; } /****************************************************************/ /* LoadPhoneLibrary */ /** * Get a libRef to the Phone library * * \param libRefP OUT Pointer to library reference ID to return. If NULL, * library isn't loaded and an error is returned. * \param libLoadedP INOUT Pointer to boolean indicating whether the library * had to be loaded or not. If the value is true on * entry, it will be left as true, even if the library * was found already loaded - this allows reuse of the * same boolean indicating load status throughout the * application. This routine NEVER sets this parameter * to false. * * \retval none * ****************************************************************/ static Err LoadPhoneLibrary(UInt16* libRefP, Boolean* libLoadedP) { Err error = 0; error = HsGetPhoneLibrary(libRefP); if (error) return error; error = PhnLibOpen(*libRefP); if (error) return error; *libLoadedP = true; return error; } /****************************************************************/ /* SMSRegister */ /** * This routine registers this application with the SMS library. * * \param bReg IN true to register; false to unregister * * \retval none * ****************************************************************/ static Err SMSRegister (Boolean bReg) { Boolean libLoaded = smsLibLoaded; Err error = 0; UInt16 libRef = smsLibRef; if (!libLoaded) error = LoadPhoneLibrary (&libRef, &libLoaded); if (!error) { // Register this application with the Phone library so it will receive the appropriate events error = PhnLibRegister (libRef, appFileCreator, bReg ? phnServiceSMS : 0); if (libLoaded) (void)PhnLibClose (libRef); } // Register NBS Notification { UInt16 cardNo; LocalID dbID; DmSearchStateType searchState; DmGetNextDatabaseByTypeCreator(true, &searchState, sysFileTApplication, appFileCreator, true, &cardNo, &dbID); if ( bReg) { error = SysNotifyRegister (cardNo, dbID, phnNBSEvent, NULL, sysNotifyNormalPriority, NULL); if (error == sysNotifyErrDuplicateEntry) // Already registered error = 0; // This is OK. Clear this error } else error = SysNotifyUnregister (cardNo, dbID, phnNBSEvent,sysNotifyNormalPriority); } return error; } /****************************************************************/ /* SendTheMessage */ /** * This routine will send the message * * \retval none * ****************************************************************/ static void SendTheMessage(void) { UInt32 msgID = 0; PhnAddressList addList; PhnAddressHandle addressH; Err error = 0; DmOpenRef smsRefNum = 0; // CDMA workaround // verify the Phone is powered if (!PhnLibModulePowered (smsLibRef)) { FrmAlert (resAlertPhoneNotReady); return; } // verify the Phone is on the network if (!PhnLibRegistered (smsLibRef)) { FrmAlert (resAlertNotRegistered); return; } smsRefNum = PhnLibGetDBRef(smsLibRef); // CDMA workaround // get the values from the fields into the globals // check if there is no address input. if not, do not send SMS and return. Modified by TaoC if (CopyFromFieldsToGlobal() == 1) { FrmAlert (resAlertNoAddressInput); return ; } // now, create the new message msgID = PhnLibNewMessage (smsLibRef, kMTOutgoing); if (!msgID) goto SendMessage_CloseAndRelease; // the the owner of this new message to this application PhnLibSetOwner (smsLibRef, msgID, appFileCreator); // fill in the text of this message PhnLibSetText (smsLibRef, msgID, smsPrefs.message, (short) StrLen(smsPrefs.message)); // fill in the address addList = PhnLibNewAddressList (smsLibRef); if (!addList) goto SendMessage_CloseAndRelease; addressH = PhnLibNewAddress (smsLibRef, smsPrefs.address, phnLibUnknownID); if (!addressH) goto SendMessage_CloseAndRelease; PhnLibAddAddress (smsLibRef, addList, addressH); MemHandleFree (addressH); PhnLibSetAddresses (smsLibRef, msgID, addList); // PhnLibDisposeAddressList (smsLibRef, addList); // and fire the message off! error = PhnLibSendMessage (smsLibRef, msgID, true); SendMessage_CloseAndRelease: PhnLibReleaseDBRef(smsLibRef, smsRefNum); // CDMA workaround } /****************************************************************/ /* DisplayMessage */ /** * This routine displays information about the given message * * \param messageID IN The message to display * * \retval none * ****************************************************************/ static void DisplayMessage(UInt32 messageID) { PhnAddressList addList; PhnAddressHandle addressH; MemHandle messageH; char* addressP; char* messageP; Err error = 0; DmOpenRef smsRefNum = 0; // CDMA workaround // we cannot use the global smsLibRef, // because this can be called when the application is not running UInt16 localSmsLibRef; Boolean localSmsLibLoaded; char errMsg[10]; UInt16 msgIndex; //Initialize some values localSmsLibLoaded = false; localSmsLibRef = 0; error = LoadPhoneLibrary (&localSmsLibRef, &localSmsLibLoaded); if (error) { StrIToA(errMsg, error); FrmCustomAlert (resAlertPhoneLibrary, "Can't Load the Phone Library", errMsg, NULL); return; } smsRefNum = PhnLibGetDBRef(localSmsLibRef); // CDMA workaround // get the address list error = PhnLibGetAddresses (localSmsLibRef, messageID, &addList); if (error) goto DisplayMessage_CloseAndRelease; // now get the first entry from the list error = PhnLibGetNth (localSmsLibRef, addList, 1, &addressH); if (error) goto DisplayMessage_CloseAndRelease; // now get the actual address addressP = PhnLibGetField (localSmsLibRef, addressH, phnAddrFldPhone); // and get the message text error = PhnLibGetText (localSmsLibRef, messageID, &messageH); if (error) goto DisplayMessage_CloseAndRelease; messageP = MemHandleLock (messageH); // display the incoming message alert FrmCustomAlert (resAlertIncomingMessage, addressP, messageP, NULL); // and clean up if (addressP) MemPtrFree (addressP); if (messageH) { MemHandleUnlock (messageH); MemHandleFree (messageH); } if (!DmFindRecordByID(smsRefNum, messageID, &msgIndex)) { DmRemoveRecord(smsRefNum, msgIndex); // CDMA workaround } else { FrmCustomAlert(resAlertIncomingMessage, "000000", "Record not found!", NULL); } DisplayMessage_CloseAndRelease: PhnLibReleaseDBRef(localSmsLibRef, smsRefNum); // CDMA workaround // unload if necessary if (localSmsLibLoaded) (void)PhnLibClose (localSmsLibRef); } /****************************************************************/ /* TurnOnOptionLock */ /** * Turn on the option lock on keyboard enabled devices. * * \param none * * \retval none * ****************************************************************/ static void TurnOnOptionLock (void) { // See if we are on a keyboard device and enable the option-lock UInt16 err; UInt32 keyboardType; // See if we have a keyboard err = FtrGet (hsFtrCreator, hsFtrIDTypeOfKeyboard, &keyboardType); if (!err && (keyboardType & hsFtrValKeyboardQwerty)) HsGrfSetStateExt (false, // capsLock false, // numLock true, // optLock false, // upperShift false, // optShift false); // autoShift } /****************************************************************/ /* GetObjectPtr */ /** * This routine returns a pointer to an object in the current form. * * \param formId IN ID of the form to display * * \retval void* - Pointer to the object * ****************************************************************/ static void* GetObjectPtr (UInt16 objectID) { FormPtr frmP; frmP = FrmGetActiveForm (); return FrmGetObjectPtr (frmP, FrmGetObjectIndex(frmP, objectID)); } /****************************************************************/ /* CopyFromGlobalsToFields */ /** * This routine will copy the text strings from the global prefs into * the text fields on the main form * * \param none * * \retval none * ****************************************************************/ static void CopyFromGlobalToFields (void) { Err error = 0; FieldPtr fld; MemHandle h, oldH; PhnAddressHandle addressH; CharPtr addressP; UInt32 phnType; HsAttrGet(hsAttrPhoneType, 0, &phnType); // Fill in the fields from the prefs fld = GetObjectPtr (resFieldAddress); h = MemHandleNew (StrLen(smsPrefs.address) + 1); if (!h) return; StrCopy (MemHandleLock (h), smsPrefs.address); MemHandleUnlock (h); oldH = FldGetTextHandle (fld); FldSetTextHandle (fld, h); FldDrawField (fld); if (oldH) MemHandleFree (oldH); fld = GetObjectPtr (resFieldMessage); h = MemHandleNew (StrLen (smsPrefs.message) + 1); if (!h) return; StrCopy (MemHandleLock (h), smsPrefs.message); MemHandleUnlock (h); oldH = FldGetTextHandle (fld); FldSetTextHandle (fld, h); FldDrawField (fld); if (oldH) MemHandleFree (oldH); if (phnType == hsAttrPhoneTypeGSM) { // fill in the service center from the Phone library fld = GetObjectPtr (resFieldServiceCenter); error = PhnLibGetServiceCentreAddress (smsLibRef, &addressH); if (!error && addressH) { addressP = PhnLibGetField (smsLibRef, addressH, phnAddrFldPhone); h = MemHandleNew (serviceCenterSize); if (!h) { MemHandleFree (addressH); MemPtrFree (addressP); return; } StrCopy (MemHandleLock (h), addressP); MemHandleUnlock (h); oldH = FldGetTextHandle (fld); FldSetTextHandle (fld, h); FldDrawField (fld); if (oldH) MemHandleFree (oldH); // clean up MemHandleFree (addressH); MemPtrFree (addressP); } } } /****************************************************************/ /* CopyFromFieldsToGlobal */ /** * This routine will copy the text strings from the text * fields on the main form into the global prefs. * * \param none * * \retval none - rev.1 * 0: success - rev.2 * 1: no address input added by TaoC * ****************************************************************/ static UInt32 CopyFromFieldsToGlobal (void) { FieldPtr fld; char* s; UInt16 len = 0; // Get the pref values from the fields fld = GetObjectPtr(resFieldAddress); s = FldGetTextPtr(fld); if (s) { // check if user has enter an address. if not, then do not send SMS. Added by TaoC len = StrLen(s); if ( len > 0) // copy contents of address field into prefs StrCopy(smsPrefs.address, s); else return 1 ; } fld = GetObjectPtr(resFieldMessage); s = FldGetTextPtr(fld); if (s) { // copy contents of message field into prefs StrCopy(smsPrefs.message, s); } return 0; }