HTTP service implemented

This commit is contained in:
Caesar2011
2019-02-11 00:20:38 +01:00
parent b4e11e3d10
commit d098093c8a
8 changed files with 408 additions and 167 deletions

View File

@@ -87,6 +87,13 @@
<action android:name="android.app.Service" />
</intent-filter>
</service>
<service
android:name=".tools.network.HTTPNetwork"
android:exported="false">
<intent-filter>
<action android:name="android.app.Service" />
</intent-filter>
</service>
</application>

View File

@@ -56,7 +56,6 @@ public class KVV extends Service {
public KVV() {
Logger log = new Logger(this);
log.d("constructor");
}
// Binder given to clients

View File

@@ -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();

View File

@@ -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(() -> {});
}

View File

@@ -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<AsyncQueueCallback> 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);

View File

@@ -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<VolleyError> errorResponseListener = new EventListener<>();
private final EventListener<Result> 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<VolleyError> listener) {
errorResponseListener.add(id, listener);
}
public void removeErrorListener(String id) {
errorResponseListener.remove(id);
}
public void addSuccessListener(String id, EventListener.EventFunction<Result> listener) {
successResponseListener.add(id, listener);
}
public void removeSuccessListener(String id) {
successResponseListener.remove(id);
}
protected void head(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> response, Response.ErrorListener error) {
get(url, cookies, response, error, true);
}
protected void get(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> response, Response.ErrorListener error) {
get(url, cookies, response, error, false);
}
private void get(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> 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<String, String> getHeaders() throws AuthFailureError {
Map<String, String> 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<String, String> cookies, @Nullable final HashMap<String, String> body, Response.Listener<Result> 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<String, String> 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<String, String> getHeaders() throws AuthFailureError {
Map<String, String> 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;
}
}
}

View File

@@ -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<VolleyError> errorResponseListener = new EventListener<>();
private final EventListener<Result> successResponseListener = new EventListener<>();
private int mRequestCount = 0;
private final HashMap<String, EventListener.EventFunction<VolleyError>> errorListeners = new HashMap<>();
private final HashMap<String, EventListener.EventFunction<Result>> 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<VolleyError> 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<Result> 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<String, String> cookies, Response.Listener<Result> 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<String, String> cookies, Response.Listener<Result> response, Response.ErrorListener error) {
get(url, cookies, response, error, false);
}
private void get(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> 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<String, String> getHeaders() throws AuthFailureError {
Map<String, String> 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<String, String> cookies, @Nullable final HashMap<String, String> body, Response.Listener<Result> 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<String, String> 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<String, String> getHeaders() throws AuthFailureError {
Map<String, String> 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;
}
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="de.sebse.fuplanner.contentprovider.kvv.modules"
android:accountType="de.sebse.fuplanner.fuauth"
android:userVisible="true"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
android:supportsUploading="false"/>