Tutorial: Python For S60

Agathe Battestini
Nokia Research Center - Palo Alto

Dec 2nd, 2008

Agathe.Battestini@gmail.com

TOC
• Introduction: phones, download, installation, SDK • PyS60 phone modules • Extensions, Security platform • PythonForS60 for middleware development Videos of all the test scripts: http://agathe.blip.tv/
Dec 2nd, 2008 Agathe.Battestini@gmail.com

Python For S60
• Available for Symbian S60 phones
3rd Ed 3rd Ed FP1

3250, 5500 Sport, E50, E60, E61, E62, E65, E70, N71, N73, N75, N77, N80, N91, N91 8GB, N92, N93 5700 XpressMusic, 6110 Navigator, 6120 Classic, 6121 Classic, 6124 Classic, 6290, E51, E66, E71, E90 Communicator, N76, N81, N81 8GB, N82, N95, N95 8GB 5320 XpressMusic, 6210 Navigator, 6220 Classic, 6650, N78, N79, N85, N96 6600, 3230, 6260, 6620, 6670, 7610 6630, 6680, 6681, 6682
N70, N72, N90

3rd Ed FP2 2nd Ed FP1 2nd Ed FP2 2nd Ed FP3

http://en.wikipedia.org/wiki/Nokia_Series_60
Dec 2nd, 2008 Agathe.Battestini@gmail.com

Download
• http://sourceforge.net/projects/pys60/

Documentation: • http://downloads.sourceforge.net/pys60/Pyth

Dec 2nd, 2008

Agathe.Battestini@gmail.com

PythonS60
• Open Source • Is based on Python interpreter 2.2.2 • Latest version of PythonS60: 1.4.4 (June 2008) • Release 2-3 times a year or more

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Installation on the phone
• Install the Python S60 interpreter .sis
QuickTimeª see this picture. TIFF (LZW) decompressor are needed toand a

PythonForS60_1_4_4_3rdEd.sis

• Install the Python Shell.sis
QuickTimeª see this picture. TIFF (LZW) decompressor are needed toand a

PythonScriptShell_1_4_4_3rdEd.SIS

The shell sis has to be signed e.g to access location/positioning modules

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Windows Emulator
• Download S60 Platform SDKs for Symbian
or Nokia Forum → Tools and SDK → Symbian/C++ tools

http://www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af06

• Download SDK Python For S60
http://sourceforge.net/projects/pys60/

• You can run Python scripts in the emulator

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Firefox emulator + lighttpd (experimental)
• Improve development cycles by being able to run a PyS60 code on the destop • Similar to PDIS compatibility library http://sourceforge.net/projects/pys60-compat/ • Works with SCGI server and Lighttpd • The PyS60 runs in the browser
Dec 2nd, 2008 Agathe.Battestini@gmail.com

QuickTimeª and a TIFF (LZW) decompressor are needed to see this picture.

... PyS60 modules...

Dec 2nd, 2008

Agathe.Battestini@gmail.com

location
import location cell_info = location.gsm_location() if 4==len(cell_info): mcc,mnc,lac,cid = cell_info

import location, e32 cell_info = location.gsm_location() while 1: if 4==len(cell_info): mcc,mnc,lac,cid = cell_info e32.sleep(60*2)

Need location capability

Exercise: write function to get location, display the country (from the list of MCCs), and in menu: items start / stop
Dec 2nd, 2008 Agathe.Battestini@gmail.com

positioning
import positioning requestor = {'type':'service', 'format':'application', 'data':'myappname'} positioning.set_requestors([requestor]) pos = positioning.position(course=1, satellites=1) if pos and pos.has_key('position'): lat = pos['position']['latitude'] lon = pos['position']['longitude'] print "GPS:", lat, lon

Need location capability

Exercise: poll positioning every x seconds, write into a file
Dec 2nd, 2008 Agathe.Battestini@gmail.com

inbox (to read SMS)
import inbox received_box = inbox.Inbox(inbox.EInbox) sent_box = inbox.Inbox(inbox.ESent) msg_ids = received_box.sms_messages() msg_ids.sort() # ascending order id = msg_ids[0] # get the first one (the oldest SMS) sms = {'content' : received_box.content(id), 'date' : received_box.time(id), 'address' : received_box.address(id), 'status' : received_box.unread(id), 'id' : id, } print "The oldest SMS I received: ", sms

