512 lines
21 KiB
Java
512 lines
21 KiB
Java
package com.rubenvandeven.emotionhero;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.util.Log;
|
|
|
|
import com.google.gson.Gson;
|
|
import com.loopj.android.http.AsyncHttpClient;
|
|
import com.loopj.android.http.AsyncHttpResponseHandler;
|
|
import com.loopj.android.http.JsonHttpResponseHandler;
|
|
import com.loopj.android.http.RequestHandle;
|
|
import com.loopj.android.http.RequestParams;
|
|
import com.loopj.android.http.SyncHttpClient;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.text.DateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Locale;
|
|
import java.util.TimeZone;
|
|
|
|
import cz.msebera.android.httpclient.Header;
|
|
import cz.msebera.android.httpclient.HeaderElement;
|
|
import cz.msebera.android.httpclient.ParseException;
|
|
import cz.msebera.android.httpclient.entity.StringEntity;
|
|
|
|
/**
|
|
* Created by ruben on 01/09/16.
|
|
*
|
|
* A way to interact with api.emotionhero.com
|
|
*/
|
|
|
|
public class ApiRestClient {
|
|
private static final String BASE_URL = "https://api.emotionhero.com";
|
|
|
|
/**
|
|
* For some reason validation of SSL certificate needs to be disabled (using true).
|
|
* Otherwise async-http-client tries to validate other virtualhost (rubenvandeven.com) with api.emotionhero.com
|
|
* which obviously is not the same domain. For some reason the connection is handles properly
|
|
* by apache (so it detects the right VirtualHost...) ... odd :-(
|
|
*/
|
|
private static AsyncHttpClient client = new AsyncHttpClient(true, 80, 443);
|
|
|
|
private String jwt;
|
|
|
|
private Player player;
|
|
|
|
public ApiRestClient(Player player) {
|
|
this.player = player;
|
|
}
|
|
|
|
public void registerIfNeeded() {
|
|
if(player.getJWT() == null) {
|
|
requestWithJWT(null, null, null, null, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For now call register endpoint. Given JWT should have a long enough lifetime (for now)
|
|
* @todo However, custom token endpoint should be used eventually
|
|
* @param method
|
|
* @param url
|
|
* @param postBody
|
|
* @param getParams
|
|
* @param responseHandler
|
|
*/
|
|
public void requestWithJWT(final String method, final String url, final StringEntity postBody, final RequestParams getParams, final AsyncHttpResponseHandler responseHandler) {
|
|
// sync call, so we can return a value!
|
|
Log.v("API", "register");
|
|
client.post(BASE_URL + "/api/register", null, new JsonHttpResponseHandler() {
|
|
@Override
|
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
|
// If the response is JSONObject instead of expected JSONArray
|
|
try {
|
|
String token = response.getString("jwt");
|
|
String remoteId = response.getString("id");
|
|
Log.d("API", "Token " + token);
|
|
Log.d("API", "RemoteId" + remoteId);
|
|
getPlayer().setJWT(token);
|
|
getPlayer().setRemoteId(remoteId);
|
|
|
|
if(method != null) {
|
|
ApiRestClient.this.request( method, url, postBody, getParams, responseHandler);
|
|
}
|
|
} catch (JSONException e) {
|
|
// responseHandler.sendFailureMessage(500, null, null);
|
|
// failure!!
|
|
// retrying later probably....
|
|
Log.e("API", "Failed request");
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
|
|
handleOnFailure(statusCode, headers, responseString, throwable);
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject response) {
|
|
onFailure(statusCode, headers, response == null ? null:response.toString(), throwable);
|
|
}
|
|
});
|
|
}
|
|
|
|
public Player getPlayer() {
|
|
return player;
|
|
}
|
|
|
|
class TokenHeader implements Header {
|
|
/**
|
|
* Get the name of the Header.
|
|
*
|
|
* @return the name of the Header, never {@code null}
|
|
*/
|
|
@Override
|
|
public String getName() {
|
|
return "X-Access-Token";
|
|
}
|
|
|
|
/**
|
|
* Get the value of the Header.
|
|
*
|
|
* @return the value of the Header, may be {@code null}
|
|
*/
|
|
@Override
|
|
public String getValue() {
|
|
return "Bearer " + player.getJWT();
|
|
}
|
|
|
|
/**
|
|
* Parses the value.
|
|
*
|
|
* @return an array of {@link HeaderElement} entries, may be empty, but is never {@code null}
|
|
* @throws ParseException
|
|
*/
|
|
@Override
|
|
public HeaderElement[] getElements() throws ParseException {
|
|
return new HeaderElement[]{};
|
|
}
|
|
}
|
|
|
|
public void request(String method, String url, StringEntity postBody, RequestParams requestParams, AsyncHttpResponseHandler responseHandler) {
|
|
Header[] headers = new Header[]{new TokenHeader()};
|
|
Log.d("API", "Do request to: " + url);
|
|
if(method == "post") {
|
|
if(postBody != null) {
|
|
client.post(player.getContext(), url, headers, postBody,"application/json", responseHandler);
|
|
} else {
|
|
// let content type be determinded by sender
|
|
client.post(player.getContext(), url, headers, requestParams, null, responseHandler);
|
|
}
|
|
} else {
|
|
client.get(player.getContext(), url, headers, requestParams, responseHandler);
|
|
}
|
|
}
|
|
|
|
public void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
|
|
String jwt = player.getJWT();
|
|
if(jwt != null) {
|
|
request("get", getAbsoluteUrl(url), null, params, responseHandler);
|
|
} else {
|
|
requestWithJWT("get", getAbsoluteUrl(url), null, params, responseHandler);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Post string body with JWT (stringentity can be json-data)
|
|
* @param url
|
|
* @param postBody
|
|
* @param responseHandler
|
|
*/
|
|
public void post(String url, StringEntity postBody, AsyncHttpResponseHandler responseHandler) {
|
|
String jwt = player.getJWT();
|
|
if(jwt != null) {
|
|
request("post", getAbsoluteUrl(url), postBody, null, responseHandler);
|
|
} else {
|
|
requestWithJWT("post", getAbsoluteUrl(url), postBody, null, responseHandler);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Post params (ie. multipart) with JWT (stringentity can be json-data)
|
|
* @param url
|
|
* @param params
|
|
* @param responseHandler
|
|
*/
|
|
public void post(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
|
|
String jwt = player.getJWT();
|
|
if(jwt != null) {
|
|
request("post", getAbsoluteUrl(url), null, params, responseHandler);
|
|
} else {
|
|
requestWithJWT("post", getAbsoluteUrl(url), null, params, responseHandler);
|
|
}
|
|
}
|
|
|
|
private static String getAbsoluteUrl(String relativeUrl) {
|
|
return BASE_URL + relativeUrl;
|
|
}
|
|
|
|
public void syncGame(final Game game) {
|
|
if(game.remoteId != null)
|
|
return;
|
|
|
|
RequestParams params = new RequestParams();
|
|
|
|
JSONObject j = new JSONObject();
|
|
try {
|
|
j.put("lvl_id", game.scenario.id);
|
|
j.put("score", game.score);
|
|
j.put("bonus", game.bonus);
|
|
TimeZone tz = TimeZone.getTimeZone("UTC");
|
|
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
df.setTimeZone(tz);
|
|
j.put("time", df.format(game.time));
|
|
j.put("lost_face_time", game.lostFaceTime);
|
|
|
|
JSONArray jHits = new JSONArray();
|
|
j.put("hits", jHits);
|
|
|
|
for(Hit hit: game.hits.values()) {
|
|
JSONObject jHit = new JSONObject();
|
|
jHit.put("id", hit.id);
|
|
jHit.put("target_index", hit.target.index);
|
|
jHit.put("score", hit.score);
|
|
jHit.put("bonus", hit.bonus);
|
|
jHit.put("glasses", hit.glasses);
|
|
jHit.put("ethnicity", hit.ethnicity);
|
|
jHit.put("age", hit.age);
|
|
jHit.put("gender", hit.gender);
|
|
|
|
JSONObject jExpressions = new JSONObject();
|
|
jHit.put("expressions", jExpressions);
|
|
JSONObject jEmotions = new JSONObject();
|
|
jHit.put("emotions", jEmotions);
|
|
JSONObject jPoints = new JSONObject();
|
|
jHit.put("points", jPoints);
|
|
|
|
for(Expression e: Expression.values()) {
|
|
jExpressions.put(e.getDbName(), hit.expressions.get(e));
|
|
}
|
|
for(Emotion e: Emotion.values()) {
|
|
jEmotions.put(e.getDbName(), hit.emotions.get(e));
|
|
}
|
|
int i=0;
|
|
for(PointF p: hit.points) {
|
|
JSONObject jPoint = new JSONObject();
|
|
jPoint.put("x", p.x);
|
|
jPoint.put("y", p.y);
|
|
jPoints.put(""+i, jPoint);
|
|
i++;
|
|
}
|
|
jHits.put(jHit);
|
|
}
|
|
|
|
|
|
JSONArray jAchievements = new JSONArray();
|
|
j.put("achievements", jAchievements);
|
|
for(Achievement achievement: game.achievements) {
|
|
jAchievements.put(achievement.getId());
|
|
}
|
|
|
|
} catch (JSONException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
StringEntity postBody = null;
|
|
|
|
try {
|
|
postBody = new StringEntity(j.toString());
|
|
} catch (UnsupportedEncodingException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
this.post("/games", postBody, new JsonHttpResponseHandler(){
|
|
|
|
@Override
|
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
|
Log.d("API",response.toString());
|
|
GameOpenHelper gameHelper = new GameOpenHelper(player.getContext());
|
|
// set remote ids on hits and game.
|
|
try {
|
|
game.remoteId = response.getString("id");
|
|
JSONObject hits = response.getJSONObject("hits");
|
|
gameHelper.saveRemoteId(game);
|
|
for(Hit hit: game.hits.values()) {
|
|
hit.remoteId = hits.getString(Long.toString(hit.id));
|
|
gameHelper.saveRemoteId(hit);
|
|
}
|
|
// Achievement checking now in-game.
|
|
/*JSONArray jAchievements = response.getJSONArray("achievements");
|
|
if(jAchievements.length() > 0){
|
|
ArrayList<Achievement> achievements = new ArrayList<Achievement>(jAchievements.length());
|
|
for (int ai = 0; ai < jAchievements .length(); ai++) {
|
|
achievements.add(AchievementCollection.getInstance().get(jAchievements.getInt(ai)));
|
|
}
|
|
game.achievements = achievements;
|
|
gameHelper.saveAchievementsForGame(game);
|
|
}*/
|
|
|
|
} catch (JSONException e) {
|
|
Log.e("API","Invalid data: " + response.toString());
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
|
|
handleOnFailure(statusCode, headers, responseString, throwable);
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject response) {
|
|
onFailure(statusCode, headers, response == null ? null:response.toString(), throwable);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 1. Cut out the essential parts of the image (privacy)
|
|
* 2. send to server in one batch
|
|
* 3. if successful, remove from device to save storage.
|
|
* @param hits
|
|
*/
|
|
public void sendHitImages(final Collection<Hit> hits) {
|
|
// 1. Cut out the essential parts
|
|
GameOpenHelper gameHelper = player.getGameOpenHelper();
|
|
if(hits == null || hits.size() < 1) {
|
|
return;
|
|
}
|
|
|
|
RequestParams params = new RequestParams();
|
|
|
|
boolean noImages = true;
|
|
|
|
for(Hit hit: hits) {
|
|
Bitmap img = gameHelper.getImageForHit(hit);
|
|
|
|
if(img == null) {
|
|
Log.e("API", "no image for hit " + hit.id);
|
|
continue;
|
|
}
|
|
|
|
noImages = false;
|
|
|
|
// Crop: http://stackoverflow.com/a/31698091
|
|
int margin = (int) ((hit.points[10].x - hit.points[5].x) * 0.05);
|
|
int left = (int) (hit.points[5].x) - margin;
|
|
int right = (int) (hit.points[10].x) + margin;
|
|
int top = (int) (hit.points[6].y < hit.points[9].y ? hit.points[6].y : hit.points[9].y) - 2*margin; // a bit of forehead
|
|
int bottom = (int) (hit.points[5].y > hit.points[10].y ? hit.points[5].y : hit.points[10].y) + margin;
|
|
Rect rect = new Rect(left, top, right, bottom);
|
|
// Be sure that there is at least 1px to slice.
|
|
if(!(rect.left < rect.right && rect.top < rect.bottom)) {
|
|
Log.e("API", "Error in point positions."+" left: " + rect.left + " right: " + rect.right + " top: " + rect.top + " bottom: " + rect.bottom );
|
|
continue; // strange bug... skip it and drop the file anyway
|
|
}
|
|
// Create our resulting image (150--50),(75--25) = 200x100px
|
|
Bitmap croppedBmp = Bitmap.createBitmap(rect.right-rect.left, rect.bottom-rect.top, Bitmap.Config.ARGB_8888);
|
|
// draw source bitmap into resulting image at given position:
|
|
new Canvas(croppedBmp).drawBitmap(img, -rect.left, -rect.top, null);
|
|
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
croppedBmp.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
|
byte[] imageBytes = stream.toByteArray();
|
|
|
|
params.put(hit.remoteId + ":brows", new ByteArrayInputStream(imageBytes), hit.remoteId + "-brows.jpg", "image/jpeg");
|
|
Log.v("API", "add param: " + hit.remoteId + ":brows - length:" + imageBytes.length );
|
|
|
|
// NOSE
|
|
// Crop: http://stackoverflow.com/a/31698091
|
|
// reuse margin of brows
|
|
left = (int) hit.points[13].x - margin;
|
|
right = (int) hit.points[15].x + margin;
|
|
top = (int) hit.points[11].y - margin;
|
|
bottom = (int) hit.points[14].y + margin;
|
|
Rect rect2 = new Rect(left, top, right, bottom);
|
|
// Be sure that there is at least 1px to slice.
|
|
if(!(rect2.left < rect2.right && rect2.top < rect2.bottom)) {
|
|
Log.e("API", "Error in point positions."+" left: " + rect2.left + " right: " + rect2.right + " top: " + rect2.top + " bottom: " + rect2.bottom );
|
|
continue; // strange bug... skip it and drop the file anyway
|
|
}
|
|
// Create our resulting image (150--50),(75--25) = 200x100px
|
|
croppedBmp = Bitmap.createBitmap(rect2.right-rect2.left, rect2.bottom-rect2.top, Bitmap.Config.ARGB_8888);
|
|
// draw source bitmap into resulting image at given position:
|
|
new Canvas(croppedBmp).drawBitmap(img, -rect2.left, -rect2.top, null);
|
|
|
|
ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
|
|
croppedBmp.compress(Bitmap.CompressFormat.JPEG, 90, stream2);
|
|
byte[] imageBytes2 = stream2.toByteArray();
|
|
|
|
params.put(hit.remoteId + ":nose", new ByteArrayInputStream(imageBytes2), hit.remoteId + "-nose.jpg", "image/jpeg");
|
|
Log.v("API", "add param: " + hit.remoteId + ":nose - length:" + imageBytes.length );
|
|
|
|
// RIGHT MOUTH CORNER
|
|
// Crop: http://stackoverflow.com/a/31698091
|
|
// reuse margin of brows to make a square image
|
|
// left = (int) hit.points[24].x - margin;
|
|
// right = (int) hit.points[24].x + 3 * margin;
|
|
// top = (int) hit.points[24].y - 2 * margin;
|
|
// bottom = (int) hit.points[24].y + 2 * margin;
|
|
// Rect rect3 = new Rect(left, top, right, bottom);
|
|
//// Be sure that there is at least 1px to slice.
|
|
// if(!(rect3.left < rect3.right && rect3.top < rect3.bottom)) {
|
|
// Log.e("API", "Error in point positions."+" left: " + rect3.left + " right: " + rect3.right + " top: " + rect3.top + " bottom: " + rect3.bottom );
|
|
// continue; // strange bug... skip it and drop the file anyway
|
|
// }
|
|
//
|
|
// croppedBmp = Bitmap.createBitmap(rect3.right-rect3.left, rect3.bottom-rect3.top, Bitmap.Config.ARGB_8888);
|
|
//// draw source bitmap into resulting image at given position:
|
|
// new Canvas(croppedBmp).drawBitmap(img, -rect3.left, -rect3.top, null);
|
|
//
|
|
// ByteArrayOutputStream stream3 = new ByteArrayOutputStream();
|
|
// croppedBmp.compress(Bitmap.CompressFormat.JPEG, 90, stream3);
|
|
// byte[] imageBytes3 = stream3.toByteArray();
|
|
//
|
|
// params.put(hit.remoteId + ":mouth_right", new ByteArrayInputStream(imageBytes3), hit.remoteId + "-mouth_right.jpg", "image/jpeg");
|
|
// Log.v("API", "add param: " + hit.remoteId + ":mouth_right - length:" + imageBytes.length );
|
|
|
|
|
|
// LEFT MOUTH CORNER (doing whole mouth :-)
|
|
// Crop: http://stackoverflow.com/a/31698091
|
|
// reuse margin of brows to make a square image
|
|
left = (int) hit.points[20].x - margin;
|
|
right = (int) hit.points[24].x + margin;
|
|
top = (int) (hit.points[21].y < hit.points[23].y ? hit.points[21].y : hit.points[23].y) - margin;
|
|
bottom = (int) hit.points[26].y + margin;
|
|
Rect rect4 = new Rect(left, top, right, bottom);
|
|
// Be sure that there is at least 1px to slice.
|
|
if(!(rect4.left < rect4.right && rect4.top < rect4.bottom)) {
|
|
Log.e("API", "Error in point positions."+" left: " + rect4.left + " right: " + rect4.right + " top: " + rect4.top + " bottom: " + rect4.bottom );
|
|
continue; // strange bug... skip it and drop the file anyway
|
|
}
|
|
|
|
croppedBmp = Bitmap.createBitmap(rect4.right-rect4.left, rect4.bottom-rect4.top, Bitmap.Config.ARGB_8888);
|
|
// draw source bitmap into resulting image at given position:
|
|
new Canvas(croppedBmp).drawBitmap(img, -rect4.left, -rect4.top, null);
|
|
|
|
ByteArrayOutputStream stream4 = new ByteArrayOutputStream();
|
|
croppedBmp.compress(Bitmap.CompressFormat.JPEG, 90, stream4);
|
|
byte[] imageBytes4 = stream4.toByteArray();
|
|
|
|
params.put(hit.remoteId + ":mouth_left", new ByteArrayInputStream(imageBytes4), hit.remoteId + "-mouth_left.jpg", "image/jpeg");
|
|
Log.v("API", "add param: " + hit.remoteId + ":mouth_left - length:" + imageBytes.length );
|
|
}
|
|
|
|
// don't post if there are no images
|
|
// this happens ie. when images are all successfully send
|
|
if(noImages) {
|
|
return;
|
|
}
|
|
|
|
post("/images", params,new JsonHttpResponseHandler(){
|
|
@Override
|
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
|
// 3. remove images :-)
|
|
for(Hit hit: hits) {
|
|
Log.v("API", "clear image for hit: "+ hit.id);
|
|
player.getGameOpenHelper().clearImageForHit(hit);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
|
|
handleOnFailure(statusCode, headers, responseString, throwable);
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject response) {
|
|
onFailure(statusCode, headers, response == null ? null:response.toString(), throwable);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static void handleOnFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
|
|
Log.e("API", "FAILURE ON REQUEST!");
|
|
Log.e("API", throwable.getMessage());
|
|
Log.e("API", "Status: "+statusCode);
|
|
if(headers == null){
|
|
Log.e("API", "\tNULL");
|
|
} else {
|
|
for(Header header: headers) {
|
|
Log.e("API", "\t" + header.getName() + ": " + header.getValue());
|
|
}
|
|
}
|
|
Log.e("API", "Response:");
|
|
if(responseString == null) {
|
|
Log.e("API", "\tNULL!");
|
|
}
|
|
else {
|
|
int maxLogSize = 1000;
|
|
for(int i = 0; i <= responseString.length() / maxLogSize; i++) {
|
|
int start = i * maxLogSize;
|
|
int end = (i+1) * maxLogSize;
|
|
end = end > responseString.length() ? responseString.length() : end;
|
|
Log.e("API", responseString.substring(start, end));
|
|
}
|
|
}
|
|
}
|
|
}
|