Modified the NetworkService and the NetworkServiceManager classes in order to pass arguments to the service via Intent extras and to allow delayed network connection

This commit is contained in:
Nelson R. Perez 2018-09-19 16:44:26 -05:00
parent 25222e5ea9
commit ccf61858e4
7 changed files with 214 additions and 75 deletions

View file

@ -2,10 +2,8 @@ package cy.agorise.graphenej.api.android;
import android.app.Service; import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -23,6 +21,7 @@ import cy.agorise.graphenej.Asset;
import cy.agorise.graphenej.AssetAmount; import cy.agorise.graphenej.AssetAmount;
import cy.agorise.graphenej.BaseOperation; import cy.agorise.graphenej.BaseOperation;
import cy.agorise.graphenej.LimitOrder; import cy.agorise.graphenej.LimitOrder;
import cy.agorise.graphenej.Memo;
import cy.agorise.graphenej.RPC; 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;
@ -49,7 +48,9 @@ import cy.agorise.graphenej.models.HistoryOperationDetail;
import cy.agorise.graphenej.models.JsonRpcNotification; import cy.agorise.graphenej.models.JsonRpcNotification;
import cy.agorise.graphenej.models.JsonRpcResponse; import cy.agorise.graphenej.models.JsonRpcResponse;
import cy.agorise.graphenej.models.OperationHistory; 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.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;
@ -75,6 +76,11 @@ public class NetworkService extends Service {
public static final String KEY_REQUESTED_APIS = "key_requested_apis"; public static final String KEY_REQUESTED_APIS = "key_requested_apis";
/**
* 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 * Constant used to pass a custom list of node URLs. This should be a simple
* comma separated list of URLs. * comma separated list of URLs.
@ -89,8 +95,6 @@ public class NetworkService extends Service {
private WebSocket mWebSocket; private WebSocket mWebSocket;
private int mSocketIndex;
// Username and password used to connect to a specific node // Username and password used to connect to a specific node
private String mUsername; private String mUsername;
private String mPassword; private String mPassword;
@ -100,13 +104,15 @@ public class NetworkService extends Service {
private String mLastCall; private String mLastCall;
private long mCurrentId = 0; private long mCurrentId = 0;
private boolean mAutoConnect;
// Requested APIs passed to this service // Requested APIs passed to this service
private int mRequestedApis; 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>();
private ArrayList<String> mNodeUrls = new ArrayList<>(); NodeProvider nodeProvider = new LatencyNodeProvider();
private Gson gson = new GsonBuilder() private Gson gson = new GsonBuilder()
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer()) .registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
@ -132,36 +138,15 @@ public class NetworkService extends Service {
// suited for every response type. // suited for every response type.
private DeserializationMap mDeserializationMap = new DeserializationMap(); private DeserializationMap mDeserializationMap = new DeserializationMap();
@Override /**
public void onCreate() { * Actually establishes a connection from this Service to one of the full nodes.
super.onCreate(); */
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); public void connect(){
// 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(){
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
String url = mNodeUrls.get(mSocketIndex % mNodeUrls.size()); FullNode fullNode = nodeProvider.getBestNode();
Log.d(TAG,"Trying to connect with: "+url); Log.d(TAG,"connect.url: "+fullNode.getUrl());
Request request = new Request.Builder().url(url).build(); Request request = new Request.Builder().url(fullNode.getUrl()).build();
client.newWebSocket(request, mWebSocketListener); mWebSocket = client.newWebSocket(request, mWebSocketListener);
} }
public long sendMessage(String message){ public long sendMessage(String message){
@ -214,14 +199,38 @@ public class NetworkService extends Service {
mWebSocket.close(NORMAL_CLOSURE_STATUS, null); mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
} }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
Log.d(TAG,"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);
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));
for(String nodeUrl : nodeUrls){
nodeProvider.addNode(new FullNode(nodeUrl));
}
if(mAutoConnect) connect();
return mBinder; return mBinder;
} }
@ -241,7 +250,6 @@ public class NetworkService extends Service {
@Override @Override
public void onOpen(WebSocket webSocket, Response response) { public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response); super.onOpen(webSocket, response);
mWebSocket = webSocket;
// Notifying all listeners about the new connection status // Notifying all listeners about the new connection status
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.CONNECTED, ApiAccess.API_NONE)); RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.CONNECTED, ApiAccess.API_NONE));
@ -494,9 +502,8 @@ public class NetworkService extends Service {
} }
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE)); 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"); Log.e(TAG,"Giving up on connections");
stopSelf(); stopSelf();
}else{ }else{
@ -516,11 +523,19 @@ public class NetworkService extends Service {
return mApiIds.get(whichApi) != null; 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
*/
List<FullNode> getSortedNodes(){
return nodeProvider.getSortedNodes();
} }
} }

View file