Exercise: Backup messages in a file ; bag of words (per person, in total) ; display all messages by one person ; search in messages
Dec 2nd, 2008 Agathe.Battestini@gmail.com

inbox (new SMS callback)
import inbox received_box = inbox.Inbox(inbox.EInbox) def callback(new_id): sms = {'content' : received_box.content(new_id), 'date' : received_box.time(new_id), 'address' : received_box.address(new_id), 'status' : received_box.unread(new_id), 'id' : new_id, } print "You got a message!" received_box.bind(callback)

Exercise: answer automatically an SMS if it comes from specific contacts and contains specific words
Dec 2nd, 2008 Agathe.Battestini@gmail.com

messaging (to send SMS)
import messaging contact_number = "+16501112222" contact_name = "John" gps = (50.87780, 4.70382) message = u("Hey, I'm in Belgium. Exactely here: %s"%gps) messaging.sms_send(contact_number, message, name=contact_name) print "Message was sent"

Exercise: send SMS with callback, send MMS with attached picture
Dec 2nd, 2008 Agathe.Battestini@gmail.com

contacts (phonebook)
import contacts # Backup all contacts as vcards, in a text file cdb = contacts.open() ids = cdb.keys() f = open('./contacts_backup.txt', 'w+') for id in ids: vcard = cdb.export_vcards( [id,]) info = { 'vcard' : vcard, 'date.modif' : cdb[id].last_modified, 'id' : id, } f.write(repr(info)) f.write("\n") f.close()

Exercise: read backup file, read vcards with vobject package http://vobject.skyhouseconsulting.com/; clean up contacts
Dec 2nd, 2008 Agathe.Battestini@gmail.com

calendar
import calendar, time cdb = calendar.CalendarDb() print "There are %s calendar entries "%len(cdb) # Get all entries until now + one month one_week_in_seconds = 60*60*24*7 entries = cdb.find_instances(time.time()-one_week_in_seconds, time.time()+one_week_in_seconds) one_id = entries[0] one = cdb[one_id['id']] print "one_id, one for el in ['type' ,'alarm', 'id', 'content', 'end_time', 'last_modified', 'location', 'originating', 'priority', 'replication', 'start_time',]: print "%s: "%el, one.__getattribute__(el) vcal = cdb.export_vcalendars((one_id['id'], ))

Exercise: read vcal with vobject package http://vobject.skyhouseconsulting.com/;
Dec 2nd, 2008 Agathe.Battestini@gmail.com

appuifw (popup things)
import appuifw for typ in ['text', 'code', 'number', 'float', 'date', 'time', 'query',]: res = appuifw.query(u"Give me a %s"%typ, typ) print res options = map ( lambda x : u'Option %s'%x, range(0,5)) res = appuifw.popup_menu(options, u"Which option do you want?") appuifw.note(u"You have selected %s (%s) "%(options[res], res), 'info')

res = appuifw.selection_list(options, search_field=0) print res res = appuifw.multi_query(u"This", u"That") options = map ( lambda x : u'Option %s'%x, range(0,100)) res = appuifw.multi_selection_list(options, style='checkbox', search_field=1) print res appuifw.note(u"Uh uh this does not work: %s (%s) "%(options[res[0]], res[0]), 'error')

Exercise: randomly ask what the user is doing and log
Dec 2nd, 2008 Agathe.Battestini@gmail.com

appuifw (app)
import appuifw t = appuifw.Text() t.add(u"You can display things here, or let the user write: \n") appuifw.app.body = t full_s = t.get() t.clear() print t.font print appuifw.available_fonts() appuifw.app.title = u"Py app" appuifw.app.screen = 'full'

# 'normal' , 'large', 'full'

def do_something(s='?'): t.add(u" %s \n\n"%s) appuifw.app.menu=[ (u"Print 1" , lambda: do_something(1) ), (u"Print ?" , do_something ), (u"Do nothing", lambda: None ), ]

