gui: run cancel action in another thread

Some actions did not have an operation_end where needed especially
when dealing with cancel actions.

Cancel actions now do not run operation_start or operation_end
and let the original action handle the operation_end so that the
GUI waits until the original action acutally cancels.

Change-Id: I28e6260abb058acb982cecd108c09fc89e0ffeed
diff --git a/gui/action.cpp b/gui/action.cpp
index f030635..2aa03a6 100644
--- a/gui/action.cpp
+++ b/gui/action.cpp
@@ -67,11 +67,36 @@
 static pthread_t terminal_command;
 pid_t sideload_child_pid;
 
-static ActionThread action_thread;
+static void *ActionThread_work_wrapper(void *data);
+
+class ActionThread
+{
+public:
+	ActionThread();
+	~ActionThread();
+
+	void threadActions(GUIAction *act);
+	void run(void *data);
+private:
+	friend void *ActionThread_work_wrapper(void*);
+	struct ThreadData
+	{
+		ActionThread *this_;
+		GUIAction *act;
+		ThreadData(ActionThread *this_, GUIAction *act) : this_(this_), act(act) {}
+	};
+
+	pthread_t m_thread;
+	bool m_thread_running;
+	pthread_mutex_t m_act_lock;
+};
+
+static ActionThread action_thread;	// for all kinds of longer running actions
+static ActionThread cancel_thread;	// for longer running "cancel" actions
 
 static void *ActionThread_work_wrapper(void *data)
 {
-	action_thread.run(data);
+	static_cast<ActionThread::ThreadData*>(data)->this_->run(data);
 	return NULL;
 }
 
@@ -103,9 +128,7 @@
 	} else {
 		m_thread_running = true;
 		pthread_mutex_unlock(&m_act_lock);
-		ThreadData *d = new ThreadData;
-		d->act = act;
-
+		ThreadData *d = new ThreadData(this, act);
 		pthread_create(&m_thread, NULL, &ActionThread_work_wrapper, d);
 	}
 }
@@ -370,9 +393,17 @@
 	return ret_val;
 }
 
-bool GUIAction::needsToRunInSeparateThread(const GUIAction::Action& action)
+GUIAction::ThreadType GUIAction::getThreadType(const GUIAction::Action& action)
 {
-	return setActionsRunningInCallerThread.find(gui_parse_text(action.mFunction)) == setActionsRunningInCallerThread.end();
+	string func = gui_parse_text(action.mFunction);
+	bool needsThread = setActionsRunningInCallerThread.find(func) == setActionsRunningInCallerThread.end();
+	if (needsThread) {
+		if (func == "cancelbackup")
+			return THREAD_CANCEL;
+		else
+			return THREAD_ACTION;
+	}
+	return THREAD_NONE;
 }
 
 int GUIAction::doActions()
@@ -380,25 +411,40 @@
 	if (mActions.size() < 1)
 		return -1;
 
-	bool needThread = false;
+	// Determine in which thread to run the actions.
+	// Do it for all actions at once before starting, so that we can cancel the whole batch if the thread is already busy.
+	ThreadType threadType = THREAD_NONE;
 	std::vector<Action>::iterator it;
