Merge branch 'feat_ping_nodes' into develop
This commit is contained in:
commit
a28775d464
17 changed files with 914 additions and 76 deletions
|
@ -23,6 +23,10 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -32,6 +36,10 @@ dependencies {
|
|||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
|
||||
implementation group: "org.tukaani", name: "xz", version: "1.6"
|
||||
|
||||
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
|
||||
// Rx dependencies
|
||||
api 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||
api 'io.reactivex.rxjava2:rxjava:2.1.16'
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.api.bitshares.Nodes;
|
||||
import cy.agorise.graphenej.network.FullNode;
|
||||
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
import cy.agorise.graphenej.network.NodeProvider;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NodeLatencyVerifierTest {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
@Test
|
||||
public void testNodeLatencyTest() throws Exception {
|
||||
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[0]));
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[1]));
|
||||
nodeList.add(new FullNode(Nodes.NODE_URLS[2]));
|
||||
final NodeLatencyVerifier nodeLatencyVerifier = new NodeLatencyVerifier(nodeList);
|
||||
PublishSubject subject = nodeLatencyVerifier.start();
|
||||
final NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||
subject.subscribe(new Observer<FullNode>() {
|
||||
int counter = 0;
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {}
|
||||
|
||||
@Override
|
||||
public void onNext(FullNode fullNode) {
|
||||
Log.i(TAG,String.format("Avg latency: %.2f, url: %s", fullNode.getLatencyValue(), fullNode.getUrl()));
|
||||
|
||||
// Updating node provider
|
||||
nodeProvider.updateNode(fullNode);
|
||||
List<FullNode> sortedNodes = nodeProvider.getSortedNodes();
|
||||
for(FullNode node : sortedNodes){
|
||||
Log.d(TAG,String.format("> %.2f, url: %s", node.getLatencyValue(), node.getUrl()));
|
||||
}
|
||||
|
||||
// Finish test after certain amount of rounds
|
||||
if(counter > 3){
|
||||
synchronized (NodeLatencyVerifierTest.this){
|
||||
nodeLatencyVerifier.stop();
|
||||
NodeLatencyVerifierTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG,"onError.Msg: "+e.getMessage());
|
||||
synchronized (NodeLatencyVerifierTest.this){
|
||||
NodeLatencyVerifierTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.d(TAG,"onComplete");
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized(this) {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
package="cy.agorise.graphenej">
|
||||
|
||||
<uses-sdk android:minSdkVersion="1" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
|
|
@ -2,10 +2,8 @@ package cy.agorise.graphenej.api.android;
|
|||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
@ -23,6 +21,7 @@ import cy.agorise.graphenej.Asset;
|
|||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.LimitOrder;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
|
@ -49,11 +48,18 @@ import cy.agorise.graphenej.models.HistoryOperationDetail;
|
|||
import cy.agorise.graphenej.models.JsonRpcNotification;
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.network.FullNode;
|
||||
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
import cy.agorise.graphenej.network.NodeProvider;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.Nullable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
@ -67,7 +73,7 @@ import okhttp3.WebSocketListener;
|
|||
public class NetworkService extends Service {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
private static final int NORMAL_CLOSURE_STATUS = 1000;
|
||||
public static final int NORMAL_CLOSURE_STATUS = 1000;
|
||||
|
||||
public static final String KEY_USERNAME = "key_username";
|
||||
|
||||
|
@ -75,6 +81,13 @@ public class NetworkService extends Service {
|
|||
|
||||
public static final String KEY_REQUESTED_APIS = "key_requested_apis";
|
||||
|
||||
public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier";
|
||||
|
||||
/**
|
||||
* Shared preference
|
||||
*/
|
||||
public static final String KEY_AUTO_CONNECT = "key_auto_connect";
|
||||
|
||||
/**
|
||||
* Constant used to pass a custom list of node URLs. This should be a simple
|
||||
* comma separated list of URLs.
|
||||
|
@ -89,8 +102,6 @@ public class NetworkService extends Service {
|
|||
|
||||
private WebSocket mWebSocket;
|
||||
|
||||
private int mSocketIndex;
|
||||
|
||||
// Username and password used to connect to a specific node
|
||||
private String mUsername;
|
||||
private String mPassword;
|
||||
|
@ -100,13 +111,25 @@ public class NetworkService extends Service {
|
|||
private String mLastCall;
|
||||
private long mCurrentId = 0;
|
||||
|
||||
private boolean mAutoConnect;
|
||||
|
||||
// Requested APIs passed to this service
|
||||
private int mRequestedApis;
|
||||
|
||||
// Variable used to keep track of the currently obtained API accesses
|
||||
private HashMap<Integer, Integer> mApiIds = new HashMap<Integer, Integer>();
|
||||
|
||||
private ArrayList<String> mNodeUrls = new ArrayList<>();
|
||||
// Variable used as a source of node information
|
||||
private NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||
|
||||
// Class used to obtain frequent node latency updates
|
||||
private NodeLatencyVerifier nodeLatencyVerifier;
|
||||
|
||||
// PublishSubject used to announce full node latencies updates
|
||||
private PublishSubject<FullNode> fullNodePublishSubject;
|
||||
|
||||
// Counter used to trigger the connection only after we've received enough node latency updates
|
||||
private long latencyUpdateCounter;
|
||||
|
||||
private Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||
|
@ -132,36 +155,15 @@ public class NetworkService extends Service {
|
|||
// suited for every response type.
|
||||
private DeserializationMap mDeserializationMap = new DeserializationMap();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
// Retrieving credentials and requested API data from the shared preferences
|
||||
mUsername = pref.getString(NetworkService.KEY_USERNAME, "");
|
||||
mPassword = pref.getString(NetworkService.KEY_PASSWORD, "");
|
||||
mRequestedApis = pref.getInt(NetworkService.KEY_REQUESTED_APIS, -1);
|
||||
|
||||
// If the user of the library desires, a custom list of node URLs can
|
||||
// be passed using the KEY_CUSTOM_NODE_URLS constant
|
||||
String serializedNodeUrls = pref.getString(NetworkService.KEY_CUSTOM_NODE_URLS, "");
|
||||
|
||||
// Deciding whether to use an externally provided list of node URLs, or use our internal one
|
||||
if(serializedNodeUrls.equals("")){
|
||||
mNodeUrls.addAll(Arrays.asList(Nodes.NODE_URLS));
|
||||
}else{
|
||||
String[] urls = serializedNodeUrls.split(",");
|
||||
mNodeUrls.addAll(Arrays.asList(urls));
|
||||
}
|
||||
connect();
|
||||
}
|
||||
|
||||
private void connect(){
|
||||
/**
|
||||
* Actually establishes a connection from this Service to one of the full nodes.
|
||||
*/
|
||||
public void connect(){
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
String url = mNodeUrls.get(mSocketIndex % mNodeUrls.size());
|
||||
Log.d(TAG,"Trying to connect with: "+url);
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
client.newWebSocket(request, mWebSocketListener);
|
||||
FullNode fullNode = nodeProvider.getBestNode();
|
||||
Log.v(TAG,"connect.url: "+fullNode.getUrl());
|
||||
Request request = new Request.Builder().url(fullNode.getUrl()).build();
|
||||
mWebSocket = client.newWebSocket(request, mWebSocketListener);
|
||||
}
|
||||
|
||||
public long sendMessage(String message){
|
||||
|
@ -212,19 +214,90 @@ public class NetworkService extends Service {
|
|||
public void onDestroy() {
|
||||
if(mWebSocket != null)
|
||||
mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
nodeLatencyVerifier.stop();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// Retrieving credentials and requested API data from the shared preferences
|
||||
mUsername = intent.getStringExtra(NetworkService.KEY_USERNAME);
|
||||
mPassword = intent.getStringExtra(NetworkService.KEY_PASSWORD);
|
||||
mRequestedApis = intent.getIntExtra(NetworkService.KEY_REQUESTED_APIS, 0);
|
||||
mAutoConnect = intent.getBooleanExtra(NetworkService.KEY_AUTO_CONNECT, true);
|
||||
boolean verifyNodeLatency = intent.getBooleanExtra(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, false);
|
||||
|
||||
ArrayList<String> nodeUrls = new ArrayList<>();
|
||||
// If the user of the library desires, a custom list of node URLs can
|
||||
// be passed using the KEY_CUSTOM_NODE_URLS constant
|
||||
String customNodeUrls = intent.getStringExtra(NetworkService.KEY_CUSTOM_NODE_URLS);
|
||||
|
||||
// Adding user-provided list of node URLs first
|
||||
if(customNodeUrls != null){
|
||||
String[] urls = customNodeUrls.split(",");
|
||||
ArrayList<String> urlList = new ArrayList<>(Arrays.asList(urls));
|
||||
nodeUrls.addAll(urlList);
|
||||
}
|
||||
|
||||
// Adding the library-provided list of nodes second
|
||||
nodeUrls.addAll(Arrays.asList(Nodes.NODE_URLS));
|
||||
|
||||
// Feeding all node information to the NodeProvider instance
|
||||
for(String nodeUrl : nodeUrls){
|
||||
nodeProvider.addNode(new FullNode(nodeUrl));
|
||||
}
|
||||
|
||||
// We only connect automatically if the auto-connect flag is true AND
|
||||
// we are not going to care about node latency ordering.
|
||||
if(mAutoConnect && !verifyNodeLatency) {
|
||||
connect();
|
||||
}else{
|
||||
// In case we care about node latency ordering, we must first obtain
|
||||
// a first round of measurements in order to be sure to select the
|
||||
// best node.
|
||||
if(verifyNodeLatency){
|
||||
ArrayList<FullNode> fullNodes = new ArrayList<>();
|
||||
for(String url : nodeUrls){
|
||||
fullNodes.add(new FullNode(url));
|
||||
}
|
||||
nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes);
|
||||
fullNodePublishSubject = nodeLatencyVerifier.start();
|
||||
fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver);
|
||||
}
|
||||
}
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer used to be notified about node latency measurement updates.
|
||||
*/
|
||||
private Observer<FullNode> nodeLatencyObserver = new Observer<FullNode>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) { }
|
||||
|
||||
@Override
|
||||
public void onNext(FullNode fullNode) {
|
||||
latencyUpdateCounter++;
|
||||
// Updating the node with the new latency measurement
|
||||
nodeProvider.updateNode(fullNode);
|
||||
|
||||
// Once we have the latency value of all available nodes,
|
||||
// we can safely proceed to start the connection
|
||||
if(latencyUpdateCounter == nodeProvider.getSortedNodes().size()){
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG,"nodeLatencyObserver.onError.Msg: "+e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() { }
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -241,7 +314,6 @@ public class NetworkService extends Service {
|
|||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
mWebSocket = webSocket;
|
||||
|
||||
// Notifying all listeners about the new connection status
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.CONNECTED, ApiAccess.API_NONE));
|
||||
|
@ -481,7 +553,7 @@ public class NetworkService extends Service {
|
|||
Log.e(TAG,"onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage());
|
||||
// Logging error stack trace
|
||||
for(StackTraceElement element : t.getStackTrace()){
|
||||
Log.e(TAG,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()));
|
||||
}
|
||||
// Registering current status
|
||||
isLoggedIn = false;
|
||||
|
@ -494,9 +566,8 @@ public class NetworkService extends Service {
|
|||
}
|
||||
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
||||
mSocketIndex++;
|
||||
|
||||
if(mSocketIndex > mNodeUrls.size() * 3){
|
||||
if(nodeProvider.getBestNode() == null){
|
||||
Log.e(TAG,"Giving up on connections");
|
||||
stopSelf();
|
||||
}else{
|
||||
|
@ -516,11 +587,27 @@ public class NetworkService extends Service {
|
|||
return mApiIds.get(whichApi) != null;
|
||||
}
|
||||
|
||||
public ArrayList<String> getNodeUrls() {
|
||||
return mNodeUrls;
|
||||
/**
|
||||
* Updates the full node details
|
||||
* @param fullNode Updated {@link FullNode} instance
|
||||
*/
|
||||
public void updateNode(FullNode fullNode){
|
||||
nodeProvider.updateNode(fullNode);
|
||||
}
|
||||
|
||||
public void setNodeUrls(ArrayList<String> mNodeUrls) {
|
||||
this.mNodeUrls = mNodeUrls;
|
||||
/**
|
||||
* Returns a list of {@link FullNode} instances
|
||||
* @return List of full nodes
|
||||
*/
|
||||
public List<FullNode> getNodes(){
|
||||
return nodeProvider.getSortedNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that will notify its observers about node latency updates.
|
||||
* @return Observer of {@link FullNode} instances.
|
||||
*/
|
||||
public PublishSubject<FullNode> getNodeLatencyObservable(){
|
||||
return fullNodePublishSubject;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ 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;
|
||||
|
||||
/**
|
||||
* This class should be instantiated at the application level of the android app.
|
||||
|
@ -20,8 +23,7 @@ import java.lang.ref.WeakReference;
|
|||
*/
|
||||
|
||||
public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
private final static String TAG = "NetworkServiceManager";
|
||||
/**
|
||||
* 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.
|
||||
|
@ -43,6 +45,14 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
|
|||
// 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;
|
||||
|
||||
/**
|
||||
* Runnable used to schedule a service disconnection once the app is not visible to the user for
|
||||
* more than DISCONNECT_DELAY milliseconds.
|
||||
|
@ -77,8 +87,27 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
|
|||
public void onActivityStarted(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);
|
||||
|
||||
// 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();
|
||||
|
||||
// Adding all
|
||||
intent.putExtra(NetworkService.KEY_USERNAME, mUserName)
|
||||
.putExtra(NetworkService.KEY_PASSWORD, mPassword)
|
||||
.putExtra(NetworkService.KEY_REQUESTED_APIS, mRequestedApis)
|
||||
.putExtra(NetworkService.KEY_CUSTOM_NODE_URLS, customNodes)
|
||||
.putExtra(NetworkService.KEY_AUTO_CONNECT, mAutoConnect)
|
||||
.putExtra(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, mVerifyLatency);
|
||||
|
||||
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
@ -114,4 +143,156 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
|
|||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {}
|
||||
};
|
||||
|
||||
public String getUserName() {
|
||||
return mUserName;
|
||||
}
|
||||
|
||||
public void setUserName(String mUserName) {
|
||||
this.mUserName = mUserName;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if(password != null) manager.setPassword(password);
|
||||
if(customNodeUrls != null) manager.setCustomNodeUrls(customNodeUrls);
|
||||
manager.setRequestedApis(requestedApis);
|
||||
manager.setAutoConnect(autoconnect);
|
||||
manager.setVerifyLatency(verifyNodeLatency);
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,7 @@ public class Nodes {
|
|||
"wss://dexnode.net/ws", // Dallas, USA
|
||||
"wss://bitshares.crypto.fans/ws", // Munich, Germany
|
||||
"wss://bitshares.openledger.info/ws", // Openledger node
|
||||
"wss://us.nodes.bitshares.ws",
|
||||
"wss://eu.nodes.bitshares.ws"
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||
|
||||
/**
|
||||
* Class that represents a full node and is used to keep track of its round-trip time measured in milliseconds.
|
||||
*/
|
||||
public class FullNode implements Comparable {
|
||||
|
||||
private String mUrl;
|
||||
private ExponentialMovingAverage latency;
|
||||
|
||||
private FullNode(){}
|
||||
|
||||
public FullNode(String url){
|
||||
latency = new ExponentialMovingAverage(ExponentialMovingAverage.DEFAULT_ALPHA);
|
||||
this.mUrl = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full node URL getter
|
||||
* @return
|
||||
*/
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full node URL setter
|
||||
* @param mUrl
|
||||
*/
|
||||
public void setUrl(String mUrl) {
|
||||
this.mUrl = mUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The exponential moving average object instance
|
||||
*/
|
||||
public ExponentialMovingAverage getLatencyAverage(){
|
||||
return latency;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The latest latency average value
|
||||
*/
|
||||
public double getLatencyValue() {
|
||||
return latency.getAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that updates the latency average with a new value.
|
||||
* @param latency Most recent latency sample to be added to the exponential average
|
||||
*/
|
||||
public void addLatencyValue(double latency) {
|
||||
this.latency.updateValue(latency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
FullNode node = (FullNode) o;
|
||||
return (int) Math.ceil(latency.getAverage() - node.getLatencyValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FullNode fullNode = (FullNode) o;
|
||||
return mUrl.equals(fullNode.getUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUrl.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
public class LatencyNodeProvider implements NodeProvider {
|
||||
private final String TAG = "LatencyNodeProvider";
|
||||
private PriorityQueue<FullNode> mFullNodeHeap;
|
||||
|
||||
public LatencyNodeProvider(){
|
||||
mFullNodeHeap = new PriorityQueue<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FullNode getBestNode() {
|
||||
return mFullNodeHeap.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNode(FullNode fullNode) {
|
||||
mFullNodeHeap.add(fullNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateNode(FullNode fullNode) {
|
||||
mFullNodeHeap.remove(fullNode);
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing node with the new latency value.
|
||||
*
|
||||
* @param fullNode Existing full node instance
|
||||
* @param latency New latency measurement
|
||||
* @return True if the node priority was updated successfully
|
||||
*/
|
||||
public boolean updateNode(FullNode fullNode, int latency){
|
||||
if(mFullNodeHeap.remove(fullNode)){
|
||||
fullNode.addLatencyValue(latency);
|
||||
return mFullNodeHeap.add(fullNode);
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FullNode> getSortedNodes() {
|
||||
FullNode[] nodeArray = mFullNodeHeap.toArray(new FullNode[mFullNodeHeap.size()]);
|
||||
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||
for(FullNode fullNode : nodeList){
|
||||
if(fullNode != null){
|
||||
nodeList.add(fullNode);
|
||||
}
|
||||
}
|
||||
Collections.sort(nodeList);
|
||||
return Arrays.asList(nodeArray);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.api.android.NetworkService;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
||||
/**
|
||||
* Class that encapsulates the node latency verification task
|
||||
*/
|
||||
public class NodeLatencyVerifier {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
public static final int DEFAULT_LATENCY_VERIFICATION_PERIOD = 5 * 1000;
|
||||
|
||||
// Variable used to store the list of nodes that should be verified
|
||||
private List<FullNode> mNodeList;
|
||||
|
||||
// Variable used to store the desired verification period
|
||||
private long verificationPeriod;
|
||||
|
||||
// Subject used to publish the result to interested parties
|
||||
private PublishSubject<FullNode> subject = PublishSubject.create();
|
||||
|
||||
private HashMap<HttpUrl, FullNode> nodeURLMap = new HashMap<>();
|
||||
|
||||
// private WebSocket webSocket;
|
||||
|
||||
// Map used to store the first timestamp required for a RTT (Round Trip Time) measurement.
|
||||
// If:
|
||||
// RTT = t2 - t1
|
||||
// This map will hold the value of t1 for each one of the nodes to be measured.
|
||||
private HashMap<FullNode, Long> timestamps = new HashMap<>();
|
||||
|
||||
private HashMap<String, Request> requestMap = new HashMap<>();
|
||||
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private OkHttpClient client;
|
||||
|
||||
public NodeLatencyVerifier(List<FullNode> nodes){
|
||||
this(nodes, DEFAULT_LATENCY_VERIFICATION_PERIOD);
|
||||
}
|
||||
|
||||
public NodeLatencyVerifier(List<FullNode> nodes, long period){
|
||||
mNodeList = nodes;
|
||||
verificationPeriod = period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to start the latency verification task.
|
||||
* <p>
|
||||
* The returning object can be used for interested parties to receive constant updates
|
||||
* regarding new latency measurements for every full node.
|
||||
* </p>
|
||||
* @return A {@link PublishSubject} class instance.
|
||||
*/
|
||||
public PublishSubject start(){
|
||||
mHandler.post(mVerificationTask);
|
||||
return subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to cancel the verification task.
|
||||
*/
|
||||
public void stop(){
|
||||
mHandler.removeCallbacks(mVerificationTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Node latency verification task.
|
||||
*/
|
||||
private final Runnable mVerificationTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for(FullNode fullNode : mNodeList){
|
||||
long before = System.currentTimeMillis();
|
||||
timestamps.put(fullNode, before);
|
||||
|
||||
// We want to reuse the same OkHttoClient instance if possible
|
||||
if(client == null) client = new OkHttpClient();
|
||||
|
||||
// Same thing with the Request instance, we want to reuse them. But since
|
||||
// we might have one request per node, we keep them in a map.
|
||||
Request request;
|
||||
if(requestMap.containsKey(fullNode.getUrl())){
|
||||
request = requestMap.get(fullNode.getUrl());
|
||||
}else{
|
||||
// If the map had no entry for the request we want, we create one
|
||||
// and add it to the map.
|
||||
request = new Request.Builder().url(fullNode.getUrl()).build();
|
||||
requestMap.put(fullNode.getUrl(), request);
|
||||
}
|
||||
|
||||
String normalURL = fullNode.getUrl().replace("wss://", "https://");
|
||||
Log.d(TAG,"normal URL : "+normalURL);
|
||||
if(!nodeURLMap.containsKey(fullNode.getUrl().replace("wss://", "https://"))){
|
||||
HttpUrl key = HttpUrl.parse(normalURL);
|
||||
Log.i(TAG, "Inserting key: "+key.toString());
|
||||
nodeURLMap.put(key, fullNode);
|
||||
}
|
||||
|
||||
client.newWebSocket(request, mWebSocketListener);
|
||||
}
|
||||
mHandler.postDelayed(this, verificationPeriod);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener that will be called upon a server response.
|
||||
*/
|
||||
private WebSocketListener mWebSocketListener = new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
handleResponse(webSocket, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
handleResponse(webSocket, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to handle the node's first response. The idea here is to obtain
|
||||
* the RTT (Round Trip Time) measurement and publish it using the PublishSubject.
|
||||
*
|
||||
* @param webSocket Websocket instance
|
||||
* @param response Response instance
|
||||
*/
|
||||
private void handleResponse(WebSocket webSocket, Response response){
|
||||
// Obtaining the HttpUrl instance that was previously used as a key
|
||||
HttpUrl url = webSocket.request().url();
|
||||
if(nodeURLMap.containsKey(url)){
|
||||
FullNode fullNode = nodeURLMap.get(url);
|
||||
long after = System.currentTimeMillis();
|
||||
long before = timestamps.get(fullNode);
|
||||
long delay = after - before;
|
||||
fullNode.addLatencyValue(delay);
|
||||
subject.onNext(fullNode);
|
||||
}else{
|
||||
// We cannot properly handle a response to a request whose
|
||||
// URL was not registered at the nodeURLMap. This is because without this,
|
||||
// we cannot know to which node this response corresponds. This should not happen.
|
||||
Log.e(TAG,"nodeURLMap does not contain url: "+url);
|
||||
for(HttpUrl key : nodeURLMap.keySet()){
|
||||
Log.e(TAG,"> "+key);
|
||||
}
|
||||
}
|
||||
webSocket.close(NetworkService.NORMAL_CLOSURE_STATUS, null);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface used to describe the high level characteristics of a class that will
|
||||
* hold and manage a list of {@link FullNode} instances.
|
||||
*
|
||||
* The idea is that the class implementing this interface should provide node instances
|
||||
* and thus URLs for the {@link cy.agorise.graphenej.api.android.NetworkService} with
|
||||
* different sorting heuristics.
|
||||
*/
|
||||
public interface NodeProvider {
|
||||
|
||||
/**
|
||||
* Returns the node with the best characteristics. Returns null if there is no {@link FullNode}
|
||||
* @return A FullNode instance
|
||||
*/
|
||||
FullNode getBestNode();
|
||||
|
||||
/**
|
||||
* Adds a new node to the queue
|
||||
* @param fullNode {@link FullNode} instance to add.
|
||||
*/
|
||||
void addNode(FullNode fullNode);
|
||||
|
||||
/**
|
||||
* Updates the rating of a specific node that is already in the NodeProvider
|
||||
* @param fullNode
|
||||
*/
|
||||
boolean updateNode(FullNode fullNode);
|
||||
|
||||
/**
|
||||
* Returns an ordered list of {@link FullNode} instances.
|
||||
* @return
|
||||
*/
|
||||
List<FullNode> getSortedNodes();
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package cy.agorise.graphenej.stats;
|
||||
|
||||
/**
|
||||
* Class used to compute the Exponential Moving Average of a sequence of values.
|
||||
* For more details see <a href="https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average">here</a>.
|
||||
*/
|
||||
public class ExponentialMovingAverage {
|
||||
public static final double DEFAULT_ALPHA = 0.5;
|
||||
private double alpha;
|
||||
private Double accumulatedValue;
|
||||
|
||||
/**
|
||||
* Constructor, which takes only the alpha parameter as an argument.
|
||||
*
|
||||
* @param alpha The coefficient alpha represents the degree of weighting decrease, a constant
|
||||
* smoothing factor between 0 and 1. A higher alpha discounts older observations faster.
|
||||
*/
|
||||
public ExponentialMovingAverage(double alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that updates the average with a new sample
|
||||
* @param value New value
|
||||
* @return The updated average value
|
||||
*/
|
||||
public double updateValue(double value) {
|
||||
if (accumulatedValue == null) {
|
||||
accumulatedValue = value;
|
||||
return value;
|
||||
}
|
||||
double newValue = accumulatedValue + alpha * (value - accumulatedValue);
|
||||
accumulatedValue = newValue;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Returns the current average value
|
||||
*/
|
||||
public double getAverage(){
|
||||
return accumulatedValue == null ? 0 : accumulatedValue;
|
||||
}
|
||||
|
||||
public void setAlpha(double alpha){
|
||||
this.alpha = alpha;
|
||||
this.accumulatedValue = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class FullNodeTest {
|
||||
|
||||
@Test
|
||||
public void testFullNodeComparable(){
|
||||
FullNode nodeA = new FullNode("wss://dummy");
|
||||
FullNode nodeB = new FullNode("wss://dummy");
|
||||
FullNode nodeC = new FullNode("wss://dummy");
|
||||
nodeA.addLatencyValue(100);
|
||||
nodeB.addLatencyValue(200);
|
||||
nodeC.addLatencyValue(100);
|
||||
Assert.assertTrue("Makes sure the node nodeA.compareTo(nodeB) returns a negative value", nodeA.compareTo(nodeB) < 0);
|
||||
Assert.assertTrue("Makes sure nodeA.compareTo(nodeB) returns zero", nodeA.compareTo(nodeC) == 0);
|
||||
Assert.assertTrue("Makes sure nodeB.compareTo(nodeA) returns a positive value", nodeB.compareTo(nodeA) > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullNodeAverageLatency(){
|
||||
FullNode fullNode = new FullNode("wss://dummy");
|
||||
fullNode.getLatencyAverage().setAlpha(0.5);
|
||||
fullNode.addLatencyValue(100);
|
||||
Assert.assertEquals(100.0, fullNode.getLatencyValue());
|
||||
fullNode.addLatencyValue(50);
|
||||
Assert.assertEquals(75.0, fullNode.getLatencyValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LatencyNodeProviderTest {
|
||||
private FullNode nodeA, nodeB, nodeC;
|
||||
private LatencyNodeProvider latencyNodeProvider;
|
||||
|
||||
private void setupTestNodes(){
|
||||
// Creating 3 nodes with different latencies
|
||||
nodeA = new FullNode("wss://nodeA");
|
||||
nodeB = new FullNode("wss://nodeB");
|
||||
nodeC = new FullNode("wss://nodeC");
|
||||
|
||||
// Adding latencies measurements
|
||||
nodeA.addLatencyValue(100);
|
||||
nodeB.addLatencyValue(50);
|
||||
nodeC.addLatencyValue(20);
|
||||
|
||||
// Creating a node provider and adding the nodes created previously
|
||||
latencyNodeProvider = new LatencyNodeProvider();
|
||||
latencyNodeProvider.addNode(nodeC);
|
||||
latencyNodeProvider.addNode(nodeA);
|
||||
latencyNodeProvider.addNode(nodeB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSorting(){
|
||||
setupTestNodes();
|
||||
|
||||
// Confirming that the best node is nodeC
|
||||
FullNode bestNode = latencyNodeProvider.getBestNode();
|
||||
System.out.println("Best node latency: "+bestNode.getLatencyValue());
|
||||
Assert.assertSame("Check that the best node is nodeC", nodeC, bestNode);
|
||||
|
||||
// Improving nodeA score by feeding it with new better latency measurements
|
||||
latencyNodeProvider.updateNode(nodeA, 10);
|
||||
latencyNodeProvider.updateNode(nodeA, 10);
|
||||
latencyNodeProvider.updateNode(nodeA, 10);
|
||||
latencyNodeProvider.updateNode(nodeA, 10);
|
||||
|
||||
// Updating the nodeA position in the provider
|
||||
latencyNodeProvider.updateNode(nodeA);
|
||||
bestNode = latencyNodeProvider.getBestNode();
|
||||
System.out.println("Best node latency after update: "+bestNode.getLatencyValue());
|
||||
Assert.assertSame("Check that the best node now is the nodeA", nodeA, bestNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortedList(){
|
||||
setupTestNodes();
|
||||
|
||||
// Confirming that the getSortedNodes gives us a sorted list of nodes in increasing latency order
|
||||
List<FullNode> fullNodeList = latencyNodeProvider.getSortedNodes();
|
||||
Assert.assertSame(nodeC, fullNodeList.get(0));
|
||||
Assert.assertSame(nodeB, fullNodeList.get(1));
|
||||
Assert.assertSame(nodeA, fullNodeList.get(2));
|
||||
}
|
||||
}
|
|
@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
|||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("com.luminiasoft.labs.sample", appContext.getPackageName());
|
||||
assertEquals("cy.agorise.labs.sample", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import butterknife.ButterKnife;
|
|||
import cy.agorise.graphenej.RPC;
|
||||
|
||||
public class CallsActivity extends AppCompatActivity {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
@BindView(R.id.call_list)
|
||||
RecyclerView mRecyclerView;
|
||||
|
@ -26,10 +25,9 @@ public class CallsActivity extends AppCompatActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_calls);
|
||||
ButterKnife.bind(this);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
mRecyclerView.setLayoutManager(linearLayoutManager);
|
||||
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, linearLayoutManager.getOrientation()));
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
|
||||
mRecyclerView.setAdapter(new CallAdapter());
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.support.v7.app.AppCompatActivity;
|
|||
import android.util.Log;
|
||||
|
||||
import cy.agorise.graphenej.api.android.NetworkService;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
|
||||
public abstract class ConnectedActivity extends AppCompatActivity implements ServiceConnection {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
@ -28,7 +29,6 @@ public abstract class ConnectedActivity extends AppCompatActivity implements Ser
|
|||
// 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);
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,8 @@ public abstract class ConnectedActivity extends AppCompatActivity implements Ser
|
|||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// Binding to NetworkService
|
||||
Intent intent = new Intent(this, NetworkService.class);
|
||||
// Binding to NetworkService
|
||||
if(bindService(intent, mNetworkServiceConnection, Context.BIND_AUTO_CREATE)){
|
||||
mShouldUnbindNetwork = true;
|
||||
}else{
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package cy.agorise.labs.sample;
|
||||
|
||||
import android.app.Application;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.api.android.NetworkService;
|
||||
import cy.agorise.graphenej.api.android.NetworkServiceManager;
|
||||
|
||||
/**
|
||||
|
@ -12,30 +10,25 @@ import cy.agorise.graphenej.api.android.NetworkServiceManager;
|
|||
*/
|
||||
|
||||
public class SampleApplication extends Application {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// This variable would hold a list of custom nodes
|
||||
String customNodes = "wss://mydomain.net/ws,wss://myotherdomain.com/ws";
|
||||
|
||||
// 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;
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putString(NetworkService.KEY_USERNAME, "nelson")
|
||||
.putString(NetworkService.KEY_PASSWORD, "secret")
|
||||
.putInt(NetworkService.KEY_REQUESTED_APIS, requestedApis)
|
||||
// .putString(NetworkService.KEY_CUSTOM_NODE_URLS, customNodes)
|
||||
.apply();
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
registerActivityLifecycleCallbacks(new NetworkServiceManager(this));
|
||||
NetworkServiceManager networkManager = new NetworkServiceManager.Builder()
|
||||
.setUserName("nelson")
|
||||
.setPassword("secret")
|
||||
.setRequestedApis(requestedApis)
|
||||
.setCustomNodeUrls("wss://eu.nodes.bitshares.ws")
|
||||
.setAutoConnect(true)
|
||||
.setNodeLatencyVerification(true)
|
||||
.build(this);
|
||||
|
||||
// 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
|
||||
registerActivityLifecycleCallbacks(networkManager);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue