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 32e6918..f51a448 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 @@ -1,13 +1,7 @@ package cy.agorise.graphenej.api.android; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.Looper; -import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -62,10 +56,8 @@ 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 cy.agorise.graphenej.stats.ExponentialMovingAverage; 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; @@ -75,12 +67,10 @@ import okhttp3.WebSocket; import okhttp3.WebSocketListener; /** - * Service in charge of maintaining a connection to the full node. + * Class in charge of maintaining a connection to the full node. */ -public class NetworkService extends Service { - private final String TAG = this.getClass().getName(); - +public class NetworkService { public static final int NORMAL_CLOSURE_STATUS = 1000; private static final int GOING_AWAY_STATUS = 1001; @@ -92,72 +82,6 @@ public class NetworkService extends Service { // which we can choose from. private final int DEFAULT_INITIAL_DELAY = 500; - /** - * Constant to be used as a key in order to pass the user name information, in case the - * provided API nodes might require this information. - */ - public static final String KEY_USERNAME = "key_username"; - - /** - * Constant to be used as a key in order to pass the password information, in case the - * provided API nodes might require this information. - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_PASSWORD = "key_password"; - - /** - * Constant used as a key in order to specify which APIs the application will be requiring. - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_REQUESTED_APIS = "key_requested_apis"; - - /** - * Constant used as a key in order to let the NetworkService know whether or not it should - * start a recurring node latency verification task. - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier"; - - /** - * Constant used as a key in order to specify the alpha (or smoothing) factor to be used in - * the exponential moving average calculated from the different latency samples. This only - * makes sense if the latency verification feature is enabled of course. - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_NODE_LATENCY_SMOOTHING_FACTOR = "key_node_latency_smoothing_factor"; - - /** - * Key used to pass via intent a boolean extra to specify whether the connection should - * be automatically established. - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_AUTO_CONNECT = "key_auto_connect"; - - /** - * Key used to pass via intent a list of node URLs. The value passed should be a String - * containing a simple comma separated list of URLs. - *

- * For example: - * - * wss://domain1.com/ws,wss://domain2.com/ws,wss://domain3.com/ws - *

- * This information should be passed as an intent extra when calling the bindService - * or startService methods. - */ - public static final String KEY_NODE_URLS = "key_node_urls"; - - private final IBinder mBinder = new LocalBinder(); - private WebSocket mWebSocket; // Username and password used to connect to a specific node @@ -170,7 +94,7 @@ public class NetworkService extends Service { private long mCurrentId = 0; // Requested APIs passed to this service - private int mRequestedApis; + private int mRequestedApis = ApiAccess.API_DATABASE | ApiAccess.API_HISTORY | ApiAccess.API_NETWORK_BROADCAST; // Variable used to keep track of the currently obtained API accesses private HashMap mApiIds = new HashMap(); @@ -216,6 +140,31 @@ public class NetworkService extends Service { // suited for every response type. private DeserializationMap mDeserializationMap = new DeserializationMap(); + /** + * Singleton reference + */ + private static NetworkService instance; + + /** + * Private constructor + */ + private NetworkService(){} + + /** + * Thread-safe singleton getter. + * @return A NetworkService instance. + */ + public static NetworkService getInstance(){ + if(instance == null) { + synchronized (NetworkService.class) { + if(instance == null) { + instance = new NetworkService(); + } + } + } + return instance; + } + /** * Actually establishes a connection from this Service to one of the full nodes. */ @@ -230,11 +179,11 @@ public class NetworkService extends Service { synchronized (mWebSocketListener){ mSelectedNode = nodeProvider.getBestNode(); if(mSelectedNode != null){ - Log.d(TAG,"Trying to connect to: "+ mSelectedNode.getUrl()); + System.out.println("Trying to connect to: "+ mSelectedNode.getUrl()); Request request = new Request.Builder().url(mSelectedNode.getUrl()).build(); mWebSocket = client.newWebSocket(request, mWebSocketListener); }else{ - Log.d(TAG,"Could not find best node, reescheduling"); + System.out.println("Could not find best node, reescheduling"); // If no node could be found yet, schedule a new attempt in DEFAULT_INITIAL_DELAY ms mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY); } @@ -244,7 +193,7 @@ public class NetworkService extends Service { public long sendMessage(String message){ if(mWebSocket != null){ if(mWebSocket.send(message)){ - Log.v(TAG,"-> " + message); + System.out.println("-> " + message); return mCurrentId; } }else{ @@ -270,7 +219,7 @@ public class NetworkService extends Service { ApiCall call = apiCallable.toApiCall(apiId, ++mCurrentId); mRequestClassMap.put(mCurrentId, apiCallable.getClass()); if(mWebSocket != null && mWebSocket.send(call.toJsonString())){ - Log.v(TAG,"-> "+call.toJsonString()); + System.out.println("-> "+call.toJsonString()); return mCurrentId; } } @@ -285,8 +234,10 @@ public class NetworkService extends Service { return mWebSocket != null && isLoggedIn; } - @Override - public void onDestroy() { + /** + * Stops the service by closing the connection and stopping the latency verifier. + */ + public void stop() { if(mWebSocket != null) mWebSocket.close(NORMAL_CLOSURE_STATUS, null); @@ -294,63 +245,32 @@ public class NetworkService extends Service { nodeLatencyVerifier.stop(); } - @Nullable - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - /** - * Initialize information and try to connect to a node accordingly. This methods were moved - * from onBind to avoid crashes due to components other than {@link NetworkServiceManager} - * binding to the service without submitting the proper information. - * - * @param extras Bundle that contains all required information for a proper initialization + * Starts the connection */ - public void bootstrapService(Bundle extras) { + public void start(String[] urls, double alpha) { // Retrieving credentials and requested API data from the shared preferences - mUsername = extras.getString(NetworkService.KEY_USERNAME, ""); - mPassword = extras.getString(NetworkService.KEY_PASSWORD, ""); - mRequestedApis = extras.getInt(NetworkService.KEY_REQUESTED_APIS, 0); - boolean mAutoConnect = extras.getBoolean(NetworkService.KEY_AUTO_CONNECT, true); - boolean verifyNodeLatency = extras.getBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, false); + mUsername = ""; + mPassword = ""; - // If the user of the library desires, a custom list of node URLs can - // be passed using the KEY_NODE_URLS constant - String nodeURLStr = extras.getString(NetworkService.KEY_NODE_URLS, ""); - if(nodeURLStr.equals("")){ - throw new MissingResourceException("A comma-separated list of node URLs must be provided as an intent extra", String.class.getName(), NetworkService.KEY_NODE_URLS); + if(urls == null || urls.length == 0){ + throw new MissingResourceException("Expecting at least a node URL to be provided", String.class.getName(), "urls"); } - // Adding user-provided list of node URLs - String[] urls = nodeURLStr.split(","); - // Feeding all node information to the NodeProvider instance for(String nodeUrl : urls){ nodeProvider.addNode(new FullNode(nodeUrl)); } - if (!mAutoConnect && !verifyNodeLatency) { - throw new IllegalArgumentException("NetworkService$bootstrapService: verifyNodeLatency cannot be false when autoConnect is false too."); + ArrayList fullNodes = new ArrayList<>(); + for(String url : urls){ + fullNodes.add(new FullNode(url, alpha)); } + nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes); + fullNodePublishSubject = nodeLatencyVerifier.start(); + fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver); - if (verifyNodeLatency) { - double alpha = extras.getDouble(KEY_NODE_LATENCY_SMOOTHING_FACTOR, ExponentialMovingAverage.DEFAULT_ALPHA); - ArrayList fullNodes = new ArrayList<>(); - for(String url : urls){ - fullNodes.add(new FullNode(url, alpha)); - } - nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes); - fullNodePublishSubject = nodeLatencyVerifier.start(); - fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver); - } - - if (mAutoConnect) - connect(); - else - mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY); - - // TODO make sure (verifyNodeLatency==false && mAutoConnect==true) is a valid/useful combination, else simplify and use only one of those arguments + mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY); } /** @@ -374,7 +294,7 @@ public class NetworkService extends Service { public void run() { FullNode fullNode = nodeProvider.getBestNode(); if(fullNode != null){ - Log.i(TAG, String.format("Connected with %d latency results", latencyUpdateCounter)); + System.out.println( String.format("Connected with %d latency results", latencyUpdateCounter)); connect(); }else{ mHandler.postDelayed(this, DEFAULT_INITIAL_DELAY); @@ -398,24 +318,13 @@ public class NetworkService extends Service { @Override public void onError(Throwable e) { - Log.e(TAG,"nodeLatencyObserver.onError.Msg: "+e.getMessage()); + System.out.println("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. - */ - public class LocalBinder extends Binder { - public NetworkService getService() { - // Return this instance of LocalService so clients can call public methods - return NetworkService.this; - } - } - private WebSocketListener mWebSocketListener = new WebSocketListener() { @Override @@ -446,7 +355,7 @@ public class NetworkService extends Service { @Override public synchronized void onMessage(WebSocket webSocket, String text) { super.onMessage(webSocket, text); - Log.v(TAG,"<- "+text); + System.out.println("<- "+text); JsonRpcNotification notification = gson.fromJson(text, JsonRpcNotification.class); if(notification.method != null){ @@ -514,7 +423,7 @@ public class NetworkService extends Service { } if(response.error != null && response.error.message != null){ // We could not make sense of this incoming message, just log a warning - Log.w(TAG,"Error.Msg: "+response.error.message); + System.out.println("Error.Msg: "+response.error.message); } // Properly de-serialize all other fields and broadcasts to the event bus handleJsonRpcResponse(response, text); @@ -597,10 +506,10 @@ public class NetworkService extends Service { Type GetAssetsResponse = new TypeToken>>(){}.getType(); parsedResponse = gson.fromJson(text, GetAssetsResponse); }else { - Log.w(TAG,"Unknown request class"); + System.out.println("Unknown request class"); } }else{ - Log.w(TAG,"Unhandled situation"); + System.out.println("Unhandled situation"); } } @@ -680,15 +589,15 @@ public class NetworkService extends Service { @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); - Log.e(TAG,"onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage()); + System.out.println("onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage()); // Logging error stack trace for(StackTraceElement element : t.getStackTrace()){ - Log.v(TAG,String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber())); + System.out.println(String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber())); } // If there is a response, we print it if(response != null){ - Log.e(TAG,"Response: "+response.message()); + System.out.println("Response: "+response.message()); } handleWebSocketDisconnection(true, true); @@ -702,7 +611,7 @@ public class NetworkService extends Service { * @param penalizeNode Whether or not to penalize the current node with a very high latency reading. */ private synchronized void handleWebSocketDisconnection(boolean tryReconnection, boolean penalizeNode) { - Log.d(TAG,"handleWebSocketDisconnection. try reconnection: " + tryReconnection + ", penalizeNode: " + penalizeNode); + System.out.println("handleWebSocketDisconnection. try reconnection: " + tryReconnection + ", penalizeNode: " + penalizeNode); RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE)); isLoggedIn = false; @@ -733,8 +642,7 @@ public class NetworkService extends Service { RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE)); if (nodeProvider.getBestNode() == null) { - Log.e(TAG, "Giving up on connections"); - stopSelf(); + System.out.println( "Giving up on connections"); } else { mHandler.postDelayed(new Runnable() { @Override 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 deleted file mode 100644 index f3b65be..0000000 --- a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkServiceManager.java +++ /dev/null @@ -1,325 +0,0 @@ -package cy.agorise.graphenej.api.android; - -import android.app.Activity; -import android.app.Application; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import cy.agorise.graphenej.stats.ExponentialMovingAverage; - -/** - * This class should be instantiated at the application level of the android app. - * - * It will monitor the interaction between the different activities of an app and help us decide - * when the connection to the full node should be interrupted. - */ - -public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks { - private final String TAG = this.getClass().getName(); - /** - * Constant used to specify how long will the app wait for another activity to go through its starting life - * cycle events before running the teardownConnectionTask task. - * - * This is used as a means to detect whether or not the user has left the app. - */ - private static final int DISCONNECT_DELAY = 1500; - - /** - * Handler instance used to schedule tasks back to the main thread - */ - private Handler mHandler = new Handler(); - - /** - * Weak reference to the application context - */ - private WeakReference mContextReference; - - // In case we want to interact directly with the service - private NetworkService mService; - - // Attributes that might need to be passed to the NetworkService - private String mUserName = ""; - private String mPassword = ""; - private int mRequestedApis; - private List mCustomNodeUrls = new ArrayList<>(); - private boolean mAutoConnect; - private boolean mVerifyLatency; - // Flag used to make sure we only call 'bindService' once. -// private boolean mStartingService; - - /** - * Runnable used to schedule a service disconnection once the app is not visible to the user for - * more than DISCONNECT_DELAY milliseconds. - */ - private final Runnable mDisconnectRunnable = new Runnable() { - @Override - public void run() { - Context context = mContextReference.get(); - if(mService != null){ - context.unbindService(mServiceConnection); - mService = null; - } - context.stopService(new Intent(context, NetworkService.class)); - } - }; - - private NetworkServiceManager(Context context){ - mContextReference = new WeakReference<>(context); - } - - @Override - public void onActivityCreated(Activity activity, Bundle bundle) { } - - @Override - public void onActivityStarted(Activity activity) { } - - @Override - public void onActivityResumed(Activity activity) { - mHandler.removeCallbacks(mDisconnectRunnable); - if(mService == null){ - // Creating a new Intent that will be used to start the NetworkService - Context context = mContextReference.get(); - Intent intent = new Intent(context, NetworkService.class); - context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); - } - } - - /** - * This method passes all the required information to the NetworkService to properly - * initialize itself - */ - private void passRequiredInfoToConfigureService() { - // Adding user-provided node URLs - StringBuilder stringBuilder = new StringBuilder(); - Iterator it = mCustomNodeUrls.iterator(); - while(it.hasNext()){ - stringBuilder.append(it.next()); - if(it.hasNext()) stringBuilder.append(","); - } - String customNodes = stringBuilder.toString(); - - Bundle b = new Bundle(); - - // Adding all - b.putString(NetworkService.KEY_USERNAME, mUserName); - b.putString(NetworkService.KEY_PASSWORD, mPassword); - b.putInt(NetworkService.KEY_REQUESTED_APIS, mRequestedApis); - b.putString(NetworkService.KEY_NODE_URLS, customNodes); - b.putBoolean(NetworkService.KEY_AUTO_CONNECT, mAutoConnect); - b.putBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, mVerifyLatency); - - mService.bootstrapService(b); - } - - @Override - public void onActivityPaused(Activity activity) { - mHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY); - } - - @Override - public void onActivityStopped(Activity activity) {} - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {} - - @Override - public void onActivityDestroyed(Activity activity) {} - - /** Defines callbacks for backend binding, passed to bindService() */ - private ServiceConnection mServiceConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, - IBinder service) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service; - boolean passInfo = false; - if(mService == null){ - mService = binder.getService(); - // We only pass the required information in case this is the first time we get a reference - // to the NetworkService instance. - passInfo = true; - } - if(passInfo) - passRequiredInfoToConfigureService(); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - } - }; - - public String getUserName() { - return mUserName; - } - - public void setUserName(String userName) { - this.mUserName = userName; - } - - public String getPassword() { - return mPassword; - } - - public void setPassword(String mPassword) { - this.mPassword = mPassword; - } - - public int getRequestedApis() { - return mRequestedApis; - } - - public void setRequestedApis(int mRequestedApis) { - this.mRequestedApis = mRequestedApis; - } - - public List getCustomNodeUrls() { - return mCustomNodeUrls; - } - - public void setCustomNodeUrls(List 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 customNodeUrls; - private boolean autoconnect = true; - private boolean verifyNodeLatency; - private double alpha = ExponentialMovingAverage.DEFAULT_ALPHA; - - /** - * Sets the user name, if required to connect to a node. - * @param name User name - * @return The Builder instance - */ - public Builder setUserName(String name){ - this.username = name; - return this; - } - - /** - * Sets the password, if required to connect to a node. - * @param password Password - * @return The Builder instance - */ - public Builder setPassword(String password){ - this.password = password; - return this; - } - - /** - * Sets an integer with the requested APIs encoded as binary flags. - * @param apis Integer representing the different APIs we require from the node. - * @return The Builder instance - */ - public Builder setRequestedApis(int apis){ - this.requestedApis = apis; - return this; - } - - /** - * Adds a list of custom node URLs. - * @param nodeUrls List of custom full node URLs. - * @return The Builder instance - */ - public Builder setCustomNodeUrls(List nodeUrls){ - this.customNodeUrls = nodeUrls; - return this; - } - - /** - * Adds a list of custom node URLs. - * @param nodeUrls List of custom full node URLs. - * @return The Builder instance - */ - public Builder setCustomNodeUrls(String nodeUrls){ - String[] urls = nodeUrls.split(","); - for(String url : urls){ - if(customNodeUrls == null) customNodeUrls = new ArrayList<>(); - customNodeUrls.add(url); - } - return this; - } - - /** - * Sets the autoconnect flag. This is true by default. - * @param autoConnect True if we want the service to connect automatically, false otherwise. - * @return The Builder instance - */ - public Builder setAutoConnect(boolean autoConnect){ - this.autoconnect = autoConnect; - return this; - } - - /** - * Sets the node-verification flag. This is false by default. - * @param verifyLatency True if we want the service to perform a latency analysis before connecting. - * @return The Builder instance. - */ - public Builder setNodeLatencyVerification(boolean verifyLatency){ - this.verifyNodeLatency = verifyLatency; - return this; - } - - /** - * Sets the node latency verification's exponential moving average alpha parameter. - * @param alpha The alpha parameter to use when computing the exponential moving average of the - * measured latencies. - * @return The Builder instance. - */ - public Builder setLatencyAverageAlpha(double alpha){ - this.alpha = alpha; - return this; - } - - /** - * Method used to build a {@link NetworkServiceManager} instance with all of the characteristics - * passed as parameters. - * @param context A Context of the application package implementing - * this class. - * @return Instance of the NetworkServiceManager class. - */ - public NetworkServiceManager build(Context context){ - NetworkServiceManager manager = new NetworkServiceManager(context); - if(username != null) manager.setUserName(username); else manager.setUserName(""); - if(password != null) manager.setPassword(password); else manager.setPassword(""); - if(customNodeUrls != null) manager.setCustomNodeUrls(customNodeUrls); - manager.setRequestedApis(requestedApis); - manager.setAutoConnect(autoconnect); - manager.setVerifyLatency(verifyNodeLatency); - return manager; - } - } -} 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 805f850..723af72 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/ConnectedActivity.java @@ -1,62 +1,20 @@ package cy.agorise.labs.sample; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; +import android.os.Bundle; +import android.support.annotation.Nullable; 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 { +public abstract class ConnectedActivity extends AppCompatActivity { private final String TAG = this.getClass().getName(); /* Network service connection */ protected NetworkService mNetworkService; - /** - * Flag used to keep track of the NetworkService binding state - */ - private boolean mShouldUnbindNetwork; - - private ServiceConnection mNetworkServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, - IBinder service) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service; - mNetworkService = binder.getService(); - ConnectedActivity.this.onServiceConnected(className, service); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - ConnectedActivity.this.onServiceDisconnected(componentName); - } - }; - @Override - protected void onStart() { - super.onStart(); - Intent intent = new Intent(this, NetworkService.class); - // Binding to NetworkService - if(bindService(intent, mNetworkServiceConnection, Context.BIND_AUTO_CREATE)){ - mShouldUnbindNetwork = true; - }else{ - Log.e(TAG,"Binding to the network service failed."); - } - } - - @Override - protected void onPause() { - super.onPause(); - // Unbinding from network service - if(mShouldUnbindNetwork){ - unbindService(mNetworkServiceConnection); - mShouldUnbindNetwork = false; - } + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNetworkService = NetworkService.getInstance(); } } diff --git a/sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java b/sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java index d452898..95f8aec 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/HtlcActivity.java @@ -1,8 +1,6 @@ package cy.agorise.labs.sample; -import android.content.ComponentName; import android.os.Bundle; -import android.os.IBinder; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.Toolbar; @@ -239,10 +237,4 @@ public class HtlcActivity extends ConnectedActivity implements // Return a newly built transaction return new Transaction(privKey, blockData, operations); } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { } - - @Override - public void onServiceDisconnected(ComponentName componentName) { } } diff --git a/sample/src/main/java/cy/agorise/labs/sample/PerformCallActivity.java b/sample/src/main/java/cy/agorise/labs/sample/PerformCallActivity.java index 041b4e8..8f7cd4a 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/PerformCallActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/PerformCallActivity.java @@ -1,9 +1,7 @@ package cy.agorise.labs.sample; -import android.content.ComponentName; import android.content.res.Resources; import android.os.Bundle; -import android.os.IBinder; import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputLayout; import android.text.InputType; @@ -418,12 +416,14 @@ public class PerformCallActivity extends ConnectedActivity { } private void sendGetObjectsRequest(){ + Log.d(TAG,"sendGetObjectsRequest"); String objectId = param1.getText().toString(); if(objectId.matches("\\d\\.\\d{1,3}\\.\\d{1,10}")){ ArrayList array = new ArrayList<>(); array.add(objectId); GetObjects getObjects = new GetObjects(array); long id = mNetworkService.sendMessage(getObjects, GetObjects.REQUIRED_API); + Log.d(TAG,"sendGetObnjetcsRequest id: "+id); responseMap.put(id, mRPC); }else{ param1.setError(getResources().getString(R.string.error_input_id)); @@ -639,14 +639,4 @@ public class PerformCallActivity extends ConnectedActivity { if(!mDisposable.isDisposed()) mDisposable.dispose(); } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - // Called upon NetworkService connection - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - // Called upon NetworkService disconnection - } } diff --git a/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java b/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java index db7f861..499b4ca 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java @@ -1,17 +1,12 @@ package cy.agorise.labs.sample; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import android.os.IBinder; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; import android.support.v7.util.SortedList; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -40,7 +35,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.subjects.PublishSubject; -public class RemoveNodeActivity extends AppCompatActivity implements ServiceConnection { +public class RemoveNodeActivity extends ConnectedActivity { private final String TAG = this.getClass().getName(); @@ -66,18 +61,6 @@ public class RemoveNodeActivity extends AppCompatActivity implements ServiceConn rvNodes.setLayoutManager(new LinearLayoutManager(this)); nodesAdapter = new FullNodesAdapter(this, LATENCY_COMPARATOR); rvNodes.setAdapter(nodesAdapter); - } - - @OnClick(R.id.btnReconnectNode) - public void removeCurrentNode() { - mNetworkService.reconnectNode(); - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - NetworkService.LocalBinder binder = (NetworkService.LocalBinder) iBinder; - mNetworkService = binder.getService(); if(mNetworkService != null){ // PublishSubject used to announce full node latencies updates @@ -90,9 +73,9 @@ public class RemoveNodeActivity extends AppCompatActivity implements ServiceConn } } - @Override - public void onServiceDisconnected(ComponentName componentName) { - mNetworkService = null; + @OnClick(R.id.btnReconnectNode) + public void removeCurrentNode() { + mNetworkService.reconnectNode(); } /** @@ -119,20 +102,6 @@ public class RemoveNodeActivity extends AppCompatActivity implements ServiceConn public void onComplete() { } }; - @Override - protected void onStart() { - super.onStart(); - // Bind to LocalService - Intent intent = new Intent(this, NetworkService.class); - bindService(intent, this, Context.BIND_AUTO_CREATE); - } - - @Override - protected void onPause() { - super.onPause(); - unbindService(this); - } - class FullNodesAdapter extends RecyclerView.Adapter { class ViewHolder extends RecyclerView.ViewHolder { 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 4039e61..80b3d2b 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java +++ b/sample/src/main/java/cy/agorise/labs/sample/SampleApplication.java @@ -2,12 +2,7 @@ package cy.agorise.labs.sample; import android.app.Application; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import cy.agorise.graphenej.api.ApiAccess; -import cy.agorise.graphenej.api.android.NetworkServiceManager; +import cy.agorise.labs.sample.network.NetworkServiceManager; /** * Sample application class @@ -18,9 +13,6 @@ public class SampleApplication extends Application { @Override public void onCreate() { super.onCreate(); - // Specifying some important information regarding the connection, such as the - // credentials and the requested API accesses - int requestedApis = ApiAccess.API_DATABASE | ApiAccess.API_HISTORY | ApiAccess.API_NETWORK_BROADCAST; String[] nodeURLs = new String[]{ "wss://bitshares.openledger.info/ws", @@ -29,38 +21,11 @@ public class SampleApplication extends Application { "wss://citadel.li/node", "wss://api.bts.mobi/ws" }; - List nodeList = Arrays.asList(nodeURLs); - String nodes = join(nodeList, ","); - NetworkServiceManager networkManager = new NetworkServiceManager.Builder() - .setUserName("username") - .setPassword("secret") - .setRequestedApis(requestedApis) - .setCustomNodeUrls(nodes) - .setAutoConnect(true) - .setNodeLatencyVerification(true) - .setLatencyAverageAlpha(0.1f) - .build(this); + NetworkServiceManager networkManager = new NetworkServiceManager(nodeURLs); // 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); } - - /** - * Private method used to join a sequence of Strings given a iterable representation - * and a delimiter. - * - * - * @param s Any collection of CharSequence that implements the Iterable interface. - * @param delimiter The delimiter which will be used to join the different strings together. - * @return A single string combining all the iterable pieces with the delimiter. - */ - private String join(Iterable s, String delimiter) { - Iterator iter = s.iterator(); - if (!iter.hasNext()) return ""; - StringBuilder buffer = new StringBuilder(iter.next()); - while (iter.hasNext()) buffer.append(delimiter).append(iter.next()); - return buffer.toString(); - } } diff --git a/sample/src/main/java/cy/agorise/labs/sample/SubscriptionActivity.java b/sample/src/main/java/cy/agorise/labs/sample/SubscriptionActivity.java index a6b7daa..d3e5787 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/SubscriptionActivity.java +++ b/sample/src/main/java/cy/agorise/labs/sample/SubscriptionActivity.java @@ -1,12 +1,6 @@ package cy.agorise.labs.sample; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.os.Bundle; -import android.os.IBinder; -import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -14,7 +8,6 @@ import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import cy.agorise.graphenej.api.android.NetworkService; import cy.agorise.graphenej.api.android.RxBus; import cy.agorise.graphenej.api.calls.CancelAllSubscriptions; import cy.agorise.graphenej.api.calls.SetSubscribeCallback; @@ -23,16 +16,13 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -public class SubscriptionActivity extends AppCompatActivity { +public class SubscriptionActivity extends ConnectedActivity { private final String TAG = this.getClass().getName(); @BindView(R.id.text_field) TextView mTextField; - // In case we want to interact directly with the service - private NetworkService mService; - private Disposable mDisposable; // Notification counter @@ -63,51 +53,19 @@ public class SubscriptionActivity extends AppCompatActivity { }); } - @Override - protected void onStart() { - super.onStart(); - // Bind to LocalService - Intent intent = new Intent(this, NetworkService.class); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - } - - @Override - protected void onPause() { - super.onPause(); - unbindService(mConnection); - } - @Override protected void onDestroy() { super.onDestroy(); mDisposable.dispose(); } - /** Defines callbacks for backend binding, passed to bindService() */ - private ServiceConnection mConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, - IBinder service) { - Log.d(TAG,"onServiceConnected"); - // We've bound to LocalService, cast the IBinder and get LocalService instance - NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service; - mService = binder.getService(); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.d(TAG,"onServiceDisconnected"); - } - }; - @OnClick(R.id.subscribe) public void onTransferFeeUsdClicked(View v){ - mService.sendMessage(new SetSubscribeCallback(true), SetSubscribeCallback.REQUIRED_API); + mNetworkService.sendMessage(new SetSubscribeCallback(true), SetSubscribeCallback.REQUIRED_API); } @OnClick(R.id.unsubscribe) public void onTransferFeeBtsClicked(View v){ - mService.sendMessage(new CancelAllSubscriptions(), CancelAllSubscriptions.REQUIRED_API); + mNetworkService.sendMessage(new CancelAllSubscriptions(), CancelAllSubscriptions.REQUIRED_API); } } diff --git a/sample/src/main/java/cy/agorise/labs/sample/network/NetworkServiceManager.java b/sample/src/main/java/cy/agorise/labs/sample/network/NetworkServiceManager.java new file mode 100644 index 0000000..151037b --- /dev/null +++ b/sample/src/main/java/cy/agorise/labs/sample/network/NetworkServiceManager.java @@ -0,0 +1,76 @@ +package cy.agorise.labs.sample.network; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; + +import cy.agorise.graphenej.api.android.NetworkService; +import cy.agorise.graphenej.stats.ExponentialMovingAverage; + +public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks { + private final String TAG = this.getClass().getName(); + + /** + * Constant used to specify how long will the app wait for another activity to go through its starting life + * cycle events before running the teardownConnectionTask task. + * + * This is used as a means to detect whether or not the user has left the app. + */ + private static final int DISCONNECT_DELAY = 1500; + + /** + * Handler instance used to schedule tasks back to the main thread + */ + private Handler mHandler = new Handler(); + + private NetworkService mNetworkService; + + private String[] mNodeUrls; + + public NetworkServiceManager(String[] nodes){ + this.mNodeUrls = nodes; + } + + /** + * Runnable used to schedule a service disconnection once the app is not visible to the user for + * more than DISCONNECT_DELAY milliseconds. + */ + private final Runnable mDisconnectRunnable = new Runnable() { + @Override + public void run() { + mNetworkService.stop(); + mNetworkService = null; + } + }; + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { } + + @Override + public void onActivityStarted(Activity activity) { } + + @Override + public void onActivityResumed(Activity activity) { + mHandler.removeCallbacks(mDisconnectRunnable); + if(mNetworkService == null) { + mNetworkService = NetworkService.getInstance(); + mNetworkService.start(this.mNodeUrls, ExponentialMovingAverage.DEFAULT_ALPHA); + } + } + + @Override + public void onActivityPaused(Activity activity) { + mHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY); + } + + @Override + public void onActivityStopped(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {} + + @Override + public void onActivityDestroyed(Activity activity) {} +}