Exercise: app that shows a random selection of pictures, SMS
Dec 2nd, 2008 Agathe.Battestini@gmail.com

appuifw (a template)
import appuifw, e32 class MyApp: def __init__(self): self.lock = e32.Ao_lock() appuifw.app.title = u"My App" # create UI elements self.lb = appuifw.Listbox( [u"Item 1", u"Item 2"], self.lb_callback) self.activate() def activate(self): appuifw.app.menu=[ (u"Help", lambda: None), (u"Exit", self.key_exit), ] appuifw.app.exit_key_handler = self.key_exit appuifw.app.body = self.lb def key_exit(self): self.lock.signal() def lb_callback(self): i = self.lb.current() appuifw.note(u"List callback selection: %s"%i, 'info') def run(self): self.lock.wait() if __name__=="__main__": global myapp e32.ao_yield() myapp = MyApp() myapp.run()

Dec 2nd, 2008

Agathe.Battestini@gmail.com

appuifw (Listbox)
import appuifw # List with a single line def callback(): current_i = lb.current() appuifw.note(u"'%s' (%s) was selected"%(l[current_i], current_i), 'info') l = [ u"Thing %s"%i for i in range(0, 10) ] lb = appuifw.Listbox(l, callback) appuifw.app.body = lb

# List with double line l = [ (u"Thing %s"%i, u"with more info about thing %s"%i) for i in range(0, 10) ] lb = appuifw.Listbox(l, callback) appuifw.app.body = lb

Dec 2nd, 2008

Agathe.Battestini@gmail.com

key_codes (binding key events)
import key_codes, appuifw # You can delete things off the list def delete_callback(): current_i = lb.current() l.pop(current_i) if len(l)==0: l.append(u"Nothing") # you cannot have an empty list if len(l)>current_i-1 and current_i-1>=0: lb.set_list(l, current_i-1) else: lb.set_list(l) l = [ u"Thing %s"%i for i in range(0, 10) ] lb = appuifw.Listbox(l, callback) lb.bind(key_codes.EStdKeyHome, delete_callback) # it's the 'C - delete' key appuifw.app.body = lb

Dec 2nd, 2008

Agathe.Battestini@gmail.com

keycapture (capturing global key events)
import keycapture, appuifw, e32, key_codes # create a dict of the integer code values, and the name of the key code values_keynames = dict( [ [val, key] for key, val in key_codes.__dict__.items() if key[0]=='E']) t = appuifw.Text(u"Press a key\n") appuifw.app.body = t def callback(key): t.clear() t.add(u"\nKey pressed: keycodes.%s (%s)"%(values_keynames[key], key)) capturer = keycapture.KeyCapturer(callback) capturer.keys = keycapture.all_keys capturer.forwarding = 0 capturer.start() e32.ao_sleep(20) capturer.stop()

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Canvas
import appuifw # draw once c = appuifw.Canvas() appuifw.app.body = c c.line([0,0, 200, 250], outline=0x00ff00)

# redrawn after the application screen was hidden by something def redraw(area_coords): c.rectangle([0,0, c.size[0], c.size[1]], fill=0x88eeee) c.line([0,0, 200, 250], outline=0x00ff00, width=3) c = appuifw.Canvas(redraw) appuifw.app.body = c

Exercise: use the other functions - ellipse, arc, polygon, point, text, clear
Dec 2nd, 2008 Agathe.Battestini@gmail.com

Canvas and graphics
import appuifw, graphics c = appuifw.Canvas() appuifw.app.body = c # draw on a buffer buf = graphics.Image.new(c.size) im = graphics.Image.open("C:\\Data\\myapp\\miata.png") buf.clear() buf.blit(im) buf.line([0,0, im.size[0],im.size[1]], outline=0xff0000, width=10) buf.line([0,im.size[1],im.size[0], 0], outline=0xff0000, width=10) buf.text([100, 220], u"SOLD", font=(appuifw.available_fonts()[0], 20,graphics.FONT_BOLD)) c.blit(buf)

Exercise: add a redraw callback ; take a screenshot
Dec 2nd, 2008 Agathe.Battestini@gmail.com

