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

develop
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.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,7 +48,9 @@ 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.NodeProvider;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
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";
/**
* 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 +95,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 +104,15 @@ 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<>();
NodeProvider nodeProvider = new LatencyNodeProvider();
private Gson gson = new GsonBuilder()
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
@ -132,36 +138,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.d(TAG,"connect.url: "+fullNode.getUrl());
Request request = new Request.Builder().url(fullNode.getUrl()).build();
mWebSocket = client.newWebSocket(request, mWebSocketListener);
}
public long sendMessage(String message){
@ -214,14 +199,38 @@ public class NetworkService extends Service {
mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
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;
}
@ -241,7 +250,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));
@ -494,9 +502,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 +523,19 @@ 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
*/
List<FullNode> getSortedNodes(){
return nodeProvider.getSortedNodes();
}
}

View File

@ -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,13 @@ 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;
/**
* Runnable used to schedule a service disconnection once the app is not visible to the user for
* more than DISCONNECT_DELAY milliseconds.
@ -77,8 +86,26 @@ 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);
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
}
@ -114,4 +141,99 @@ 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;
}
/**
* 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;
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 {
/**
* 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
*/
FullNode getBestNode();

View File

@ -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());
}

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
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
mNetworkService = binder.getService();
ConnectedActivity.this.onServiceConnected(className, service);
}
@ -41,8 +40,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{

View File

@ -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,24 @@ 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)
.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);
}
}