package fr.prcaen.externalresources; import android.content.Context; import android.net.NetworkInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import fr.prcaen.externalresources.exception.ExternalResourceException; import fr.prcaen.externalresources.model.Resources; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static fr.prcaen.externalresources.ResourcesRunnable.RETRY_COUNT; public final class Dispatcher { public static final int REQUEST_DONE = 5; public static final int REQUEST_FAILED = 6; private static final int REQUEST_LAUNCH = 1; private static final int REQUEST_RETRY = 2; private static final int NETWORK_STATE_CHANGE = 3; private static final int AIRPLANE_MODE_CHANGE = 4; private static final int RETRY_DELAY = 1000; private final Context context; private final ExecutorService service; private final DispatcherThread dispatcherThread; private final NetworkBroadcastReceiver networkBroadcastReceiver; private final Handler handler; private final Handler mainHandler; @Nullable private NetworkInfo networkInfo; private boolean airPlaneMode; private ResourcesRunnable resourcesRunnable; private boolean needReplay = false; public Dispatcher(@NonNull Context context, @NonNull Downloader downloader, @NonNull Handler mainHandler, @Cache.Policy int cachePolicy) { this.context = context; this.service = Executors.newSingleThreadExecutor(); this.dispatcherThread = new DispatcherThread(); this.dispatcherThread.start(); this.mainHandler = mainHandler; this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this); this.networkBroadcastReceiver = new NetworkBroadcastReceiver(context, this); this.networkBroadcastReceiver.register(); this.networkInfo = Utils.getActiveNetworkInfo(context); this.airPlaneMode = Utils.isAirplaneModeOn(context); this.resourcesRunnable = new ResourcesRunnable(downloader, this, cachePolicy); } public void stop() { service.shutdown(); dispatcherThread.quit(); networkBroadcastReceiver.unregister(); } public void dispatchLaunch() { Logger.v(ExternalResources.TAG, "dispatch launch"); handler.sendMessage(handler.obtainMessage(REQUEST_LAUNCH)); } public void dispatchRetry() { Logger.v(ExternalResources.TAG, "dispatch retry"); handler.sendMessageDelayed(handler.obtainMessage(REQUEST_RETRY), RETRY_DELAY); } public void dispatchFailed(ExternalResourceException e) { Logger.v(ExternalResources.TAG, "dispatch failed"); mainHandler.sendMessage(mainHandler.obtainMessage(REQUEST_FAILED, e)); } public void dispatchDone(Resources resources) { Logger.v(ExternalResources.TAG, "dispatch launch"); mainHandler.sendMessage(mainHandler.obtainMessage(REQUEST_DONE, resources)); } public void dispatchAirplaneModeChange(boolean airPlaneMode) { handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE, airPlaneMode)); } public void dispatchNetworkStateChange(@NonNull NetworkInfo networkInfo) { handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, networkInfo)); } private void performLaunch() { boolean canRetryConnectivity = null == networkInfo || networkInfo.isConnected(); if (!airPlaneMode && canRetryConnectivity) { Logger.v(ExternalResources.TAG, "perform launch"); service.submit(resourcesRunnable); } else { Logger.v(ExternalResources.TAG, "wait until connectivity"); this.needReplay = true; } } private void performRetry() { boolean canRetryConnectivity = null == networkInfo || networkInfo.isConnected(); if (resourcesRunnable.canRetry() && !airPlaneMode && canRetryConnectivity) { Logger.v(ExternalResources.TAG, "perform retry"); resourcesRunnable.decreaseRetryCount(); service.submit(resourcesRunnable); } else if (resourcesRunnable.canRetry()) { Logger.v(ExternalResources.TAG, "wait until connectivity"); this.needReplay = true; } else { dispatchFailed(new ExternalResourceException("Perform retry failed after " + RETRY_COUNT)); } } private void performAirPlaneModeChange(boolean airPlaneMode) { this.airPlaneMode = airPlaneMode; if (!Utils.hasNetworkStatePermission(context)) { performReplay(); } } private void performNetworkStateChange(NetworkInfo networkInfo) { this.networkInfo = networkInfo; performReplay(); } private void performReplay() { if (!needReplay || airPlaneMode || (null != networkInfo && !networkInfo.isConnected())) { return; } Logger.v(ExternalResources.TAG, "perform replay"); performLaunch(); } private static class DispatcherHandler extends Handler { private final Dispatcher dispatcher; public DispatcherHandler(Looper looper, Dispatcher dispatcher) { super(looper); this.dispatcher = dispatcher; } @Override public void handleMessage(Message message) { switch (message.what) { case REQUEST_LAUNCH: dispatcher.performLaunch(); break; case REQUEST_RETRY: dispatcher.performRetry(); break; case NETWORK_STATE_CHANGE: dispatcher.performNetworkStateChange((NetworkInfo) message.obj); break; case AIRPLANE_MODE_CHANGE: dispatcher.performAirPlaneModeChange((Boolean) message.obj); break; default: Logger.e(ExternalResources.TAG, "Unknown message: " + message.what); break; } } } private static class DispatcherThread extends HandlerThread { private static final String THREAD_NAME = "dispatcher-external-resources"; DispatcherThread() { super(THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND); } } }