Content_handler
import appuifw # Create a simple html file open("C:/Data/myapp/waffles.html", "w+").write( """ <html> <body><h1>Best Waffles are in Belgium</h1><img src='http://static.flickr.com/97/265787242_a8d2018f53.jpg' /> </body></html> """) ch = appuifw.Content_handler() # This opens the default web browser because it's a .html file ch.open(u"C:\\Data\\myapp\\waffles.html")

Exercise: pass a callback to Content_handler; use the open_standalone function; download favorite URLs for offline browsing;
Dec 2nd, 2008 Agathe.Battestini@gmail.com

camera (pictures)
import camera, appuifw # Take a photo and display it appuifw.app.body = appuifw.Canvas() image = camera.take_photo() appuifw.app.body.blit(image)

# Use the viewfinder def callback(image): appuifw.app.body.blit(image) camera.start_finder(callback) camera.release() camera.stop_finder()

Exercise: bind a key event to a function that takes the photo and saves it in a file
Dec 2nd, 2008 Agathe.Battestini@gmail.com

camera (videos)
import camera, appuifw, e32 appuifw.app.body = appuifw.Canvas() def callback(image): appuifw.app.body.blit(image) camera.start_finder(callback) def video_cb(code, status): print code, status fn = "C:\\Data\\myapp\\video01.mp4" camera.start_record(fn,video_cb) e32.ao_sleep(10) # make a 10 seconds video camera.stop_record() camera.release() camera.stop_finder()

Exercise: upload the video
Dec 2nd, 2008 Agathe.Battestini@gmail.com

telephone
import telephone, e32 telephone.dial(u"+16505758524") e32.ao_sleep(20) # gives some time to the other person to answer telephone.hang_up()

Exercise: Implement the SOS phone call - 2 features: - The application listens for incoming SMS, if a new SMS includes 'SOS call me', a call is made to the corresponding number - Click 'send SOS', an SMS is sent to your emergency contact with 'SOS call me'
Dec 2nd, 2008 Agathe.Battestini@gmail.com

audio
import audio, e32 # Play an mp3 file fn = u"C:\\Data\\myapp\\secosmic_lo.mp3" sound = audio.Sound.open(fn) sound.play(times=1) e32.ao_sleep(sound.duration()/1000000 + 3) # convert in s + 3s sound.close() # text to speech audio.say(u"Hello! How are you?")

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

audio
import audio, e32, os, random, time random.seed(time.time()) dir = u"C:\\Data\\myapp\\wavs\\" list_wavs=[f for f in os.listdir(dir) if f[-4:]==".wav"] sounds = [audio.Sound.open(dir+fn) for fn in list_wavs] for i in range(0, 30): r_i = int(random.random() * len(sounds)) if sounds[r_i].state() != audio.EOpen: continue sounds[r_i].play(1) e32.ao_sleep(random.random()*2) for s in sounds: s.close()

Exercise: record the music at the same time ; use the accelerometer as input
Dec 2nd, 2008 Agathe.Battestini@gmail.com

e32
import e32, appuifw e32.ao_sleep(2) # in seconds. time.sleep(2) would make the UI freeze too inactivity_in_seconds = e32.inactivity() e32.reset_inactivity() # copy a file: (target, source), and only \\ as separator e32.file_copy('E:\\'+random_script, 'C:\\Python\\'+random_script) # let the user select a drive i = appuifw.popup_menu(e32.drive_list(), u"Select where to put the secret files") appuifw.note(u"You have selected %s"% e32.drive_list()[i], 'info') # Other functions are available for f in dir(e32): print f

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

simple serialization (conf, data files)
import os, os.path FILE_PATH = "C:/Data/myapp" user_conf = {} # empty or with default values def open_config_file(): conf_f = os.path.join(FILE_PATH, "user.conf") if os.path.exists(conf_f): s = open(conf_f, 'r').read() if len(s)>0: user_conf.update(eval(s)) def write_config_file(): conf_f = os.path.join(FILE_PATH, "user.conf") open(conf_f, 'w+').write(repr(user_conf)) if __name__=="__main__": open_config_file() user_conf['login'] = 'email@gmail.com' write_config_file()

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