@ -11,6 +11,9 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import java.lang.ref.WeakReference; 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. * 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 { 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 * 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. * cycle events before running the teardownConnectionTask task.
@ -43,6 +45,13 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
// In case we want to interact directly with the service // In case we want to interact directly with the service
private NetworkService mService; 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;
/** /**
* Runnable used to schedule a service disconnection once the app is not visible to the user for * Runnable used to schedule a service disconnection once the app is not visible to the user for
* more than DISCONNECT_DELAY milliseconds. * more than DISCONNECT_DELAY milliseconds.
@ -77,8 +86,26 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
public void onActivityStarted(Activity activity) { public void onActivityStarted(Activity activity) {
mHandler.removeCallbacks(mDisconnectRunnable); mHandler.removeCallbacks(mDisconnectRunnable);
if(mService == null){ if(mService == null){
// Creating a new Intent that will be used to start the NetworkService
Context context = mContextReference.get(); Context context = mContextReference.get();
Intent intent = new Intent(context, NetworkService.class); 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);
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
} }
} }
@ -114,4 +141,99 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
@Override @Override
public void onServiceDisconnected(ComponentName componentName) {} 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;
}
/**
* 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;
public Builder setUserName(String name){
this.username = name;
return this;
}
public Builder setPassword(String password){
this.password = password;
return this;
}
public Builder setRequestedApis(int apis){
this.requestedApis = apis;
return this;
}
public Builder setCustomNodeUrls(List<String> nodeUrls){
this.customNodeUrls = nodeUrls;
return this;
}
public Builder setCustomNodeUrls(String nodeUrls){
String[] urls = nodeUrls.split(",");
for(String url : urls){
if(customNodeUrls == null) customNodeUrls = new ArrayList<>();
customNodeUrls.add(url);
}
return this;
}
public Builder setAutoConnect(boolean autoConnect){
this.autoconnect = autoConnect;
return this;
}
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);
return manager;
}
}
} }

View file

@ -60,4 +60,17 @@ public class FullNode implements Comparable {
FullNode node = (FullNode) o; FullNode node = (FullNode) o;
return (int) Math.ceil(latency.getAverage() - node.getLatencyValue()); 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();
}
} }

View file

@ -13,7 +13,7 @@ import java.util.List;
public interface NodeProvider { public interface NodeProvider {
/** /**
* Returns the node with the best characteristics. * Returns the node with the best characteristics. Returns null if there is no {@link FullNode}
* @return A FullNode instance * @return A FullNode instance
*/ */
FullNode getBestNode(); FullNode getBestNode();

View file

@ -16,7 +16,6 @@ import butterknife.ButterKnife;
import cy.agorise.graphenej.RPC; import cy.agorise.graphenej.RPC;
public class CallsActivity extends AppCompatActivity { public class CallsActivity extends AppCompatActivity {
private final String TAG = this.getClass().getName();
@BindView(R.id.call_list) @BindView(R.id.call_list)
RecyclerView mRecyclerView; RecyclerView mRecyclerView;
@ -26,10 +25,9 @@ public class CallsActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calls); setContentView(R.layout.activity_calls);
ButterKnife.bind(this); ButterKnife.bind(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setHasFixedSize(true); mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(linearLayoutManager); mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, linearLayoutManager.getOrientation())); mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
mRecyclerView.setAdapter(new CallAdapter()); mRecyclerView.setAdapter(new CallAdapter());
} }

View file

@ -28,7 +28,6 @@ public abstract class ConnectedActivity extends AppCompatActivity implements Ser
// We've bound to LocalService, cast the IBinder and get LocalService instance // We've bound to LocalService, cast the IBinder and get LocalService instance
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service; NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
mNetworkService = binder.getService(); mNetworkService = binder.getService();
ConnectedActivity.this.onServiceConnected(className, service); ConnectedActivity.this.onServiceConnected(className, service);
} }
@ -41,8 +40,8 @@ public abstract class ConnectedActivity extends AppCompatActivity implements Ser
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
// Binding to NetworkService
Intent intent = new Intent(this, NetworkService.class); Intent intent = new Intent(this, NetworkService.class);
// Binding to NetworkService
if(bindService(intent, mNetworkServiceConnection, Context.BIND_AUTO_CREATE)){ if(bindService(intent, mNetworkServiceConnection, Context.BIND_AUTO_CREATE)){
mShouldUnbindNetwork = true; mShouldUnbindNetwork = true;
}else{ }else{

View file

@ -1,10 +1,8 @@
package cy.agorise.labs.sample; package cy.agorise.labs.sample;
import android.app.Application; import android.app.Application;
import android.preference.PreferenceManager;
import cy.agorise.graphenej.api.ApiAccess; import cy.agorise.graphenej.api.ApiAccess;
import cy.agorise.graphenej.api.android.NetworkService;
import cy.agorise.graphenej.api.android.NetworkServiceManager; import cy.agorise.graphenej.api.android.NetworkServiceManager;
/** /**
@ -12,30 +10,24 @@ import cy.agorise.graphenej.api.android.NetworkServiceManager;
*/ */
public class SampleApplication extends Application { public class SampleApplication extends Application {
private final String TAG = this.getClass().getName();
@Override @Override
public void onCreate() { public void onCreate() {
super.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 // Specifying some important information regarding the connection, such as the
// credentials and the requested API accesses // credentials and the requested API accesses
int requestedApis = ApiAccess.API_DATABASE | ApiAccess.API_HISTORY | ApiAccess.API_NETWORK_BROADCAST; 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();
/* NetworkServiceManager networkManager = new NetworkServiceManager.Builder()
* Registering this class as a listener to all activity's callback cycle events, in order to .setUserName("nelson")
* better estimate when the user has left the app and it is safe to disconnect the websocket connection .setPassword("secret")
*/ .setRequestedApis(requestedApis)
registerActivityLifecycleCallbacks(new NetworkServiceManager(this)); .setCustomNodeUrls("wss://eu.nodes.bitshares.ws")
.setAutoConnect(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);
} }
} }