
. I really want to see how looks the source of some specific app/game.|
First of all, to create Android applications you have to be comfortable with Java. Start reading Java tutorials (and not Android) to learn all the basics of the language and create some programs (console, then GUI with Swing). After that, start the Android dev training : http://developer.android.com/training/index.html
You can also take a look at the source of my SA-MP App (my sig). |
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright © 2011 thomasv@gitorious
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import android
from electrum import SimpleConfig, Interface, WalletSynchronizer, Wallet, format_satoshis, mnemonic_encode, mnemonic_decode
from decimal import Decimal
import datetime, re
def modal_dialog(title, msg = None):
droid.dialogCreateAlert(title,msg)
droid.dialogSetPositiveButtonText('OK')
droid.dialogShow()
droid.dialogGetResponse()
droid.dialogDismiss()
def modal_input(title, msg, value = None, etype=None):
droid.dialogCreateInput(title, msg, value, etype)
droid.dialogSetPositiveButtonText('OK')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse()
result = response.result
if result is None:
print "modal input: result is none"
return False
droid.dialogDismiss()
if result.get('which') == 'positive':
return result.get('value')
def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
droid.dialogCreateAlert(q, msg)
droid.dialogSetPositiveButtonText(pos_text)
droid.dialogSetNegativeButtonText(neg_text)
droid.dialogShow()
response = droid.dialogGetResponse()
result = response.result
if result is None:
print "modal question: result is none"
return False
droid.dialogDismiss()
return result.get('which') == 'positive'
def edit_label(addr):
v = modal_input('Edit label',None,wallet.labels.get(addr))
if v is not None:
if v:
wallet.labels[addr] = v
else:
if addr in wallet.labels.keys():
wallet.labels.pop(addr)
wallet.update_tx_history()
wallet.save()
droid.fullSetProperty("labelTextView", "text", v)
def select_from_contacts():
title = 'Contacts:'
droid.dialogCreateAlert(title)
l = []
for i in range(len(wallet.addressbook)):
addr = wallet.addressbook[i]
label = wallet.labels.get(addr,addr)
l.append( label )
droid.dialogSetItems(l)
droid.dialogSetPositiveButtonText('New contact')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if response.get('which') == 'positive':
return 'newcontact'
result = response.get('item')
print result
if result is not None:
addr = wallet.addressbook[result]
return addr
def select_from_addresses():
droid.dialogCreateAlert("Addresses:")
l = []
for i in range(len(wallet.addresses)):
addr = wallet.addresses[i]
label = wallet.labels.get(addr,addr)
l.append( label )
droid.dialogSetItems(l)
droid.dialogShow()
response = droid.dialogGetResponse()
result = response.result.get('item')
droid.dialogDismiss()
if result is not None:
addr = wallet.addresses[result]
return addr
def protocol_name(p):
if p == 't': return 'TCP'
if p == 'h': return 'HTTP'
if p == 's': return 'TCP/SSL'
if p == 'g': return 'HTTPS'
def protocol_dialog(host, protocol, z):
droid.dialogCreateAlert('Protocol',host)
if z:
protocols = z.keys()
else:
protocols = 'thsg'
l = []
current = protocols.index(protocol)
for p in protocols:
l.append(protocol_name(p))
droid.dialogSetSingleChoiceItems(l, current)
droid.dialogSetPositiveButtonText('OK')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse().result
selected_item = droid.dialogGetSelectedItems().result
droid.dialogDismiss()
if not response: return
if not selected_item: return
if response.get('which') == 'positive':
p = protocols[selected_item[0]]
port = z[p]
return host + ':' + port + ':' + p
def make_layout(s, scrollable = False):
content = """
<LinearLayout
android:id="@+id/zz"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff222222">
<TextView
android:id="@+id/textElectrum"
android:text="Electrum"
android:textSize="7pt"
android:textColor="#ff4444ff"
android:gravity="left"
android:layout_height="wrap_content"
android:layout_width="match_parent"
/>
</LinearLayout>
%s """%s
if scrollable:
content = """
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
%s
</LinearLayout>
</ScrollView>
"""%content
return """<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/background"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000022">
%s
</LinearLayout>"""%content
def main_layout():
return make_layout("""
<TextView android:id="@+id/balanceTextView"
android:layout_width="match_parent"
android:text=""
android:textColor="#ffffffff"
android:textAppearance="?android:attr/textAppearanceLarge"
android:padding="7dip"
android:textSize="8pt"
android:gravity="center_vertical|center_horizontal|left">
</TextView>
<TextView android:id="@+id/historyTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Recent transactions"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>
%s """%get_history_layout(15),True)
def qr_layout(addr):
return make_layout("""
<TextView android:id="@+id/addrTextView"
android:layout_width="match_parent"
android:layout_height="50"
android:text="%s"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>
<ImageView
android:id="@+id/qrView"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="350"
android:antialias="false"
android:src="file:///sdcard/sl4a/qrcode.bmp" />
<TextView android:id="@+id/labelTextView"
android:layout_width="match_parent"
android:layout_height="50"
android:text="%s"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical|center_horizontal|center">
</TextView>
"""%(addr,wallet.labels.get(addr,'')), True)
payto_layout = make_layout("""
<TextView android:id="@+id/recipientTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pay to:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/recipient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="text">
</EditText>
<LinearLayout android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="From QR code"></Button>
<Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="From Contacts"></Button>
</LinearLayout>
<TextView android:id="@+id/labelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="text">
</EditText>
<TextView android:id="@+id/amountLabelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Amount:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="left">
</TextView>
<EditText android:id="@+id/amount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="Tag Me" android:inputType="numberDecimal">
</EditText>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/linearLayout1">
<Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Send"></Button>
</LinearLayout>""",False)
settings_layout = make_layout(""" <ListView
android:id="@+id/myListView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />""")
def get_history_values(n):
values = []
h = wallet.get_tx_history()
length = min(n, len(h))
for i in range(length):
tx_hash, conf, is_mine, value, fee, balance, timestamp = h[-i-1]
try:
dt = datetime.datetime.fromtimestamp( timestamp )
if dt.date() == dt.today().date():
time_str = str( dt.time() )
else:
time_str = str( dt.date() )
except:
time_str = 'pending'
conf_str = 'v' if conf else 'o'
label, is_default_label = wallet.get_label(tx_hash)
values.append((conf_str, ' ' + time_str, ' ' + format_satoshis(value,True), ' ' + label ))
return values
def get_history_layout(n):
rows = ""
i = 0
values = get_history_values(n)
for v in values:
a,b,c,d = v
color = "#ff00ff00" if a == 'v' else "#ffff0000"
rows += """
<TableRow>
<TextView
android:id="@+id/hl_%d_col1"
android:layout_column="0"
android:text="%s"
android:textColor="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col2"
android:layout_column="1"
android:text="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col3"
android:layout_column="2"
android:text="%s"
android:padding="3" />
<TextView
android:id="@+id/hl_%d_col4"
android:layout_column="3"
android:text="%s"
android:padding="4" />
</TableRow>"""%(i,a,color,i,b,i,c,i,d)
i += 1
output = """
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="0,1,2,3">
%s
</TableLayout>"""% rows
return output
def set_history_layout(n):
values = get_history_values(n)
i = 0
for v in values:
a,b,c,d = v
droid.fullSetProperty("hl_%d_col1"%i,"text", a)
if a == 'v':
droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
else:
droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
droid.fullSetProperty("hl_%d_col2"%i,"text", b)
droid.fullSetProperty("hl_%d_col3"%i,"text", c)
droid.fullSetProperty("hl_%d_col4"%i,"text", d)
i += 1
status_text = ''
def update_layout():
global status_text
if not wallet.interface.is_connected:
text = "Not connected..."
elif not wallet.up_to_date:
text = "Synchronizing..."
else:
c, u = wallet.get_balance()
text = "Balance:"+format_satoshis©
if u : text += ' [' + format_satoshis(u,True).strip() + ']'
# vibrate if status changed
if text != status_text:
if status_text and wallet.interface.is_connected and wallet.up_to_date:
droid.vibrate()
status_text = text
droid.fullSetProperty("balanceTextView", "text", status_text)
if wallet.up_to_date:
set_history_layout(15)
def pay_to(recipient, amount, fee, label):
if wallet.use_encryption:
password = droid.dialogGetPassword('Password').result
if not password: return
else:
password = None
droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
droid.dialogShow()
try:
tx = wallet.mktx( [(recipient, amount)], label, password, fee)
except BaseException, e:
modal_dialog('error', e.message)
droid.dialogDismiss()
return
droid.dialogDismiss()
r, h = wallet.sendtx( tx )
if r:
modal_dialog('Payment sent', h)
return True
else:
modal_dialog('Error', h)
def make_new_contact():
code = droid.scanBarcode()
r = code.result
if r:
data = r['extras']['SCAN_RESULT']
if data:
if re.match('^bitcoin:', data):
address, _, _, _, _, _, _ = wallet.parse_url(data, None, lambda x: modal_question('Question',x))
elif wallet.is_valid(data):
address = data
else:
address = None
if address:
if modal_question('Add to contacts?', address):
wallet.addressbook.append(address)
wallet.save()
else:
modal_dialog('Invalid address', data)
do_refresh = False
def update_callback():
global do_refresh
print "gui callback", wallet.interface.is_connected, wallet.up_to_date
do_refresh = True
droid.eventPost("refresh",'z')
def main_loop():
global do_refresh
update_layout()
out = None
quitting = False
while out is None:
event = droid.eventWait(1000).result
if event is None:
if do_refresh:
update_layout()
do_refresh = False
continue
print "got event in main loop", repr(event)
if event == 'OK': continue
if event is None: continue
if not event.get("name"): continue
# request 2 taps before we exit
if event["name"]=="key":
if event["data"]["key"] == '4':
if quitting:
out = 'quit'
else:
quitting = True
else: quitting = False
if event["name"]=="click":
id=event["data"]["id"]
elif event["name"]=="settings":
out = 'settings'
elif event["name"] in menu_commands:
out = event["name"]
if out == 'contacts':
global contact_addr
contact_addr = select_from_contacts()
if contact_addr == 'newcontact':
make_new_contact()
contact_addr = None
if not contact_addr:
out = None
elif out == "receive":
global receive_addr
receive_addr = select_from_addresses()
if receive_addr:
amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal")
if amount:
receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount)
if not receive_addr:
out = None
return out
def payto_loop():
global recipient
if recipient:
droid.fullSetProperty("recipient","text",recipient)
recipient = None
out = None
while out is None:
event = droid.eventWait().result
if not event: continue
print "got event in payto loop", event
if event == 'OK': continue
if not event.get("name"): continue
if event["name"] == "click":
id = event["data"]["id"]
if id=="buttonPay":
droid.fullQuery()
recipient = droid.fullQueryDetail("recipient").result.get('text')
label = droid.fullQueryDetail("label").result.get('text')
amount = droid.fullQueryDetail('amount').result.get('text')
if not wallet.is_valid(recipient):
modal_dialog('Error','Invalid Bitcoin address')
continue
try:
amount = int( 100000000 * Decimal(amount) )
except:
modal_dialog('Error','Invalid amount')
continue
result = pay_to(recipient, amount, wallet.fee, label)
if result:
out = 'main'
elif id=="buttonContacts":
addr = select_from_contacts()
droid.fullSetProperty("recipient","text",addr)
elif id=="buttonQR":
code = droid.scanBarcode()
r = code.result
if r:
data = r['extras']['SCAN_RESULT']
if data:
if re.match('^bitcoin:', data):
payto, amount, label, _, _, _, _ = wallet.parse_url(data, None, lambda x: modal_question('Question', x))
droid.fullSetProperty("recipient", "text",payto)
droid.fullSetProperty("amount", "text", amount)
droid.fullSetProperty("label", "text", label)
else:
droid.fullSetProperty("recipient", "text", data)
elif event["name"] in menu_commands:
out = event["name"]
elif event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
#elif event["name"]=="screen":
# if event["data"]=="destroy":
# out = 'main'
return out
receive_addr = ''
contact_addr = ''
recipient = ''
def receive_loop():
out = None
while out is None:
event = droid.eventWait().result
print "got event", event
if event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
elif event["name"]=="clipboard":
droid.setClipboard(receive_addr)
modal_dialog('Address copied to clipboard',receive_addr)
elif event["name"]=="edit":
edit_label(receive_addr)
return out
def contacts_loop():
global recipient
out = None
while out is None:
event = droid.eventWait().result
print "got event", event
if event["name"]=="key":
if event["data"]["key"] == '4':
out = 'main'
elif event["name"]=="clipboard":
droid.setClipboard(contact_addr)
modal_dialog('Address copied to clipboard',contact_addr)
elif event["name"]=="edit":
edit_label(contact_addr)
elif event["name"]=="paytocontact":
recipient = contact_addr
out = 'send'
elif event["name"]=="deletecontact":
if modal_question('delete contact', contact_addr):
out = 'main'
return out
def server_dialog(plist):
droid.dialogCreateAlert("Public servers")
droid.dialogSetItems( plist.keys() )
droid.dialogSetPositiveButtonText('Private server')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if not response: return
if response.get('which') == 'positive':
return modal_input('Private server', None)
i = response.get('item')
if i is not None:
response = plist.keys()[i]
return response
def seed_dialog():
if wallet.use_encryption:
password = droid.dialogGetPassword('Seed').result
if not password: return
else:
password = None
try:
seed = wallet.pw_decode( wallet.seed, password)
except:
modal_dialog('error','incorrect password')
return
modal_dialog('Your seed is',seed)
modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(seed)) )
def change_password_dialog():
if wallet.use_encryption:
password = droid.dialogGetPassword('Your wallet is encrypted').result
if password is None: return
else:
password = None
try:
seed = wallet.pw_decode( wallet.seed, password)
except:
modal_dialog('error','incorrect password')
return
new_password = droid.dialogGetPassword('Choose a password').result
if new_password == None:
return
if new_password != '':
password2 = droid.dialogGetPassword('Confirm new password').result
if new_password != password2:
modal_dialog('error','passwords do not match')
return
wallet.update_password(seed, password, new_password)
if new_password:
modal_dialog('Password updated','your wallet is encrypted')
else:
modal_dialog('No password','your wallet is not encrypted')
return True
def settings_loop():
def set_listview():
server, port, p = interface.server.split(':')
fee = str( Decimal( wallet.fee)/100000000 )
is_encrypted = 'yes' if wallet.use_encryption else 'no'
protocol = protocol_name(p)
droid.fullShow(settings_layout)
droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
set_listview()
out = None
while out is None:
event = droid.eventWait()
event = event.result
print "got event", event
if event == 'OK': continue
if not event: continue
plist, servers_list = interface.get_servers_list()
name = event.get("name")
if not name: continue
if name == "itemclick":
pos = event["data"]["position"]
host, port, protocol = interface.server.split(':')
if pos == "0": #server
host = server_dialog(plist)
if host:
p = plist[host]
port = p['t']
srv = host + ':' + port + ':t'
wallet.config.set_key("server", srv, True)
try:
wallet.interface.set_server(srv)
except:
modal_dialog('error','invalid server')
set_listview()
elif pos == "1": #protocol
if host in plist:
srv = protocol_dialog(host, protocol, plist[host])
if srv:
wallet.config.set_key("server", srv, True)
try:
wallet.interface.set_server(srv)
except:
modal_dialog('error','invalid server')
set_listview()
elif pos == "2": #port
a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
if a_port:
if a_port != port:
srv = host + ':' + a_port + ':'+ protocol
wallet.config.set_key("server", srv, True)
try:
wallet.interface.set_server(srv)
except:
modal_dialog('error','invalid port number')
set_listview()
elif pos == "3": #fee
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")
if fee:
try:
fee = int( 100000000 * Decimal(fee) )
except:
modal_dialog('error','invalid fee value')
if wallet.fee != fee:
wallet.fee = fee
wallet.save()
set_listview()
elif pos == "4":
if change_password_dialog():
set_listview()
elif pos == "5":
seed_dialog()
elif name in menu_commands:
out = event["name"]
elif name == 'cancel':
out = 'main'
elif name == "key":
if event["data"]["key"] == '4':
out = 'main'
return out
def add_menu(s):
droid.clearOptionsMenu()
if s == 'main':
droid.addOptionsMenuItem("Send","send",None,"")
droid.addOptionsMenuItem("Receive","receive",None,"")
droid.addOptionsMenuItem("Contacts","contacts",None,"")
droid.addOptionsMenuItem("Settings","settings",None,"")
elif s == 'receive':
droid.addOptionsMenuItem("Copy","clipboard",None,"")
droid.addOptionsMenuItem("Label","edit",None,"")
elif s == 'contacts':
droid.addOptionsMenuItem("Copy","clipboard",None,"")
droid.addOptionsMenuItem("Label","edit",None,"")
droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
#droid.addOptionsMenuItem("Delete","deletecontact",None,"")
def make_bitmap(addr):
# fixme: this is highly inefficient
droid.dialogCreateSpinnerProgress("please wait")
droid.dialogShow()
try:
import pyqrnative, bmp
qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
qr.addData(addr)
qr.make()
k = qr.getModuleCount()
assert k == 33
bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
finally:
droid.dialogDismiss()
droid = android.Android()
menu_commands = ["send", "receive", "settings", "contacts", "main"]
wallet = None
interface = None
class ElectrumGui:
def __init__(self, w, config, app=None):
global wallet, interface
wallet = w
interface = wallet.interface
interface.register_callback('updated',update_callback)
interface.register_callback('connected', update_callback)
interface.register_callback('disconnected', update_callback)
interface.register_callback('disconnecting', update_callback)
def main(self, url):
s = 'main'
while True:
add_menu(s)
if s == 'main':
droid.fullShow(main_layout())
s = main_loop()
elif s == 'send':
droid.fullShow(payto_layout)
s = payto_loop()
elif s == 'receive':
make_bitmap(receive_addr)
droid.fullShow(qr_layout(receive_addr))
s = receive_loop()
elif s == 'Contacte':
make_bitmap(contact_addr)
droid.fullShow(qr_layout(contact_addr))
s = contacts_loop()
elif s == 'settings':
s = settings_loop()
else:
break
droid.makeToast("Bye!")
def restore_or_create(self):
droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
droid.dialogSetPositiveButtonText('Create')
droid.dialogSetNeutralButtonText('Restore')
droid.dialogSetNegativeButtonText('Cancel')
droid.dialogShow()
response = droid.dialogGetResponse().result
droid.dialogDismiss()
if not response: return
if response.get('which') == 'negative':
return
return 'restore' if response.get('which') == 'neutral' else 'create'
def seed_dialog(self):
if modal_question("Input method",None,'QR Code', 'mnemonic'):
code = droid.scanBarcode()
r = code.result
if r:
seed = r['extras']['SCAN_RESULT']
else:
return
else:
m = modal_input('Mnemonic','please enter your code')
try:
seed = mnemonic_decode(m.split(' '))
except:
modal_dialog('error: could not decode this seed')
return
wallet.seed = str(seed)
return True
def network_dialog(self):
return True
def show_seed(self):
modal_dialog('Your seed is:', wallet.seed)
modal_dialog('Mnemonic code:', ' '.join(mnemonic_encode(wallet.seed)) )
def password_dialog(self):
change_password_dialog()
def restore_wallet(self):
msg = "recovering wallet..."
droid.dialogCreateSpinnerProgress("Electrum", msg)
droid.dialogShow()
wallet.update()
droid.dialogDismiss()
droid.vibrate()
if wallet.is_found():
wallet.fill_addressbook()
modal_dialog("recovery successful")
else:
if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
return False
wallet.save()
return True
|
Android studio is in early version and not considered as stable, so you might choose eclipse as ide for your android apps development.
|
|
Android studio is in early version and not considered as stable, so you might choose eclipse as ide for your android apps development.
|