sensor (log sensor data)
import sensor, appuifw, e32 FILENAME = "C:/Data/myapp/sensor_data.txt" global data, gesture, sensor def callback_acc(val): data.append( [val['data_1'], val['data_2'], val['data_3'] ]) def stop_acc(): sensor.disconnect() f = open(FILENAME, "ab+") f.write(repr( {'gesture' : gesture, 'data': data } )) f.write("\n") ; f.close() def get_gesture_data(): global data, gesture data = [] gesture = appuifw.query(u"Information about the recorded data", 'text') sensor.connect(callback_acc) e32.ao_sleep(2) # get data for 2s stop_acc() acc_info = sensor.sensors()['AccSensor'] sensor = sensor.Sensor(acc_info['id'], acc_info['category']) get_gesture_data()

Exercise: process the data to correlate gesture and accelerometer
Dec 2nd, 2008 Agathe.Battestini@gmail.com

logs
import logs # easy access to the phone logs for typ in ['call', 'sms', 'data', 'fax', 'email', 'scheduler']: data = logs.log_data(typ) print "\n%s logs: "%typ if len(data)>0: print len(data), data[0] else: print 0

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

urllib
-standard Python moduleimport urllib, appuifw f = urllib.urlopen("http://www.google.com") html = f.read() # post GET data to an online image collection keyword = appuifw.query(u"Keyword for the image search:", 'text') keyword = keyword and keyword or 'ship' params = urllib.urlencode({'f' : 'search', 'txt' : '%s'%keyword, 'w':'1', 'x' : '0', 'y' : '0'}) f = urllib.urlopen("http://www.sxc.hu/browse.phtml?"+params) html = f.read() print "Length of the HTML file ", len(html)

Exercise: Save the html locally, open it in the browser (using Content_handler) ; Parse the html
Dec 2nd, 2008 Agathe.Battestini@gmail.com

ftplib (upload the video)
-standard Python moduleimport ftplib fn = "C:\\Data\\myapp\\video01.mp4" f = open(fn, 'rb') host = "ftp.blip.tv" user = "pymobmid" pwd = "pymid" ftp = ftplib.FTP(host) ftp.login(user, pwd) ftp.storbinary("STOR video01.mp4", f, 1024) ftp.quit() f.close()

ftplib is not installed by default. Copy ftplib.py to the phone.

Exercise: upload the video
Dec 2nd, 2008 Agathe.Battestini@gmail.com

socket (Bluetooth)
import socket, appuifw # Send a file to a Bluetooth device selected_bt = socket.bt_obex_discover() fn = u"C:\\Data\\contacts_backup.txt" socket.bt_obex_send_file(selected_bt[0], selected_bt[1].values()[0], fn)

# List of Bluetooth devices list_bt = socket.bt_discover()

Exercise: Create a BT server For better BT support, see http://lightblue.sourceforge.net/
Dec 2nd, 2008 Agathe.Battestini@gmail.com

socket (inet)
-modified standard Python# localserver.py import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind( ("127.0.0.1", 18000) ) s.listen(1) conn, addr = s.accept() while 1: data = conn.read(1024) if not data: break question = eval(data) conn.send( repr({'result': eval(question['exp'])})) conn.close() # Start server with: e32.start_server(u"C:\\Data\\myapp\\localserver.py") # localclient.py import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 18000)) s.send( repr({'exp': '10*80'})) data = s.read(1024) print "Result", eval(data) s.close()

Exercise: make the server more robust, handle more commands
Dec 2nd, 2008 Agathe.Battestini@gmail.com

