Package phones :: Module com_lglx570
[hide private]
[frames] | no frames]

Source Code for Module phones.com_lglx570

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2008 Joe Pham <djpham@bitpim.org> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: com_lglx570.py 4695 2008-08-20 22:29:12Z djpham $ 
  9   
 10  """Communicate with the LG LX570 (Muziq) cell phone""" 
 11   
 12  import common 
 13  import com_brew 
 14  import com_lg 
 15  import com_lgvx4400 
 16  import p_lglx570 
 17  import prototypes 
 18  import helpids 
 19  import sms 
 20   
 21  #------------------------------------------------------------------------------- 
 22  parentphone=com_lgvx4400.Phone 
23 -class Phone(com_brew.RealBrewProtocol2, parentphone):
24 "Talk to the LG LX570 (Muziq) cell phone" 25 26 desc="LG-LX570" 27 helpid=helpids.ID_PHONE_LGLX570 28 protocolclass=p_lglx570 29 serialsname='lglx570' 30 my_model='LX570' 31 32 builtinringtones=( 'Tone 1', 'Tone 2', 'Tone 3', 'Tone 4', 'Tone 5', 'Tone 6', 33 'Tone 7', 'Tone 8', 'Tone 9', 'Tone 10', 34 'Alert 1', 'Alert 2', 'Alert 3', 'Alert 4', 'Alert 5') 35 ringtonelocations=( 36 # offset, index file, files location, type, maximumentries 37 (0x1100, "setas/voicememoRingerIndex.map", "VoiceDB/All/Memos", "voice memo", 35), 38 (0x1200, "setas/mcRingerIndex.map", "melodyComposer", "my melodies", 20), 39 ) 40 builtinimages=() 41 imagelocations=( 42 # offset, index file, files location, type, maximumentries 43 (0x600, "setas/dcamIndex.map", "Dcam/Wallet", "images", 255), 44 ) 45 wallpaperdirs=('Dcam/Review', 'Dcam/Wallet') 46
47 - def __init__(self, logtarget, commport):
48 parentphone.__init__(self, logtarget, commport)
49 50 # supporting routines for getfundamentals
51 - def get_esn(self, data=None):
52 # return the ESN of this phone 53 return self.get_brew_esn()
54
55 - def getgroups(self, result):
56 self.log("Reading group information") 57 _buf=prototypes.buffer(self.getfilecontents2( 58 self.protocolclass.PB_FILENAME, 0x1E000, 2016)) 59 _groups={} 60 _grp=self.protocolclass.pbgroup() 61 while True: 62 _grp.readfrombuffer(_buf) 63 if _grp.valid: 64 _groups[_grp.groupid]={ 'name': _grp.name } 65 else: 66 break 67 result['groups']=_groups 68 return _groups
69 70 # Media stuff---------------------------------------------------------------
71 - def getwallpaperindices(self, results):
72 # index the list of files in known camera dirs 73 _res={} 74 _idx=1 75 for _dir in self.wallpaperdirs: 76 for _file in self.listfiles(_dir): 77 _res[_idx]={ 'name': common.basename(_file), 78 'filename': _file, 79 'origin': 'images' } 80 _idx+=1 81 results['wallpaper-index']=_res 82 return results
83
84 - def getwallpapers(self, result):
85 # retrieve all camera images 86 _media={} 87 for _wp in result.get('wallpaper-index', {}).values(): 88 _media[_wp['name']]=self.getfilecontents(_wp['filename'], True) 89 result['wallpapers']=_media 90 return result
91
92 - def getringtoneindices(self, results):
93 return self.getmediaindex(self.builtinringtones, 94 self.ringtonelocations, 95 results, 'ringtone-index')
96
97 - def _fix_melodies_filename(self, name):
98 # the "My Melodies" file name must have '.mid' extension and 99 # no other '.' character. 100 if name[-4:]=='.mid': 101 # name already has the correct extenstion 102 _new_name=name[:-4] 103 else: 104 _new_name=name 105 return _new_name.replace('.', '_')+'.mid'
106
107 - def _update_media_file(self, name, data):
108 # Check the if the size of the media file is different than the data 109 # and rewrite the file if necessary. 110 self.log('Updating media file: %s'%name) 111 _filename='%s/%s'%(self.protocolclass.RT_MC_PATH, name) 112 try: 113 _stat=self.statfile(_filename) 114 except com_brew.BrewNoSuchFileException: 115 _stat=None 116 if _stat and _stat.get('size', None)==len(data): 117 # file size and data size are the same, bail 118 self.log('File %s is unchanged'%_filename) 119 return 120 self.writefile(_filename, data)
121
122 - def saveringtones(self, results, merge):
123 # Saving My Melodies media files to be used as ringtones 124 _media_index=results.get('ringtone-index', {}) 125 _media=results.get('ringtone', {}) 126 # build a dict of my "my melodies" (file) name and data 127 _melodies_data={} 128 for _item in _media.values(): 129 if _item.get('origin', None)=='my melodies': 130 _melodies_data[self._fix_melodies_filename(_item['name'])]=_item['data'] 131 # read the existing index file 132 _indexfile=self.readobject(self.protocolclass.RT_MC_INDEX_FILENAME, 133 self.protocolclass.indexfile, 134 logtitle='Reading MC Index File', 135 uselocalfs=False) 136 # go through the index file and rewrite media files as needed 137 self.log('Updating media files') 138 for _idx in range(_indexfile.numactiveitems): 139 _item=_indexfile.items[_idx] 140 if _item.name and _melodies_data.has_key(_item.name): 141 # check the file size and rewrite the file as needed 142 self._update_media_file(_item.name, _melodies_data[_item.name]) 143 # remove the entry from the dict 144 del _melodies_data[_item.name] 145 # go through the index file and add new items 146 _empty_index=range(_indexfile.numactiveitems, len(_indexfile.items)) 147 _available_media=_melodies_data.keys() 148 self.log('Adding new media files') 149 for _idx, _name in zip(_empty_index, _available_media): 150 # update the index entry 151 _indexfile.items[_idx].name=_name 152 # write out the media file 153 self._update_media_file(_name, _melodies_data[_name]) 154 # update the count 155 _indexfile.numactiveitems+=1 156 # write the new index file 157 self.writeobject(self.protocolclass.RT_MC_INDEX_FILENAME, 158 _indexfile, logtitle='Writing new MC Index file', 159 uselocalfs=False) 160 # and re-read the index file 161 self.getringtoneindices(results) 162 return results
163 164 # Phonebook stuff-----------------------------------------------------------
165 - def _assignpbtypeandspeeddialsbyposition(self, entry, speeds, res):
166 # numbers 167 res['numbers']=[] 168 for i in range(self.protocolclass.NUMPHONENUMBERS): 169 num=entry.numbers[i].number 170 numtype=entry.numbertypes[i].numbertype 171 if len(num): 172 t=self.protocolclass.numbertypetab[numtype] 173 if t[-1]=='2': 174 t=t[:-1] 175 _numdict={ 'number': num, 'type': t } 176 if entry.speeddials[i].speeddial!=0xff: 177 _numdict['speeddial']=entry.speeddials[i].speeddial 178 res['numbers'].append(_numdict) 179 return res
180 181 # Copy this from the VX4400 module, with changes to support for 182 # different handling of speed dials data
183 - def savephonebook(self, data):
184 "Saves out the phonebook" 185 # we can't save groups 186 progressmax=len(data['phonebook'].keys()) 187 188 # To write the phone book, we scan through all existing entries 189 # and record their record number and serials. 190 # We then delete any entries that aren't in data 191 # We then write out our records, using overwrite or append 192 # commands as necessary 193 serialupdates=[] 194 existingpbook={} # keep track of the phonebook that is on the phone 195 self.mode=self.MODENONE 196 self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary 197 self.setmode(self.MODEPHONEBOOK) 198 # similar loop to reading 199 req=self.protocolclass.pbinforequest() 200 res=self.sendpbcommand(req, self.protocolclass.pbinforesponse) 201 numexistingentries=res.numentries 202 if numexistingentries<0 or numexistingentries>1000: 203 self.log("The phone is lying about how many entries are in the phonebook so we are doing it the hard way") 204 numexistingentries=0 205 firstserial=None 206 loop=xrange(0,1000) 207 hardway=True 208 else: 209 self.log("There are %d existing entries" % (numexistingentries,)) 210 progressmax+=numexistingentries 211 loop=xrange(0, numexistingentries) 212 hardway=False 213 progresscur=0 214 # reset cursor 215 self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse) 216 for i in loop: 217 ### Read current entry 218 if hardway: 219 numexistingentries+=1 220 progressmax+=1 221 req=self.protocolclass.pbreadentryrequest() 222 res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse) 223 224 entry={ 'number': res.entry.entrynumber, 'serial1': res.entry.serial1, 225 'serial2': res.entry.serial2, 'name': res.entry.name} 226 assert entry['serial1']==entry['serial2'] # always the same 227 self.log("Reading entry "+`i`+" - "+entry['name']) 228 if hardway and firstserial is None: 229 firstserial=res.entry.serial1 230 existingpbook[i]=entry 231 self.progress(progresscur, progressmax, "existing "+entry['name']) 232 #### Advance to next entry 233 req=self.protocolclass.pbnextentryrequest() 234 res=self.sendpbcommand(req, self.protocolclass.pbnextentryresponse) 235 progresscur+=1 236 if hardway: 237 # look to see if we have looped 238 if res.serial==firstserial or res.serial==0: 239 break 240 # we have now looped around back to begining 241 242 # Find entries that have been deleted 243 pbook=data['phonebook'] 244 dellist=[] 245 for i in range(0, numexistingentries): 246 ii=existingpbook[i] 247 serial=ii['serial1'] 248 item=self._findserial(serial, pbook) 249 if item is None: 250 dellist.append(i) 251 252 progressmax+=len(dellist) # more work to do 253 254 # Delete those entries 255 for i in dellist: 256 progresscur+=1 257 numexistingentries-=1 # keep count right 258 ii=existingpbook[i] 259 self.log("Deleting entry "+`i`+" - "+ii['name']) 260 req=self.protocolclass.pbdeleteentryrequest() 261 req.serial1=ii['serial1'] 262 req.serial2=ii['serial2'] 263 req.entrynumber=ii['number'] 264 self.sendpbcommand(req, self.protocolclass.pbdeleteentryresponse) 265 self.progress(progresscur, progressmax, "Deleting "+ii['name']) 266 # also remove them from existingpbook 267 del existingpbook[i] 268 269 # counter to keep track of record number (otherwise appends don't work) 270 counter=0 271 # Now rewrite out existing entries 272 keys=existingpbook.keys() 273 existingserials=[] 274 keys.sort() # do in same order as existingpbook 275 for i in keys: 276 progresscur+=1 277 ii=pbook[self._findserial(existingpbook[i]['serial1'], pbook)] 278 self.log("Rewriting entry "+`i`+" - "+ii['name']) 279 self.progress(progresscur, progressmax, "Rewriting "+ii['name']) 280 entry=self.makeentry(counter, ii, data) 281 counter+=1 282 existingserials.append(existingpbook[i]['serial1']) 283 req=self.protocolclass.pbupdateentryrequest() 284 req.entry=entry 285 res=self.sendpbcommand(req, self.protocolclass.pbupdateentryresponse) 286 serialupdates.append( ( ii["bitpimserial"], 287 {'sourcetype': self.serialsname, 'serial1': res.serial1, 'serial2': res.serial1, 288 'sourceuniqueid': data['uniqueserial']}) 289 ) 290 assert ii['serial1']==res.serial1 # serial should stay the same 291 292 # Finally write out new entries 293 keys=pbook.keys() 294 keys.sort() 295 for i in keys: 296 try: 297 ii=pbook[i] 298 if ii['serial1'] in existingserials: 299 continue # already wrote this one out 300 progresscur+=1 301 entry=self.makeentry(counter, ii, data) 302 counter+=1 303 self.log("Appending entry "+ii['name']) 304 self.progress(progresscur, progressmax, "Writing "+ii['name']) 305 req=self.protocolclass.pbappendentryrequest() 306 req.entry=entry 307 res=self.sendpbcommand(req, self.protocolclass.pbappendentryresponse) 308 serialupdates.append( ( ii["bitpimserial"], 309 {'sourcetype': self.serialsname, 'serial1': res.newserial, 'serial2': res.newserial, 310 'sourceuniqueid': data['uniqueserial']}) 311 ) 312 except: 313 self.log('Failed to write entry: '+ii['name']) 314 if __debug__: 315 raise 316 data["serialupdates"]=serialupdates 317 self.progress(progressmax, progressmax, "Rebooting phone") 318 data["rebootphone"]=True 319 return data
320
321 - def makeentry(self, counter, entry, data):
322 """Creates pbentry object 323 324 @param counter: The new entry number 325 @param entry: The phonebook object (as returned from convertphonebooktophone) that we 326 are using as the source 327 @param data: The main dictionary, which we use to get access to media indices amongst 328 other things 329 """ 330 e=self.protocolclass.pbentry() 331 e.entrynumber=counter 332 for k in entry: 333 # special treatment for lists 334 if k in ('emails', 'numbers', 'numbertypes', 'speeddials'): 335 l=getattr(e,k) 336 for item in entry[k]: 337 l.append(item) 338 elif k=='ringtone': 339 e.ringtone=self._findmediainindex(data['ringtone-index'], entry['ringtone'], entry['name'], 'ringtone') 340 elif k in e.getfields(): 341 # everything else we just set 342 setattr(e,k,entry[k]) 343 return e
344 345 # SMS Stuff ----------------------------------------------------------------
346 - def _getquicktext(self):
347 quicks=[] 348 try: 349 sf=self.readobject(self.protocolclass.SMS_CANNED_FILENAME, 350 self.protocolclass.sms_canned_file, 351 logtitle="SMS quicktext file sms/mediacan000.dat", 352 uselocalfs=False) 353 for rec in sf.msgs: 354 if rec.msg: 355 quicks.append({ 'text': rec.msg, 356 'type': sms.CannedMsgEntry.user_type }) 357 except com_brew.BrewNoSuchFileException: 358 pass # do nothing if file doesn't exist 359 return quicks
360
361 - def _getinboxmessage(self, sf):
362 entry=sms.SMSEntry() 363 entry.folder=entry.Folder_Inbox 364 entry.datetime="%d%02d%02dT%02d%02d%02d" % (sf.GPStime) 365 entry._from=self._getsender(sf.sender, sf.sender_length) 366 entry.subject=sf.subject 367 entry.locked=sf.locked 368 entry.priority=sms.SMSEntry.Priority_Normal 369 entry.read=sf.read 370 entry.text=sf.msg 371 entry.callback=sf.callback 372 return entry
373
374 - def _getoutboxmessage(self, sf):
375 entry=sms.SMSEntry() 376 entry.folder=entry.Folder_Saved if sf.saved \ 377 else entry.Folder_Sent 378 entry.datetime="%d%02d%02dT%02d%02d00" % ((sf.timesent)) 379 # add all the recipients 380 for r in sf.recipients: 381 if r.number: 382 confirmed=(r.status==2) 383 confirmed_date=None 384 if confirmed: 385 confirmed_date="%d%02d%02dT%02d%02d00" % r.time 386 entry.add_recipient(r.number, confirmed, confirmed_date) 387 entry.subject=sf.msg[:28] 388 entry.text=sf.msg 389 entry.priority=entry.Priority_High if sf.priority else \ 390 entry.Priority_Normal 391 entry.locked=sf.locked 392 entry.callback=sf.callback 393 return entry
394
395 - def _readsms(self):
396 res={} 397 # go through the sms directory looking for messages 398 for item in self.listfiles("sms").values(): 399 folder=None 400 for f,pat in self.protocolclass.SMS_PATTERNS.items(): 401 if pat.match(item['name']): 402 folder=f 403 break 404 if folder: 405 buf=prototypes.buffer(self.getfilecontents(item['name'], True)) 406 self.logdata("SMS message file " +item['name'], buf.getdata()) 407 if folder=='Inbox': 408 sf=self.protocolclass.sms_in() 409 sf.readfrombuffer(buf, logtitle="SMS inbox item") 410 entry=self._getinboxmessage(sf) 411 res[entry.id]=entry 412 elif folder=='Sent': 413 sf=self.protocolclass.sms_out() 414 sf.readfrombuffer(buf, logtitle="SMS sent item") 415 entry=self._getoutboxmessage(sf) 416 res[entry.id]=entry 417 418 return res
419
420 - def _setquicktext(self, result):
421 sf=self.protocolclass.sms_canned_file() 422 quicktext=result.get('canned_msg', [])[:self.protocolclass.SMS_CANNED_MAX_ITEMS] 423 if quicktext: 424 for entry in quicktext: 425 msg_entry=self.protocolclass.sms_quick_text() 426 msg_entry.msg=entry['text'][:self.protocolclass.SMS_CANNED_MAX_LENGTH-1] 427 sf.msgs.append(msg_entry) 428 self.writeobject(self.protocolclass.SMS_CANNED_FILENAME, 429 sf, logtitle="Writing quicktext", 430 uselocalfs=False)
431 432 #------------------------------------------------------------------------------- 433 parentprofile=com_lgvx4400.Profile
434 -class Profile(parentprofile):
435 protocolclass=Phone.protocolclass 436 serialsname=Phone.serialsname 437 438 BP_Calendar_Version=3 439 phone_manufacturer='LG Electronics Inc' 440 phone_model='LX570' 441 442 # Need to update this 443 WALLPAPER_WIDTH=176 444 WALLPAPER_HEIGHT=220 445 # outside LCD: 128x160 446 447 imageorigins={} 448 imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
449 - def GetImageOrigins(self):
450 return self.imageorigins
451 452 ringtoneorigins=('my melodies', 'voice memo') 453 excluded_ringtone_origins=('ringers', 'sounds', 'my melodies', 'voice memo') 454 excluded_wallpaper_origins=('images',) 455 456 # our targets are the same for all origins 457 # Need to work the correct resolutions 458 imagetargets={} 459 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper", 460 {'width': 176, 'height': 220, 'format': "JPEG"})) 461 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd", 462 {'width': 128, 'height': 160, 'format': "JPEG"})) 463
464 - def GetTargetsForImageOrigin(self, origin):
465 return self.imagetargets
466 467
468 - def convertphonebooktophone(self, helper, data):
469 """Converts the data to what will be used by the phone 470 471 @param data: contains the dict returned by getfundamentals 472 as well as where the results go""" 473 results={} 474 475 self.normalisegroups(helper, data) 476 477 for pbentry in data['phonebook']: 478 if len(results)==self.protocolclass.NUMPHONEBOOKENTRIES: 479 break 480 e={} # entry out 481 entry=data['phonebook'][pbentry] # entry in 482 try: 483 # serials 484 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', 0) 485 serial2=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial2', serial1) 486 487 e['serial1']=serial1 488 e['serial2']=serial2 489 for ss in entry["serials"]: 490 if ss["sourcetype"]=="bitpim": 491 e['bitpimserial']=ss 492 assert e['bitpimserial'] 493 494 # name 495 e['name']=helper.getfullname(entry.get('names', []),1,1,22)[0] 496 497 # categories/groups 498 cat=helper.makeone(helper.getcategory(entry.get('categories', []),0,1,22), None) 499 if cat is None: 500 e['group']=0 501 else: 502 key,value=self._getgroup(cat, data['groups']) 503 if key is not None: 504 e['group']=key 505 else: 506 # sorry no space for this category 507 e['group']=0 508 509 # email addresses 510 emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.NUMEMAILS,48) 511 e['emails']=helper.filllist(emails, self.protocolclass.NUMEMAILS, "") 512 513 # url 514 e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "") 515 516 # memo (-1 is to leave space for null terminator - not all software puts it in, but we do) 517 e['memo']=helper.makeone(helper.getmemos(entry.get('memos', []), 0, 1, self.protocolclass.MEMOLENGTH-1), "") 518 519 # phone numbers 520 # there must be at least one email address or phonenumber 521 minnumbers=1 522 if len(emails): minnumbers=0 523 numbers=helper.getnumbers(entry.get('numbers', []),minnumbers,self.protocolclass.NUMPHONENUMBERS) 524 e['numbertypes']=[] 525 e['numbers']=[] 526 e['speeddials']=[] 527 for numindex in range(len(numbers)): 528 num=numbers[numindex] 529 # deal with type 530 b4=len(e['numbertypes']) 531 type=num['type'] 532 for i,t in enumerate(self.protocolclass.numbertypetab): 533 if type==t: 534 # some voodoo to ensure the second home becomes home2 535 if i in e['numbertypes'] and t[-1]!='2': 536 type+='2' 537 continue 538 e['numbertypes'].append(i) 539 break 540 if t=='none': # conveniently last entry 541 e['numbertypes'].append(i) 542 break 543 if len(e['numbertypes'])==b4: 544 # we couldn't find a type for the number 545 helper.add_error_message('Number %s (%s/%s) not supported and ignored.'% 546 (num['number'], e['name'], num['type'])) 547 continue 548 # deal with number 549 number=self.phonize(num['number']) 550 if len(number)==0: 551 # no actual digits in the number 552 continue 553 if len(number)>48: # get this number from somewhere sensible 554 # ::TODO:: number is too long and we have to either truncate it or ignore it? 555 number=number[:48] # truncate for moment 556 e['numbers'].append(number) 557 # deal with speed dial 558 sd=num.get("speeddial", -1) 559 if self.protocolclass.NUMSPEEDDIALS: 560 if sd>=self.protocolclass.FIRSTSPEEDDIAL and sd<=self.protocolclass.LASTSPEEDDIAL: 561 e['speeddials'].append(sd) 562 else: 563 e['speeddials'].append(0xff) 564 565 if len(e['numbers'])<minnumbers: 566 # we couldn't find any numbers 567 # for this entry, so skip it, entries with no numbers cause error 568 helper.add_error_message("Name: %s. No suitable numbers or emails found" % e['name']) 569 continue 570 e['numbertypes']=helper.filllist(e['numbertypes'], 5, 0) 571 e['numbers']=helper.filllist(e['numbers'], 5, "") 572 e['speeddials']=helper.filllist(e['speeddials'], 5, 0xff) 573 574 # ringtones, wallpaper 575 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None) 576 577 578 results[pbentry]=e 579 580 except helper.ConversionFailed: 581 continue 582 583 data['phonebook']=results 584 return data
585 586 _supportedsyncs=( 587 ('phonebook', 'read', None), # all phonebook reading 588 ## ('calendar', 'read', None), # all calendar reading 589 ('wallpaper', 'read', None), # all wallpaper reading 590 ('ringtone', 'read', None), # all ringtone reading 591 ## ('call_history', 'read', None),# all call history list reading 592 ('sms', 'read', None), # all SMS list reading 593 ('memo', 'read', None), # all memo list reading 594 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 595 ## ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 596 ## ('wallpaper', 'write', 'MERGE'), # merge and overwrite wallpaper 597 ## ('wallpaper', 'write', 'OVERWRITE'), 598 ('ringtone', 'write', 'MERGE'), # merge and overwrite ringtone 599 ## ('ringtone', 'write', 'OVERWRITE'), 600 ('sms', 'write', 'OVERWRITE'), # all SMS list writing 601 ('memo', 'write', 'OVERWRITE'), # all memo list writing 602 ## ('playlist', 'read', 'OVERWRITE'), 603 ## ('playlist', 'write', 'OVERWRITE'), 604 ) 605 606 field_color_data={ 607 'phonebook': { 608 'name': { 609 'first': 1, 'middle': 1, 'last': 1, 'full': 1, 610 'nickname': 0, 'details': 1 }, 611 'number': { 612 'type': 5, 'speeddial': 5, 'number': 5, 613 'details': 5, 614 'ringtone': False, 'wallpaper': False }, 615 'email': 3, 616 'email_details': { 617 'emailspeeddial': False, 'emailringtone': False, 618 'emailwallpaper': False }, 619 'address': { 620 'type': 0, 'company': 0, 'street': 0, 'street2': 0, 621 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0, 622 'details': 0 }, 623 'url': 1, 624 'memo': 1, 625 'category': 1, 626 'wallpaper': 0, 627 'ringtone': 0, 628 'storage': 0, 629 }, 630 'calendar': { 631 'description': False, 'location': False, 'allday': False, 632 'start': False, 'end': False, 'priority': False, 633 'alarm': False, 'vibrate': False, 634 'repeat': False, 635 'memo': False, 636 'category': False, 637 'wallpaper': False, 638 'ringtone': False, 639 }, 640 'memo': { 641 'subject': False, 642 'date': False, 643 'secret': False, 644 'category': False, 645 'memo': True, 646 }, 647 'todo': { 648 'summary': False, 649 'status': False, 650 'due_date': False, 651 'percent_complete': False, 652 'completion_date': False, 653 'private': False, 654 'priority': False, 655 'category': False, 656 'memo': False, 657 }, 658 }
659