/* * @copyright 2011 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import java.util.ArrayList; import com.eleybourn.bookcatalogue.messaging.MessageSwitch; import com.eleybourn.bookcatalogue.messaging.MessageSwitch.Message; import com.eleybourn.bookcatalogue.utils.Logger; /** * Class used to manager a collection of backgroud threads for an AcitivityWithTasks subclass. * * Part of three components that make this easier: * - TaskManager -- handles the management of multiple threads sharing a progressDialog * - ActivityWithTasks -- uses a TaskManager (and communicates with it) to handle progress * messages for threads. Deals with orientation changes in cooperation with TaskManager. * - ManagedTask -- Background task that is managed by TaskManager and uses TaskManager to * do all display activities. * * @author Philip Warner */ public class TaskManager { /** * Allows other objects to know when a task completed. See SearchManager for an example. * * @author Philip Warner */ public interface TaskManagerListener { void onTaskEnded(TaskManager manager, ManagedTask task); void onProgress(int count, int max, String message); void onToast(String message); void onFinished(); } public interface TaskManagerController { void requestAbort(); TaskManager getManager(); } private TaskManagerController mController = new TaskManagerController() { @Override public void requestAbort() { TaskManager.this.cancelAllTasks(); } @Override public TaskManager getManager() { return TaskManager.this; } }; public static class OnTaskEndedMessage implements Message<TaskManagerListener> { private TaskManager mManager; private ManagedTask mTask; public OnTaskEndedMessage(TaskManager manager, ManagedTask task) { mManager = manager; mTask = task; } @Override public boolean deliver(TaskManagerListener listener) { listener.onTaskEnded(mManager, mTask); return false; } }; public static class OnProgressMessage implements Message<TaskManagerListener> { private int mCount; private int mMax; private String mMessage; public OnProgressMessage(int count, int max, String message) { mCount = count; mMax = max; mMessage = message; } @Override public boolean deliver(TaskManagerListener listener) { listener.onProgress(mCount, mMax, mMessage); return false; } }; public static class OnToastMessage implements Message<TaskManagerListener> { private String mMessage; public OnToastMessage(String message) { mMessage = message; } @Override public boolean deliver(TaskManagerListener listener) { listener.onToast(mMessage); return false; } }; public static class OnFinshedMessage implements Message<TaskManagerListener> { @Override public boolean deliver(TaskManagerListener listener) { listener.onFinished(); return false; } }; /* ==================================================================================================== * OnTaskManagerListener handling */ /** * STATIC Object for passing messages from background tasks to activities that may be recreated * * This object handles all underlying OnTaskEndedListener messages for every instance of this class. */ private static final MessageSwitch<TaskManagerListener, TaskManagerController> mMessageSwitch = new MessageSwitch<TaskManagerListener, TaskManagerController>(); public static final MessageSwitch<TaskManagerListener, TaskManagerController> getMessageSwitch() { return mMessageSwitch; } /** * Object for SENDING messages specific to this instance */ private final long mMessageSenderId = mMessageSwitch.createSender(mController); public long getSenderId() { return mMessageSenderId; } /* ==================================================================================================== * END OnTaskManagerListener handling */ // Current progress message to display, even if no tasks running. Setting to blank // will remove the ProgressDialog String mBaseMessage = ""; // Last task-related message displayed (used when rebuilding progress) String mProgressMessage = ""; // Max value of progress. Set to 0 if no bar needed. int mProgressMax = 0; // Current value of progress. int mProgressCount = 0; /** Flag indicating tasks are being cancelled. This is reset when a new task is added */ private boolean mCancelling = false; /** Flag indicating the TaskManager is terminating; will close after last task exits */ private boolean mIsClosing = false; // List of tasks being managed by this object ArrayList<TaskInfo> mTasks = new ArrayList<TaskInfo> (); // Task info for each ManagedTask object private class TaskInfo { ManagedTask task; String progressMessage; int progressMax; int progressCurrent; TaskInfo(ManagedTask t) { this(t, 0, 0, ""); } TaskInfo(ManagedTask t, int max, int curr, String message) { task = t; progressMax = max; progressCurrent = curr; progressMessage = message; } } /** * Constructor. * */ TaskManager() { } /** * Add a task to this object. Ignores duplicates if already present. * * @param t Task to add */ void addTask(ManagedTask t) { if (mIsClosing) throw new RuntimeException("Can not add a task when closing down"); mCancelling = false; synchronized(mTasks) { if (getTaskInfo(t) == null) { mTasks.add(new TaskInfo(t)); ManagedTask.getMessageSwitch().addListener(t.getSenderId(), mTaskListener, true); } } } /** * Listen for task messages, specifically, task termination */ private ManagedTask.TaskListener mTaskListener = new ManagedTask.TaskListener() { @Override public void onTaskFinished(ManagedTask t) { TaskManager.this.onTaskFinished(t); } }; /** * Accessor */ public boolean isCancelling() { return mCancelling; } /** * Called when the onTaskFinished message is received by the listener object. * * @param task */ private void onTaskFinished(ManagedTask task) { boolean doClose; // Remove from the list of tasks. From now on, it should // not send any progress requests. synchronized(mTasks) { for(TaskInfo i : mTasks) { if (i.task == task) { mTasks.remove(i); break; } } doClose = (mIsClosing && mTasks.size() == 0); } // Tell all listeners that it has ended. mMessageSwitch.send(mMessageSenderId, new OnTaskEndedMessage(TaskManager.this, task)); // Update the progress dialog updateProgressDialog(); // Call close() if necessary if (doClose) close(); } /** * Get the number of tasks currently managed. * * @return Number of tasks */ public int count() { return mTasks.size(); } /** * Return the associated activity object. * * @return The context */ // private Context getContext() { // synchronized(this) { // return mContext; // } //} /** * Utility routine to cancel all tasks. */ public void cancelAllTasks() { synchronized(mTasks) { mCancelling = true; for(TaskInfo t : mTasks) { t.task.cancelTask(); } } } /** * Update the base progress message. Used (generally) by the ActivityWuthTasks to * display some text above the task info. Set to blank to ensure ProgressDialog will * be removed. * * @param message */ public void doProgress(String message) { mBaseMessage = message; updateProgressDialog(); } /** * Update the current ProgressDialog based on information about a task. * * @param task The task associated with this message * @param message Message text * @param count Counter for progress */ public void doProgress(ManagedTask task, String message, int count) { TaskInfo t = getTaskInfo(task); if (t != null) { t.progressMessage = message; t.progressCurrent = count; updateProgressDialog(); return; } } /** * If in the UI thread, update the progress dialog, otherwise resubmit to UI thread. */ private void updateProgressDialog() { try { // Start with the base message if present if (mBaseMessage != null && mBaseMessage.length() > 0) mProgressMessage = mBaseMessage; else mProgressMessage = ""; synchronized(mTasks) { // Append each task message if (mTasks.size() > 0) { if (mProgressMessage.length() > 0) mProgressMessage += "\n"; if (mTasks.size() == 1) { String oneMsg = mTasks.get(0).progressMessage; if (oneMsg != null && oneMsg.trim().length() > 0) mProgressMessage += oneMsg; } else { String taskMsgs = ""; boolean got = false; // Don't append blank messages; allows tasks to hide. for(int i = 0; i < mTasks.size(); i++) { String oneMsg = mTasks.get(i).progressMessage; if (oneMsg != null && oneMsg.trim().length() > 0) { if (got) taskMsgs += "\n"; else got = true; taskMsgs += " - " + oneMsg; } } if (taskMsgs.length() > 0) mProgressMessage += taskMsgs; } } } // Sum the current & max values for each active task. This will be our new values. mProgressMax = 0; mProgressCount = 0; synchronized(mTasks) { for (TaskInfo t : mTasks) { mProgressMax += t.progressMax; mProgressCount += t.progressCurrent; } } // Now, display it if we have a context; if it is empty and complete, delete the progress. mMessageSwitch.send(mMessageSenderId, new OnProgressMessage(mProgressCount, mProgressMax, mProgressMessage)); } catch (Exception e) { Logger.logError(e, "Error updating progress"); } } /** * Make a toast message for the caller. Queue in UI thread if necessary. * * @param message Message to send */ public void doToast(String message) { mMessageSwitch.send(mMessageSenderId, new OnToastMessage(message)); } /** * Lookup the TaskInfo for the passed task. * * @param task Task to lookup * * @return TaskInfo associated with task. */ private TaskInfo getTaskInfo(ManagedTask task) { synchronized(mTasks) { for(TaskInfo t : mTasks) { if (t.task == task) { return t; } } } return null; } /** * Set the maximum value for progress for the passed task. * * @param task * @param max */ public void setMax(ManagedTask task, int max) { TaskInfo t = getTaskInfo(task); if (t != null) { t.progressMax = max; updateProgressDialog(); return; } } /** * Set the count value for progress for the passed task. * * @param task * @param max */ public void setCount(ManagedTask task, int count) { TaskInfo t = getTaskInfo(task); if (t != null) { t.progressCurrent = count; updateProgressDialog(); return; } } /** * Cancel all tasks and close dialogs then cleanup; if no tasks running, just close dialogs and cleanup */ protected void close() { System.out.println("DBG: Task Manager close requested"); mIsClosing = true; synchronized(mTasks) { for(TaskInfo t : mTasks) { t.task.cancelTask(); } } } @Override protected void finalize() throws Throwable { super.finalize(); } }