sysinfo (phone params)
import sysinfo info = {'imei' 'battery.level' 'active.profile' 'ring.type' 'signal' 'signal.bars' 'ram.total' 'ram.free' # 'ram.max' 'rom' 'space.free' 'display.pixels' 'display.twips' 'sw.version' } : : : : : : : : : : : : : : sysinfo.imei(), sysinfo.battery(), sysinfo.active_profile(), sysinfo.ring_type(), sysinfo.signal_dbm(), sysinfo.signal_bars(), sysinfo.total_ram(), sysinfo.free_ram(), sysinfo.max_ramdrive_size(), sysinfo.total_rom(), sysinfo.free_drivespace(), sysinfo.display_pixels(), sysinfo.display_twips(), sysinfo.sw_version(),

Exercise: log battery level and activity
Dec 2nd, 2008 Agathe.Battestini@gmail.com

_ _builtins_ _
-standard Python module# get the list of the methods (for objects), or functions available in modules print dir(__builtins__) print dir(''), dir([]), dir({}) # call a script: import os random_script = os.listdir('C:/Python')[2] print "We are calling ", random_script execfile(u'C:/Python/'+random_script) # local variables are stored in a dict returned by locals() print locals() city = 'Leuven' country = 'belgium' print "I am in %(city)s, in %(country)s"%({'city' : city, 'country': country}) # or simpler: print "I am in %(city)s, in %(country)s"%locals()

Exercise: map, filter, lambda, globals, unicode, hex, chr, int, ...
Dec 2nd, 2008 Agathe.Battestini@gmail.com

... modules presented here
e32 sysinfo appuifw graphics camera sensor audio telephone messaging inbox location positioning contacts logs keycapture calendar # # # # # # # # # # # # # # # # symbian OS functions/class system info GUI framework images, drawing to take photos or videos access to the phone sensors play mp3, wav, record audio dial, hang up send, receive SMS access to the messaging inbox, sent boxes cellid location GPS location access to the phone book access to the phone's logs capture global key events access to the calendar

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

... modules not presented here
globalui topwindow gles, glcanvas e32db, e32dbm # # # # notifiers for global events to show windows on top of the others open GL bindings interface to the symbian native database

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

... About PyS60...

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Availability of standard Python modules in PyS60
PY : Python module PYD: DLL (C/C++)module built-in: C/C++ module X : included in PyS60 (X): not included but works both on phone and SDK

QuickTimeª and a TIFF (LZW) decompressor are needed to see this picture.

QuickTimeª and a TIFF (LZW) decompressor are needed to see this picture.

Exercise:
Dec 2nd, 2008 Agathe.Battestini@gmail.com

How to extend PyS60
1. Try to import / run the Python files on the phone
• Use files from Python 2.2.2 / 2.2.3 distribution Sometime existing extensions do that for you Otherwise, it requires installing the Symbian SDK + PyS60 SDK and writing Symbian C++ or OpenC code

Create a PyS60 extension (Symbian DLL .pyd)
• •

http://wiki.opensource.nokia.com/projects/PyS60_extensions

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Advantages of PyS60
• All advantages of Python
– Good support for client-server communications – Dynamic programming, etc

• Connectivity: WIFI, GPRS/3G, Bluetooth • Stable, access to a lot of the phone features • A good choice for prototyping
– And products: www.iyouit.eu

• If you do not need advanced GUIs or a look&feel different from basic S60: PyS60 is an excellent choice

Dec 2nd, 2008

Agathe.Battestini@gmail.com

Alternatives (Nokia products)
• Nokia widgets:
– Javascript APIs to access the phone resources: location, calendar, contacts, logging, media, messaging, system info
http://www.forum.nokia.com/document/Web_Developers_Library/

– http://www.forum.nokia.com/Resources_and_Information/Explore/Web_Technologies/Web_

• Linux tablets: N800, N810
– http://maemo.org/

– Normal Debian Linux distribution

• Qt for S60:
– http://trolltech.com/developer/technical-preview-qt-for-s60

– Currently preview release

• S60 Open C

– http://www.forum.nokia.com/Resources_and_Information/Explore/Runtime_Platfo
Dec 2nd, 2008 Agathe.Battestini@gmail.com

Symbian Security platform

an TIFF QuickTimeªsee are (Uncompresse needed to

• To install an application on the phone, the application needs to be signed with a certificate that itself defines what capabilities it can grant. • Applications/extensions specify what capabilities need to be granted for them to function. https://www.symbiansigned.com The best strategy for academic groups: • Request an R&D certificate with ALL capabilities except DRM and TCB. • Register the phone IMEIs with the certificate (online) https://rdcertification.nokia.com/rdcertification/app
Dec 2nd, 2008 Agathe.Battestini@gmail.com