diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85a0697..7e2ef90 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -87,6 +87,13 @@ + + + + + diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/KVV.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/KVV.java index aae95ac..b96d380 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/KVV.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/KVV.java @@ -56,7 +56,6 @@ public class KVV extends Service { public KVV() { Logger log = new Logger(this); - log.d("constructor"); } // Binder given to clients diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java index 1183158..6525c08 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java @@ -39,7 +39,7 @@ public class ModulesList extends HTTPService { private CacheKVVCourse mKVVCache; ModulesList(Login login, KVVListener listener, Context context) { - super(context); + super(context, "ModuleList"); this.mLogin = login; this.mListener = listener; restore(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java index d52c6e5..9caf96e 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java @@ -101,9 +101,9 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { String authority, ContentProviderClient provider, SyncResult syncResult) { - Intent intent = new Intent(getContext(), KVV.class); - getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); if (!mBound) { + Intent intent = new Intent(getContext(), KVV.class); + getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); mWaitForBound = true; mQueue.add(() -> {}); } diff --git a/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java b/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java index 8a39c91..226b7d1 100644 --- a/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java +++ b/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java @@ -2,14 +2,25 @@ package de.sebse.fuplanner.tools; import java.util.LinkedList; +import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.network.NetworkCallback; import de.sebse.fuplanner.tools.network.NetworkErrorCallback; public class NewAsyncQueue { private final LinkedList mQueue = new LinkedList<>(); private boolean mIsRunning = false; + private Logger log = new Logger(this); + public String name = null; + + public NewAsyncQueue(String name) { + this.name = name; + } + + public NewAsyncQueue() { + } public void add(AsyncQueueCallback callback) { + //if ("ModuleList".equals(name)) log.t("add start", name, mIsRunning, mQueue.size()); if (isRunning()) getQueue().addLast(callback); else { @@ -19,6 +30,7 @@ public class NewAsyncQueue { } public void next() { + //if ("ModuleList".equals(name)) log.t("next start", name, mIsRunning, mQueue.size()); AsyncQueueCallback callback = getQueue().pollFirst(); if (callback == null) setRunning(false); diff --git a/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPNetwork.java b/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPNetwork.java new file mode 100644 index 0000000..90c7f48 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPNetwork.java @@ -0,0 +1,225 @@ +package de.sebse.fuplanner.tools.network; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +import com.android.volley.AuthFailureError; +import com.android.volley.NetworkResponse; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.Response; +import com.android.volley.TimeoutError; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.Volley; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import androidx.annotation.Nullable; +import de.sebse.fuplanner.tools.EventListener; +import de.sebse.fuplanner.tools.logging.Logger; + +/** + * Created by sebastian on 24.10.17. + */ + +public class HTTPNetwork extends Service { + private RequestQueue requestQueue; + protected final Logger log = new Logger(this); + private final EventListener errorResponseListener = new EventListener<>(); + private final EventListener successResponseListener = new EventListener<>(); + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + + + @Override + public void onCreate() { + super.onCreate(); + requestQueue = Volley.newRequestQueue(getApplicationContext(), new BetterHurlStack(false)); + } + + public void addErrorListener(String id, EventListener.EventFunction listener) { + errorResponseListener.add(id, listener); + } + + public void removeErrorListener(String id) { + errorResponseListener.remove(id); + } + + public void addSuccessListener(String id, EventListener.EventFunction listener) { + successResponseListener.add(id, listener); + } + + public void removeSuccessListener(String id) { + successResponseListener.remove(id); + } + + protected void head(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error) { + get(url, cookies, response, error, true); + } + + protected void get(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error) { + get(url, cookies, response, error, false); + } + + private void get(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error, boolean noBody) { + int requestMethod = noBody ? Request.Method.HEAD : Request.Method.GET; + HttpRequest request = new HttpRequest(requestMethod, url, response, error) { + @Override + public void deliverError(VolleyError error) { + if (error == null) { + deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null))); + } else if (error.networkResponse == null) { + int statusCode; + if (error instanceof TimeoutError) + statusCode = 408; + else + statusCode = 500; + deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null))); + } else { + final int status = error.networkResponse.statusCode; + if (status == 302) { + deliverResponse(new Result(null, error.networkResponse.headers)); + } else { + deliver(error); + } + } + } + + @Override + protected void deliverResponse(Result response) { + successResponseListener.emit(response); + super.deliverResponse(response); + } + + private void deliver(VolleyError error) { + errorResponseListener.emit(error); + super.deliverError(error); + } + + public Map getHeaders() throws AuthFailureError { + Map params = super.getHeaders(); + if (cookies != null) { + if (params==null) + params = new HashMap<>(); + else + params = new HashMap<>(params); + StringBuilder newStr = new StringBuilder(); + for (String key : cookies.keySet()) + newStr.append(key).append("=").append(cookies.get(key)).append(";"); + newStr = new StringBuilder(newStr.substring(0, newStr.length() - 1)); + params.put("Cookie", newStr.toString()); + } + + return params; + } + }; + requestQueue.add(request); + } + + protected void post(String url, @Nullable final HashMap cookies, @Nullable final HashMap body, Response.Listener response, Response.ErrorListener error) { + HttpRequest request = new HttpRequest(Request.Method.POST, url, response, error) { + @Override + public String getBodyContentType() { + return "application/x-www-form-urlencoded"; + } + + @Override + public byte[] getBody() { + if (body==null) { + return null; + } + StringBuilder sb = new StringBuilder(); + for(HashMap.Entry e: body.entrySet()){ + if(sb.length() > 0){ + sb.append('&'); + } + try { + sb.append(URLEncoder.encode(e.getKey(), "UTF-8")).append('=').append(URLEncoder.encode(e.getValue(), "UTF-8")); + } catch (UnsupportedEncodingException ignored) { + } + } + String requestBody = sb.toString(); + try { + return requestBody.getBytes("utf-8"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + @Override + public void deliverError(VolleyError error) { + if (error == null) { + deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null))); + } else if (error.networkResponse == null) { + int statusCode; + if (error instanceof TimeoutError) + statusCode = 408; + else + statusCode = 500; + deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null))); + } else { + final int status = error.networkResponse.statusCode; + if (status == 302) { + deliverResponse(new Result(null, error.networkResponse.headers)); + } else { + deliver(error); + } + } + } + + private void deliver(VolleyError error) { + errorResponseListener.emit(error); + super.deliverError(error); + } + + @Override + protected void deliverResponse(Result response) { + successResponseListener.emit(response); + super.deliverResponse(response); + } + + public Map getHeaders() throws AuthFailureError { + Map params = super.getHeaders(); + if (cookies != null) { + if (params==null) + params = new HashMap<>(); + else + params = new HashMap<>(params); + StringBuilder newStr = new StringBuilder(); + for (String key : cookies.keySet()) + newStr.append(key).append("=").append(cookies.get(key)).append(";"); + newStr = new StringBuilder(newStr.substring(0, newStr.length() - 1)); + params.put("Cookie", newStr.toString()); + } + + return params; + } + }; + requestQueue.add(request); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + public HTTPNetwork getService() { + // Return this instance of LocalService so clients can call public methods + return HTTPNetwork.this; + } + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPService.java b/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPService.java index 0425330..a22d6aa 100644 --- a/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPService.java +++ b/app/src/main/java/de/sebse/fuplanner/tools/network/HTTPService.java @@ -1,25 +1,22 @@ package de.sebse.fuplanner.tools.network; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.CountDownTimer; +import android.os.IBinder; -import com.android.volley.AuthFailureError; -import com.android.volley.NetworkResponse; -import com.android.volley.Request; -import com.android.volley.RequestQueue; import com.android.volley.Response; -import com.android.volley.TimeoutError; import com.android.volley.VolleyError; -import com.android.volley.toolbox.Volley; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.HashMap; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import androidx.annotation.Nullable; import de.sebse.fuplanner.tools.EventListener; +import de.sebse.fuplanner.tools.NewAsyncQueue; import de.sebse.fuplanner.tools.logging.Logger; /** @@ -27,180 +24,185 @@ import de.sebse.fuplanner.tools.logging.Logger; */ public class HTTPService { - private final RequestQueue requestQueue; private final Context mContext; + private NewAsyncQueue mQueue = new NewAsyncQueue("HTTPService"); protected final Logger log = new Logger(this); - private final EventListener errorResponseListener = new EventListener<>(); - private final EventListener successResponseListener = new EventListener<>(); + + private int mRequestCount = 0; + private final HashMap> errorListeners = new HashMap<>(); + private final HashMap> successListeners = new HashMap<>(); + private CountDownTimer mDisconnectTimer = null; + private HTTPNetwork mService; + private boolean mBound = false; + private boolean mWaitForBound = false; + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + log.d("connected HTTP", HTTPService.this.mQueue.name, mWaitForBound); + HTTPNetwork.LocalBinder binder = (HTTPNetwork.LocalBinder) service; + mService = binder.getService(); + mBound = true; + for (String listenerKey: errorListeners.keySet()) { + mService.addErrorListener(listenerKey, errorListeners.get(listenerKey)); + } + for (String listenerKey: successListeners.keySet()) { + mService.addSuccessListener(listenerKey, successListeners.get(listenerKey)); + } + if (mWaitForBound) { + mWaitForBound = false; + mQueue.next(); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + log.d("disconnected HTTP", HTTPService.this.mQueue.name, mWaitForBound); + mBound = false; + mService = null; + } + }; public HTTPService(Context context) { this.mContext = context; - requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(false)); + } + + public HTTPService(Context context, String name) { + this.mContext = context; + this.mQueue.name = name; } public void addErrorListener(String id, EventListener.EventFunction listener) { - errorResponseListener.add(id, listener); + errorListeners.put(id, listener); + connect(); + mQueue.add(() -> { + mService.addErrorListener(id, listener); + mQueue.next(); + disconnect(); + }); } public void removeErrorListener(String id) { - errorResponseListener.remove(id); + errorListeners.remove(id); + connect(); + mQueue.add(() -> { + mService.removeErrorListener(id); + mQueue.next(); + disconnect(); + }); } public void addSuccessListener(String id, EventListener.EventFunction listener) { - successResponseListener.add(id, listener); + successListeners.put(id, listener); + connect(); + mQueue.add(() -> { + mService.addSuccessListener(id, listener); + mQueue.next(); + disconnect(); + }); } public void removeSuccessListener(String id) { - successResponseListener.remove(id); + successListeners.remove(id); + connect(); + mQueue.add(() -> { + mService.removeSuccessListener(id); + mQueue.next(); + disconnect(); + }); } protected void head(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error) { - get(url, cookies, response, error, true); + log.d("HEAD", url); + connect(); + mQueue.add(() -> { + mService.head(url, cookies, response1 -> { + log.d("response", url); + response.onResponse(response1); + disconnect(); + }, error1 -> { + log.d("error", url); + error.onErrorResponse(error1); + disconnect(); + }); + mQueue.next(); + }); } protected void get(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error) { - get(url, cookies, response, error, false); - } - - private void get(String url, @Nullable final HashMap cookies, Response.Listener response, Response.ErrorListener error, boolean noBody) { - int requestMethod = noBody ? Request.Method.HEAD : Request.Method.GET; - HttpRequest request = new HttpRequest(requestMethod, url, response, error) { - @Override - public void deliverError(VolleyError error) { - if (error == null) { - deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null))); - } else if (error.networkResponse == null) { - int statusCode; - if (error instanceof TimeoutError) - statusCode = 408; - else - statusCode = 500; - deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null))); - } else { - final int status = error.networkResponse.statusCode; - if (status == 302) { - deliverResponse(new Result(null, error.networkResponse.headers)); - } else { - deliver(error); - } - } - } - - @Override - protected void deliverResponse(Result response) { - successResponseListener.emit(response); - super.deliverResponse(response); - } - - private void deliver(VolleyError error) { - errorResponseListener.emit(error); - super.deliverError(error); - } - - public Map getHeaders() throws AuthFailureError { - Map params = super.getHeaders(); - if (cookies != null) { - if (params==null) - params = new HashMap<>(); - else - params = new HashMap<>(params); - StringBuilder newStr = new StringBuilder(); - for (String key : cookies.keySet()) - newStr.append(key).append("=").append(cookies.get(key)).append(";"); - newStr = new StringBuilder(newStr.substring(0, newStr.length() - 1)); - params.put("Cookie", newStr.toString()); - } - - return params; - } - }; - requestQueue.add(request); + log.d("GET", url); + connect(); + mQueue.add(() -> { + mService.get(url, cookies, response1 -> { + log.d("response", url); + response.onResponse(response1); + disconnect(); + }, error1 -> { + log.d("error", url); + error.onErrorResponse(error1); + disconnect(); + }); + mQueue.next(); + }); } protected void post(String url, @Nullable final HashMap cookies, @Nullable final HashMap body, Response.Listener response, Response.ErrorListener error) { - HttpRequest request = new HttpRequest(Request.Method.POST, url, response, error) { - @Override - public String getBodyContentType() { - return "application/x-www-form-urlencoded"; - } - - @Override - public byte[] getBody() { - if (body==null) { - return null; - } - StringBuilder sb = new StringBuilder(); - for(HashMap.Entry e: body.entrySet()){ - if(sb.length() > 0){ - sb.append('&'); - } - try { - sb.append(URLEncoder.encode(e.getKey(), "UTF-8")).append('=').append(URLEncoder.encode(e.getValue(), "UTF-8")); - } catch (UnsupportedEncodingException ignored) { - } - } - String requestBody = sb.toString(); - try { - return requestBody.getBytes("utf-8"); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - @Override - public void deliverError(VolleyError error) { - if (error == null) { - deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null))); - } else if (error.networkResponse == null) { - int statusCode; - if (error instanceof TimeoutError) - statusCode = 408; - else - statusCode = 500; - deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null))); - } else { - final int status = error.networkResponse.statusCode; - if (status == 302) { - deliverResponse(new Result(null, error.networkResponse.headers)); - } else { - deliver(error); - } - } - } - - private void deliver(VolleyError error) { - errorResponseListener.emit(error); - super.deliverError(error); - } - - @Override - protected void deliverResponse(Result response) { - successResponseListener.emit(response); - super.deliverResponse(response); - } - - public Map getHeaders() throws AuthFailureError { - Map params = super.getHeaders(); - if (cookies != null) { - if (params==null) - params = new HashMap<>(); - else - params = new HashMap<>(params); - StringBuilder newStr = new StringBuilder(); - for (String key : cookies.keySet()) - newStr.append(key).append("=").append(cookies.get(key)).append(";"); - newStr = new StringBuilder(newStr.substring(0, newStr.length() - 1)); - params.put("Cookie", newStr.toString()); - } - - return params; - } - }; - requestQueue.add(request); + log.d("POST", url); + connect(); + mQueue.add(() -> { + mService.post(url, cookies, body, response1 -> { + log.d("response", url); + response.onResponse(response1); + disconnect(); + }, error1 -> { + log.d("error", url); + error.onErrorResponse(error1); + disconnect(); + }); + mQueue.next(); + }); } - protected Context getContext() { - return mContext; + private void connect() { + if (!mBound) { + Intent intent = new Intent(getContext(), HTTPNetwork.class); + getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + if (!mWaitForBound) mQueue.add(() -> {}); + mWaitForBound = true; + } + if (mDisconnectTimer != null) { + mDisconnectTimer.cancel(); + mDisconnectTimer = null; + } + mRequestCount++; + log.d("connect count", HTTPService.this.mQueue.name, mRequestCount); + } + + private void disconnect() { + mRequestCount--; + log.d("disconnect count", HTTPService.this.mQueue.name, mRequestCount); + if (mDisconnectTimer == null) { + if (mBound && mRequestCount == 0) { + mBound = false; + mService = null; + getContext().unbindService(mConnection); + } + /*mDisconnectTimer = new CountDownTimer(60 * 1000, 60 * 1000) { + @Override + public void onTick(long millisUntilFinished) { + + } + + @Override + public void onFinish() { + if (mBound) { + getContext().unbindService(mConnection); + mQueue.add(() -> {}); + mWaitForUnbound = true; + } + } + };*/ + } } @@ -222,4 +224,8 @@ public class HTTPService { } return result; } + + protected Context getContext() { + return mContext; + } } diff --git a/app/src/main/res/xml/service_kvv.xml b/app/src/main/res/xml/service_kvv.xml deleted file mode 100644 index 1dbe851..0000000 --- a/app/src/main/res/xml/service_kvv.xml +++ /dev/null @@ -1,8 +0,0 @@ - -