package com.hokolinks.utils.networking; import android.content.Context; import android.util.Log; import com.hokolinks.model.Device; import com.hokolinks.utils.Utils; import com.hokolinks.utils.lifecycle.ApplicationLifecycle; import com.hokolinks.utils.lifecycle.ApplicationLifecycleCallback; import com.hokolinks.utils.log.HokoLog; import com.hokolinks.utils.networking.async.HttpRequest; import com.hokolinks.utils.networking.async.HttpRequestCallback; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; /** * The Networking class is a wrapper around HokoHttpRequests in order to process them in a * serial queue, where requests can be retried in case of failure. The flushes are triggered by * a timer to avoid clogging the device's network. This class will also handle the persistence of * HokoHttpRequests in order to avoid losing any data on application closes/crashes and network * problems. It will also only try to flush in case the device recognizes it has internet * connectivity. */ public class Networking { // Filename to save the http tasks to storage private static final String HTTP_TASKS_FILENAME = "http_tasks"; // Configuration of the Networking private static final int FLUSH_TIMER_INTERVAL = 30000; // in millis private static final int HTTP_TASKS_NUMBER_OF_RETRIES = 1; // Static class to avoid duplication of Networking instances private static Networking sInstance; final private List<HttpRequest> mHttpTasks; private Context mContext; private Timer mTimer; /** * Private constructor, will try to load the tasks from file to resume them as soon as possible. * * @param context A context object. */ @SuppressWarnings("unchecked") private Networking(Context context) { mContext = context; List<HttpRequest> httpTasks; try { httpTasks = (List<HttpRequest>) Utils.loadFromFile(HTTP_TASKS_FILENAME, context); } catch (ClassCastException e) { httpTasks = new ArrayList<>(); } if (httpTasks == null) { httpTasks = new ArrayList<>(); } mHttpTasks = httpTasks; flush(); registerActivityLifecycleCallbacks(); } /** * This function will setup the Networking static instance, resuming pending http requests * and starting normal functionality. * * @param context A context object. */ public static void setupNetworking(Context context) { if (sInstance == null) { sInstance = new Networking(context); } } /** * Returns the static Networking instance. * * @return The static Networking instance. */ public static Networking getNetworking() { return sInstance; } public Context getContext() { return mContext; } //Tasks /** * Executes the pending http request tasks in case the device has internet connectivity, * otherwise it will just reset the timer. */ private void flush() { if (mHttpTasks.size() > 0 && Device.hasInternetConnectivity(mContext)) { stopFlushTimer(); executeTasks(); } else { startFlushTimer(); } } /** * Adds an http request to the queue, will only add it in case it has not surpassed the maximum * number of retries. * * @param httpRequest A HttpRequest object. */ public void addRequest(HttpRequest httpRequest) { if (httpRequest.getNumberOfRetries() < HTTP_TASKS_NUMBER_OF_RETRIES) { HokoLog.d("Adding request to queue"); synchronized (mHttpTasks) { mHttpTasks.add(httpRequest); } } saveTasks(); } private void removeRequest(HttpRequest httpRequest) { synchronized (mHttpTasks) { mHttpTasks.remove(httpRequest); } } /** * Executes the HttpRequests in a serial queue. Will only start the next request in case the * previous one has finished. This will handle incrementing the number of retries in case of * failure and re-adding them to the http request queue. */ private void executeTasks() { ExecutorService service = Executors.newFixedThreadPool(1); synchronized (mHttpTasks) { for (final HttpRequest httpRequest : mHttpTasks) { service.execute(httpRequest.toRunnable(new HttpRequestCallback() { @Override public void onSuccess(JSONObject jsonObject) { HokoLog.d("Success " + jsonObject.toString()); removeRequest(httpRequest); saveTasks(); } @Override public void onFailure(Exception e) { httpRequest.incrementNumberOfRetries(); removeRequest(httpRequest); addRequest(httpRequest); } })); } } service.execute(new Runnable() { @Override public void run() { startFlushTimer(); } }); } /** * Saves all the current http requests to file, guaranteeing persistence. */ private void saveTasks() { synchronized (mHttpTasks) { Utils.saveToFile(mHttpTasks, HTTP_TASKS_FILENAME, mContext); } } //Timer /** * Starts the flush timer in case it is stopped, will do nothing otherwise. */ private synchronized void startFlushTimer() { if (mTimer != null) { return; } mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { stopFlushTimer(); flush(); } }, FLUSH_TIMER_INTERVAL); } /** * Stops the flush timer. */ private synchronized void stopFlushTimer() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } } // Application Lifecycle /** * Registers activity lifecycle callbacks to know when the application is in background and * foreground, stopping the timer or flushing. */ private void registerActivityLifecycleCallbacks() { ApplicationLifecycle.registerApplicationLifecycleCallback(mContext, new ApplicationLifecycleCallback() { @Override public void onResume() { flush(); } @Override public void onPause() { stopFlushTimer(); } }); } }