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; } } }