Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
4631ec94a9 | |||
|
1129a92aa3 | ||
|
9b915b3d36 | ||
|
823fc27c28 | ||
|
51f98e12a4 | ||
|
5440286448 | ||
|
e872f480cd |
15 changed files with 675 additions and 257 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
custom: https://www.blockchain.com/btc/payment_request?address=1AFGT5gVj7xhfjgHTuwEoaV56WTCh7Gjf1#BITCOIN_ONLY
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 AGORISE, LTD.
|
Copyright (c) 2019 AGORISE, LTD.
|
||||||
An International Business Company, Cyprus Reg# ΗΕ375959
|
An International Business Company, Cyprus Reg# ΗΕ375959
|
||||||
|
|
||||||
Contains works from BitShares Munich IVS
|
Contains works from BitShares Munich IVS
|
||||||
|
|
|
@ -13,7 +13,7 @@ allprojects {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In yout app module, add the following dependency:
|
In your app module, add the following dependency:
|
||||||
|
|
||||||
```Groovy
|
```Groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -43,7 +43,7 @@ operationList.add(transferOperation);
|
||||||
Transaction transaction = new Transaction(sourcePrivateKey, null, operationList);
|
Transaction transaction = new Transaction(sourcePrivateKey, null, operationList);
|
||||||
```
|
```
|
||||||
|
|
||||||
From here on, it is just a matter of creating a websocket connection and using a custom handler called
|
From here on out, it is just a matter of creating a websocket connection and using a custom handler called
|
||||||
```TransactionBroadcastSequence``` in order to broadcast it to the witness node.
|
```TransactionBroadcastSequence``` in order to broadcast it to the witness node.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
|
@ -23,7 +23,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
classpath 'com.android.tools.build:gradle:3.4.2'
|
||||||
classpath 'com.novoda:bintray-release:0.9.1'
|
classpath 'com.novoda:bintray-release:0.9.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||||
/**
|
/**
|
||||||
* Class used to represent a specific amount of a certain asset
|
* Class used to represent a specific amount of a certain asset
|
||||||
*/
|
*/
|
||||||
public class AssetAmount implements ByteSerializable, JsonSerializable, Comparable<AssetAmount> {
|
public class AssetAmount implements ByteSerializable, JsonSerializable {
|
||||||
/**
|
/**
|
||||||
* Constants used in the JSON serialization procedure.
|
* Constants used in the JSON serialization procedure.
|
||||||
*/
|
*/
|
||||||
|
@ -196,14 +196,6 @@ public class AssetAmount implements ByteSerializable, JsonSerializable, Comparab
|
||||||
return String.format("(asset=%s, amount=%s)", asset.getObjectId(), amount.toString(10));
|
return String.format("(asset=%s, amount=%s)", asset.getObjectId(), amount.toString(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(AssetAmount other) {
|
|
||||||
if(!other.asset.equals(this.asset)) throw new AssertionError("Cannot compare amounts of different assets");
|
|
||||||
if(this.amount == null) throw new NullPointerException("Asset amount field is null");
|
|
||||||
if(other.amount == null) throw new NullPointerException("Asser amount field is null");
|
|
||||||
return amount.compareTo(other.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
|
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
package cy.agorise.graphenej.api;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import cy.agorise.graphenej.models.JsonRpcResponse;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface defining the basic contract an API request can expect.
|
|
||||||
*/
|
|
||||||
public interface ApiCallback {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called whenever we have a successful response from the node.
|
|
||||||
*
|
|
||||||
* @param response Parsed response
|
|
||||||
* @param text Raw text response
|
|
||||||
*/
|
|
||||||
<T> void onResponse(JsonRpcResponse<T> response, String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called whenever there was an error and the response could not be delivered.
|
|
||||||
*
|
|
||||||
* @param t Error Throwable, potentially with some details about the problem.
|
|
||||||
* @param response Node response, if any
|
|
||||||
*/
|
|
||||||
void onFailure(Throwable t, @Nullable Response response);
|
|
||||||
}
|
|
|
@ -1,6 +1,13 @@
|
||||||
package cy.agorise.graphenej.api.android;
|
package cy.agorise.graphenej.api.android;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
@ -11,7 +18,6 @@ import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.MissingResourceException;
|
import java.util.MissingResourceException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -24,7 +30,6 @@ import cy.agorise.graphenej.RPC;
|
||||||
import cy.agorise.graphenej.Transaction;
|
import cy.agorise.graphenej.Transaction;
|
||||||
import cy.agorise.graphenej.UserAccount;
|
import cy.agorise.graphenej.UserAccount;
|
||||||
import cy.agorise.graphenej.api.ApiAccess;
|
import cy.agorise.graphenej.api.ApiAccess;
|
||||||
import cy.agorise.graphenej.api.ApiCallback;
|
|
||||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate;
|
import cy.agorise.graphenej.api.ConnectionStatusUpdate;
|
||||||
import cy.agorise.graphenej.api.calls.ApiCallable;
|
import cy.agorise.graphenej.api.calls.ApiCallable;
|
||||||
import cy.agorise.graphenej.api.calls.GetAccountBalances;
|
import cy.agorise.graphenej.api.calls.GetAccountBalances;
|
||||||
|
@ -57,12 +62,11 @@ import cy.agorise.graphenej.network.NodeProvider;
|
||||||
import cy.agorise.graphenej.operations.CustomOperation;
|
import cy.agorise.graphenej.operations.CustomOperation;
|
||||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||||
import cy.agorise.graphenej.operations.TransferOperation;
|
import cy.agorise.graphenej.operations.TransferOperation;
|
||||||
import io.reactivex.Observable;
|
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||||
import io.reactivex.Observer;
|
import io.reactivex.Observer;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.annotations.Nullable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
import io.reactivex.subjects.PublishSubject;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
@ -71,10 +75,12 @@ import okhttp3.WebSocket;
|
||||||
import okhttp3.WebSocketListener;
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class in charge of maintaining a connection to the full node.
|
* Service in charge of maintaining a connection to the full node.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class NetworkService {
|
public class NetworkService extends Service {
|
||||||
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
public static final int NORMAL_CLOSURE_STATUS = 1000;
|
public static final int NORMAL_CLOSURE_STATUS = 1000;
|
||||||
private static final int GOING_AWAY_STATUS = 1001;
|
private static final int GOING_AWAY_STATUS = 1001;
|
||||||
|
|
||||||
|
@ -86,6 +92,72 @@ public class NetworkService {
|
||||||
// which we can choose from.
|
// which we can choose from.
|
||||||
private final int DEFAULT_INITIAL_DELAY = 500;
|
private final int DEFAULT_INITIAL_DELAY = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant to be used as a key in order to pass the user name information, in case the
|
||||||
|
* provided API nodes might require this information.
|
||||||
|
*/
|
||||||
|
public static final String KEY_USERNAME = "key_username";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant to be used as a key in order to pass the password information, in case the
|
||||||
|
* provided API nodes might require this information.
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_PASSWORD = "key_password";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant used as a key in order to specify which APIs the application will be requiring.
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_REQUESTED_APIS = "key_requested_apis";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant used as a key in order to let the NetworkService know whether or not it should
|
||||||
|
* start a recurring node latency verification task.
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant used as a key in order to specify the alpha (or smoothing) factor to be used in
|
||||||
|
* the exponential moving average calculated from the different latency samples. This only
|
||||||
|
* makes sense if the latency verification feature is enabled of course.
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_NODE_LATENCY_SMOOTHING_FACTOR = "key_node_latency_smoothing_factor";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key used to pass via intent a boolean extra to specify whether the connection should
|
||||||
|
* be automatically established.
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_AUTO_CONNECT = "key_auto_connect";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key used to pass via intent a list of node URLs. The value passed should be a String
|
||||||
|
* containing a simple comma separated list of URLs.
|
||||||
|
* <p>
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* wss://domain1.com/ws,wss://domain2.com/ws,wss://domain3.com/ws
|
||||||
|
* <p>
|
||||||
|
* This information should be passed as an intent extra when calling the bindService
|
||||||
|
* or startService methods.
|
||||||
|
*/
|
||||||
|
public static final String KEY_NODE_URLS = "key_node_urls";
|
||||||
|
|
||||||
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
|
||||||
private WebSocket mWebSocket;
|
private WebSocket mWebSocket;
|
||||||
|
|
||||||
// Username and password used to connect to a specific node
|
// Username and password used to connect to a specific node
|
||||||
|
@ -98,7 +170,7 @@ public class NetworkService {
|
||||||
private long mCurrentId = 0;
|
private long mCurrentId = 0;
|
||||||
|
|
||||||
// Requested APIs passed to this service
|
// Requested APIs passed to this service
|
||||||
private int mRequestedApis = ApiAccess.API_DATABASE | ApiAccess.API_HISTORY | ApiAccess.API_NETWORK_BROADCAST;
|
private int mRequestedApis;
|
||||||
|
|
||||||
// Variable used to keep track of the currently obtained API accesses
|
// Variable used to keep track of the currently obtained API accesses
|
||||||
private HashMap<Integer, Integer> mApiIds = new HashMap<Integer, Integer>();
|
private HashMap<Integer, Integer> mApiIds = new HashMap<Integer, Integer>();
|
||||||
|
@ -118,9 +190,7 @@ public class NetworkService {
|
||||||
// Property used to keep track of the currently active node
|
// Property used to keep track of the currently active node
|
||||||
private FullNode mSelectedNode;
|
private FullNode mSelectedNode;
|
||||||
|
|
||||||
private CompositeDisposable mCompositeDisposable;
|
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private HashMap<Long, ApiCallback> mCallbackMap = new HashMap<Long, ApiCallback>();
|
|
||||||
|
|
||||||
private Gson gson = new GsonBuilder()
|
private Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||||
|
@ -146,31 +216,6 @@ public class NetworkService {
|
||||||
// suited for every response type.
|
// suited for every response type.
|
||||||
private DeserializationMap mDeserializationMap = new DeserializationMap();
|
private DeserializationMap mDeserializationMap = new DeserializationMap();
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton reference
|
|
||||||
*/
|
|
||||||
private static NetworkService instance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private constructor
|
|
||||||
*/
|
|
||||||
private NetworkService(){}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread-safe singleton getter.
|
|
||||||
* @return A NetworkService instance.
|
|
||||||
*/
|
|
||||||
public static NetworkService getInstance(){
|
|
||||||
if(instance == null) {
|
|
||||||
synchronized (NetworkService.class) {
|
|
||||||
if(instance == null) {
|
|
||||||
instance = new NetworkService();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actually establishes a connection from this Service to one of the full nodes.
|
* Actually establishes a connection from this Service to one of the full nodes.
|
||||||
*/
|
*/
|
||||||
|
@ -185,14 +230,13 @@ public class NetworkService {
|
||||||
synchronized (mWebSocketListener){
|
synchronized (mWebSocketListener){
|
||||||
mSelectedNode = nodeProvider.getBestNode();
|
mSelectedNode = nodeProvider.getBestNode();
|
||||||
if(mSelectedNode != null){
|
if(mSelectedNode != null){
|
||||||
System.out.println("Trying to connect to: "+ mSelectedNode.getUrl());
|
Log.d(TAG,"Trying to connect to: "+ mSelectedNode.getUrl());
|
||||||
Request request = new Request.Builder().url(mSelectedNode.getUrl()).build();
|
Request request = new Request.Builder().url(mSelectedNode.getUrl()).build();
|
||||||
mWebSocket = client.newWebSocket(request, mWebSocketListener);
|
mWebSocket = client.newWebSocket(request, mWebSocketListener);
|
||||||
}else{
|
}else{
|
||||||
System.out.println("Could not find best node, reescheduling");
|
Log.d(TAG,"Could not find best node, reescheduling");
|
||||||
// If no node could be found yet, schedule a new attempt in DEFAULT_INITIAL_DELAY ms
|
// If no node could be found yet, schedule a new attempt in DEFAULT_INITIAL_DELAY ms
|
||||||
Disposable d = Observable.timer(DEFAULT_INITIAL_DELAY, TimeUnit.MILLISECONDS).subscribe(mConnectAttempt);
|
mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY);
|
||||||
mCompositeDisposable.add(d);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +244,7 @@ public class NetworkService {
|
||||||
public long sendMessage(String message){
|
public long sendMessage(String message){
|
||||||
if(mWebSocket != null){
|
if(mWebSocket != null){
|
||||||
if(mWebSocket.send(message)){
|
if(mWebSocket.send(message)){
|
||||||
System.out.println("-> " + message);
|
Log.v(TAG,"-> " + message);
|
||||||
return mCurrentId;
|
return mCurrentId;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
@ -226,25 +270,13 @@ public class NetworkService {
|
||||||
ApiCall call = apiCallable.toApiCall(apiId, ++mCurrentId);
|
ApiCall call = apiCallable.toApiCall(apiId, ++mCurrentId);
|
||||||
mRequestClassMap.put(mCurrentId, apiCallable.getClass());
|
mRequestClassMap.put(mCurrentId, apiCallable.getClass());
|
||||||
if(mWebSocket != null && mWebSocket.send(call.toJsonString())){
|
if(mWebSocket != null && mWebSocket.send(call.toJsonString())){
|
||||||
System.out.println("-> "+call.toJsonString());
|
Log.v(TAG,"-> "+call.toJsonString());
|
||||||
return mCurrentId;
|
return mCurrentId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized long sendMessage(ApiCallable apiCallable, int requiredApi, ApiCallback callback){
|
|
||||||
long id = this.sendMessage(apiCallable, requiredApi);
|
|
||||||
if(callback != null){
|
|
||||||
if(id != -1){
|
|
||||||
mCallbackMap.put(id, callback);
|
|
||||||
}else{
|
|
||||||
callback.onFailure(new Exception("Message could not be sent"), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method used to inform any external party a clue about the current connectivity status
|
* Method used to inform any external party a clue about the current connectivity status
|
||||||
* @return True if the service is currently connected and logged in, false otherwise.
|
* @return True if the service is currently connected and logged in, false otherwise.
|
||||||
|
@ -253,39 +285,57 @@ public class NetworkService {
|
||||||
return mWebSocket != null && isLoggedIn;
|
return mWebSocket != null && isLoggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Stops the service by closing the connection and stopping the latency verifier.
|
public void onDestroy() {
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
if(mWebSocket != null)
|
if(mWebSocket != null)
|
||||||
mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
|
mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
|
||||||
|
|
||||||
if(nodeLatencyVerifier != null)
|
if(nodeLatencyVerifier != null)
|
||||||
nodeLatencyVerifier.stop();
|
nodeLatencyVerifier.stop();
|
||||||
|
}
|
||||||
|
|
||||||
mCompositeDisposable.dispose();
|
@Nullable
|
||||||
mCallbackMap.clear();
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the connection
|
* Initialize information and try to connect to a node accordingly. This methods were moved
|
||||||
|
* from onBind to avoid crashes due to components other than {@link NetworkServiceManager}
|
||||||
|
* binding to the service without submitting the proper information.
|
||||||
|
*
|
||||||
|
* @param extras Bundle that contains all required information for a proper initialization
|
||||||
*/
|
*/
|
||||||
public void start(String[] urls, double alpha) {
|
public void bootstrapService(Bundle extras) {
|
||||||
mCompositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
// Retrieving credentials and requested API data from the shared preferences
|
// Retrieving credentials and requested API data from the shared preferences
|
||||||
mUsername = "";
|
mUsername = extras.getString(NetworkService.KEY_USERNAME, "");
|
||||||
mPassword = "";
|
mPassword = extras.getString(NetworkService.KEY_PASSWORD, "");
|
||||||
|
mRequestedApis = extras.getInt(NetworkService.KEY_REQUESTED_APIS, 0);
|
||||||
|
boolean mAutoConnect = extras.getBoolean(NetworkService.KEY_AUTO_CONNECT, true);
|
||||||
|
boolean verifyNodeLatency = extras.getBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, false);
|
||||||
|
|
||||||
if(urls == null || urls.length == 0){
|
// If the user of the library desires, a custom list of node URLs can
|
||||||
throw new MissingResourceException("Expecting at least a node URL to be provided", String.class.getName(), "urls");
|
// be passed using the KEY_NODE_URLS constant
|
||||||
|
String nodeURLStr = extras.getString(NetworkService.KEY_NODE_URLS, "");
|
||||||
|
if(nodeURLStr.equals("")){
|
||||||
|
throw new MissingResourceException("A comma-separated list of node URLs must be provided as an intent extra", String.class.getName(), NetworkService.KEY_NODE_URLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adding user-provided list of node URLs
|
||||||
|
String[] urls = nodeURLStr.split(",");
|
||||||
|
|
||||||
// Feeding all node information to the NodeProvider instance
|
// Feeding all node information to the NodeProvider instance
|
||||||
for(String nodeUrl : urls){
|
for(String nodeUrl : urls){
|
||||||
nodeProvider.addNode(new FullNode(nodeUrl));
|
nodeProvider.addNode(new FullNode(nodeUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mAutoConnect && !verifyNodeLatency) {
|
||||||
|
throw new IllegalArgumentException("NetworkService$bootstrapService: verifyNodeLatency cannot be false when autoConnect is false too.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifyNodeLatency) {
|
||||||
|
double alpha = extras.getDouble(KEY_NODE_LATENCY_SMOOTHING_FACTOR, ExponentialMovingAverage.DEFAULT_ALPHA);
|
||||||
ArrayList<FullNode> fullNodes = new ArrayList<>();
|
ArrayList<FullNode> fullNodes = new ArrayList<>();
|
||||||
for(String url : urls){
|
for(String url : urls){
|
||||||
fullNodes.add(new FullNode(url, alpha));
|
fullNodes.add(new FullNode(url, alpha));
|
||||||
|
@ -293,9 +343,14 @@ public class NetworkService {
|
||||||
nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes);
|
nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes);
|
||||||
fullNodePublishSubject = nodeLatencyVerifier.start();
|
fullNodePublishSubject = nodeLatencyVerifier.start();
|
||||||
fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver);
|
fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver);
|
||||||
|
}
|
||||||
|
|
||||||
Disposable d = Observable.timer(DEFAULT_INITIAL_DELAY, TimeUnit.MILLISECONDS).subscribe(mConnectAttempt);
|
if (mAutoConnect)
|
||||||
mCompositeDisposable.add(d);
|
connect();
|
||||||
|
else
|
||||||
|
mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY);
|
||||||
|
|
||||||
|
// TODO make sure (verifyNodeLatency==false && mAutoConnect==true) is a valid/useful combination, else simplify and use only one of those arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,7 +361,7 @@ public class NetworkService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumer that will perform a connection attempt with the best node after DEFAULT_INITIAL_DELAY
|
* Runnable that will perform a connection attempt with the best node after DEFAULT_INITIAL_DELAY
|
||||||
* milliseconds. This is used only if the node latency verification is activated.
|
* milliseconds. This is used only if the node latency verification is activated.
|
||||||
*
|
*
|
||||||
* The reason to delay the initial connection is that we want to ideally connect to the best node,
|
* The reason to delay the initial connection is that we want to ideally connect to the best node,
|
||||||
|
@ -314,18 +369,15 @@ public class NetworkService {
|
||||||
* first node latency measurement round to finish in order to have at least a partial result set
|
* first node latency measurement round to finish in order to have at least a partial result set
|
||||||
* that could be used.
|
* that could be used.
|
||||||
*/
|
*/
|
||||||
private Consumer mConnectAttempt = new Consumer() {
|
private Runnable mConnectAttempt = new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(Object o) throws Exception {
|
public void run() {
|
||||||
FullNode fullNode = nodeProvider.getBestNode();
|
FullNode fullNode = nodeProvider.getBestNode();
|
||||||
if(fullNode != null){
|
if(fullNode != null){
|
||||||
System.out.println(String.format(Locale.ROOT, "Connected with %d latency results", latencyUpdateCounter));
|
Log.i(TAG, String.format("Connected with %d latency results", latencyUpdateCounter));
|
||||||
mApiIds.clear();
|
|
||||||
connect();
|
connect();
|
||||||
}else{
|
}else{
|
||||||
Disposable d = Observable.timer(DEFAULT_INITIAL_DELAY, TimeUnit.MILLISECONDS).subscribe(this);
|
mHandler.postDelayed(this, DEFAULT_INITIAL_DELAY);
|
||||||
mCompositeDisposable.add(d);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -346,7 +398,7 @@ public class NetworkService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable e) {
|
public void onError(Throwable e) {
|
||||||
System.out.println("nodeLatencyObserver.onError.Msg: "+e.getMessage());
|
Log.e(TAG,"nodeLatencyObserver.onError.Msg: "+e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -354,17 +406,13 @@ public class NetworkService {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method used to execute every callback failure method and remove them from the SparseArray.
|
* Class used for the client Binder. Because we know this service always
|
||||||
*
|
* runs in the same process as its clients, we don't need to deal with IPC.
|
||||||
* @param throwable
|
|
||||||
* @param response
|
|
||||||
*/
|
*/
|
||||||
private void resetCallbacks(Throwable throwable, Response response){
|
public class LocalBinder extends Binder {
|
||||||
for(ApiCallback callback : mCallbackMap.values()) {
|
public NetworkService getService() {
|
||||||
if(callback != null) {
|
// Return this instance of LocalService so clients can call public methods
|
||||||
callback.onFailure(throwable, response);
|
return NetworkService.this;
|
||||||
mCallbackMap.remove(callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +446,7 @@ public class NetworkService {
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onMessage(WebSocket webSocket, String text) {
|
public synchronized void onMessage(WebSocket webSocket, String text) {
|
||||||
super.onMessage(webSocket, text);
|
super.onMessage(webSocket, text);
|
||||||
System.out.println("<- "+text);
|
Log.v(TAG,"<- "+text);
|
||||||
JsonRpcNotification notification = gson.fromJson(text, JsonRpcNotification.class);
|
JsonRpcNotification notification = gson.fromJson(text, JsonRpcNotification.class);
|
||||||
|
|
||||||
if(notification.method != null){
|
if(notification.method != null){
|
||||||
|
@ -466,7 +514,7 @@ public class NetworkService {
|
||||||
}
|
}
|
||||||
if(response.error != null && response.error.message != null){
|
if(response.error != null && response.error.message != null){
|
||||||
// We could not make sense of this incoming message, just log a warning
|
// We could not make sense of this incoming message, just log a warning
|
||||||
System.out.println("Error.Msg: "+response.error.message);
|
Log.w(TAG,"Error.Msg: "+response.error.message);
|
||||||
}
|
}
|
||||||
// Properly de-serialize all other fields and broadcasts to the event bus
|
// Properly de-serialize all other fields and broadcasts to the event bus
|
||||||
handleJsonRpcResponse(response, text);
|
handleJsonRpcResponse(response, text);
|
||||||
|
@ -482,7 +530,6 @@ public class NetworkService {
|
||||||
*/
|
*/
|
||||||
private void handleJsonRpcResponse(JsonRpcResponse response, String text){
|
private void handleJsonRpcResponse(JsonRpcResponse response, String text){
|
||||||
JsonRpcResponse parsedResponse = null;
|
JsonRpcResponse parsedResponse = null;
|
||||||
|
|
||||||
Class requestClass = mRequestClassMap.get(response.id);
|
Class requestClass = mRequestClassMap.get(response.id);
|
||||||
if(requestClass != null){
|
if(requestClass != null){
|
||||||
// Removing the class entry in the map
|
// Removing the class entry in the map
|
||||||
|
@ -550,10 +597,10 @@ public class NetworkService {
|
||||||
Type GetAssetsResponse = new TypeToken<JsonRpcResponse<List<Asset>>>(){}.getType();
|
Type GetAssetsResponse = new TypeToken<JsonRpcResponse<List<Asset>>>(){}.getType();
|
||||||
parsedResponse = gson.fromJson(text, GetAssetsResponse);
|
parsedResponse = gson.fromJson(text, GetAssetsResponse);
|
||||||
}else {
|
}else {
|
||||||
System.out.println("Unknown request class");
|
Log.w(TAG,"Unknown request class");
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
System.out.println("Unhandled situation");
|
Log.w(TAG,"Unhandled situation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,17 +608,6 @@ public class NetworkService {
|
||||||
if(parsedResponse == null){
|
if(parsedResponse == null){
|
||||||
parsedResponse = response;
|
parsedResponse = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executing callback, if present with the parsed response
|
|
||||||
if(mCallbackMap.containsKey(response.id)){
|
|
||||||
ApiCallback callback = mCallbackMap.get(response.id);
|
|
||||||
if(response.error == null)
|
|
||||||
callback.onResponse(parsedResponse, text);
|
|
||||||
else
|
|
||||||
callback.onFailure(new Exception("Exception while trying to parse node response. Message: " + response.error.message), null);
|
|
||||||
mCallbackMap.remove(response.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcasting the parsed response to all interested listeners
|
// Broadcasting the parsed response to all interested listeners
|
||||||
RxBus.getBusInstance().send(parsedResponse);
|
RxBus.getBusInstance().send(parsedResponse);
|
||||||
}
|
}
|
||||||
|
@ -635,7 +671,6 @@ public class NetworkService {
|
||||||
@Override
|
@Override
|
||||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||||
super.onClosed(webSocket, code, reason);
|
super.onClosed(webSocket, code, reason);
|
||||||
resetCallbacks(new Exception("Websocket closed. Reason: " + reason), null);
|
|
||||||
if(code == GOING_AWAY_STATUS)
|
if(code == GOING_AWAY_STATUS)
|
||||||
handleWebSocketDisconnection(true, false);
|
handleWebSocketDisconnection(true, false);
|
||||||
else
|
else
|
||||||
|
@ -645,16 +680,15 @@ public class NetworkService {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||||
super.onFailure(webSocket, t, response);
|
super.onFailure(webSocket, t, response);
|
||||||
resetCallbacks(t, response);
|
Log.e(TAG,"onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage());
|
||||||
System.out.println("onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage());
|
|
||||||
// Logging error stack trace
|
// Logging error stack trace
|
||||||
for(StackTraceElement element : t.getStackTrace()){
|
for(StackTraceElement element : t.getStackTrace()){
|
||||||
System.out.println(String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber()));
|
Log.v(TAG,String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a response, we print it
|
// If there is a response, we print it
|
||||||
if(response != null){
|
if(response != null){
|
||||||
System.out.println("Response: "+response.message());
|
Log.e(TAG,"Response: "+response.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWebSocketDisconnection(true, true);
|
handleWebSocketDisconnection(true, true);
|
||||||
|
@ -668,7 +702,7 @@ public class NetworkService {
|
||||||
* @param penalizeNode Whether or not to penalize the current node with a very high latency reading.
|
* @param penalizeNode Whether or not to penalize the current node with a very high latency reading.
|
||||||
*/
|
*/
|
||||||
private synchronized void handleWebSocketDisconnection(boolean tryReconnection, boolean penalizeNode) {
|
private synchronized void handleWebSocketDisconnection(boolean tryReconnection, boolean penalizeNode) {
|
||||||
System.out.println("handleWebSocketDisconnection. try reconnection: " + tryReconnection + ", penalizeNode: " + penalizeNode);
|
Log.d(TAG,"handleWebSocketDisconnection. try reconnection: " + tryReconnection + ", penalizeNode: " + penalizeNode);
|
||||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
||||||
isLoggedIn = false;
|
isLoggedIn = false;
|
||||||
|
|
||||||
|
@ -699,15 +733,15 @@ public class NetworkService {
|
||||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
||||||
|
|
||||||
if (nodeProvider.getBestNode() == null) {
|
if (nodeProvider.getBestNode() == null) {
|
||||||
System.out.println( "Giving up on connections");
|
Log.e(TAG, "Giving up on connections");
|
||||||
|
stopSelf();
|
||||||
} else {
|
} else {
|
||||||
Disposable d = Observable.timer(DEFAULT_RETRY_DELAY, TimeUnit.MILLISECONDS).subscribe(new Consumer<Long>() {
|
mHandler.postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(Long aLong) throws Exception {
|
public void run() {
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
});
|
}, DEFAULT_RETRY_DELAY);
|
||||||
mCompositeDisposable.add(d);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,325 @@
|
||||||
|
package cy.agorise.graphenej.api.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class should be instantiated at the application level of the android app.
|
||||||
|
*
|
||||||
|
* It will monitor the interaction between the different activities of an app and help us decide
|
||||||
|
* when the connection to the full node should be interrupted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks {
|
||||||
|
private final String TAG = this.getClass().getName();
|
||||||
|
/**
|
||||||
|
* Constant used to specify how long will the app wait for another activity to go through its starting life
|
||||||
|
* cycle events before running the teardownConnectionTask task.
|
||||||
|
*
|
||||||
|
* This is used as a means to detect whether or not the user has left the app.
|
||||||
|
*/
|
||||||
|
private static final int DISCONNECT_DELAY = 1500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler instance used to schedule tasks back to the main thread
|
||||||
|
*/
|
||||||
|
private Handler mHandler = new Handler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weak reference to the application context
|
||||||
|
*/
|
||||||
|
private WeakReference<Context> mContextReference;
|
||||||
|
|
||||||
|
// In case we want to interact directly with the service
|
||||||
|
private NetworkService mService;
|
||||||
|
|
||||||
|
// Attributes that might need to be passed to the NetworkService
|
||||||
|
private String mUserName = "";
|
||||||
|
private String mPassword = "";
|
||||||
|
private int mRequestedApis;
|
||||||
|
private List<String> mCustomNodeUrls = new ArrayList<>();
|
||||||
|
private boolean mAutoConnect;
|
||||||
|
private boolean mVerifyLatency;
|
||||||
|
// Flag used to make sure we only call 'bindService' once.
|
||||||
|
// private boolean mStartingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable used to schedule a service disconnection once the app is not visible to the user for
|
||||||
|
* more than DISCONNECT_DELAY milliseconds.
|
||||||
|
*/
|
||||||
|
private final Runnable mDisconnectRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Context context = mContextReference.get();
|
||||||
|
if(mService != null){
|
||||||
|
context.unbindService(mServiceConnection);
|
||||||
|
mService = null;
|
||||||
|
}
|
||||||
|
context.stopService(new Intent(context, NetworkService.class));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private NetworkServiceManager(Context context){
|
||||||
|
mContextReference = new WeakReference<>(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle bundle) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(Activity activity) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(Activity activity) {
|
||||||
|
mHandler.removeCallbacks(mDisconnectRunnable);
|
||||||
|
if(mService == null){
|
||||||
|
// Creating a new Intent that will be used to start the NetworkService
|
||||||
|
Context context = mContextReference.get();
|
||||||
|
Intent intent = new Intent(context, NetworkService.class);
|
||||||
|
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method passes all the required information to the NetworkService to properly
|
||||||
|
* initialize itself
|
||||||
|
*/
|
||||||
|
private void passRequiredInfoToConfigureService() {
|
||||||
|
// Adding user-provided node URLs
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
Iterator<String> it = mCustomNodeUrls.iterator();
|
||||||
|
while(it.hasNext()){
|
||||||
|
stringBuilder.append(it.next());
|
||||||
|
if(it.hasNext()) stringBuilder.append(",");
|
||||||
|
}
|
||||||
|
String customNodes = stringBuilder.toString();
|
||||||
|
|
||||||
|
Bundle b = new Bundle();
|
||||||
|
|
||||||
|
// Adding all
|
||||||
|
b.putString(NetworkService.KEY_USERNAME, mUserName);
|
||||||
|
b.putString(NetworkService.KEY_PASSWORD, mPassword);
|
||||||
|
b.putInt(NetworkService.KEY_REQUESTED_APIS, mRequestedApis);
|
||||||
|
b.putString(NetworkService.KEY_NODE_URLS, customNodes);
|
||||||
|
b.putBoolean(NetworkService.KEY_AUTO_CONNECT, mAutoConnect);
|
||||||
|
b.putBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, mVerifyLatency);
|
||||||
|
|
||||||
|
mService.bootstrapService(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(Activity activity) {
|
||||||
|
mHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(Activity activity) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(Activity activity) {}
|
||||||
|
|
||||||
|
/** Defines callbacks for backend binding, passed to bindService() */
|
||||||
|
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className,
|
||||||
|
IBinder service) {
|
||||||
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||||
|
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
|
||||||
|
boolean passInfo = false;
|
||||||
|
if(mService == null){
|
||||||
|
mService = binder.getService();
|
||||||
|
// We only pass the required information in case this is the first time we get a reference
|
||||||
|
// to the NetworkService instance.
|
||||||
|
passInfo = true;
|
||||||
|
}
|
||||||
|
if(passInfo)
|
||||||
|
passRequiredInfoToConfigureService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return mUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.mUserName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return mPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String mPassword) {
|
||||||
|
this.mPassword = mPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRequestedApis() {
|
||||||
|
return mRequestedApis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestedApis(int mRequestedApis) {
|
||||||
|
this.mRequestedApis = mRequestedApis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCustomNodeUrls() {
|
||||||
|
return mCustomNodeUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomNodeUrls(List<String> mCustomNodeUrls) {
|
||||||
|
this.mCustomNodeUrls = mCustomNodeUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoConnect() {
|
||||||
|
return mAutoConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoConnect(boolean mAutoConnect) {
|
||||||
|
this.mAutoConnect = mAutoConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVerifyLatency() {
|
||||||
|
return mVerifyLatency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerifyLatency(boolean mVerifyLatency) {
|
||||||
|
this.mVerifyLatency = mVerifyLatency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to create a {@link NetworkServiceManager} with specific attributes.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private int requestedApis;
|
||||||
|
private List<String> customNodeUrls;
|
||||||
|
private boolean autoconnect = true;
|
||||||
|
private boolean verifyNodeLatency;
|
||||||
|
private double alpha = ExponentialMovingAverage.DEFAULT_ALPHA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user name, if required to connect to a node.
|
||||||
|
* @param name User name
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setUserName(String name){
|
||||||
|
this.username = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the password, if required to connect to a node.
|
||||||
|
* @param password Password
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setPassword(String password){
|
||||||
|
this.password = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an integer with the requested APIs encoded as binary flags.
|
||||||
|
* @param apis Integer representing the different APIs we require from the node.
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setRequestedApis(int apis){
|
||||||
|
this.requestedApis = apis;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a list of custom node URLs.
|
||||||
|
* @param nodeUrls List of custom full node URLs.
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setCustomNodeUrls(List<String> nodeUrls){
|
||||||
|
this.customNodeUrls = nodeUrls;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a list of custom node URLs.
|
||||||
|
* @param nodeUrls List of custom full node URLs.
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setCustomNodeUrls(String nodeUrls){
|
||||||
|
String[] urls = nodeUrls.split(",");
|
||||||
|
for(String url : urls){
|
||||||
|
if(customNodeUrls == null) customNodeUrls = new ArrayList<>();
|
||||||
|
customNodeUrls.add(url);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the autoconnect flag. This is true by default.
|
||||||
|
* @param autoConnect True if we want the service to connect automatically, false otherwise.
|
||||||
|
* @return The Builder instance
|
||||||
|
*/
|
||||||
|
public Builder setAutoConnect(boolean autoConnect){
|
||||||
|
this.autoconnect = autoConnect;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the node-verification flag. This is false by default.
|
||||||
|
* @param verifyLatency True if we want the service to perform a latency analysis before connecting.
|
||||||
|
* @return The Builder instance.
|
||||||
|
*/
|
||||||
|
public Builder setNodeLatencyVerification(boolean verifyLatency){
|
||||||
|
this.verifyNodeLatency = verifyLatency;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the node latency verification's exponential moving average alpha parameter.
|
||||||
|
* @param alpha The alpha parameter to use when computing the exponential moving average of the
|
||||||
|
* measured latencies.
|
||||||
|
* @return The Builder instance.
|
||||||
|
*/
|
||||||
|
public Builder setLatencyAverageAlpha(double alpha){
|
||||||
|
this.alpha = alpha;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to build a {@link NetworkServiceManager} instance with all of the characteristics
|
||||||
|
* passed as parameters.
|
||||||
|
* @param context A Context of the application package implementing
|
||||||
|
* this class.
|
||||||
|
* @return Instance of the NetworkServiceManager class.
|
||||||
|
*/
|
||||||
|
public NetworkServiceManager build(Context context){
|
||||||
|
NetworkServiceManager manager = new NetworkServiceManager(context);
|
||||||
|
if(username != null) manager.setUserName(username); else manager.setUserName("");
|
||||||
|
if(password != null) manager.setPassword(password); else manager.setPassword("");
|
||||||
|
if(customNodeUrls != null) manager.setCustomNodeUrls(customNodeUrls);
|
||||||
|
manager.setRequestedApis(requestedApis);
|
||||||
|
manager.setAutoConnect(autoconnect);
|
||||||
|
manager.setVerifyLatency(verifyNodeLatency);
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,62 @@
|
||||||
package cy.agorise.labs.sample;
|
package cy.agorise.labs.sample;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.content.ComponentName;
|
||||||
import android.support.annotation.Nullable;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import cy.agorise.graphenej.api.android.NetworkService;
|
import cy.agorise.graphenej.api.android.NetworkService;
|
||||||
|
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||||
|
|
||||||
public abstract class ConnectedActivity extends AppCompatActivity {
|
public abstract class ConnectedActivity extends AppCompatActivity implements ServiceConnection {
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
/* Network service connection */
|
/* Network service connection */
|
||||||
protected NetworkService mNetworkService;
|
protected NetworkService mNetworkService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag used to keep track of the NetworkService binding state
|
||||||
|
*/
|
||||||
|
private boolean mShouldUnbindNetwork;
|
||||||
|
|
||||||
|
private ServiceConnection mNetworkServiceConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onServiceConnected(ComponentName className,
|
||||||
super.onCreate(savedInstanceState);
|
IBinder service) {
|
||||||
mNetworkService = NetworkService.getInstance();
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||||
|
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
|
||||||
|
mNetworkService = binder.getService();
|
||||||
|
ConnectedActivity.this.onServiceConnected(className, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
ConnectedActivity.this.onServiceDisconnected(componentName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
Intent intent = new Intent(this, NetworkService.class);
|
||||||
|
// Binding to NetworkService
|
||||||
|
if(bindService(intent, mNetworkServiceConnection, Context.BIND_AUTO_CREATE)){
|
||||||
|
mShouldUnbindNetwork = true;
|
||||||
|
}else{
|
||||||
|
Log.e(TAG,"Binding to the network service failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
// Unbinding from network service
|
||||||
|
if(mShouldUnbindNetwork){
|
||||||
|
unbindService(mNetworkServiceConnection);
|
||||||
|
mShouldUnbindNetwork = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package cy.agorise.labs.sample;
|
package cy.agorise.labs.sample;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
@ -237,4 +239,10 @@ public class HtlcActivity extends ConnectedActivity implements
|
||||||
// Return a newly built transaction
|
// Return a newly built transaction
|
||||||
return new Transaction(privKey, blockData, operations);
|
return new Transaction(privKey, blockData, operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package cy.agorise.labs.sample;
|
package cy.agorise.labs.sample;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.support.design.widget.TextInputEditText;
|
import android.support.design.widget.TextInputEditText;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
@ -416,14 +418,12 @@ public class PerformCallActivity extends ConnectedActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendGetObjectsRequest(){
|
private void sendGetObjectsRequest(){
|
||||||
Log.d(TAG,"sendGetObjectsRequest");
|
|
||||||
String objectId = param1.getText().toString();
|
String objectId = param1.getText().toString();
|
||||||
if(objectId.matches("\\d\\.\\d{1,3}\\.\\d{1,10}")){
|
if(objectId.matches("\\d\\.\\d{1,3}\\.\\d{1,10}")){
|
||||||
ArrayList<String> array = new ArrayList<>();
|
ArrayList<String> array = new ArrayList<>();
|
||||||
array.add(objectId);
|
array.add(objectId);
|
||||||
GetObjects getObjects = new GetObjects(array);
|
GetObjects getObjects = new GetObjects(array);
|
||||||
long id = mNetworkService.sendMessage(getObjects, GetObjects.REQUIRED_API);
|
long id = mNetworkService.sendMessage(getObjects, GetObjects.REQUIRED_API);
|
||||||
Log.d(TAG,"sendGetObnjetcsRequest id: "+id);
|
|
||||||
responseMap.put(id, mRPC);
|
responseMap.put(id, mRPC);
|
||||||
}else{
|
}else{
|
||||||
param1.setError(getResources().getString(R.string.error_input_id));
|
param1.setError(getResources().getString(R.string.error_input_id));
|
||||||
|
@ -639,4 +639,14 @@ public class PerformCallActivity extends ConnectedActivity {
|
||||||
if(!mDisposable.isDisposed())
|
if(!mDisposable.isDisposed())
|
||||||
mDisposable.dispose();
|
mDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
|
// Called upon NetworkService connection
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
// Called upon NetworkService disconnection
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package cy.agorise.labs.sample;
|
package cy.agorise.labs.sample;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.util.SortedList;
|
import android.support.v7.util.SortedList;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
@ -35,7 +40,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.subjects.PublishSubject;
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
|
||||||
public class RemoveNodeActivity extends ConnectedActivity {
|
public class RemoveNodeActivity extends AppCompatActivity implements ServiceConnection {
|
||||||
|
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
|
@ -61,6 +66,18 @@ public class RemoveNodeActivity extends ConnectedActivity {
|
||||||
rvNodes.setLayoutManager(new LinearLayoutManager(this));
|
rvNodes.setLayoutManager(new LinearLayoutManager(this));
|
||||||
nodesAdapter = new FullNodesAdapter(this, LATENCY_COMPARATOR);
|
nodesAdapter = new FullNodesAdapter(this, LATENCY_COMPARATOR);
|
||||||
rvNodes.setAdapter(nodesAdapter);
|
rvNodes.setAdapter(nodesAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btnReconnectNode)
|
||||||
|
public void removeCurrentNode() {
|
||||||
|
mNetworkService.reconnectNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||||
|
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) iBinder;
|
||||||
|
mNetworkService = binder.getService();
|
||||||
|
|
||||||
if(mNetworkService != null){
|
if(mNetworkService != null){
|
||||||
// PublishSubject used to announce full node latencies updates
|
// PublishSubject used to announce full node latencies updates
|
||||||
|
@ -73,9 +90,9 @@ public class RemoveNodeActivity extends ConnectedActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.btnReconnectNode)
|
@Override
|
||||||
public void removeCurrentNode() {
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
mNetworkService.reconnectNode();
|
mNetworkService = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,6 +119,20 @@ public class RemoveNodeActivity extends ConnectedActivity {
|
||||||
public void onComplete() { }
|
public void onComplete() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
// Bind to LocalService
|
||||||
|
Intent intent = new Intent(this, NetworkService.class);
|
||||||
|
bindService(intent, this, Context.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
unbindService(this);
|
||||||
|
}
|
||||||
|
|
||||||
class FullNodesAdapter extends RecyclerView.Adapter<FullNodesAdapter.ViewHolder> {
|
class FullNodesAdapter extends RecyclerView.Adapter<FullNodesAdapter.ViewHolder> {
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
|
@ -2,7 +2,12 @@ package cy.agorise.labs.sample;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import cy.agorise.labs.sample.network.NetworkServiceManager;
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.graphenej.api.ApiAccess;
|
||||||
|
import cy.agorise.graphenej.api.android.NetworkServiceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sample application class
|
* Sample application class
|
||||||
|
@ -13,6 +18,9 @@ public class SampleApplication extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
// Specifying some important information regarding the connection, such as the
|
||||||
|
// credentials and the requested API accesses
|
||||||
|
int requestedApis = ApiAccess.API_DATABASE | ApiAccess.API_HISTORY | ApiAccess.API_NETWORK_BROADCAST;
|
||||||
|
|
||||||
String[] nodeURLs = new String[]{
|
String[] nodeURLs = new String[]{
|
||||||
"wss://bitshares.openledger.info/ws",
|
"wss://bitshares.openledger.info/ws",
|
||||||
|
@ -21,11 +29,38 @@ public class SampleApplication extends Application {
|
||||||
"wss://citadel.li/node",
|
"wss://citadel.li/node",
|
||||||
"wss://api.bts.mobi/ws"
|
"wss://api.bts.mobi/ws"
|
||||||
};
|
};
|
||||||
|
List<String> nodeList = Arrays.asList(nodeURLs);
|
||||||
|
String nodes = join(nodeList, ",");
|
||||||
|
|
||||||
NetworkServiceManager networkManager = new NetworkServiceManager(nodeURLs);
|
NetworkServiceManager networkManager = new NetworkServiceManager.Builder()
|
||||||
|
.setUserName("username")
|
||||||
|
.setPassword("secret")
|
||||||
|
.setRequestedApis(requestedApis)
|
||||||
|
.setCustomNodeUrls(nodes)
|
||||||
|
.setAutoConnect(true)
|
||||||
|
.setNodeLatencyVerification(true)
|
||||||
|
.setLatencyAverageAlpha(0.1f)
|
||||||
|
.build(this);
|
||||||
|
|
||||||
// Registering this class as a listener to all activity's callback cycle events, in order to
|
// Registering this class as a listener to all activity's callback cycle events, in order to
|
||||||
// better estimate when the user has left the app and it is safe to disconnect the websocket connection
|
// better estimate when the user has left the app and it is safe to disconnect the websocket connection
|
||||||
registerActivityLifecycleCallbacks(networkManager);
|
registerActivityLifecycleCallbacks(networkManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private method used to join a sequence of Strings given a iterable representation
|
||||||
|
* and a delimiter.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param s Any collection of CharSequence that implements the Iterable interface.
|
||||||
|
* @param delimiter The delimiter which will be used to join the different strings together.
|
||||||
|
* @return A single string combining all the iterable pieces with the delimiter.
|
||||||
|
*/
|
||||||
|
private String join(Iterable<? extends CharSequence> s, String delimiter) {
|
||||||
|
Iterator<? extends CharSequence> iter = s.iterator();
|
||||||
|
if (!iter.hasNext()) return "";
|
||||||
|
StringBuilder buffer = new StringBuilder(iter.next());
|
||||||
|
while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package cy.agorise.labs.sample;
|
package cy.agorise.labs.sample;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -8,6 +14,7 @@ import android.widget.TextView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
import cy.agorise.graphenej.api.android.NetworkService;
|
||||||
import cy.agorise.graphenej.api.android.RxBus;
|
import cy.agorise.graphenej.api.android.RxBus;
|
||||||
import cy.agorise.graphenej.api.calls.CancelAllSubscriptions;
|
import cy.agorise.graphenej.api.calls.CancelAllSubscriptions;
|
||||||
import cy.agorise.graphenej.api.calls.SetSubscribeCallback;
|
import cy.agorise.graphenej.api.calls.SetSubscribeCallback;
|
||||||
|
@ -16,13 +23,16 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
|
|
||||||
public class SubscriptionActivity extends ConnectedActivity {
|
public class SubscriptionActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
@BindView(R.id.text_field)
|
@BindView(R.id.text_field)
|
||||||
TextView mTextField;
|
TextView mTextField;
|
||||||
|
|
||||||
|
// In case we want to interact directly with the service
|
||||||
|
private NetworkService mService;
|
||||||
|
|
||||||
private Disposable mDisposable;
|
private Disposable mDisposable;
|
||||||
|
|
||||||
// Notification counter
|
// Notification counter
|
||||||
|
@ -53,19 +63,51 @@ public class SubscriptionActivity extends ConnectedActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
// Bind to LocalService
|
||||||
|
Intent intent = new Intent(this, NetworkService.class);
|
||||||
|
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
unbindService(mConnection);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
mDisposable.dispose();
|
mDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines callbacks for backend binding, passed to bindService() */
|
||||||
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className,
|
||||||
|
IBinder service) {
|
||||||
|
Log.d(TAG,"onServiceConnected");
|
||||||
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||||
|
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
|
||||||
|
mService = binder.getService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
Log.d(TAG,"onServiceDisconnected");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@OnClick(R.id.subscribe)
|
@OnClick(R.id.subscribe)
|
||||||
public void onTransferFeeUsdClicked(View v){
|
public void onTransferFeeUsdClicked(View v){
|
||||||
mNetworkService.sendMessage(new SetSubscribeCallback(true), SetSubscribeCallback.REQUIRED_API);
|
mService.sendMessage(new SetSubscribeCallback(true), SetSubscribeCallback.REQUIRED_API);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.unsubscribe)
|
@OnClick(R.id.unsubscribe)
|
||||||
public void onTransferFeeBtsClicked(View v){
|
public void onTransferFeeBtsClicked(View v){
|
||||||
mNetworkService.sendMessage(new CancelAllSubscriptions(), CancelAllSubscriptions.REQUIRED_API);
|
mService.sendMessage(new CancelAllSubscriptions(), CancelAllSubscriptions.REQUIRED_API);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
package cy.agorise.labs.sample.network;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import cy.agorise.graphenej.api.android.NetworkService;
|
|
||||||
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
|
||||||
|
|
||||||
public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks {
|
|
||||||
private final String TAG = this.getClass().getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant used to specify how long will the app wait for another activity to go through its starting life
|
|
||||||
* cycle events before running the teardownConnectionTask task.
|
|
||||||
*
|
|
||||||
* This is used as a means to detect whether or not the user has left the app.
|
|
||||||
*/
|
|
||||||
private static final int DISCONNECT_DELAY = 1500;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler instance used to schedule tasks back to the main thread
|
|
||||||
*/
|
|
||||||
private Handler mHandler = new Handler();
|
|
||||||
|
|
||||||
private NetworkService mNetworkService;
|
|
||||||
|
|
||||||
private String[] mNodeUrls;
|
|
||||||
|
|
||||||
public NetworkServiceManager(String[] nodes){
|
|
||||||
this.mNodeUrls = nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runnable used to schedule a service disconnection once the app is not visible to the user for
|
|
||||||
* more than DISCONNECT_DELAY milliseconds.
|
|
||||||
*/
|
|
||||||
private final Runnable mDisconnectRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mNetworkService.stop();
|
|
||||||
mNetworkService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Activity activity, Bundle bundle) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStarted(Activity activity) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResumed(Activity activity) {
|
|
||||||
mHandler.removeCallbacks(mDisconnectRunnable);
|
|
||||||
if(mNetworkService == null) {
|
|
||||||
mNetworkService = NetworkService.getInstance();
|
|
||||||
mNetworkService.start(this.mNodeUrls, ExponentialMovingAverage.DEFAULT_ALPHA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityPaused(Activity activity) {
|
|
||||||
mHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStopped(Activity activity) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityDestroyed(Activity activity) {}
|
|
||||||
}
|
|
Loading…
Reference in a new issue