1 package org.coolreader.crengine;
2 
3 import java.util.ArrayList;
4 import java.util.concurrent.Callable;
5 
6 import org.coolreader.crengine.ReaderView.Sync;
7 
8 import android.os.Build;
9 import android.os.Handler;
10 import android.os.Looper;
11 import android.os.Message;
12 import android.util.Log;
13 
14 /**
15  * Allows running tasks either in background thread or in GUI thread.
16  */
17 public class BackgroundThread extends Thread {
18 
19 	private final static Object LOCK = new Object();
20 
21 	private static BackgroundThread instance;
22 
23 	// singleton
instance()24 	public static BackgroundThread instance()
25 	{
26 		if ( instance==null ) {
27 			synchronized( LOCK ) {
28 				if ( instance==null ) {
29 					instance = new BackgroundThread();
30 					instance.start();
31 				}
32 			}
33 		}
34 		return instance;
35 	}
36 
getBackgroundHandler()37 	public static Handler getBackgroundHandler() {
38 		if (instance == null)
39 			return null;
40 		return instance.handler;
41 	}
42 
getGUIHandler()43 	public static Handler getGUIHandler() {
44 		if (instance().guiHandler == null)
45 			return null;
46 		return instance().guiHandler;
47 	}
48 
49 	public final static boolean CHECK_THREAD_CONTEXT = true;
50 
51 	/**
52 	 * Throws exception if not in background thread.
53 	 */
ensureBackground()54 	public final static void ensureBackground()
55 	{
56 		if ( CHECK_THREAD_CONTEXT && !isBackgroundThread() ) {
57 			L.e("not in background thread", new Exception("ensureInBackgroundThread() is failed"));
58 			throw new RuntimeException("ensureInBackgroundThread() is failed");
59 		}
60 	}
61 
62 	/**
63 	 * Throws exception if not in GUI thread.
64 	 */
ensureGUI()65 	public final static void ensureGUI()
66 	{
67 		if ( CHECK_THREAD_CONTEXT && isBackgroundThread() ) {
68 			L.e("not in GUI thread", new Exception("ensureGUI() is failed"));
69 			throw new RuntimeException("ensureGUI() is failed");
70 		}
71 	}
72 
73 	//
74 	private Handler handler;
75 	private final ArrayList<Runnable> posted = new ArrayList<>();
76 	private Handler guiHandler;
77 	private final ArrayList<Runnable> postedGUI = new ArrayList<>();
78 
79 	/**
80 	 * Set view to post GUI tasks to.
81 	 * @param guiTarget is view to post GUI tasks to.
82 	 */
setGUIHandler(Handler guiHandler)83 	public void setGUIHandler(Handler guiHandler) {
84 		this.guiHandler = guiHandler;
85 		if (guiHandler != null) {
86 			// forward already posted events
87 			synchronized(postedGUI) {
88 				L.d("Engine.setGUI: " + postedGUI.size() + " posted tasks to copy");
89 				for ( Runnable task : postedGUI )
90 					guiHandler.post( task );
91 			}
92 		}
93 	}
94 
95 	/**
96 	 * Create background thread executor.
97 	 */
BackgroundThread()98 	private BackgroundThread() {
99 		super();
100 		setName("BackgroundThread" + Integer.toHexString(hashCode()));
101 		Log.i("cr3", "Created new background thread instance");
102 	}
103 
104 	@Override
run()105 	public void run() {
106 		Log.i("cr3", "Entering background thread");
107 		Looper.prepare();
108 		handler = new Handler() {
109 			public void handleMessage( Message message )
110 			{
111 				Log.d("cr3", "message: " + message);
112 			}
113 		};
114 		Log.i("cr3", "Background thread handler is created");
115 		synchronized(posted) {
116 			for ( Runnable task : posted ) {
117 				Log.i("cr3", "Copying posted bg task to handler : " + task);
118 				handler.post(task);
119 			}
120 			posted.clear();
121 		}
122 		Looper.loop();
123 		handler = null;
124 		instance = null;
125 		Log.i("cr3", "Exiting background thread");
126 	}
127 
128 	//private final static boolean USE_LOCK = false;
guard( final Runnable r )129 	private Runnable guard( final Runnable r )
130 	{
131 		return r;
132 //		if ( !USE_LOCK )
133 //			return r;
134 //		return new Runnable() {
135 //			public void run() {
136 //				synchronized (LOCK) {
137 //					r.run();
138 //				}
139 //			}
140 //		};
141 	}
142 
143 	/**
144 	 * Post runnable to be executed in background thread.
145 	 * @param task is runnable to execute in background thread.
146 	 */
postBackground( Runnable task )147 	public void postBackground( Runnable task )
148 	{
149 		postBackground(task, 0);
150 	}
151 
152 	/**
153 	 * Post runnable to be executed in background thread.
154 	 * @param task is runnable to execute in background thread.
155 	 * @param delay is delay before running task, in millis
156 	 */
postBackground( Runnable task, long delay )157 	public void postBackground( Runnable task, long delay )
158 	{
159 		Engine.suspendLongOperation();
160 		if ( mStopped ) {
161 			L.i("Posting task " + task + " to GUI queue since background thread is stopped");
162 			postGUI( task );
163 			return;
164 		}
165 		task = guard(task);
166 		if ( handler==null ) {
167 			synchronized(posted) {
168 				L.i("Adding task " + task + " to posted list since handler is not yet created");
169 				posted.add(task);
170 			}
171 		} else {
172 			if (delay > 0) {
173 				Runnable finalTask = task;
174 				handler.postDelayed(() -> {
175 					try {
176 						finalTask.run();
177 					} catch (Throwable e) {
178 						Log.e("cr3", "Exception while processing task in Background thread: " + finalTask, e);
179 					}
180 				}, delay);
181 			} else
182 				handler.post(task);
183 		}
184 	}
185 
186 	/**
187 	 * Post runnable to be executed in GUI thread
188 	 * @param task is runnable to execute in GUI thread
189 	 */
postGUI( Runnable task )190 	public void postGUI( Runnable task )
191 	{
192 		postGUI(task, 0);
193 	}
194 
195 	static int delayedTaskId = 0;
196 	/**
197 	 * Post runnable to be executed in GUI thread
198 	 * @param task is runnable to execute in GUI thread
199 	 * @param delay is delay before running task, in millis
200 	 */
postGUI(final Runnable task, final long delay)201 	public void postGUI(final Runnable task, final long delay)
202 	{
203 		try {
204 			if (guiHandler == null) {
205 				synchronized (postedGUI) {
206 					postedGUI.add(task);
207 				}
208 			} else {
209 				if (delay > 0) {
210 					final int id = ++delayedTaskId;
211 					//L.v("posting delayed (" + delay + ") task " + id + " " + task);
212 					guiHandler.postDelayed(() -> {
213 						try {
214 							task.run();
215 							//L.v("finished delayed (" + delay + ") task " + id + " " + task);
216 						} catch (Throwable e) {
217 							Log.e("cr3", "Exception while processing task in GUI thread: " + task, e);
218 						}
219 					}, delay);
220 				} else
221 					guiHandler.post(task);
222 			}
223 		} catch (Throwable e) {
224 			Log.e("cr3", "Exception while posting task to GUI thread: " + task, e);
225 		}
226 	}
227 
228 	/**
229 	 * Run task instantly if called from the same thread, or post it through message queue otherwise.
230 	 * @param task is task to execute
231 	 */
executeBackground( Runnable task )232 	public void executeBackground( Runnable task )
233 	{
234 		Engine.suspendLongOperation();
235 		task = guard(task);
236 		if (isBackgroundThread() || mStopped)
237 			task.run(); // run in this thread
238 		else
239 			postBackground(task); // post
240 	}
241 
242 	// assume there are NOT only two threads: main GUI, this background and other thread (Synchronizer, etc)
isGUIThread()243 	public static boolean isGUIThread()
244 	{
245 		//return !isBackgroundThread();
246 		return Thread.currentThread() == Looper.getMainLooper().getThread();
247 	}
248 
isBackgroundThread()249 	public static boolean isBackgroundThread()
250 	{
251 		return (Thread.currentThread() == instance);
252 	}
253 
executeGUI( final Runnable task )254 	public void executeGUI( final Runnable task )
255 	{
256 		//Handler guiHandler = guiTarget.getHandler();
257 		//if ( guiHandler!=null && guiHandler.getLooper().getThread()==Thread.currentThread() )
258 		try {
259 			if (isGUIThread())
260 				task.run(); // run in this thread
261 			else {
262 				postGUI(() -> {
263 					try {
264 						task.run();
265 					} catch (Throwable e) {
266 						Log.e("cr3",
267 								"Exception while executing task in GUI thread: " + task, e);
268 					}
269 				});
270 			}
271 		} catch (Throwable e) {
272 			Log.e("cr3", "Exception in executeGUI: " + task, e);
273 		}
274 	}
275 
guard( final Callable<T> task )276     public <T> Callable<T> guard( final Callable<T> task )
277     {
278     	return new Callable<T>() {
279     		public T call() throws Exception {
280     			return task.call();
281     		}
282     	};
283     }
284 
285 
286     /**
287      * Waits until all pending background tasks are executed.
288      */
289     public void syncWithBackground() {
290     	callBackground((Callable<Integer>) () -> null);
291     }
292 
293     public <T> T callBackground( final Callable<T> srcTask )
294     {
295     	final Callable<T> task = srcTask; //guard(srcTask);
296     	if ( isBackgroundThread() ) {
297     		try {
298     			return task.call();
299     		} catch ( Exception e ) {
300     			return null;
301     		}
302     	}
303     	//L.d("executeSync called");
304     	if(DBG) L.d("callBackground : posting Background task " + Thread.currentThread().getName());
305     	final Sync<T> sync = new Sync<T>();
306     	postBackground(() -> {
307 			if(DBG) L.d("callBackground : inside background thread " + Thread.currentThread().getName());
308 			try {
309 				sync.set( task.call() );
310 			} catch ( Exception e ) {
311 				sync.set( null );
312 			}
313 		});
314     	if(DBG) L.d("callBackground : calling get " + Thread.currentThread().getName());
315     	T res = sync.get();
316     	if(DBG) L.d("callBackground : returned from get " + Thread.currentThread().getName());
317     	//L.d("executeSync done");
318     	return res;
319     }
320 
321     private final static boolean DBG = false;
322 
323     public <T> T callGUI( final Callable<T> task )
324     {
325     	if ( isGUIThread() ) {
326     		try {
327     			return task.call();
328     		} catch ( Exception e ) {
329     			return null;
330     		}
331     	}
332     	if(DBG) L.d("callGUI : posting GUI task " + Thread.currentThread().getName());
333     	final Sync<T> sync = new Sync<T>();
334     	postGUI(() -> {
335 			if(DBG) L.d("callGUI : inside GUI thread " + Thread.currentThread().getName());
336 			T result = null;
337 			try {
338 				L.d("callGUI : calling source callable " + Thread.currentThread().getName());
339 				result = task.call();
340 			} catch ( Exception e ) {
341 				if(DBG) L.e("exception in postGUI", e);
342 				throw new RuntimeException(e);
343 			}
344 			try {
345 				if(DBG) L.d("callGUI : calling sync.set " + Thread.currentThread().getName());
346 				sync.set( result );
347 				if(DBG) L.d("callGUI : returned from sync.set " + Thread.currentThread().getName());
348 			} catch ( Exception e ) {
349 				if(DBG) L.e("exception in postGUI", e);
350 				throw new RuntimeException(e);
351 			}
352 		});
353     	if(DBG) L.d("callGUI : calling get " + Thread.currentThread().getName());
354     	T res = sync.get();
355     	if(DBG) L.d("callGUI : returned from get " + Thread.currentThread().getName());
356     	return res;
357     }
358 
359 	private boolean mStopped = false;
360 
361 	public void waitForBackgroundCompletion() {
362 		Engine.suspendLongOperation();
363 		callBackground(() -> null);
364 	}
365 
366 	public void quit() {
367 		postBackground(() -> {
368 			if (handler != null) {
369 				L.i("Calling quit() on background thread looper.");
370 				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
371 					handler.getLooper().quitSafely();
372 				else
373 					handler.getLooper().quit();
374 			}
375 		});
376 	}
377 }
378