public interface Constants {
    String TAG = "Kandroid";
    int ResultChanged = 1;
    int RequestEditTask = 1;
    int FileSelectCode = 10;
    int RequestStoragePermission = 20;
    int[] minKanboardVersion = new int[] {1, 0, 38};
} public interface OnCloseTaskListener {
    void onCloseTask(boolean success);
} public interface OnCreateCommentListener {
    void onCreateComment(boolean success, Integer commentid);
} public interface OnCreateSubtaskListener {
    void onCreateSubtask(boolean success, Integer result);
} public interface OnCreateTaskListener {
    void onCreateTask(boolean success, Integer taskid);
} public interface OnDownloadTaskFileListener {
    void onDownloadTaskFile(boolean success, int id, String data);
} public interface OnDuplicateTaskToProjectListener {
    void onDuplicateTaskToProject(boolean success, Integer newTaskID);
} public interface OnErrorListener {
    void onError(KanboardError error);
} public interface OnGetActiveSwimlanesListener {
    void onGetActiveSwimlanes(boolean success, List<KanboardSwimlane> result);
} public interface OnGetAllCategoriesListener {
    void onGetAllCategories(boolean success, List<KanboardCategory> result);
} public interface OnGetAllCommentsListener {
    void onGetAllComments(boolean success, List<KanboardComment> comments);
} public interface OnGetAllSubtasksListener {
    void onGetAllSubtasks(boolean success, List<KanboardSubtask> result);
} public interface OnGetAllTaskFilesListener {
    void onGetAllTaskFiles(boolean success, List<KanboardTaskFile> result);
} public interface OnGetAllTasksListener {
    void onGetAllTasks(boolean success, int status, List<KanboardTask> result);
} public interface OnGetCategoryListener {
    void onGetCategory(boolean success, KanboardCategory result);
} public interface OnGetColumnsListener {
    void onGetColumns(boolean success, List<KanboardColumn> result);
} public interface OnGetDefaultColorListener {
    void onGetDefaultColor(boolean success, String colorid);
} public interface OnGetDefaultColorsListener {
    void onGetDefaultColors(boolean success, Dictionary<String, KanboardColor> colors);
} public interface OnGetMeListener {
    void onGetMe(boolean success, KanboardUserInfo result);

} public interface OnGetMyActivityStreamListener {
    void onGetMyActivityStream(boolean success, List<KanboardActivity> result);
} public interface OnGetMyDashboardListener {
    void onGetMyDashboard(boolean success, KanboardDashboard result);
} public interface OnGetMyOverdueTasksListener {
    void onGetMyOverdueTasks(boolean success, List<KanboardTask> result);
} public interface OnGetMyProjectsListListener {
    void onGetMyProjectsList(boolean success, List<KanboardProjectInfo> projects);
} public interface OnGetMyProjectsListener {
    void onGetMyProjects(boolean success, List<KanboardProject> result);
} public interface OnGetOverdueTasksByProjectListener {
    void onGetOverdueTasksByProject(boolean success, List<KanboardTask> result);
} public interface OnGetProjectByIdListener {
    void onGetProjectById(boolean success, KanboardProject result);
} public interface OnGetProjectUsersListener {
    void onGetProjectUsers(boolean success, Dictionary<Integer, String> result);
} public interface OnGetSwimlaneListener {
    void onGetSwimlane(boolean success, KanboardSwimlane result);
} public interface OnGetTaskListener {
    void onGetTask(boolean success, KanboardTask result);
} public interface OnGetVersionListener {
    void onGetVersion(boolean success, int[] version, String tag);
} public interface OnMoveTaskPositionListener {
    void onMoveTaskPosition(boolean success);
} public interface OnMoveTaskToProjectListener {
    void onMoveTaskToProject(boolean success);
} public interface OnOpenTaskListener {
    void onOpenTask(boolean success);
} public interface OnRemoveCommentListener {
    void onRemoveComment(boolean success);
} public interface OnRemoveSubtaskListener {
    void onRemoveSubtask(boolean success);
} public interface OnRemoveTaskFileListener extends Serializable {
    void onRemoveTaskFile(boolean success);
} public interface OnRemoveTaskListener {
    void onRemoveTask(boolean success);
} public interface OnSubtaskTimetrackingListener {
    void onSubtaskTimetracking(boolean result, double time);
} public interface OnUpdateCommentListener {
    void onUpdateComment(boolean success);
} public interface OnUpdateSubtaskListener {
    void onUpdateSubtask(boolean success);
} public interface OnUpdateTaskListener {
    void onUpdateTask(boolean success);
}
import android.graphics.Color;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.acra.ACRA;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import in.andres.kandroid.BuildConfig;
import in.andres.kandroid.Constants;
import in.andres.kandroid.kanboard.events.OnCloseTaskListener;
import in.andres.kandroid.kanboard.events.OnCreateCommentListener;
import in.andres.kandroid.kanboard.events.OnCreateSubtaskListener;
import in.andres.kandroid.kanboard.events.OnCreateTaskListener;
import in.andres.kandroid.kanboard.events.OnDownloadTaskFileListener;
import in.andres.kandroid.kanboard.events.OnDuplicateTaskToProjectListener;
import in.andres.kandroid.kanboard.events.OnErrorListener;
import in.andres.kandroid.kanboard.events.OnGetActiveSwimlanesListener;
import in.andres.kandroid.kanboard.events.OnGetAllCategoriesListener;
import in.andres.kandroid.kanboard.events.OnGetAllCommentsListener;
import in.andres.kandroid.kanboard.events.OnGetAllSubtasksListener;
import in.andres.kandroid.kanboard.events.OnGetAllTaskFilesListener;
import in.andres.kandroid.kanboard.events.OnGetAllTasksListener;
import in.andres.kandroid.kanboard.events.OnGetCategoryListener;
import in.andres.kandroid.kanboard.events.OnGetColumnsListener;
import in.andres.kandroid.kanboard.events.OnGetDefaultColorListener;
import in.andres.kandroid.kanboard.events.OnGetDefaultColorsListener;
import in.andres.kandroid.kanboard.events.OnGetMeListener;
import in.andres.kandroid.kanboard.events.OnGetMyActivityStreamListener;
import in.andres.kandroid.kanboard.events.OnGetMyDashboardListener;
import in.andres.kandroid.kanboard.events.OnGetMyOverdueTasksListener;
import in.andres.kandroid.kanboard.events.OnGetMyProjectsListListener;
import in.andres.kandroid.kanboard.events.OnGetMyProjectsListener;
import in.andres.kandroid.kanboard.events.OnGetOverdueTasksByProjectListener;
import in.andres.kandroid.kanboard.events.OnGetProjectByIdListener;
import in.andres.kandroid.kanboard.events.OnGetProjectUsersListener;
import in.andres.kandroid.kanboard.events.OnGetSwimlaneListener;
import in.andres.kandroid.kanboard.events.OnGetTaskListener;
import in.andres.kandroid.kanboard.events.OnGetVersionListener;
import in.andres.kandroid.kanboard.events.OnMoveTaskPositionListener;
import in.andres.kandroid.kanboard.events.OnMoveTaskToProjectListener;
import in.andres.kandroid.kanboard.events.OnOpenTaskListener;
import in.andres.kandroid.kanboard.events.OnRemoveCommentListener;
import in.andres.kandroid.kanboard.events.OnRemoveSubtaskListener;
import in.andres.kandroid.kanboard.events.OnRemoveTaskFileListener;
import in.andres.kandroid.kanboard.events.OnRemoveTaskListener;
import in.andres.kandroid.kanboard.events.OnSubtaskTimetrackingListener;
import in.andres.kandroid.kanboard.events.OnUpdateCommentListener;
import in.andres.kandroid.kanboard.events.OnUpdateSubtaskListener;
import in.andres.kandroid.kanboard.events.OnUpdateTaskListener;
    private static Pattern urlPattern = Pattern.compile("(?i)[^/]+?\\.(php|html|htm|asp){1}?$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

    public static URL sanitizeURL(String url) throws MalformedURLException {
        Matcher regexMatcher = urlPattern.matcher(url);
        if (regexMatcher.find()) {
            //URL has filename, replace it
            return new URL(regexMatcher.replaceAll("jsonrpc.php"));
        } else {
            //URL hs no filename, add one
            if (!url.endsWith("/"))
                url += "/";
            url += "jsonrpc.php";
            return new URL(url);
        }
    }

    private class KanboardAsync extends AsyncTask<KanboardRequest, Void, KanboardResult> {
        @Override
        protected KanboardResult doInBackground(KanboardRequest... params) {
            HttpURLConnection con = null;
            SSLContext sslcontext = null;
            int httpResponseCode = 0;
            List<JSONObject> responseList = new ArrayList<>();
            for (String s: params[0].JSON) {
                try {

                    Log.i(Constants.TAG, String.format("API: Send Request \"%s\"", params[0].Command));
                    if (BuildConfig.DEBUG) Log.v(Constants.TAG, String.format("API: Data:\n%s", s));
                    con = KanboardAPI.openConnection(kanboardURL, s);
                    if (con == null)
                        return new KanboardResult(params[0], new JSONObject[]{new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":0,\"message\":\"Unable to open connection\"},\"id\":null}")}, 0);

                    Log.i(Constants.TAG, String.format("API: HTTP Return Code for \"%s\": %d", params[0].Command, con.getResponseCode()));


                    BufferedReader in;
                    if (con.getResponseCode() < 400)
                        in = new BufferedReader(new InputStreamReader(con.getInputStream()));
                    else
                        in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
                    String line;
                    StringBuilder responseStr = new StringBuilder();
                    while ((line = in.readLine()) != null) {
                        responseStr.append(line);
                    }
                    in.close();
                    httpResponseCode = con.getResponseCode();

                    Log.i(Constants.TAG, String.format("API: Received Response \"%s\"", params[0].Command));
                    if (BuildConfig.DEBUG)
                        Log.v(Constants.TAG, String.format("API: Data:\n%s", responseStr.toString()));
                    JSONObject response;
                    try {
                        response = new JSONObject(responseStr.toString());
                    } catch (JSONException e) {
                        //server response is not a JSON string, print it for debugging
                        Log.d(Constants.TAG, String.format(Locale.getDefault(), "Erroneous response: %s", responseStr.toString()));
                        response = new JSONObject();
                        response.put("jsonrpc", "2.0");
                        response.put("id", null);
                        response.put("error", new JSONObject()
                                .put("code", -50)
                                .put("responseCode", con.getResponseCode())
                                .put("message", con.getResponseMessage()))
                                .put("data", responseStr.toString());
                    }
                    responseList.add(response);

                    con.disconnect();

                } catch (SSLException e) {
                    Log.e(Constants.TAG, "API: SSL Error.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-40,\"message\":\"" + e.getLocalizedMessage() + "\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (UnknownHostException e) {
                    Log.e(Constants.TAG, "API: Unknown Host.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-10,\"message\":\"Unknown Host\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (ProtocolException e) {
                    Log.e(Constants.TAG, "API: Protocol Exception.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-20,\"message\":\"Protocol Exception\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (SocketTimeoutException e) {
                    Log.e(Constants.TAG, String.format("API: Connection timed out.\tRequest: %s", params[0].Command));
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-30,\"message\":\"Network Timeout\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (Exception e) {
                    Log.e(Constants.TAG, String.format("API: CatchAll.\tRequest: %s", params[0].Command));
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-1,\"message\":\"" + e.getMessage() + "\"},\"id\":null}\""));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                }
            }

            return new KanboardResult(params[0], responseList.toArray(new JSONObject[]{}), httpResponseCode);
        }
        @Override
        protected void onPostExecute(KanboardResult s) {
            // Handle Errors
            if (s == null || s.Result == null || s.Result[0] == null) {
                KanboardError res = new KanboardError(null, null, 0);
                for (OnErrorListener l: onErrorListeners)