fixes for android: restore from seed
[electrum-nvc.git] / gui / android.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20
21
22 from __future__ import absolute_import
23 import android
24
25 from electrum import SimpleConfig, Wallet, WalletStorage, format_satoshis, mnemonic_encode, mnemonic_decode
26 from electrum.bitcoin import is_valid
27 from electrum import util
28 from decimal import Decimal
29 import datetime, re
30
31
32
33 def modal_dialog(title, msg = None):
34     droid.dialogCreateAlert(title,msg)
35     droid.dialogSetPositiveButtonText('OK')
36     droid.dialogShow()
37     droid.dialogGetResponse()
38     droid.dialogDismiss()
39
40 def modal_input(title, msg, value = None, etype=None):
41     droid.dialogCreateInput(title, msg, value, etype)
42     droid.dialogSetPositiveButtonText('OK')
43     droid.dialogSetNegativeButtonText('Cancel')
44     droid.dialogShow()
45     response = droid.dialogGetResponse()
46     result = response.result
47     if result is None:
48         print "modal input: result is none"
49         return False
50     droid.dialogDismiss()
51     if result.get('which') == 'positive':
52         return result.get('value')
53
54 def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
55     droid.dialogCreateAlert(q, msg)
56     droid.dialogSetPositiveButtonText(pos_text)
57     droid.dialogSetNegativeButtonText(neg_text)
58     droid.dialogShow()
59     response = droid.dialogGetResponse()
60     result = response.result
61     if result is None:
62         print "modal question: result is none"
63         return False
64     droid.dialogDismiss()
65     return result.get('which') == 'positive'
66
67 def edit_label(addr):
68     v = modal_input('Edit label',None,wallet.labels.get(addr))
69     if v is not None:
70         if v:
71             wallet.labels[addr] = v
72         else:
73             if addr in wallet.labels.keys():
74                 wallet.labels.pop(addr)
75         wallet.update_tx_history()
76         wallet.save()
77         droid.fullSetProperty("labelTextView", "text", v)
78
79 def select_from_contacts():
80     title = 'Contacts:'
81     droid.dialogCreateAlert(title)
82     l = []
83     for i in range(len(wallet.addressbook)):
84         addr = wallet.addressbook[i]
85         label = wallet.labels.get(addr,addr)
86         l.append( label )
87     droid.dialogSetItems(l)
88     droid.dialogSetPositiveButtonText('New contact')
89     droid.dialogShow()
90     response = droid.dialogGetResponse().result
91     droid.dialogDismiss()
92
93     if response.get('which') == 'positive':
94         return 'newcontact'
95
96     result = response.get('item')
97     print result
98     if result is not None:
99         addr = wallet.addressbook[result]
100         return addr
101
102
103 def select_from_addresses():
104     droid.dialogCreateAlert("Addresses:")
105     l = []
106     addresses = wallet.addresses()
107     for i in range(len(addresses)):
108         addr = addresses[i]
109         label = wallet.labels.get(addr,addr)
110         l.append( label )
111     droid.dialogSetItems(l)
112     droid.dialogShow()
113     response = droid.dialogGetResponse()
114     result = response.result.get('item')
115     droid.dialogDismiss()
116     if result is not None:
117         addr = addresses[result]
118         return addr
119
120
121 def protocol_name(p):
122     if p == 't': return 'TCP'
123     if p == 'h': return 'HTTP'
124     if p == 's': return 'SSL'
125     if p == 'g': return 'HTTPS'
126
127
128 def protocol_dialog(host, protocol, z):
129     droid.dialogCreateAlert('Protocol',host)
130     if z:
131         protocols = z.keys()
132     else:
133         protocols = 'thsg'
134     l = []
135     current = protocols.index(protocol)
136     for p in protocols:
137         l.append(protocol_name(p))
138     droid.dialogSetSingleChoiceItems(l, current)
139     droid.dialogSetPositiveButtonText('OK')
140     droid.dialogSetNegativeButtonText('Cancel')
141     droid.dialogShow()
142     response = droid.dialogGetResponse().result
143     selected_item = droid.dialogGetSelectedItems().result
144     droid.dialogDismiss()
145
146     if not response: return
147     if not selected_item: return
148     if response.get('which') == 'positive':
149         return protocols[selected_item[0]]
150
151
152
153
154 def make_layout(s, scrollable = False):
155     content = """
156
157       <LinearLayout 
158         android:id="@+id/zz"
159         android:layout_width="match_parent"
160         android:layout_height="wrap_content" 
161         android:background="#ff222222">
162
163         <TextView
164           android:id="@+id/textElectrum"
165           android:text="Electrum"
166           android:textSize="7pt"
167           android:textColor="#ff4444ff"
168           android:gravity="left"
169           android:layout_height="wrap_content"
170           android:layout_width="match_parent"
171         />
172       </LinearLayout>
173
174         %s   """%s
175
176     if scrollable:
177         content = """
178       <ScrollView 
179         android:id="@+id/scrollview"
180         android:layout_width="match_parent"
181         android:layout_height="match_parent" >
182
183       <LinearLayout
184         android:orientation="vertical" 
185         android:layout_width="match_parent"
186         android:layout_height="wrap_content" >
187
188       %s
189
190       </LinearLayout>
191       </ScrollView>
192       """%content
193
194
195     return """<?xml version="1.0" encoding="utf-8"?>
196       <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
197         android:id="@+id/background"
198         android:orientation="vertical" 
199         android:layout_width="match_parent"
200         android:layout_height="match_parent" 
201         android:background="#ff000022">
202
203       %s 
204       </LinearLayout>"""%content
205
206
207
208
209 def main_layout():
210     return make_layout("""
211         <TextView android:id="@+id/balanceTextView" 
212                 android:layout_width="match_parent"
213                 android:text=""
214                 android:textColor="#ffffffff"
215                 android:textAppearance="?android:attr/textAppearanceLarge" 
216                 android:padding="7dip"
217                 android:textSize="8pt"
218                 android:gravity="center_vertical|center_horizontal|left">
219         </TextView>
220
221         <TextView android:id="@+id/historyTextView" 
222                 android:layout_width="match_parent"
223                 android:layout_height="wrap_content" 
224                 android:text="Recent transactions"
225                 android:textAppearance="?android:attr/textAppearanceLarge" 
226                 android:gravity="center_vertical|center_horizontal|center">
227         </TextView>
228
229         %s """%get_history_layout(15),True)
230
231
232
233 def qr_layout(addr):
234     return make_layout("""
235
236      <TextView android:id="@+id/addrTextView" 
237                 android:layout_width="match_parent"
238                 android:layout_height="50" 
239                 android:text="%s"
240                 android:textAppearance="?android:attr/textAppearanceLarge" 
241                 android:gravity="center_vertical|center_horizontal|center">
242      </TextView>
243
244      <ImageView
245         android:id="@+id/qrView"
246         android:gravity="center"
247         android:layout_width="match_parent"
248         android:layout_height="350"
249         android:antialias="false"
250         android:src="file:///sdcard/sl4a/qrcode.bmp" /> 
251
252      <TextView android:id="@+id/labelTextView" 
253                 android:layout_width="match_parent"
254                 android:layout_height="50" 
255                 android:text="%s"
256                 android:textAppearance="?android:attr/textAppearanceLarge" 
257                 android:gravity="center_vertical|center_horizontal|center">
258      </TextView>
259
260      """%(addr,wallet.labels.get(addr,'')), True)
261
262 payto_layout = make_layout("""
263
264         <TextView android:id="@+id/recipientTextView" 
265                 android:layout_width="match_parent"
266                 android:layout_height="wrap_content" 
267                 android:text="Pay to:"
268                 android:textAppearance="?android:attr/textAppearanceLarge" 
269                 android:gravity="left">
270         </TextView>
271
272
273         <EditText android:id="@+id/recipient"
274                 android:layout_width="match_parent"
275                 android:layout_height="wrap_content" 
276                 android:tag="Tag Me" android:inputType="text">
277         </EditText>
278
279         <LinearLayout android:id="@+id/linearLayout1"
280                 android:layout_width="match_parent"
281                 android:layout_height="wrap_content">
282                 <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
283                         android:layout_height="wrap_content" android:text="From QR code"></Button>
284                 <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
285                         android:layout_height="wrap_content" android:text="From Contacts"></Button>
286         </LinearLayout>
287
288
289         <TextView android:id="@+id/labelTextView" 
290                 android:layout_width="match_parent"
291                 android:layout_height="wrap_content" 
292                 android:text="Description:"
293                 android:textAppearance="?android:attr/textAppearanceLarge" 
294                 android:gravity="left">
295         </TextView>
296
297         <EditText android:id="@+id/label"
298                 android:layout_width="match_parent"
299                 android:layout_height="wrap_content" 
300                 android:tag="Tag Me" android:inputType="text">
301         </EditText>
302
303         <TextView android:id="@+id/amountLabelTextView" 
304                 android:layout_width="match_parent"
305                 android:layout_height="wrap_content" 
306                 android:text="Amount:"
307                 android:textAppearance="?android:attr/textAppearanceLarge" 
308                 android:gravity="left">
309         </TextView>
310
311         <EditText android:id="@+id/amount"
312                 android:layout_width="match_parent"
313                 android:layout_height="wrap_content" 
314                 android:tag="Tag Me" android:inputType="numberDecimal">
315         </EditText>
316
317         <LinearLayout android:layout_width="match_parent"
318                 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
319                 <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
320                         android:layout_height="wrap_content" android:text="Send"></Button>
321         </LinearLayout>""",False)
322
323
324
325 settings_layout = make_layout(""" <ListView
326            android:id="@+id/myListView" 
327            android:layout_width="match_parent"
328            android:layout_height="wrap_content" />""")
329
330
331
332 def get_history_values(n):
333     values = []
334     h = wallet.get_tx_history()
335     length = min(n, len(h))
336     for i in range(length):
337         tx_hash, conf, is_mine, value, fee, balance, timestamp = h[-i-1]
338         try:
339             dt = datetime.datetime.fromtimestamp( timestamp )
340             if dt.date() == dt.today().date():
341                 time_str = str( dt.time() )
342             else:
343                 time_str = str( dt.date() )
344         except:
345             time_str = 'pending'
346
347         conf_str = 'v' if conf else 'o'
348         label, is_default_label = wallet.get_label(tx_hash)
349         values.append((conf_str, '  ' + time_str, '  ' + format_satoshis(value,True), '  ' + label ))
350
351     return values
352
353
354 def get_history_layout(n):
355     rows = ""
356     i = 0
357     values = get_history_values(n)
358     for v in values:
359         a,b,c,d = v
360         color = "#ff00ff00" if a == 'v' else "#ffff0000"
361         rows += """
362         <TableRow>
363           <TextView
364             android:id="@+id/hl_%d_col1" 
365             android:layout_column="0"
366             android:text="%s"
367             android:textColor="%s"
368             android:padding="3" />
369           <TextView
370             android:id="@+id/hl_%d_col2" 
371             android:layout_column="1"
372             android:text="%s"
373             android:padding="3" />
374           <TextView
375             android:id="@+id/hl_%d_col3" 
376             android:layout_column="2"
377             android:text="%s"
378             android:padding="3" />
379           <TextView
380             android:id="@+id/hl_%d_col4" 
381             android:layout_column="3"
382             android:text="%s"
383             android:padding="4" />
384         </TableRow>"""%(i,a,color,i,b,i,c,i,d)
385         i += 1
386
387     output = """
388 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
389     android:layout_width="fill_parent"
390     android:layout_height="wrap_content"
391     android:stretchColumns="0,1,2,3">
392     %s
393 </TableLayout>"""% rows
394     return output
395
396
397 def set_history_layout(n):
398     values = get_history_values(n)
399     i = 0
400     for v in values:
401         a,b,c,d = v
402         droid.fullSetProperty("hl_%d_col1"%i,"text", a)
403
404         if a == 'v':
405             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
406         else:
407             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
408
409         droid.fullSetProperty("hl_%d_col2"%i,"text", b)
410         droid.fullSetProperty("hl_%d_col3"%i,"text", c)
411         droid.fullSetProperty("hl_%d_col4"%i,"text", d)
412         i += 1
413
414
415
416
417 status_text = ''
418 def update_layout():
419     global status_text
420     if not network.is_connected():
421         text = "Not connected..."
422     elif not wallet.up_to_date:
423         text = "Synchronizing..."
424     else:
425         c, u = wallet.get_balance()
426         text = "Balance:"+format_satoshis(c) 
427         if u : text += '   [' + format_satoshis(u,True).strip() + ']'
428
429
430     # vibrate if status changed
431     if text != status_text:
432         if status_text and network.is_connected() and wallet.up_to_date:
433             droid.vibrate()
434         status_text = text
435
436     droid.fullSetProperty("balanceTextView", "text", status_text)
437
438     if wallet.up_to_date:
439         set_history_layout(15)
440
441
442
443
444 def pay_to(recipient, amount, fee, label):
445
446     if wallet.use_encryption:
447         password  = droid.dialogGetPassword('Password').result
448         if not password: return
449     else:
450         password = None
451
452     droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
453     droid.dialogShow()
454
455     try:
456         tx = wallet.mktx( [(recipient, amount)], password, fee)
457     except BaseException, e:
458         modal_dialog('error', e.message)
459         droid.dialogDismiss()
460         return
461
462     if label: 
463         wallet.labels[tx.hash()] = label
464
465     droid.dialogDismiss()
466
467     r, h = wallet.sendtx( tx )
468     if r:
469         modal_dialog('Payment sent', h)
470         return True
471     else:
472         modal_dialog('Error', h)
473
474
475
476
477
478
479
480 def make_new_contact():
481     code = droid.scanBarcode()
482     r = code.result
483     if r:
484         data = r['extras']['SCAN_RESULT']
485         if data:
486             if re.match('^bitcoin:', data):
487                 address, _, _, _, _, _, _ = util.parse_url(data)
488             elif is_valid(data):
489                 address = data
490             else:
491                 address = None
492             if address:
493                 if modal_question('Add to contacts?', address):
494                     wallet.add_contact(address)
495         else:
496             modal_dialog('Invalid address', data)
497
498
499 do_refresh = False
500
501 def update_callback():
502     global do_refresh
503     print "gui callback", network.is_connected()
504     do_refresh = True
505     droid.eventPost("refresh",'z')
506
507 def main_loop():
508     global do_refresh
509
510     update_layout()
511     out = None
512     quitting = False
513     while out is None:
514
515         event = droid.eventWait(1000).result
516         if event is None:
517             if do_refresh: 
518                 update_layout()
519                 do_refresh = False
520             continue
521
522         print "got event in main loop", repr(event)
523         if event == 'OK': continue
524         if event is None: continue
525         if not event.get("name"): continue
526
527         # request 2 taps before we exit
528         if event["name"]=="key":
529             if event["data"]["key"] == '4':
530                 if quitting:
531                     out = 'quit'
532                 else: 
533                     quitting = True
534         else: quitting = False
535
536         if event["name"]=="click":
537             id=event["data"]["id"]
538
539         elif event["name"]=="settings":
540             out = 'settings'
541
542         elif event["name"] in menu_commands:
543             out = event["name"]
544
545             if out == 'contacts':
546                 global contact_addr
547                 contact_addr = select_from_contacts()
548                 if contact_addr == 'newcontact':
549                     make_new_contact()
550                     contact_addr = None
551                 if not contact_addr:
552                     out = None
553
554             elif out == "receive":
555                 global receive_addr
556                 receive_addr = select_from_addresses()
557                 if receive_addr:
558                     amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal")
559                     if amount:
560                         receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount)
561
562                 if not receive_addr:
563                     out = None
564
565
566     return out
567                     
568
569 def payto_loop():
570     global recipient
571     if recipient:
572         droid.fullSetProperty("recipient","text",recipient)
573         recipient = None
574
575     out = None
576     while out is None:
577         event = droid.eventWait().result
578         if not event: continue
579         print "got event in payto loop", event
580         if event == 'OK': continue
581         if not event.get("name"): continue
582
583         if event["name"] == "click":
584             id = event["data"]["id"]
585
586             if id=="buttonPay":
587
588                 droid.fullQuery()
589                 recipient = droid.fullQueryDetail("recipient").result.get('text')
590                 label  = droid.fullQueryDetail("label").result.get('text')
591                 amount = droid.fullQueryDetail('amount').result.get('text')
592
593                 if not is_valid(recipient):
594                     modal_dialog('Error','Invalid Bitcoin address')
595                     continue
596
597                 try:
598                     amount = int( 100000000 * Decimal(amount) )
599                 except:
600                     modal_dialog('Error','Invalid amount')
601                     continue
602
603                 result = pay_to(recipient, amount, wallet.fee, label)
604                 if result:
605                     out = 'main'
606
607             elif id=="buttonContacts":
608                 addr = select_from_contacts()
609                 droid.fullSetProperty("recipient","text",addr)
610
611             elif id=="buttonQR":
612                 code = droid.scanBarcode()
613                 r = code.result
614                 if r:
615                     data = r['extras']['SCAN_RESULT']
616                     if data:
617                         if re.match('^bitcoin:', data):
618                             payto, amount, label, _, _, _, _ = util.parse_url(data)
619                             droid.fullSetProperty("recipient", "text",payto)
620                             droid.fullSetProperty("amount", "text", amount)
621                             droid.fullSetProperty("label", "text", label)
622                         else:
623                             droid.fullSetProperty("recipient", "text", data)
624
625                     
626         elif event["name"] in menu_commands:
627             out = event["name"]
628
629         elif event["name"]=="key":
630             if event["data"]["key"] == '4':
631                 out = 'main'
632
633         #elif event["name"]=="screen":
634         #    if event["data"]=="destroy":
635         #        out = 'main'
636
637     return out
638
639
640 receive_addr = ''
641 contact_addr = ''
642 recipient = ''
643
644 def receive_loop():
645     out = None
646     while out is None:
647         event = droid.eventWait().result
648         print "got event", event
649         if event["name"]=="key":
650             if event["data"]["key"] == '4':
651                 out = 'main'
652
653         elif event["name"]=="clipboard":
654             droid.setClipboard(receive_addr)
655             modal_dialog('Address copied to clipboard',receive_addr)
656
657         elif event["name"]=="edit":
658             edit_label(receive_addr)
659
660     return out
661
662 def contacts_loop():
663     global recipient
664     out = None
665     while out is None:
666         event = droid.eventWait().result
667         print "got event", event
668         if event["name"]=="key":
669             if event["data"]["key"] == '4':
670                 out = 'main'
671
672         elif event["name"]=="clipboard":
673             droid.setClipboard(contact_addr)
674             modal_dialog('Address copied to clipboard',contact_addr)
675
676         elif event["name"]=="edit":
677             edit_label(contact_addr)
678
679         elif event["name"]=="paytocontact":
680             recipient = contact_addr
681             out = 'send'
682
683         elif event["name"]=="deletecontact":
684             if modal_question('delete contact', contact_addr):
685                 out = 'main'
686
687     return out
688
689
690 def server_dialog(servers):
691     droid.dialogCreateAlert("Public servers")
692     droid.dialogSetItems( servers.keys() )
693     droid.dialogSetPositiveButtonText('Private server')
694     droid.dialogShow()
695     response = droid.dialogGetResponse().result
696     droid.dialogDismiss()
697     if not response: return
698
699     if response.get('which') == 'positive':
700         return modal_input('Private server', None)
701
702     i = response.get('item')
703     if i is not None:
704         response = servers.keys()[i]
705         return response
706
707
708 def show_seed():
709     if wallet.use_encryption:
710         password  = droid.dialogGetPassword('Seed').result
711         if not password: return
712     else:
713         password = None
714     
715     try:
716         seed = wallet.decode_seed(password)
717     except:
718         modal_dialog('error','incorrect password')
719         return
720
721     modal_dialog('Your seed is',seed)
722     modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(seed)) )
723
724 def change_password_dialog():
725     if wallet.use_encryption:
726         password  = droid.dialogGetPassword('Your wallet is encrypted').result
727         if password is None: return
728     else:
729         password = None
730
731     try:
732         seed = wallet.decode_seed(password)
733     except:
734         modal_dialog('error','incorrect password')
735         return
736
737     new_password  = droid.dialogGetPassword('Choose a password').result
738     if new_password == None:
739         return
740
741     if new_password != '':
742         password2  = droid.dialogGetPassword('Confirm new password').result
743         if new_password != password2:
744             modal_dialog('error','passwords do not match')
745             return
746
747     wallet.update_password(seed, password, new_password)
748     if new_password:
749         modal_dialog('Password updated','your wallet is encrypted')
750     else:
751         modal_dialog('No password','your wallet is not encrypted')
752     return True
753
754
755 def settings_loop():
756
757
758     def set_listview():
759         server, port, p = network.default_server.split(':')
760         fee = str( Decimal( wallet.fee)/100000000 )
761         is_encrypted = 'yes' if wallet.use_encryption else 'no'
762         protocol = protocol_name(p)
763         droid.fullShow(settings_layout)
764         droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
765
766     set_listview()
767
768     out = None
769     while out is None:
770         event = droid.eventWait()
771         event = event.result
772         print "got event", event
773         if event == 'OK': continue
774         if not event: continue
775
776         servers = network.get_servers()
777         name = event.get("name")
778         if not name: continue
779
780         if name == "itemclick":
781             pos = event["data"]["position"]
782             host, port, protocol = network.default_server.split(':')
783             network_changed = False
784
785             if pos == "0": #server
786                 host = server_dialog(servers)
787                 if host:
788                     p = servers[host]
789                     port = p[protocol]
790                     network_changed = True
791
792             elif pos == "1": #protocol
793                 if host in servers:
794                     protocol = protocol_dialog(host, protocol, servers[host])
795                     z = servers[host]
796                     port = z[protocol]
797                     network_changed = True
798
799             elif pos == "2": #port
800                 a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
801                 if a_port != port:
802                     port = a_port
803                     network_changed = True
804
805             elif pos == "3": #fee
806                 fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal")
807                 if fee:
808                     try:
809                         fee = int( 100000000 * Decimal(fee) )
810                     except:
811                         modal_dialog('error','invalid fee value')
812                     wallet.set_fee(fee)
813                     set_listview()
814
815             elif pos == "4":
816                 if change_password_dialog():
817                     set_listview()
818
819             elif pos == "5":
820                 show_seed()
821
822             if network_changed:
823                 proxy = None
824                 auto_connect = False
825                 try:
826                     network.set_parameters(host, port, protocol, proxy, auto_connect)
827                 except:
828                     modal_dialog('error','invalid server')
829                 set_listview()
830
831         elif name in menu_commands:
832             out = event["name"]
833
834         elif name == 'cancel':
835             out = 'main'
836
837         elif name == "key":
838             if event["data"]["key"] == '4':
839                 out = 'main'
840
841     return out
842
843 def add_menu(s):
844     droid.clearOptionsMenu()
845     if s == 'main':
846         droid.addOptionsMenuItem("Send","send",None,"")
847         droid.addOptionsMenuItem("Receive","receive",None,"")
848         droid.addOptionsMenuItem("Contacts","contacts",None,"")
849         droid.addOptionsMenuItem("Settings","settings",None,"")
850     elif s == 'receive':
851         droid.addOptionsMenuItem("Copy","clipboard",None,"")
852         droid.addOptionsMenuItem("Label","edit",None,"")
853     elif s == 'contacts':
854         droid.addOptionsMenuItem("Copy","clipboard",None,"")
855         droid.addOptionsMenuItem("Label","edit",None,"")
856         droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
857         #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
858
859
860 def make_bitmap(addr):
861     # fixme: this is highly inefficient
862     droid.dialogCreateSpinnerProgress("please wait")
863     droid.dialogShow()
864     try:
865         import pyqrnative, bmp
866         qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
867         qr.addData(addr)
868         qr.make()
869         k = qr.getModuleCount()
870         assert k == 33
871         bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
872     finally:
873         droid.dialogDismiss()
874
875         
876
877
878 droid = android.Android()
879 menu_commands = ["send", "receive", "settings", "contacts", "main"]
880 wallet = None
881 network = None
882
883 class ElectrumGui:
884
885     def __init__(self, config, _network):
886         global wallet, network
887         network = _network
888         network.register_callback('updated', update_callback)
889         network.register_callback('connected', update_callback)
890         network.register_callback('disconnected', update_callback)
891         network.register_callback('disconnecting', update_callback)
892         
893         storage = WalletStorage(config)
894         if not storage.file_exists:
895             action = self.restore_or_create()
896             if not action: exit()
897
898             wallet = Wallet(storage)
899             if action == 'create':
900                 wallet.init_seed(None)
901                 self.show_seed()
902                 wallet.save_seed()
903                 wallet.create_accounts()
904                 wallet.synchronize()  # generate first addresses offline
905                 
906             elif action == 'restore':
907                 seed = self.seed_dialog()
908                 if not seed:
909                     exit()
910                 wallet.init_seed(str(seed))
911                 wallet.save_seed()
912             else:
913                 exit()
914
915             wallet.start_threads(network)
916
917             if action == 'restore':
918                 if not self.restore_wallet():
919                     exit()
920
921             self.password_dialog(wallet)
922
923         else:
924             wallet = Wallet(storage)
925             wallet.start_threads(network)
926
927
928     def main(self, url):
929         s = 'main'
930         while True:
931             add_menu(s)
932             if s == 'main':
933                 droid.fullShow(main_layout())
934                 s = main_loop()
935
936             elif s == 'send':
937                 droid.fullShow(payto_layout)
938                 s = payto_loop()
939
940             elif s == 'receive':
941                 make_bitmap(receive_addr)
942                 droid.fullShow(qr_layout(receive_addr))
943                 s = receive_loop()
944
945             elif s == 'contacts':
946                 make_bitmap(contact_addr)
947                 droid.fullShow(qr_layout(contact_addr))
948                 s = contacts_loop()
949
950             elif s == 'settings':
951                 s = settings_loop()
952
953             else:
954                 break
955
956         droid.makeToast("Bye!")
957
958
959     def restore_or_create(self):
960         droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
961         droid.dialogSetPositiveButtonText('Create')
962         droid.dialogSetNeutralButtonText('Restore')
963         droid.dialogSetNegativeButtonText('Cancel')
964         droid.dialogShow()
965         response = droid.dialogGetResponse().result
966         droid.dialogDismiss()
967         if not response: return
968         if response.get('which') == 'negative':
969             return
970
971         return 'restore' if response.get('which') == 'neutral' else 'create'
972
973
974     def seed_dialog(self):
975         if modal_question("Enter your seed","Input method",'QR Code', 'mnemonic'):
976             code = droid.scanBarcode()
977             r = code.result
978             if r:
979                 seed = r['extras']['SCAN_RESULT']
980             else:
981                 return
982         else:
983             m = modal_input('Mnemonic','please enter your code')
984             try:
985                 seed = mnemonic_decode(m.split(' '))
986             except:
987                 modal_dialog('error: could not decode this seed')
988                 return
989
990         return str(seed)
991
992
993     def network_dialog(self):
994         return True
995
996     def verify_seed(self):
997         wallet.save_seed()
998         return True
999         
1000     def show_seed(self):
1001         modal_dialog('Your seed is:', wallet.seed)
1002         modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) )
1003
1004
1005     def password_dialog(self):
1006         change_password_dialog()
1007
1008
1009     def restore_wallet(self):
1010
1011         msg = "recovering wallet..."
1012         droid.dialogCreateSpinnerProgress("Electrum", msg)
1013         droid.dialogShow()
1014
1015         wallet.restore(lambda x: None)
1016
1017         droid.dialogDismiss()
1018         droid.vibrate()
1019
1020         if wallet.is_found():
1021             wallet.fill_addressbook()
1022             modal_dialog("recovery successful")
1023         else:
1024             if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
1025                 return False
1026
1027         return True
1028