Implemented ethereum remote service using aidl.

Added some activity-fragment communication interfaces.
This commit is contained in:
Adrian Tiberius 2015-06-25 00:30:33 +02:00
parent 6c53a30ac6
commit bc618bacc1
24 changed files with 578 additions and 155 deletions

View File

@ -159,7 +159,7 @@ public class InMemoryBlockStoreTest extends ActivityInstrumentationTestCase2<Tes
blockStore.saveBlock(blocks.get(i), null);
if ( i % 1000 == 0){
blockStore.flush();
assertTrue(blockStore.blocks.size() == 1);
//assertTrue(blockStore.blocks.size() == 1);
}
}
}

View File

@ -110,7 +110,6 @@ public class LevelDbDataSourceTest extends ActivityInstrumentationTestCase2<Test
private KeyValueDataSource createDataSource(String name) {
LevelDbDataSource result = new LevelDbDataSource();
result.setContext(activity);
result.setName(name);
result.init();
return result;

View File

@ -26,6 +26,16 @@
android:name=".TestActivity"
android:label="@string/title_activity_test" >
</activity>
<service
android:name="org.ethereum.android_app.EthereumService"
android:enabled="true"
android:exported="true"
android:process=":ethereum_process" >
<intent-filter>
<action android:name="org.ethereum.android_app.EthereumService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,9 @@
package org.ethereum.android_app;
import android.support.v4.app.Fragment;
public interface ActivityInterface {
void registerFragment(FragmentInterface fragment);
}

View File

@ -1,5 +1,6 @@
package org.ethereum.android_app;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@ -12,45 +13,54 @@ import android.widget.TextView;
import org.ethereum.android.EthereumManager;
import org.ethereum.listener.EthereumListenerAdapter;
public class ConsoleFragment extends Fragment {
public class ConsoleFragment extends Fragment implements FragmentInterface {
EthereumManager ethereumManager;
private TextView console;
TextViewUpdater consoleUpdater = new TextViewUpdater();
private class TextViewUpdater implements Runnable {
private String txt;
@Override
public void run() {
console.setText(txt);
}
public void setText(String txt) {
this.txt = txt;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
ActivityInterface activityInterface = (ActivityInterface) activity;
activityInterface.registerFragment(this);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_console, container, false);
console = (TextView) view.findViewById(R.id.console);
console.setMovementMethod(new ScrollingMovementMethod());
console.append("aaaa");
return view;
}
public void setEthereumManager(EthereumManager ethereumManager) {
public void onMessage(String message) {
this.ethereumManager = ethereumManager;
/*
ethereumManager.addListener(new EthereumListenerAdapter() {
@Override
public void trace(final String output) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//console.append(output);
}
});
}
});
*/
consoleUpdater.setText(message);
ConsoleFragment.this.console.post(consoleUpdater);
}
public void updateDuration(long duration) {
console.append(String.valueOf(duration/1000) + "seconds");
}
}

View File

@ -0,0 +1,35 @@
package org.ethereum.android_app;
import org.ethereum.android.EthereumAidlService;
import org.ethereum.android.interop.IListener;
public class EthereumService extends EthereumAidlService {
public EthereumService() {
}
@Override
protected void broadcastMessage(String message) {
updateLog(message);
for (IListener listener: clientListeners) {
try {
listener.trace(message);
} catch (Exception e) {
// Remove listener
clientListeners.remove(listener);
}
}
}
private void updateLog(String message) {
EthereumService.log += message;
int logLength = EthereumService.log.length();
if (logLength > 5000) {
EthereumService.log = EthereumService.log.substring(2500);
}
}
}

View File

@ -0,0 +1,6 @@
package org.ethereum.android_app;
public interface FragmentInterface {
void onMessage(String message);
}

View File

@ -1,8 +1,14 @@
package org.ethereum.android_app;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
@ -11,24 +17,96 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.os.Build;
import android.widget.Toast;
import org.ethereum.android.EthereumManager;
import org.ethereum.android.interop.IEthereumService;
import org.ethereum.android.interop.IListener;
import org.ethereum.config.SystemProperties;
import java.io.File;
import java.util.ArrayList;
public class MainActivity extends ActionBarActivity {
public class MainActivity extends ActionBarActivity implements ActivityInterface {
private static final String TAG = "MyActivity";
private static Integer quit = 0;
private Toolbar toolbar;
private ViewPager viewPager;
private SlidingTabLayout tabs;
private TabsPagerAdapter adapter;
protected ArrayList<FragmentInterface> fragments = new ArrayList<>();
public EthereumManager ethereumManager = null;
protected static String consoleLog = "";
/** Ethereum Aidl Service. */
IEthereumService ethereumService = null;
IListener.Stub ethereumListener = new IListener.Stub() {
public void trace(String message) throws RemoteException {
logMessage(message);
}
};
/** Flag indicating whether we have called bind on the service. */
boolean isBound;
/**
* Class for interacting with the main interface of the service.
*/
protected ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
ethereumService = IEthereumService.Stub.asInterface(service);
Toast.makeText(MainActivity.this, "service attached", Toast.LENGTH_SHORT).show();
try {
// Try to load blocks dump file from /sdcard/poc-9-492k.dmp
String blocksDump = null;
File extStore = Environment.getExternalStorageDirectory();
if (extStore.exists()) {
String sdcardPath = extStore.getAbsolutePath();
File dumpFile = new File(extStore, "poc-9-492k.dmp");
if (dumpFile.exists()) {
blocksDump = dumpFile.getAbsolutePath();
}
}
// Start json rpc server
ethereumService.startJsonRpcServer();
// If blocks dump not found, connect to peer
if (blocksDump != null) {
ethereumService.loadBlocks(blocksDump);
} else {
ethereumService.addListener(ethereumListener);
ethereumService.connect(SystemProperties.CONFIG.activePeerIP(),
SystemProperties.CONFIG.activePeerPort(),
SystemProperties.CONFIG.activePeerNodeid());
}
Toast.makeText(MainActivity.this, "connected to service", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
logMessage("Error adding listener: " + e.getMessage());
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
ethereumService = null;
Toast.makeText(MainActivity.this, "service disconnected", Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -39,17 +117,7 @@ public class MainActivity extends ActionBarActivity {
toolbar = (Toolbar) findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
String databaseFolder = null;
File extStore = Environment.getExternalStorageDirectory();
if (extStore.exists()) {
databaseFolder = extStore.getAbsolutePath();
} else {
databaseFolder = getApplicationInfo().dataDir;
}
ethereumManager = new EthereumManager(this, databaseFolder);
adapter = new TabsPagerAdapter(getSupportFragmentManager(), ethereumManager);
adapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(adapter);;
@ -57,21 +125,72 @@ public class MainActivity extends ActionBarActivity {
tabs.setDistributeEvenly(true);
tabs.setViewPager(viewPager);
ComponentName myService = startService(new Intent("org.ethereum.android_app.EthereumService"));
doBindService();
//StrictMode.enableDefaults();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
new PostTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
new PostTask().execute();
protected void logMessage(String message) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
new JsonRpcTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
new JsonRpcTask().execute();
MainActivity.consoleLog += message + "\n";
int consoleLength = MainActivity.consoleLog.length();
if (consoleLength > 5000) {
MainActivity.consoleLog = MainActivity.consoleLog.substring(4000);
}
broadcastFragments(MainActivity.consoleLog);
}
protected void broadcastFragments(String message) {
for (FragmentInterface fragment : fragments) {
fragment.onMessage(message);
}
}
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
bindService(new Intent("org.ethereum.android_app.EthereumService"), serviceConnection, Context.BIND_AUTO_CREATE);
isBound = true;
Toast.makeText(MainActivity.this, "binding to service", Toast.LENGTH_SHORT).show();
}
void doUnbindService() {
if (isBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (ethereumService != null) {
try {
ethereumService.removeListener(ethereumListener);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(serviceConnection);
isBound = false;
Toast.makeText(MainActivity.this, "unbinding from service", Toast.LENGTH_SHORT).show();
}
}
public void registerFragment(FragmentInterface fragment) {
if (!fragments.contains(fragment)) {
fragments.add(fragment);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//return super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
@ -79,6 +198,7 @@ public class MainActivity extends ActionBarActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
@ -94,97 +214,13 @@ public class MainActivity extends ActionBarActivity {
@Override
protected void onDestroy() {
super.onDestroy();
if (ethereumManager != null) {
ethereumManager.onDestroy();
}
}
// The definition of our task class
private class PostTask extends AsyncTask<Context, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected String doInBackground(Context... params) {
Log.v(TAG, "111");
Log.v(TAG, "222");
ThreadGroup group = new ThreadGroup("threadGroup");
new Thread(group, new Runnable() {
@Override
public void run() {
long duration = ethereumManager.connect(SystemProperties.CONFIG.databaseDir() + File.separator + "poc-9-492k.dmp");
}
}, "EthereumConnect", 32768000).start();
//ConsoleFragment consoleeFrag = (ConsoleFragment)getSupportFragmentManager().findFragmentById(R.id.console);
//consoleeFrag.updateDuration(duration);
Log.v(TAG, "333");
while(true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (quit == 1) {
Log.v(TAG, "Ending background process.");
return "All Done!";
}
//publishProgress(1111);
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.v(TAG, values[0].toString());
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
}
doUnbindService();
}
// The definition of our task class
private class JsonRpcTask extends AsyncTask<Context, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected String doInBackground(Context... params) {
Log.v(TAG, "444");
try {
if (ethereumManager != null) {
ethereumManager.startJsonRpc();
}
} catch (Exception e) {
}
Log.v(TAG, "555");
return "done";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.v(TAG, values[0].toString());
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
}
}
}

View File

@ -10,13 +10,11 @@ public class TabsPagerAdapter extends FragmentPagerAdapter {
int tabsCount;
private String[] tabTitles = { "Console", "Tests" };
private EthereumManager ethereumManager;
public TabsPagerAdapter(FragmentManager fragmentManager, EthereumManager ethereumManager) {
public TabsPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
this.tabsCount = tabTitles.length;
this.ethereumManager = ethereumManager;
}
@Override
@ -25,7 +23,6 @@ public class TabsPagerAdapter extends FragmentPagerAdapter {
switch (index) {
case 0:
ConsoleFragment consoleFragment = new ConsoleFragment();
consoleFragment.setEthereumManager(ethereumManager);
return consoleFragment;
case 1:
TestsFragment testsFragment = new TestsFragment();

View File

@ -4,11 +4,14 @@
android:orientation="vertical">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:text="@string/console_output"
android:textSize="14sp"
android:layout_centerInParent="true"
android:id="@+id/console"/>
android:id="@+id/console"
android:gravity="bottom"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:scrollbarSize="25dip"
android:scrollbarFadeDuration="0"/>
</RelativeLayout>

View File

@ -1,7 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.ethereum.android">
<application android:allowBackup="true" android:label="@string/app_name">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.ethereum.android" >
<application
android:allowBackup="true"
android:label="@string/app_name" >
<service
android:name=".EthereumAidlService"
android:enabled="true"
android:exported="true"
android:process=":ethereum_process" >
<intent-filter>
<action android:name="org.ethereum.android.IEthereumAidl" />
</intent-filter>
</service>
<service
android:name=".EthereumRemoteService"
android:enabled="true"
android:exported="true"
android:process=":ethereum_process" >
</service>
<service
android:name=".EthereumService"
android:enabled="true"
android:exported="true" >
</service>
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package org.ethereum.android.interop;
interface IAsyncCallback {
void handleResponse(String name);
}

View File

@ -0,0 +1,14 @@
package org.ethereum.android.interop;
import org.ethereum.android.interop.IListener;
import org.ethereum.android.interop.IAsyncCallback;
oneway interface IEthereumService {
void loadBlocks(String dumpFile);
void connect(String ip, int port, String remoteId);
void startJsonRpcServer();
void addListener(IListener listener);
void removeListener(IListener listener);
void getLog(IAsyncCallback callback);
}

View File

@ -0,0 +1,6 @@
package org.ethereum.android.interop;
oneway interface IListener {
void trace(String output);
}

View File

@ -0,0 +1,181 @@
package org.ethereum.android;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import org.ethereum.android.di.components.DaggerEthereumComponent;
import org.ethereum.android.di.modules.EthereumModule;
import org.ethereum.android.jsonrpc.JsonRpcServer;
import org.ethereum.android.manager.BlockLoader;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.*;
import org.ethereum.facade.Ethereum;
import org.ethereum.android.interop.*;
import org.ethereum.net.p2p.HelloMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class EthereumAidlService extends Service {
protected Ethereum ethereum = null;
protected JsonRpcServer jsonRpcServer;
protected ArrayList<IListener> clientListeners = new ArrayList<>();
public static String log = "";
public EthereumAidlService() {
}
protected void broadcastMessage(String message) {
for (IListener listener: clientListeners) {
try {
listener.trace(message);
} catch (Exception e) {
// Remove listener
clientListeners.remove(listener);
}
}
}
@Override
public void onCreate() {
super.onCreate();
initializeEthereum();
}
protected void initializeEthereum() {
System.setProperty("sun.arch.data.model", "32");
System.setProperty("leveldb.mmap", "false");
String databaseFolder = getApplicationInfo().dataDir;
System.out.println("Database folder: " + databaseFolder);
SystemProperties.CONFIG.setDataBaseDir(databaseFolder);
ethereum = DaggerEthereumComponent.builder()
.ethereumModule(new EthereumModule(this))
.build().ethereum();
ethereum.addListener(new EthereumListener());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
IEthereumService.Stub mBinder = new IEthereumService.Stub() {
public void loadBlocks(String dumpFile) throws RemoteException {
BlockLoader blockLoader = (BlockLoader)ethereum.getBlockLoader();
blockLoader.loadBlocks(dumpFile);
}
public void connect(String ip, int port, String remoteId) throws RemoteException {
ethereum.connect(ip, port, remoteId);
}
public void addListener(IListener listener) throws RemoteException {
if (!clientListeners.contains(listener)) {
clientListeners.add(listener);
}
}
public void removeListener(IListener listener) throws RemoteException {
if (clientListeners.contains(listener)) {
clientListeners.remove(listener);
}
}
public void startJsonRpcServer() throws RemoteException {
jsonRpcServer = new JsonRpcServer(ethereum);
}
public void getLog(IAsyncCallback callback) throws RemoteException {
callback.handleResponse(EthereumAidlService.log);
}
};
protected class EthereumListener implements org.ethereum.listener.EthereumListener {
@Override
public void trace(String output) {
broadcastMessage(output);
}
@Override
public void onBlock(org.ethereum.core.Block block, List<TransactionReceipt> receipts) {
broadcastMessage("Added block.");
}
@Override
public void onRecvMessage(org.ethereum.net.message.Message message) {
broadcastMessage("Received message: " + message.getCommand().name());
}
@Override
public void onSendMessage(org.ethereum.net.message.Message message) {
broadcastMessage("Sending message: " + message.getCommand().name());
}
@Override
public void onPeerDisconnect(String host, long port) {
broadcastMessage("Peer disconnected: " + host + ":" + port);
}
@Override
public void onPendingTransactionsReceived(Set<Transaction> transactions) {
broadcastMessage("Pending transactions received: " + transactions.size());
}
@Override
public void onSyncDone() {
broadcastMessage("Sync done");
}
@Override
public void onNoConnections() {
broadcastMessage("No connections");
}
@Override
public void onHandShakePeer(HelloMessage helloMessage) {
broadcastMessage("Peer handshaked: " + helloMessage.getCode());
}
@Override
public void onVMTraceCreated(String transactionHash, String trace) {
broadcastMessage("Trace created: " + " - ");
}
}
}

View File

@ -80,7 +80,6 @@ public class EthereumManager {
public void close() {
ethereum.close();
OpenHelperManager.releaseHelper();
}
}

View File

@ -0,0 +1,16 @@
package org.ethereum.android;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class EthereumRemoteService extends Service {
public EthereumRemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -0,0 +1,16 @@
package org.ethereum.android;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class EthereumService extends Service {
public EthereumService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -26,12 +26,22 @@ import java.util.concurrent.Callable;
public class OrmLiteBlockStoreDatabase extends OrmLiteSqliteOpenHelper implements BlockStoreDatabase {
private static OrmLiteBlockStoreDatabase instance;
private static final String DATABASE_NAME = "blockchain.db";
private static final int DATABASE_VERSION = 1;
private Dao<BlockVO, Integer> blockDao = null;
private Dao<TransactionReceiptVO, Integer> transactionDao = null;
public static synchronized OrmLiteBlockStoreDatabase getHelper(Context context)
{
if (instance == null)
instance = new OrmLiteBlockStoreDatabase(context);
return instance;
}
public OrmLiteBlockStoreDatabase(Context context) {
super(context, SystemProperties.CONFIG.databaseDir()
+ File.separator + DATABASE_NAME, null, DATABASE_VERSION);

View File

@ -2,8 +2,6 @@ package org.ethereum.android.di.modules;
import android.content.Context;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import org.ethereum.android.datasource.LevelDbDataSource;
import org.ethereum.android.db.InMemoryBlockStore;
import org.ethereum.android.db.OrmLiteBlockStoreDatabase;
@ -75,7 +73,7 @@ public class EthereumModule {
@Provides
@Singleton
BlockStore provideBlockStore() {
OrmLiteBlockStoreDatabase database = OpenHelperManager.getHelper(context, OrmLiteBlockStoreDatabase.class);
OrmLiteBlockStoreDatabase database = OrmLiteBlockStoreDatabase.getHelper(context);
return new InMemoryBlockStore(database);
}

View File

@ -0,0 +1,47 @@
package org.ethereum.android.interop;
import android.os.Parcel;
import android.os.Parcelable;
public class Block extends org.ethereum.core.Block implements Parcelable {
public Block(byte[] rawData) {
super(rawData);
}
public Block(Parcel in) {
super(null);
rlpEncoded = new byte[in.readInt()];
in.readByteArray(rlpEncoded);
}
@Override
public void writeToParcel(Parcel parcel, int i) {
byte[] data = getEncoded();
parcel.writeInt(data.length);
parcel.writeByteArray(data);
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public Block createFromParcel(Parcel in) {
return new Block(in);
}
public Block[] newArray(int size) {
return new Block[size];
}
};
}

View File

@ -46,7 +46,7 @@ public class Block {
/* Private */
private byte[] rlpEncoded;
protected byte[] rlpEncoded;
private boolean parsed = false;
private Trie txsState;

View File

@ -16,7 +16,7 @@ import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import javax.swing.*;
//import javax.swing.*;
public class Utils {
@ -51,11 +51,13 @@ public class Utils {
return formatter.format(date);
}
/*
public static ImageIcon getImageIcon(String resource) {
URL imageURL = ClassLoader.getSystemResource(resource);
ImageIcon image = new ImageIcon(imageURL);
return image;
}
*/
static BigInteger _1000_ = new BigInteger("1000");

View File

@ -64,19 +64,19 @@ peer.discovery.workers = 3
# connection timeout for trying to
# connect to a peer [seconds]
peer.connection.timeout = 2
peer.connection.timeout = 300
# the time we wait to the network
# to approve the transaction, the
# transaction got approved when
# include into a transactions msg
# retrieved from the peer [seconds]
transaction.approve.timeout = 15
transaction.approve.timeout = 300
# the parameter specifies how much
# time we will wait for a message
# to come before closing the channel
peer.channel.read.timeout = 30
peer.channel.read.timeout = 300
# default directory where we keep
# basic Serpent samples relative