diff --git a/android/app/build.gradle b/android/app/build.gradle index d2e57e779b..913abe3bc0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -133,7 +133,8 @@ dependencies { compile project(':react-native-camera') compile project(':react-native-status') compile project(':react-native-orientation') - compile(group: 'status-im', name: 'status-go', version: '0.1.0-201606301634-5d7b29', ext: 'aar') + //compile(name:'statusgo-android-16', ext:'aar') + compile(group: 'status-im', name: 'status-go', version: '0.1.0-201607011545-da53ec', ext: 'aar') compile fileTree(dir: "node_modules/realm/android/libs", include: ["*.jar"]) } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 42b5ee2517..05008651d2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,10 +23,9 @@ + android:exported="true"/> diff --git a/android/app/src/main/java/com/statusim/GethService.java b/android/app/src/main/java/com/statusim/GethService.java deleted file mode 100644 index 7a11e0e739..0000000000 --- a/android/app/src/main/java/com/statusim/GethService.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.statusim; - -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.AsyncTask; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.support.annotation.Nullable; -import android.util.Log; -import android.os.Environment; - -import java.lang.ref.WeakReference; - -import com.github.status_im.status_go.Statusgo; - -import java.io.File; - -public class GethService extends Service { - - private static final String TAG = "GethService"; - - private static boolean isGethStarted = false; - private static boolean isGethInitialized = false; - private final Handler handler = new Handler(); - - static class IncomingHandler extends Handler { - - private final WeakReference service; - - IncomingHandler(GethService service) { - - this.service = new WeakReference(service); - } - - @Override - public void handleMessage(Message message) { - - GethService service = this.service.get(); - if (service != null) { - if (!service.handleMessage(message)) { - super.handleMessage(message); - } - } - } - } - - final Messenger serviceMessenger = new Messenger(new IncomingHandler(this)); - - protected class StartTask extends AsyncTask { - - public StartTask() { - } - - protected Void doInBackground(Void... args) { - startGeth(); - return null; - } - - protected void onPostExecute(Void results) { - onGethStarted(); - } - } - - protected void onGethStarted() { - Log.d(TAG, "Geth Service started"); - isGethStarted = true; - } - - protected void startGeth() { - Log.d(TAG, "Starting background Geth Service"); - - File extStore = Environment.getExternalStorageDirectory(); - - final String dataFolder = extStore.exists() ? - extStore.getAbsolutePath() : - getApplicationInfo().dataDir; - - new Thread(new Runnable() { - public void run() { - Statusgo.StartNode(dataFolder); - } - }).start(); - } - - public void signalEvent(String jsonEvent) { - - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return serviceMessenger.getBinder(); - } - - @Override - public void onCreate() { - super.onCreate(); - System.loadLibrary("statusgo"); - - if (!isGethInitialized) { - isGethInitialized = true; - new StartTask().execute(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - //TODO: stop geth - isGethStarted = false; - isGethInitialized = false; - Log.d(TAG, "Geth Service stopped !"); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return Service.START_STICKY; - } - - protected boolean handleMessage(Message message) { - return false; - } - - public static boolean isRunning() { - return isGethInitialized; - } -} diff --git a/android/app/src/main/java/com/statusim/MainActivity.java b/android/app/src/main/java/com/statusim/MainActivity.java index 4524cb19cb..6c779cee3b 100644 --- a/android/app/src/main/java/com/statusim/MainActivity.java +++ b/android/app/src/main/java/com/statusim/MainActivity.java @@ -1,33 +1,24 @@ package com.statusim; import com.facebook.react.ReactActivity; +import com.statusim.geth.module.GethPackage; import io.realm.react.RealmReactPackage; import com.oblador.vectoricons.VectorIconsPackage; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.rt2zz.reactnativecontacts.ReactNativeContacts; import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnCancelListener; -import android.content.ComponentName; -import android.content.ServiceConnection; import android.content.Intent; -import android.content.Context; import com.bitgo.randombytes.RandomBytesPackage; import com.BV.LinearGradient.LinearGradientPackage; import com.centaurwarchief.smslistener.SmsListener; import com.github.yamill.orientation.OrientationPackage; - -import android.util.Log; - import java.util.Arrays; import java.util.List; import java.util.Properties; @@ -36,64 +27,12 @@ import com.statusim.Jail.JailPackage; import com.lwansbrough.RCTCamera.*; import com.i18n.reactnativei18n.ReactNativeI18n; -import io.realm.react.RealmReactPackage; -import android.content.Intent; import android.content.res.Configuration; public class MainActivity extends ReactActivity { private static final String TAG = "MainActivity"; - /** - * Incoming message handler. Calls to its binder are sequential! - */ - protected final IncomingHandler handler = new IncomingHandler(); - - /** Flag indicating if the service is bound. */ - protected boolean isBound; - - /** Sends messages to the service. */ - protected Messenger serviceMessenger = null; - - /** Receives messages from the service. */ - protected Messenger clientMessenger = new Messenger(handler); - - class IncomingHandler extends Handler { - - @Override - public void handleMessage(Message message) { - boolean isClaimed = false; - Log.d(TAG, "!!!!!!!!!!!!!! Received Service Message !!!!!!!!!!!!!!"); - super.handleMessage(message); - } - } - - 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 object we can use to - // interact with the service. We are communicating with the - // service using a Messenger, so here we get a client-side - // representation of that from the raw IBinder object. - serviceMessenger = new Messenger(service); - isBound = true; - onConnected(); - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - serviceMessenger = null; - isBound = false; - Log.d(TAG, "!!!!!!!!!!!!!! Geth Service Disconnected !!!!!!!!!!!!!!"); - } - }; - - protected void onConnected() { - Log.d(TAG, "!!!!!!!!!!!!!! Geth Service Connected !!!!!!!!!!!!!!"); - } - protected void startStatus() { // Required because of crazy APN settings redirecting localhost (found in GB) Properties properties = System.getProperties(); @@ -130,25 +69,11 @@ public class MainActivity extends ReactActivity { }).create(); dialog.show(); } - Intent intent = new Intent(this, GethService.class); - if (!GethService.isRunning()) { - startService(intent); - } - if (serviceConnection != null && GethService.isRunning()) { - bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); - } - } @Override protected void onDestroy() { super.onDestroy(); - try { - unbindService(serviceConnection); - } - catch (Throwable t) { - Log.e(TAG, "Failed to unbind from the geth service", t); - } } /** @@ -186,7 +111,8 @@ public class MainActivity extends ReactActivity { new LinearGradientPackage(), new RCTCameraPackage(), new SmsListener(this), - new OrientationPackage(this) + new OrientationPackage(this), + new GethPackage() ); } diff --git a/android/app/src/main/java/com/statusim/geth/module/GethModule.java b/android/app/src/main/java/com/statusim/geth/module/GethModule.java new file mode 100644 index 0000000000..058ab03667 --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/module/GethModule.java @@ -0,0 +1,219 @@ +package com.statusim.geth.module; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Message; +import com.facebook.react.bridge.*; +import com.statusim.geth.service.ConnectorHandler; +import com.statusim.geth.service.GethConnector; +import com.statusim.geth.service.GethMessages; +import com.statusim.geth.service.GethService; +import android.util.Log; + +import java.util.HashMap; +import java.util.UUID; + +public class GethModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler { + + private static final String TAG = "GethModule"; + + protected GethConnector geth = null; + protected String handlerIdentifier = createIdentifier(); + + protected HashMap startNodeCallbacks = new HashMap<>(); + protected HashMap createAccountCallbacks = new HashMap<>(); + protected HashMap addAccountCallbacks = new HashMap<>(); + protected HashMap unlockAccountCallbacks = new HashMap<>(); + + + public GethModule(ReactApplicationContext reactContext) { + super(reactContext); + reactContext.addLifecycleEventListener(this); + } + + @Override + public String getName() { + return "Geth"; + } + + @Override + public void onHostResume() { // Actvity `onResume` + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + return; + } + if (geth == null) { + geth = new GethConnector(currentActivity, GethService.class); + geth.registerHandler(this); + } + geth.bindService(); + } + + @Override + public void onHostPause() { // Actvity `onPause` + + if (geth != null) { + geth.unbindService(); + } + } + + @Override + public void onHostDestroy() { // Actvity `onDestroy` + + if (geth != null) { + geth.stopNode(null); + } + } + + @Override + public String getID() { + + return handlerIdentifier; + } + + @Override + public void onConnectorConnected() { + } + + @Override + public void onConnectorDisconnected() { + } + + @Override + public boolean handleMessage(Message message) { + + Log.d(TAG, "Received message: " + message.toString()); + boolean isClaimed = true; + Bundle data = message.getData(); + String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER); + Log.d(TAG, "callback identifier: " + callbackIdentifier); + Callback callback = null; + switch (message.what) { + case GethMessages.MSG_NODE_STARTED: + Log.d(TAG, "handle startNodeCallbacks size: " + startNodeCallbacks.size()); + callback = startNodeCallbacks.remove(callbackIdentifier); + if (callback != null) { + callback.invoke(true); + } else { + Log.d(TAG, "Could not find callback: " + callbackIdentifier); + } + break; + case GethMessages.MSG_NODE_STOPPED: + break; + case GethMessages.MSG_ACCOUNT_CREATED: + callback = createAccountCallbacks.remove(callbackIdentifier); + if (callback != null) { + callback.invoke(data.getString("data")); + } + break; + case GethMessages.MSG_ACCOUNT_ADDED: + callback = addAccountCallbacks.remove(callbackIdentifier); + if (callback != null) { + callback.invoke(null, "{ \"address\": \"" + data.getString("address") + "\"}"); + } + break; + case GethMessages.MSG_LOGGED_IN: + callback = unlockAccountCallbacks.remove(callbackIdentifier); + if (callback != null) { + callback.invoke(null, "{ \"result\": \"" + data.getString("result") + "\"}"); + } + break; + default: + isClaimed = false; + } + + return isClaimed; + } + + @ReactMethod + public void startNode(Callback callback) { + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + callback.invoke("Activity doesn't exist"); + return; + } + + if (geth == null) { + callback.invoke("Geth connector is null"); + return; + } + + String callbackIdentifier = createIdentifier(); + Log.d(TAG, "Created callback identifier: " + callbackIdentifier); + startNodeCallbacks.put(callbackIdentifier, callback); + Log.d(TAG, "startNodeCallbacks size: " + startNodeCallbacks.size()); + + geth.startNode(callbackIdentifier); + } + + @ReactMethod + public void login(String address, String password, Callback callback) { + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + callback.invoke("Activity doesn't exist"); + return; + } + + if (geth == null) { + callback.invoke("Geth connector is null"); + return; + } + + String callbackIdentifier = createIdentifier(); + unlockAccountCallbacks.put(callbackIdentifier, callback); + + geth.login(callbackIdentifier, address, password); + } + + @ReactMethod + public void createAccount(String password, Callback callback) { + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + callback.invoke("Activity doesn't exist"); + return; + } + + if (geth == null) { + callback.invoke("Geth connector is null"); + return; + } + + String callbackIdentifier = createIdentifier(); + createAccountCallbacks.put(callbackIdentifier, callback); + + geth.createAccount(callbackIdentifier, password); + } + + @ReactMethod + public void addAccount(String privateKey, Callback callback) { + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + callback.invoke("Activity doesn't exist"); + return; + } + + if (geth == null) { + callback.invoke("Geth connector is null"); + return; + } + + String callbackIdentifier = createIdentifier(); + addAccountCallbacks.put(callbackIdentifier, callback); + geth.addAccount(callbackIdentifier, privateKey); + } + + protected String createIdentifier() { + return UUID.randomUUID().toString(); + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/com/statusim/geth/module/GethPackage.java b/android/app/src/main/java/com/statusim/geth/module/GethPackage.java new file mode 100644 index 0000000000..d7dc02df6f --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/module/GethPackage.java @@ -0,0 +1,33 @@ +package com.statusim.geth.module; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GethPackage implements ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new GethModule(reactContext)); + + return modules; + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/statusim/geth/service/ConnectorHandler.java b/android/app/src/main/java/com/statusim/geth/service/ConnectorHandler.java new file mode 100644 index 0000000000..0df2737a97 --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/service/ConnectorHandler.java @@ -0,0 +1,12 @@ +package com.statusim.geth.service; + + +import android.os.Message; + +public interface ConnectorHandler { + + boolean handleMessage(Message message); + void onConnectorConnected(); + void onConnectorDisconnected(); + String getID(); +} \ No newline at end of file diff --git a/android/app/src/main/java/com/statusim/geth/service/GethConnector.java b/android/app/src/main/java/com/statusim/geth/service/GethConnector.java new file mode 100644 index 0000000000..7083dff2d5 --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/service/GethConnector.java @@ -0,0 +1,110 @@ +package com.statusim.geth.service; + + +import android.content.Context; +import android.os.Bundle; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +public class GethConnector extends ServiceConnector { + + private static final String TAG = "GethConnector"; + + public static final String CALLBACK_IDENTIFIER = "callbackIdentifier"; + + public GethConnector(Context context, Class serviceClass) { + + super(context, serviceClass); + } + + public void startNode(String callbackIdentifier) { + + if (checkBound()) { + Message msg = createMessage(callbackIdentifier, GethMessages.MSG_START_NODE, null); + try { + serviceMessenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Exception sending message(startNode) to service: ", e); + } + } + } + + public void stopNode(String callbackIdentifier) { + + if (checkBound()) { + Message msg = createMessage(callbackIdentifier, GethMessages.MSG_STOP_NODE, null); + try { + serviceMessenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Exception sending message(stopNode) to service: ", e); + } + } + } + + public void login(String callbackIdentifier, String address, String password) { + + if (checkBound()) { + Bundle data = new Bundle(); + data.putString("address", address); + data.putString("password", password); + Message msg = createMessage(callbackIdentifier, GethMessages.MSG_LOGIN, data); + try { + serviceMessenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Exception sending message(unlockAccount) to service: ", e); + } + } + } + + public void createAccount(String callbackIdentifier, String password) { + + if (checkBound()) { + Bundle data = new Bundle(); + data.putString("password", password); + Message msg = createMessage(callbackIdentifier, GethMessages.MSG_CREATE_ACCOUNT, data); + try { + serviceMessenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Exception sending message(createAccount) to service: ", e); + } + } + } + + public void addAccount(String callbackIdentifier, String privateKey) { + + if (checkBound()) { + Bundle data = new Bundle(); + data.putString("privateKey", privateKey); + Message msg = createMessage(callbackIdentifier, GethMessages.MSG_ADD_ACCOUNT, data); + try { + serviceMessenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Exception sending message(addAccount) to service: ", e); + } + } + } + + + protected boolean checkBound() { + + if (!isBound) { + Log.d(TAG, "GethConnector not bound!"); + return false; + } + return true; + } + + protected Message createMessage(String callbackIdentifier, int idMessage, Bundle data) { + + Log.d(TAG, "Client messenger: " + clientMessenger.toString()); + Message msg = Message.obtain(null, idMessage, 0, 0); + msg.replyTo = clientMessenger; + if (data == null) { + data = new Bundle(); + } + data.putString(CALLBACK_IDENTIFIER, callbackIdentifier); + msg.setData(data); + return msg; + } +} diff --git a/android/app/src/main/java/com/statusim/geth/service/GethMessages.java b/android/app/src/main/java/com/statusim/geth/service/GethMessages.java new file mode 100644 index 0000000000..f6c3e24737 --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/service/GethMessages.java @@ -0,0 +1,56 @@ +package com.statusim.geth.service; + + +public class GethMessages { + + /** + * Start the node + */ + public static final int MSG_START_NODE = 1; + + /** + * Node started event + */ + public static final int MSG_NODE_STARTED = 2; + + /** + * Stop the node + */ + public static final int MSG_STOP_NODE = 3; + + /** + * Node stopped event + */ + public static final int MSG_NODE_STOPPED = 4; + + /** + * Unlock an account + */ + public static final int MSG_LOGIN = 5; + + /** + * Account unlocked event + */ + public static final int MSG_LOGGED_IN = 6; + + /** + * Create an account + */ + public static final int MSG_CREATE_ACCOUNT = 7; + + /** + * Account created event + */ + public static final int MSG_ACCOUNT_CREATED = 8; + + /** + * Add an account + */ + public static final int MSG_ADD_ACCOUNT = 9; + + /** + * Account added event + */ + public static final int MSG_ACCOUNT_ADDED = 10; + +} diff --git a/android/app/src/main/java/com/statusim/geth/service/GethService.java b/android/app/src/main/java/com/statusim/geth/service/GethService.java new file mode 100644 index 0000000000..9900b3ef8e --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/service/GethService.java @@ -0,0 +1,255 @@ +package com.statusim.geth.service; + +import android.app.Service; +import android.content.Intent; +import android.os.*; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.lang.ref.WeakReference; + +import com.github.status_im.status_go.Statusgo; + +import java.io.File; + +public class GethService extends Service { + + private static final String TAG = "GethService"; + + private static boolean isGethStarted = false; + private static boolean isGethInitialized = false; + private final Handler handler = new Handler(); + + private static String dataFolder; + + static class IncomingHandler extends Handler { + + private final WeakReference service; + + IncomingHandler(GethService service) { + + this.service = new WeakReference(service); + } + + @Override + public void handleMessage(Message message) { + + GethService service = this.service.get(); + if (service != null) { + if (!service.handleMessage(message)) { + super.handleMessage(message); + } + } + } + } + + final Messenger serviceMessenger = new Messenger(new IncomingHandler(this)); + + + public static void signalEvent(String jsonEvent) { + System.out.println("\n\n\nIT WOOOOOORKS1111!!!!!!\n\n\n"); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return serviceMessenger.getBinder(); + } + + @Override + public void onCreate() { + super.onCreate(); + System.loadLibrary("statusgoraw"); + System.loadLibrary("statusgo"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + //TODO: stop geth + stopNode(null); + isGethStarted = false; + isGethInitialized = false; + Log.d(TAG, "Geth Service stopped !"); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return Service.START_STICKY; + } + + protected boolean handleMessage(Message message) { + switch (message.what) { + + case GethMessages.MSG_START_NODE: + Log.d(TAG, "Received start node message." + message.toString()); + startNode(message); + break; + + case GethMessages.MSG_STOP_NODE: + stopNode(message); + break; + + case GethMessages.MSG_CREATE_ACCOUNT: + createAccount(message); + break; + + case GethMessages.MSG_ADD_ACCOUNT: + addAccount(message); + break; + + case GethMessages.MSG_LOGIN: + login(message); + break; + + default: + return false; + } + + return true; + } + + protected void startNode(Message message) { + if (!isGethInitialized) { + isGethInitialized = true; + Log.d(TAG, "Client messenger1: " + message.replyTo.toString()); + Bundle data = message.getData(); + String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER); + Log.d(TAG, "Callback identifier: " + callbackIdentifier); + new StartTask(message.replyTo, callbackIdentifier).execute(); + } + } + + protected class StartTask extends AsyncTask { + + protected String callbackIdentifier; + protected Messenger messenger; + + public StartTask(Messenger messenger, String callbackIdentifier) { + this.messenger = messenger; + this.callbackIdentifier = callbackIdentifier; + } + + protected Void doInBackground(Void... args) { + startGeth(); + return null; + } + + protected void onPostExecute(Void results) { + onGethStarted(messenger, callbackIdentifier); + } + } + + protected void onGethStarted(Messenger messenger, String callbackIdentifier) { + Log.d(TAG, "Geth Service started"); + isGethStarted = true; + Message replyMessage = Message.obtain(null, GethMessages.MSG_NODE_STARTED, 0, 0, null); + Bundle replyData = new Bundle(); + Log.d(TAG, "Callback identifier: " + callbackIdentifier); + replyData.putString(GethConnector.CALLBACK_IDENTIFIER, callbackIdentifier); + replyMessage.setData(replyData); + sendReply(messenger, replyMessage); + } + + protected void startGeth() { + + + File extStore = Environment.getExternalStorageDirectory(); + + dataFolder = extStore.exists() ? + extStore.getAbsolutePath() + "/ethereum" : + getApplicationInfo().dataDir + "/ethereum"; + Log.d(TAG, "Starting background Geth Service in folder: " + dataFolder); + try { + final File newFile = new File(dataFolder); + newFile.mkdir(); + } catch (Exception e) { + Log.e(TAG, "error making folder: " + dataFolder, e); + } + + new Thread(new Runnable() { + public void run() { + + Statusgo.StartNode(dataFolder); + } + }).start(); + } + + protected void stopNode(Message message) { + // TODO: stop node + + createAndSendReply(message, GethMessages.MSG_NODE_STOPPED, null); + } + + protected void createAccount(Message message) { + Bundle data = message.getData(); + String password = data.getString("password"); + // TODO: remove second argument + Log.d(TAG, "Creating account: " + password + " - " + dataFolder); + String jsonData = Statusgo.CreateAccount(password); + Log.d(TAG, "Created account: " + jsonData); + + Bundle replyData = new Bundle(); + replyData.putString("data", jsonData); + createAndSendReply(message, GethMessages.MSG_ACCOUNT_CREATED, replyData); + } + + protected void addAccount(Message message) { + Bundle data = message.getData(); + String privateKey = data.getString("privateKey"); + String password = data.getString("password"); + // TODO: add account + //String address = Statusgo.doAddAccount(privateKey, password); + String address = "added account address"; + Log.d(TAG, "Added account: " + address); + + Bundle replyData = new Bundle(); + replyData.putString("address", address); + createAndSendReply(message, GethMessages.MSG_ACCOUNT_ADDED, replyData); + } + + protected void login(Message message) { + Bundle data = message.getData(); + String address = data.getString("address"); + String password = data.getString("password"); + // TODO: remove third argument + String result = Statusgo.Login(address, password); + Log.d(TAG, "Unlocked account: " + result); + + Bundle replyData = new Bundle(); + replyData.putString("result", result); + createAndSendReply(message, GethMessages.MSG_LOGGED_IN, replyData); + } + + public static boolean isRunning() { + return isGethInitialized; + } + + protected void createAndSendReply(Message message, int replyIdMessage, Bundle replyData) { + + if (message == null) { + return; + } + Message replyMessage = Message.obtain(null, replyIdMessage, 0, 0, message.obj); + if (replyData == null) { + replyData = new Bundle(); + } + Bundle data = message.getData(); + String callbackIdentifier = data.getString(GethConnector.CALLBACK_IDENTIFIER); + Log.d(TAG, "Callback identifier: " + callbackIdentifier); + replyData.putString(GethConnector.CALLBACK_IDENTIFIER, callbackIdentifier); + replyMessage.setData(replyData); + + sendReply(message.replyTo, replyMessage); + } + + protected void sendReply(Messenger messenger, Message message) { + try { + messenger.send(message); + + } catch (Exception e) { + + Log.e(TAG, "Exception sending message id: " + message.what, e); + } + } +} diff --git a/android/app/src/main/java/com/statusim/geth/service/ServiceConnector.java b/android/app/src/main/java/com/statusim/geth/service/ServiceConnector.java new file mode 100644 index 0000000000..f20e1fcf66 --- /dev/null +++ b/android/app/src/main/java/com/statusim/geth/service/ServiceConnector.java @@ -0,0 +1,149 @@ +package com.statusim.geth.service; + + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.*; + +import java.util.ArrayList; + +public class ServiceConnector { + + private static final String TAG = "ServiceConnector"; + + /** + * Incoming message handler. Calls to its binder are sequential! + */ + protected final IncomingHandler handler; + + /** + * Handler thread to avoid running on the main UI thread + */ + protected final HandlerThread handlerThread; + + /** Context of the activity from which this connector was launched */ + protected Context context; + + /** The class of the service we want to connect to */ + protected Class serviceClass; + + /** Flag indicating if the service is bound. */ + protected boolean isBound; + + /** Sends messages to the service. */ + protected Messenger serviceMessenger = null; + + /** Receives messages from the service. */ + protected Messenger clientMessenger = null; + + protected ArrayList handlers = new ArrayList<>(); + + /** Handles incoming messages from service. */ + class IncomingHandler extends Handler { + + public IncomingHandler(HandlerThread thread) { + + super(thread.getLooper()); + } + + @Override + public void handleMessage(Message message) { + + boolean isClaimed = false; + //if (message.obj != null) { + // String identifier = ((Bundle) message.obj).getString("identifier"); + //if (identifier != null) { + + for (ConnectorHandler handler : handlers) { + // if (identifier.equals(handler.getID())) { + isClaimed = handler.handleMessage(message); + // } + } + // } + //} + if (!isClaimed) { + super.handleMessage(message); + } + } + } + + /** + * 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 object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + serviceMessenger = new Messenger(service); + isBound = true; + for (ConnectorHandler handler: handlers) { + handler.onConnectorConnected(); + } + } + + public void onServiceDisconnected(ComponentName className) { + + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + serviceMessenger = null; + isBound = false; + for (ConnectorHandler handler: handlers) { + handler.onConnectorDisconnected(); + } + } + }; + + public ServiceConnector(Context context, Class serviceClass) { + + this.context = context; + this.serviceClass = serviceClass; + handlerThread = new HandlerThread("HandlerThread"); + handlerThread.start(); + handler = new IncomingHandler(handlerThread); + clientMessenger = new Messenger(handler); + } + + /** Bind to the service */ + public boolean bindService() { + + if (serviceConnection != null) { + Intent intent = new Intent(context, serviceClass); + context.getApplicationContext().startService(intent); + return context.getApplicationContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + } else { + return false; + } + } + + /** Unbind from the service */ + public void unbindService() { + + if (isBound && serviceConnection != null) { + context.getApplicationContext().unbindService(serviceConnection); + isBound = false; +/* + Intent intent = new Intent(context, serviceClass); + context.getApplicationContext().stopService(intent); +*/ + } + } + + public void registerHandler(ConnectorHandler handler) { + + if (!handlers.contains(handler)) { + handlers.add(handler); + } + } + + public void removeHandler(ConnectorHandler handler) { + + handlers.remove(handler); + } +} diff --git a/project.clj b/project.clj index 4007f3100d..1f1b0c77d2 100644 --- a/project.clj +++ b/project.clj @@ -10,7 +10,7 @@ [prismatic/schema "1.0.4"] ^{:voom {:repo "git@github.com:status-im/status-lib.git" :branch "master"}} - [status-im/protocol "0.1.1-20160525_083359-g53ab2c2"] + [status-im/protocol "0.1.1-20160630_153846-gbf92f5f"] [natal-shell "0.1.6"] [com.andrewmcveigh/cljs-time "0.4.0"]] :plugins [[lein-cljsbuild "1.1.1"] diff --git a/src/status_im/accounts/handlers.cljs b/src/status_im/accounts/handlers.cljs new file mode 100644 index 0000000000..cfb6d32d9b --- /dev/null +++ b/src/status_im/accounts/handlers.cljs @@ -0,0 +1,45 @@ +(ns status-im.accounts.handlers + (:require [status-im.models.accounts :as accounts] + [re-frame.core :refer [register-handler after dispatch debug]] + [status-im.utils.logging :as log] + [status-im.components.react :refer [geth]] + [status-im.utils.types :refer [json->clj]] + [status-im.persistence.simple-kv-store :as kv] + [status-im.protocol.state.storage :as storage] + [clojure.string :as str])) + + +(defn save-account [_ [_ account]] + (accounts/save-accounts [account])) + +(register-handler :add-account + (-> (fn [db [_ {:keys [address] :as account}]] + (update db :accounts assoc address account)) + ((after save-account)))) + +(defn save-password [password] + (storage/put kv/kv-store :password password)) + +(defn account-created [result password] + (let [data (json->clj result) + public-key (:pubkey data) + address (:address data) + account {:public-key public-key + :address address}] + (log/debug "Created account: " result) + (when (not (str/blank? public-key)) + (do + (save-password password) + (dispatch [:login-account address password]) + (dispatch [:initialize-protocol account]) + (dispatch [:add-account account]))))) + +(register-handler :create-account + (-> (fn [db [_ password]] + (.createAccount geth password (fn [result] (account-created result password))) + db))) + +(register-handler :login-account + (-> (fn [db [_ address password]] + (.login geth address password (fn [result] (log/debug "Logged in account: " address result))) + db))) \ No newline at end of file diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index 5405f58393..f205c1e497 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -81,8 +81,10 @@ (defn init [] (dispatch-sync [:initialize-db]) (dispatch [:initialize-crypt]) + (dispatch [:initialize-geth]) (dispatch [:initialize-chats]) - (dispatch [:initialize-protocol]) + ;protocol must be initialized after user enters password and we create account + ;(dispatch [:initialize-protocol]) (dispatch [:load-user-phone-number]) (dispatch [:load-contacts]) (dispatch [:init-console-chat]) diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 05ab26564f..97df9c4281 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -20,6 +20,8 @@ [status-im.handlers.content-suggestions :refer [get-content-suggestions]] [status-im.utils.phone-number :refer [format-phone-number]] [status-im.utils.datetime :as time] + [status-im.components.react :refer [geth]] + [status-im.utils.logging :as log] [status-im.components.jail :as j] [status-im.utils.types :refer [json->clj]] [status-im.commands.utils :refer [generate-hiccup]])) @@ -361,6 +363,7 @@ (register-handler :save-password (fn [db [_ password]] + (dispatch [:create-account password]) (sign-up-service/save-password password) (assoc db :password-saved true))) @@ -472,7 +475,9 @@ :group-chat false :is-active true :timestamp (.getTime (js/Date.)) - :contacts [{:identity contcat-id}]}] + :contacts [{:identity contcat-id}] + :dapp-url nil + :dapp-hash nil}] (assoc db :new-chat chat))) (defn add-chat [{:keys [new-chat] :as db} [_ chat-id]] diff --git a/src/status_im/components/react.cljs b/src/status_im/components/react.cljs index f931ce4e00..f180f463c2 100644 --- a/src/status_im/components/react.cljs +++ b/src/status_im/components/react.cljs @@ -4,6 +4,7 @@ [status-im.utils.utils :as u])) (def react (u/require "react-native")) +(def native-modules (.-NativeModules react)) (defn get-react-property [name] (aget react name)) @@ -78,3 +79,5 @@ (def dismiss-keyboard! (u/require "dismissKeyboard")) (def device-event-emitter (.-DeviceEventEmitter react)) (def orientation (u/require "react-native-orientation")) + +(def geth (.-Geth native-modules)) diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 08f3e8abf0..9d1cc6fa5d 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -11,6 +11,7 @@ ;; initial state of app-db (def app-db {:identity-password "replace-me-with-user-entered-password" :identity "me" + :accounts {} :contacts [] :contacts-ids #{} :selected-contacts #{} diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index 1c656f5514..ee070f1ceb 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -7,7 +7,9 @@ [status-im.protocol.state.storage :as storage] [status-im.utils.logging :as log] [status-im.utils.crypt :refer [gen-random-bytes]] + [status-im.components.react :refer [geth]] [status-im.utils.handlers :refer [register-handler] :as u] + [status-im.models.protocol :as protocol] status-im.chat.handlers status-im.chat.handlers.animation status-im.group-settings.handlers @@ -19,6 +21,7 @@ status-im.commands.handlers.loading status-im.commands.handlers.jail status-im.qr-scanner.handlers + status-im.accounts.handlers status-im.protocol.handlers status-im.chat.handlers.requests)) @@ -55,7 +58,9 @@ (register-handler :initialize-db (fn [_ _] (assoc app-db - :signed-up (storage/get kv/kv-store :signed-up)))) + :signed-up (storage/get kv/kv-store :signed-up) + :user-identity (protocol/stored-identity nil) + :password (storage/get kv/kv-store :password)))) (register-handler :initialize-crypt (u/side-effect! @@ -73,6 +78,20 @@ (.addEntropy (.. js/ecc -sjcl -random))) (dispatch [:crypt-initialized])))))))) +(defn node-started [db result] + (let [identity (:user-identity db) + password (:password db)] + (log/debug "Started Node: " result) + (when identity (do + (dispatch [:login-account (:address identity) password]) + (dispatch [:initialize-protocol identity]))))) + +(register-handler :initialize-geth + (u/side-effect! + (fn [db _] + (log/debug "Starting node") + (.startNode geth (fn [result] (node-started db result)))))) + (register-handler :crypt-initialized (u/side-effect! (fn [_ _] diff --git a/src/status_im/handlers/server.cljs b/src/status_im/handlers/server.cljs index 5d9674c1d7..565f6f90c0 100644 --- a/src/status_im/handlers/server.cljs +++ b/src/status_im/handlers/server.cljs @@ -1,17 +1,20 @@ (ns status-im.handlers.server (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] - [status-im.utils.utils :refer [log on-error http-post]] + [status-im.utils.utils :refer [on-error http-post]] [status-im.utils.logging :as log])) (defn sign-up [db phone-number handler] - ;(user-data/save-phone-number phone-number) - (http-post "sign-up" {:phone-number phone-number - :whisper-identity (get-in db [:user-identity :public])} - (fn [body] - (log body) - (handler))) - db) + (let [{:keys [public-key address] :as account} (get-in db [:user-identity])] + ;(user-data/save-phone-number phone-number) + (log/debug "signing up with public-key" public-key "and phone " phone-number) + (http-post "sign-up" {:phone-number phone-number + :whisper-identity public-key + :address address} + (fn [body] + (log/debug body) + (handler))) + db)) (defn sign-up-confirm [confirmation-code handler] diff --git a/src/status_im/models/accounts.cljs b/src/status_im/models/accounts.cljs new file mode 100644 index 0000000000..a9f333c3c7 --- /dev/null +++ b/src/status_im/models/accounts.cljs @@ -0,0 +1,22 @@ +(ns status-im.models.accounts + (:require [status-im.persistence.realm :as r])) + +(defn get-accounts [] + (-> (r/get-all :accounts) + r/collection->map)) + +(defn create-account [{:keys [address public-key] :as account}] + (->> account + (r/create :accounts))) + +(defn save-accounts [accounts] + (r/write #(mapv create-account accounts))) + + +;;;;;;;;;;;;;;;;;;;;---------------------------------------------- + +(defn accounts-list [] + (r/get-all :accounts)) + +(defn account-by-address [address] + (r/single-cljs (r/get-by-field :accounts :address address))) diff --git a/src/status_im/models/protocol.cljs b/src/status_im/models/protocol.cljs index abe2ad110f..441d433688 100644 --- a/src/status_im/models/protocol.cljs +++ b/src/status_im/models/protocol.cljs @@ -12,13 +12,11 @@ (assoc-in db db/protocol-initialized-path initialized?)) (defn update-identity [db identity] - (let [password (:identity-password db) - encrypted (password-encrypt password (to-edn-string identity))] - (s/put kv/kv-store :identity encrypted) + (let [identity-string (to-edn-string identity)] + (s/put kv/kv-store :identity identity-string) (assoc db :user-identity identity))) (defn stored-identity [db] - (let [encrypted (s/get kv/kv-store :identity) - password (:identity-password db)] - (when encrypted - (read-string (password-decrypt password encrypted))))) + (let [identity (s/get kv/kv-store :identity)] + (when identity + (read-string identity)))) diff --git a/src/status_im/persistence/realm.cljs b/src/status_im/persistence/realm.cljs index e0ee29a606..e9e9e1098d 100644 --- a/src/status_im/persistence/realm.cljs +++ b/src/status_im/persistence/realm.cljs @@ -15,6 +15,10 @@ :optional true} :photo-path {:type "string" :optinal true}}} + {:name :accounts + :primaryKey :address + :properties {:address "string" + :public-key "string"}} {:name :requests :properties {:message-id :string :chat-id :string diff --git a/src/status_im/profile/screen.cljs b/src/status_im/profile/screen.cljs index ffff2d2e47..09f2fc3d07 100644 --- a/src/status_im/profile/screen.cljs +++ b/src/status_im/profile/screen.cljs @@ -67,7 +67,7 @@ phone-number [:get :phone-number] email [:get :email] status [:get :status] - identity [:get-in [:user-identity :public]]] + identity [:get-in [:user-identity :public-key]]] [scroll-view {:style st/profile} [touchable-highlight {:style st/back-btn-touchable :on-press #(dispatch [:navigate-back])} diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index e6e8c8699c..9bb41936e0 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -18,8 +18,8 @@ (register-handler :initialize-protocol (u/side-effect! - (fn [db [_]] - (init-protocol (make-handler db))))) + (fn [db [_ account]] + (init-protocol account (make-handler db))))) (register-handler :protocol-initialized (fn [db [_ identity]]