-	for (it = mActions.begin(); it != mActions.end(); ++it)
-	{
-		if (needsToRunInSeparateThread(*it))
-		{
-			needThread = true;
+	for (it = mActions.begin(); it != mActions.end(); ++it) {
+		ThreadType tt = getThreadType(*it);
+		if (tt == THREAD_NONE)
+			continue;
+		if (threadType == THREAD_NONE)
+			threadType = tt;
+		else if (threadType != tt) {
+			LOGERR("Can't mix normal and cancel actions in the same list.\n"
+				"Running the whole batch in the cancel thread.\n");
+			threadType = THREAD_CANCEL;
 			break;
 		}
 	}
-	if (needThread)
-	{
-		action_thread.threadActions(this);
-	}
-	else
-	{
-		const size_t cnt = mActions.size();
-		for (size_t i = 0; i < cnt; ++i)
-			doAction(mActions[i]);
+
+	// Now run the actions in the desired thread.
+	switch (threadType) {
+		case THREAD_ACTION:
+			action_thread.threadActions(this);
+			break;
+
+		case THREAD_CANCEL:
+			cancel_thread.threadActions(this);
+			break;
+
+		default: {
+			// no iterators here because theme reloading might kill our object
+			const size_t cnt = mActions.size();
+			for (size_t i = 0; i < cnt; ++i)
+				doAction(mActions[i]);
+		}
 	}
 
 	return 0;
@@ -920,7 +966,6 @@
 void GUIAction::reinject_after_flash()
 {
 	if (DataManager::GetIntValue(TW_HAS_INJECTTWRP) == 1 && DataManager::GetIntValue(TW_INJECT_AFTER_ZIP) == 1) {
-		operation_start("ReinjectTWRP");
 		gui_print("Injecting TWRP into boot image...\n");
 		if (simulate) {
 			simulate_progress_bar();
@@ -1060,7 +1105,7 @@
 			}
 		} else
 			ret_val = PartitionManager.Wipe_By_Path(arg);
-#ifdef TW_OEM_BUILD
+#ifndef TW_OEM_BUILD
 		if (arg == DataManager::GetSettingsStoragePath()) {
 			// If we wiped the settings storage path, recreate the TWRP folder and dump the settings
 			string Storage_Path = DataManager::GetSettingsStoragePath();
@@ -1099,8 +1144,10 @@
 int GUIAction::nandroid(std::string arg)
 {
 	if (simulate) {
+		PartitionManager.stop_backup.set_value(0);
 		DataManager::SetValue("tw_partition", "Simulation");
 		simulate_progress_bar();
+		operation_end(0);
 	} else {
 		operation_start("Nandroid");
 		int ret = 0;
@@ -1131,13 +1178,13 @@
 			else
 				ret = 0; // 0 for success
 			DataManager::SetValue("tw_cancel_backup", 0);
-			operation_end(ret);
 		}
 		else {
 			DataManager::SetValue("tw_cancel_backup", 1);
 			gui_print("Backup Canceled.\n");
 			ret = 0;
 		}
+		operation_end(ret);
 		return ret;
 	}
 	return 0;
@@ -1145,16 +1192,12 @@
 
 int GUIAction::cancelbackup(std::string arg) {
 	if (simulate) {
-		simulate_progress_bar();
 		PartitionManager.stop_backup.set_value(1);
-		operation_end(0);
 	}
 	else {
-		operation_start("Cancel Backup");
 		int op_status = PartitionManager.Cancel_Backup();
 		if (op_status != 0)
 			op_status = 1; // failure
-		operation_end(op_status);
 	}
 
 	return 0;
@@ -1162,16 +1205,18 @@
 
 int GUIAction::fixpermissions(std::string arg)
 {
+	int op_status = 0;
+
 	operation_start("Fix Permissions");
 	LOGINFO("fix permissions started!\n");
 	if (simulate) {
 		simulate_progress_bar();
 	} else {
-		int op_status = PartitionManager.Fix_Permissions();
+		op_status = PartitionManager.Fix_Permissions();
 		if (op_status != 0)
 			op_status = 1; // failure
-		operation_end(op_status);
 	}
+	operation_end(op_status);
 	return 0;
 }
 
@@ -1481,7 +1526,6 @@
 	LOGINFO("Waiting for child sideload process to exit.\n");
 	waitpid(sideload_child_pid, &status, 0);
 	sideload_child_pid = 0;
-	operation_end(1);
 	DataManager::SetValue("tw_page_done", "1"); // For OpenRecoveryScript support
 	return 0;
 }
diff --git a/gui/objects.hpp b/gui/objects.hpp
index bdccc6e..c95a935 100644
--- a/gui/objects.hpp
+++ b/gui/objects.hpp
@@ -288,9 +288,11 @@
 	std::map<int, bool> mKeys;
 
 protected:
+	enum ThreadType { THREAD_NONE, THREAD_ACTION, THREAD_CANCEL };
+
 	int getKeyByName(std::string key);
 	int doAction(Action action);
-	bool needsToRunInSeparateThread(const Action& action);
+	ThreadType getThreadType(const Action& action);
 	void simulate_progress_bar(void);
 	int flash_zip(std::string filename, int* wipe_cache);
 	void reinject_after_flash();
@@ -365,25 +367,6 @@
 	int simulate;
 };
 
-class ActionThread
-{
-public:
-	ActionThread();
-	~ActionThread();
-
-	void threadActions(GUIAction *act);
-	void run(void *data);
-private:
-	struct ThreadData
-	{
-		GUIAction *act;
-	};
-
-	pthread_t m_thread;
-	bool m_thread_running;
-	pthread_mutex_t m_act_lock;
-};
-
 class GUIConsole : public GUIObject, public RenderObject, public ActionObject
 {
 public: