From ccf61858e495e7b7eb2af71ce555d2a769ae3e5d Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 19 Sep 2018 16:44:26 -0500 Subject: [PATCH] Modified the NetworkService and the NetworkServiceManager classes in order to pass arguments to the service via Intent extras and to allow delayed network connection --- .../graphenej/api/android/NetworkService.java | 109 ++++++++------- .../api/android/NetworkServiceManager.java | 126 +++++++++++++++++- .../agorise/graphenej/network/FullNode.java | 13 ++ .../graphenej/network/NodeProvider.java | 2 +- .../cy/agorise/labs/sample/CallsActivity.java | 6 +- .../labs/sample/ConnectedActivity.java | 3 +- .../labs/sample/SampleApplication.java | 30 ++--- 7 files changed, 214 insertions(+), 75 deletions(-) diff --git a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java index eb9c12e..db71247 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java @@ -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 mApiIds = new HashMap(); - private ArrayList 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 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 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 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 mNodeUrls) { - this.mNodeUrls = mNodeUrls; + /** + * Returns a list of {@link FullNode} instances + * @return List of full nodes + */ + List getSortedNodes(){ + return nodeProvider.getSortedNodes(); } } diff --git a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkServiceManager.java b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkServiceManager.java index 2f31c0d..c50d777 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkServiceManager.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkServiceManager.java @@ -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 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 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 getCustomNodeUrls() { + return mCustomNodeUrls; + } + + public void setCustomNodeUrls(List 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 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 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; + } + } } diff --git a/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java b/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java index 71beea3..4d46173 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java @@ -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(); + } } diff --git a/graphenej/src/main/java/cy/agorise/graphenej/network/NodeProvider.java b/graphenej/src/main/java/cy/agorise/graphenej/network/NodeProvider.java index 1a380a5..6f3821e 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/network/NodeProvider.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/network/NodeProvider.java @@ -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(); diff --git a/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java b/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java index 4db3d3e..7e72152 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java @@ -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()); } diff --git a/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java b/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java index b9e6389..eea9aff 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java @@ -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{ diff --git a/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java b/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java index ba6a0d1..b90c735 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java +++ b/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java @@ -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); } }