/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade; import android.content.Context; import android.os.Handler; import android.os.StrictMode; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.annotation.VisibleForTesting; import android.util.Log; import com.reactivecascade.functional.ImmutableValue; import com.reactivecascade.i.CallOrigin; import com.reactivecascade.i.IAltFuture; import com.reactivecascade.i.IThreadType; import com.reactivecascade.i.NotCallOrigin; import com.reactivecascade.util.AssertUtil; import com.reactivecascade.util.DefaultThreadType; import com.reactivecascade.util.DoubleQueue; import com.reactivecascade.util.TypedThread; import com.reactivecascade.util.UIExecutorService; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * <code><pre> * public static IThreadType threadType; * .. onCreate .. * if (threadType == null) { * threadType = new ThreadTypeBuilder(this.getApplicationContext()).build(); * } * * </pre></code> * <p> * The singled threaded class loader ensures all ALog variables are set only once, * but injectable for configuration split testability from values previously set here for best performance * <p> * The first ALog created because the default ALog split is accessible from that point forward by references * to {@link com.reactivecascade.Async#WORKER} */ @CallOrigin public class AsyncBuilder { private static final String TAG = AsyncBuilder.class.getSimpleName(); static final String NOT_INITIALIZED = "Please init with new AsyncBuilder(this).build() in for example Activity.onCreate() _before_ the classloader touches Async.class"; public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); public static final int NUMBER_OF_CONCURRENT_NET_READS = 4; //TODO Dynamic pool size adjustment based on current and changing connection type private static final AtomicInteger threadUid = new AtomicInteger(); // All threads created on all AsyncBuilders are assigned unique, consecutive numbers @Nullable private static volatile AsyncBuilder instance = null; // Set only from the UI thread, but may be accessed from other threads @Nullable static AsyncBuilder getInstance() { return AsyncBuilder.instance; } /** * Used to reset static state for unit testing */ @VisibleForTesting static void resetInstance() { AsyncBuilder.instance = null; } static Thread serialWorkerThread; @NonNull public final Context context; private final AtomicBoolean workerPoolIncludesSerialWorkerThread = new AtomicBoolean(false); public Thread uiThread; public ExecutorService uiExecutorService; private boolean useForkedState = BuildConfig.DEBUG; private boolean runtimeAssertionsEnabled = BuildConfig.DEBUG; private boolean strictModeEnabled = BuildConfig.DEBUG; private boolean failFast = BuildConfig.DEBUG; private boolean showErrorStackTraces = BuildConfig.DEBUG; private IThreadType workerThreadType; private IThreadType serialWorkerThreadType; private IThreadType uiThreadType; private IThreadType netReadThreadType; private IThreadType netWriteThreadType; private IThreadType fileThreadType; private BlockingQueue<Runnable> workerQueue; private BlockingQueue<Runnable> serialWorkerQueue; private BlockingQueue<Runnable> fileQueue; private BlockingQueue<Runnable> netReadQueue; private BlockingQueue<Runnable> netWriteQueue; private ExecutorService workerExecutorService; private ExecutorService serialWorkerExecutorService; private ExecutorService fileExecutorService; private ExecutorService netReadExecutorService; private ExecutorService netWriteExecutorService; /** * Create a new <code>AsyncBuilder</code> that will run as long as the specified * {@link android.content.Context} will run. * <p> * Unless you have reason to do otherwise, you probably want to pass * {@link android.app.Activity#getApplicationContext()} to ensure that the asynchronous * actions last past the end of the current {@link android.app.Activity}. If you do so, * you can for effeciency only create the one instance for the entire application lifecycle * by for example setting a static variable the first time the <code>AsyncBuilder</code> * is used. * * @param context application context */ @UiThread public AsyncBuilder(@NonNull Context context) { AssertUtil.assertNotNull(context, "Context can not be null"); Context c = context; try { c = context.getApplicationContext(); } catch (NullPointerException e) { Log.i(TAG, "Instrumentation test run detected: context is null"); } this.context = c; } @UiThread static boolean isInitialized() { return instance != null; } /** * Check if the library should behave as it this is a {@link BuildConfig#DEBUG} build * * @return mode */ @UiThread public boolean isRuntimeAssertionsEnabled() { return runtimeAssertionsEnabled; } /** * Set whether the library should treat this as a runtimeAssertionsEnabled build with regard to code flow of * runtimeAssertionsEnabled assist features * <p> * The default from is {@link BuildConfig#DEBUG} * * @param enabled mode */ @UiThread public AsyncBuilder setRuntimeAssertionsEnabled(boolean enabled) { Log.v(TAG, "setRuntimeAssertionsEnabled(" + enabled + ")"); this.runtimeAssertionsEnabled = enabled; return this; } /** * Check if the library should behave as it this is a {@link BuildConfig#DEBUG} build * * @return mode */ @UiThread public boolean isUseForkedState() { return useForkedState; } /** * Set whether the library should verify at time of {@link IAltFuture#fork()} that it has not * already been forked. This is useful for debugging, but requires in an additional state transition * so may impact performance. Force this to <code>true</code> to make your production builds perform * an additional test usually considered relevant only to testing. * <p> * The default from is {@link BuildConfig#DEBUG} * * @param enabled mode */ @UiThread public AsyncBuilder setUseForkedState(boolean enabled) { Log.v(TAG, "setUseForkedState(" + enabled + ")"); this.useForkedState = enabled; return this; } /** * Check if Android strict mode is enabled * * @return mode */ @UiThread public boolean isStrictMode() { return strictModeEnabled; } /** * Set whether strict mode is enabled * <p> * The default from is {@link BuildConfig#DEBUG} * * @param enabled mode */ @UiThread public AsyncBuilder setStrictMode(boolean enabled) { Log.v(TAG, "setStrictMode(" + enabled + ")"); this.strictModeEnabled = enabled; return this; } /** * Check if failFast mode (terminate the application on logic errors to assist in debugging) is * enabled. * * @return mode */ @UiThread public boolean isFailFast() { return failFast; } /** * Set this to false if you prefer the app to log exceptions during debugOrigin but continue running * <p> * The default from is {@link BuildConfig#DEBUG} * * @param failFast <code>true</code> to stop on first error for clear debugging * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setFailFast(boolean failFast) { Log.v(TAG, "setFailFast(" + failFast + ")"); this.failFast = failFast; return this; } @UiThread public boolean isShowErrorStackTraces() { return showErrorStackTraces; } /** * By default, error stack traces are shown in the debug output. When running system testing code which * intentionally throws errors, this may be better disabled. * * @param showErrorStackTraces <code>true</code> to show stack traces * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setShowErrorStackTraces(boolean showErrorStackTraces) { Log.v(TAG, "setShowErrorStackTraces(" + showErrorStackTraces + ")"); this.showErrorStackTraces = showErrorStackTraces; return this; } /** * Get the group of threads which execute CPU-bound tasks * * @return the threadType */ @NonNull @NotCallOrigin @VisibleForTesting @UiThread IThreadType getWorkerThreadType() { if (workerThreadType == null) { ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); setWorkerThreadType(new DefaultThreadType("WorkerThreadType", getWorkerExecutorService(threadTypeImmutableValue), getWorkerQueue() ) ); threadTypeImmutableValue.set(workerThreadType); } return workerThreadType; } /** * @param workerThreadType thread type for CPU-bound tasks * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setWorkerThreadType(@NonNull IThreadType workerThreadType) { Log.v(TAG, "setWorkerThreadType(" + workerThreadType + ")"); this.workerThreadType = workerThreadType; return this; } /** * @return the single-threaded thread type for CPU-bound tasks */ @NonNull @NotCallOrigin @VisibleForTesting @UiThread IThreadType getSerialWorkerThreadType() { if (serialWorkerThreadType == null) { ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); setSerialWorkerThreadType(new DefaultThreadType("SerialWorkerThreadType", getSerialWorkerExecutorService(threadTypeImmutableValue), getSerialWorkerQueue() ) ); threadTypeImmutableValue.set(serialWorkerThreadType); } return serialWorkerThreadType; } /** * @param serialWorkerThreadType the single-threaded thread type for CPU-bound tasks * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setSerialWorkerThreadType(@NonNull final IThreadType serialWorkerThreadType) { Log.v(TAG, "setSerialWorkerThreadType(" + serialWorkerThreadType + ")"); this.serialWorkerThreadType = serialWorkerThreadType; return this; } /** * @return a thread type wrapper for the system's UI thread */ @NonNull @VisibleForTesting @UiThread IThreadType getUiThreadType() { if (uiThreadType == null) { setUIThreadType(new DefaultThreadType("UIThreadType", getUiExecutorService(), null)); } return uiThreadType; } /** * @param uiThreadType thread type for UI activities * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setUIThreadType(@NonNull IThreadType uiThreadType) { Log.v(TAG, "setUIThreadType(" + uiThreadType + ")"); this.uiThreadType = uiThreadType; return this; } /** * @return thread type for UI activities */ @NonNull @VisibleForTesting @UiThread IThreadType getNetReadThreadType() { if (netReadThreadType == null) { final ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); setNetReadThreadType(new DefaultThreadType("NetReadThreadType", getNetReadExecutorService(threadTypeImmutableValue), getNetReadQueue() ) ); threadTypeImmutableValue.set(netReadThreadType); } return netReadThreadType; } /** * @param netReadThreadType thread type for reading (non-mutating state) from network servers * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetReadThreadType(@NonNull IThreadType netReadThreadType) { Log.v(TAG, "setNetReadThreadType(" + netReadThreadType + ")"); this.netReadThreadType = netReadThreadType; return this; } /** * @return thread type for writing (mutating state) to network servers */ @NonNull @VisibleForTesting @UiThread IThreadType getNetWriteThreadType() { if (netWriteThreadType == null) { final ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); setNetWriteThreadType(new DefaultThreadType("NetWriteThreadType", getNetWriteExecutorService(threadTypeImmutableValue), getNetWriteQueue() ) ); threadTypeImmutableValue.set(netWriteThreadType); } return netWriteThreadType; } /** * @param netWriteThreadType * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetWriteThreadType(@NonNull IThreadType netWriteThreadType) { Log.v(TAG, "setNetWriteThreadType(" + netWriteThreadType + ")"); this.netWriteThreadType = netWriteThreadType; return this; } // @NonNull// public MirrorService getFileService() { // if (fileService == null) { // Log.v(TAG, "Creating default file service"); // setFileService(new FileMirrorService("Default FileMirrorService", // "FileMirrorService", // false, // context, // Context.MODE_PRIVATE, // getFileThreadType())); // } // // return fileService; // } // @NonNull// public AsyncBuilder setFileService(@NonNull final MirrorService fileService) { // Log.v(TAG, "setFileService(" + fileService + ")"); // this.fileService = fileService; // return this; // } /** * @return */ @NonNull @VisibleForTesting @UiThread IThreadType getFileThreadType() { if (fileThreadType == null) { final ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); setFileThreadType(new DefaultThreadType("FileReadThreadType", getFileExecutorService(threadTypeImmutableValue), getFileQueue() ) ); threadTypeImmutableValue.set(fileThreadType); } return fileThreadType; } /** * @param fileThreadType * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setFileThreadType(@NonNull IThreadType fileThreadType) { Log.v(TAG, "setFileThreadType(" + fileThreadType + ")"); this.fileThreadType = fileThreadType; return this; } @NonNull @UiThread private Thread getWorkerThread(@NonNull final IThreadType threadType, @NonNull final Runnable runnable) { if (NUMBER_OF_CORES == 1 || workerPoolIncludesSerialWorkerThread.getAndSet(true)) { return new TypedThread(threadType, runnable, createThreadId("WorkerThread")); } return getSerialWorkerThread(threadType, runnable); } /** * @param threadTypeImmutableValue * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread ExecutorService getWorkerExecutorService(@NonNull ImmutableValue<IThreadType> threadTypeImmutableValue) { if (workerExecutorService == null) { Log.v(TAG, "Creating default worker executor service"); final BlockingQueue<Runnable> q = getWorkerQueue(); final int numberOfThreads = q instanceof BlockingDeque ? NUMBER_OF_CORES : 1; setWorkerExecutorService(new ThreadPoolExecutor( numberOfThreads, numberOfThreads, 1000, TimeUnit.MILLISECONDS, q, runnable -> getWorkerThread(threadTypeImmutableValue.get(), runnable) )); } return workerExecutorService; } private static String createThreadId(@NonNull String threadCategory) { return threadCategory + threadUid.getAndIncrement(); } @NonNull @UiThread private Thread getSerialWorkerThread(@NonNull IThreadType threadType, @NonNull Runnable runnable) { if (serialWorkerThread == null) { serialWorkerThread = new TypedThread(threadType, runnable, createThreadId("SerialWorkerThread")); } return serialWorkerThread; } @NonNull @VisibleForTesting @UiThread protected ExecutorService getSerialWorkerExecutorService(@NonNull ImmutableValue<IThreadType> threadTypeImmutableValue) { Log.v(TAG, "getSerialWorkerExecutorService()"); if (serialWorkerExecutorService == null) { Log.v(TAG, "Creating default serial worker executor service"); setSerialWorkerExecutorService(new ThreadPoolExecutor( 1, 1, 1000, TimeUnit.MILLISECONDS, getSerialWorkerQueue(), runnable -> getSerialWorkerThread(threadTypeImmutableValue.get(), runnable)) ); } return serialWorkerExecutorService; } /** * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread BlockingQueue<Runnable> getWorkerQueue() { Log.v(TAG, "getWorkerQueue()"); if (workerQueue == null) { Log.d(TAG, "Creating default worker queue"); setWorkerQueue(new LinkedBlockingDeque<>()); } return workerQueue; } /** * @param queue * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setWorkerQueue(@NonNull final BlockingQueue<Runnable> queue) { Log.v(TAG, "setWorkerQueue(" + queue + ")"); workerQueue = queue; return this; } /** * Call {@link #setWorkerQueue(java.util.concurrent.BlockingQueue)} before calling this method * if you wish to use something other than the default. * * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread BlockingQueue<Runnable> getSerialWorkerQueue() { Log.v(TAG, "getSerialWorkerQueue()"); if (serialWorkerQueue == null) { Log.d(TAG, "Creating default in-order worker queue"); setSerialWorkerQueue(new DoubleQueue<>(getWorkerQueue())); } return serialWorkerQueue; } /** * @param queue of CPU-bound tasks for strict in-order execution * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setSerialWorkerQueue(@NonNull final BlockingQueue<Runnable> queue) { Log.v(TAG, "setSerialWorkerQueue(" + queue + ")"); serialWorkerQueue = queue; return this; } /** * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread BlockingQueue<Runnable> getFileQueue() { Log.v(TAG, "getFileQueue()"); if (fileQueue == null) { Log.d(TAG, "Creating default file read queue"); setFileQueue(new LinkedBlockingDeque<>()); } return fileQueue; } /** * @param queue * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setFileQueue(@NonNull BlockingQueue<Runnable> queue) { Log.v(TAG, "setFileQueue(" + queue + ")"); this.fileQueue = queue; return this; } /** * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread BlockingQueue<Runnable> getNetReadQueue() { Log.v(TAG, "getNetReadQueue()"); if (netReadQueue == null) { Log.d(TAG, "Creating default net read queue"); setNetReadQueue(new LinkedBlockingDeque<>()); } return netReadQueue; } /** * @param queue * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetReadQueue(@NonNull BlockingQueue<Runnable> queue) { Log.v(TAG, "setNetReadQueue(" + queue + ")"); this.netReadQueue = queue; return this; } /** * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread BlockingQueue<Runnable> getNetWriteQueue() { Log.v(TAG, "getNetWriteQueue()"); if (netWriteQueue == null) { Log.d(TAG, "Creating default worker net write queue"); setNetWriteQueue(new LinkedBlockingDeque<>()); } return netWriteQueue; } /** * @param queue * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetWriteQueue(@NonNull BlockingQueue<Runnable> queue) { Log.v(TAG, "setNetWriteQueue(" + queue + ")"); this.netWriteQueue = queue; return this; } /** * @param threadTypeImmutableValue * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread ExecutorService getFileExecutorService(@NonNull final ImmutableValue<IThreadType> threadTypeImmutableValue) { Log.v(TAG, "getFileExecutorService()"); if (fileExecutorService == null) { Log.d(TAG, "Creating default file read executor service"); setFileExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, getFileQueue(), runnable -> new TypedThread(threadTypeImmutableValue.get(), runnable, createThreadId("FileThread"))) ); } return fileExecutorService; } /** * @param threadTypeImmutableValue * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread ExecutorService getNetReadExecutorService(@NonNull final ImmutableValue<IThreadType> threadTypeImmutableValue) { Log.v(TAG, "getNetReadExecutorService()"); if (netReadExecutorService == null) { Log.d(TAG, "Creating default net read executor service"); setNetReadExecutorService(new ThreadPoolExecutor(1, NUMBER_OF_CONCURRENT_NET_READS, 1000, TimeUnit.MILLISECONDS, getNetReadQueue(), runnable -> new TypedThread(threadTypeImmutableValue.get(), runnable, createThreadId("NetReadThread"))) ); } return netReadExecutorService; } /** * @param threadTypeImmutableValue * @return the builder, for chaining */ @NonNull @VisibleForTesting @UiThread ExecutorService getNetWriteExecutorService(@NonNull final ImmutableValue<IThreadType> threadTypeImmutableValue) { Log.v(TAG, "getNetWriteExecutorService()"); if (netWriteExecutorService == null) { Log.d(TAG, "Creating default net write executor service"); setNetWriteExecutorService(Executors.newSingleThreadExecutor( runnable -> new TypedThread(threadTypeImmutableValue.get(), runnable, createThreadId("NetWriteThread"))) ); } return netWriteExecutorService; } /** * @return the builder, for chaining */ //TODO All ExecutorService-s should be a new DelayedExecutorService which supports executeDelayed(millis) behavior @NonNull @VisibleForTesting @UiThread ExecutorService getUiExecutorService() { Log.v(TAG, "getUiExecutorService()"); AssertUtil.assertNotNull(context); if (uiExecutorService == null) { setUiExecutorService(new UIExecutorService(new Handler(context.getMainLooper()))); } return uiExecutorService; } /** * Note that if you override this, you may also want to override the associated default * {@link #setUiThread(Thread)} * * @param uiExecutorService * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setUiExecutorService(@NonNull ExecutorService uiExecutorService) { Log.v(TAG, "setUiExecutorService()"); this.uiExecutorService = uiExecutorService; return this; } /** * Note that if you override this, you may also want to override the associated * {@link #setWorkerThreadType(com.reactivecascade.i.IThreadType)} to for example match the <code>inOrderExecution</code> parameter * * @param executorService the service to be used for most callbacks split general purpose processing. The default implementation * is a threadpool sized based to match the number of CPU cores. Most operations on this executor * do not block for IO, those are relegated to specialized executors * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setWorkerExecutorService(@NonNull ExecutorService executorService) { Log.v(TAG, "setWorkerExecutorService(" + executorService + ")"); workerExecutorService = executorService; return this; } /** * @param executorService * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setSerialWorkerExecutorService(@NonNull ExecutorService executorService) { Log.v(TAG, "setSerialWorkerExecutorService(" + executorService + ")"); serialWorkerExecutorService = executorService; return this; } /** * Indicate that all {@link Async#WORKER} tasks are single threaded on {@link Async#SERIAL_WORKER}. * <p> * This may be useful to temporarily add to your builder for testing if you wish to test if issues * are facing are caused or not caused by background task concurrency. * * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder singleThreadedWorkerExecutorService() { Log.v(TAG, "singleThreadedWorkerExecutorService()"); final ImmutableValue<IThreadType> threadTypeImmutableValue = new ImmutableValue<>(); this.workerExecutorService = Executors.newSingleThreadScheduledExecutor( runnable -> new TypedThread(threadTypeImmutableValue.get(), runnable, createThreadId("SingleThreadedWorker")) ); threadTypeImmutableValue.set(getWorkerThreadType()); return this; } /** * @param fileExecutorService to be used for file reading and writing * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setFileExecutorService(@NonNull ExecutorService fileExecutorService) { Log.v(TAG, "setFileExecutorService(" + fileExecutorService + ")"); this.fileExecutorService = fileExecutorService; return this; } /** * @param netReadExecutorService * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetReadExecutorService(@NonNull ExecutorService netReadExecutorService) { Log.v(TAG, "setNetReadExecutorService(" + netReadExecutorService + ")"); this.netReadExecutorService = netReadExecutorService; return this; } /** * Retrieve or create the thread group that will hand net writes. * * @param netWriteExecutorService * @return the builder, for chaining */ @NonNull @UiThread public AsyncBuilder setNetWriteExecutorService(@NonNull ExecutorService netWriteExecutorService) { Log.v(TAG, "setNetWriteExecutorService(" + netWriteExecutorService + ")"); this.netWriteExecutorService = netWriteExecutorService; return this; } /** * Change the thread marked as the thread from which user interface calls are made * <p> * This is just a marker useful for application logic. It does not modify the UI implementation * in any way. * * @param uiThread * @return */ @NonNull @UiThread public AsyncBuilder setUiThread(@NonNull Thread uiThread) { Log.v(TAG, "setUiThread(" + uiThread + ")"); uiThread.setName("UIThread"); this.uiThread = uiThread; return this; } // public AsyncBuilder setSignalVisualizerUri(URI uri, List<BasicNameValuePair> extraHeaders) { // signalVisualizerClient = new SignalVisualizerClient.Builder() // .setUri(uri) // .setExtraHeaders(extraHeaders) // .build(); // if (signalVisualizerClient == null) { // Log.v(TAG, "signalVisualizerClient set"); // } else { // Log.v(TAG, "No signalVisualizerClient"); // } // // return this; // } // // public SignalVisualizerClient getSignalVisualizerClient() { // return signalVisualizerClient; // } /** * Complete construction of the {@link AsyncBuilder}. * <p> * The first (and usually only unless doing testing) call to this method locks in the implementation * configurion defaults of the {@link Async} utility class and all the services it provides. * * @return the newly created Async class. This from can be ignored if you prefer to <code>static import com.futurice.Async.*</code> * for convenience. */ @NonNull @NotCallOrigin @UiThread public Async build() { AssertUtil.assertNotNull(context); if (uiThread == null) { Thread thread; try { thread = context.getMainLooper().getThread(); } catch (NullPointerException e) { Log.i(TAG, "UI thread is not the default system UI thread"); thread = Thread.currentThread(); } setUiThread(thread); } if (strictModeEnabled) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); } Async async = new Async(context.getApplicationContext()); instance = this; Log.v(TAG, "AsyncBuilder complete"); return async; //TODO Pass the builder as an argument to the constructor } }