From f5fa4ead2f7f4c96f937a8a8a16f4ae6ed6a36f4 Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 3 Sep 2016 18:22:18 +0100 Subject: [PATCH] Scores are now saved in SQLite and can be send to the /me/games API endpoint --- .../emotionhero/ApiRestClient.java | 135 +++++- .../rubenvandeven/emotionhero/Emotion.java | 5 + .../rubenvandeven/emotionhero/Expression.java | 89 ++++ .../com/rubenvandeven/emotionhero/Game.java | 49 +++ .../emotionhero/GameOpenHelper.java | 401 ++++++++++++++++++ .../emotionhero/GamingActivity.java | 6 +- .../emotionhero/HighscoreActivity.java | 14 +- .../com/rubenvandeven/emotionhero/Hit.java | 130 ++++++ .../com/rubenvandeven/emotionhero/Player.java | 9 + .../rubenvandeven/emotionhero/PlayerInfo.java | 6 - .../rubenvandeven/emotionhero/Scenario.java | 71 ++-- .../emotionhero/ScenarioView.java | 7 +- 12 files changed, 852 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/com/rubenvandeven/emotionhero/Expression.java create mode 100644 app/src/main/java/com/rubenvandeven/emotionhero/Game.java create mode 100644 app/src/main/java/com/rubenvandeven/emotionhero/GameOpenHelper.java create mode 100644 app/src/main/java/com/rubenvandeven/emotionhero/Hit.java diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/ApiRestClient.java b/app/src/main/java/com/rubenvandeven/emotionhero/ApiRestClient.java index 39d4eb3..9e68d55 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/ApiRestClient.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/ApiRestClient.java @@ -1,20 +1,27 @@ package com.rubenvandeven.emotionhero; import android.content.Context; +import android.graphics.PointF; 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.UnsupportedEncodingException; + 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. @@ -26,7 +33,7 @@ public class ApiRestClient { private static final String BASE_URL = "http://api.emotionhero.com"; // TODO: https! private static AsyncHttpClient client = new AsyncHttpClient(); - private static SyncHttpClient syncClient = new SyncHttpClient(); +// private static SyncHttpClient syncClient = new SyncHttpClient(); private String jwt; @@ -38,7 +45,7 @@ public class ApiRestClient { public void registerIfNeeded() { if(player.getJWT() == null) { - requestWithJWT(null, null, null, null); + requestWithJWT(null, null, null, null, null); } } @@ -47,10 +54,11 @@ public class ApiRestClient { * @todo However, custom token endpoint should be used eventually * @param method * @param url - * @param params + * @param postBody + * @param getParams * @param responseHandler */ - public void requestWithJWT(final String method, final String url, final RequestParams params, final AsyncHttpResponseHandler 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! client.post(BASE_URL + "/api/register", null, new JsonHttpResponseHandler() { @Override @@ -65,7 +73,7 @@ public class ApiRestClient { getPlayer().setRemoteId(remoteId); if(method != null) { - ApiRestClient.this.request( method, url, params, responseHandler); + ApiRestClient.this.request( method, url, postBody, getParams, responseHandler); } } catch (JSONException e) { // responseHandler.sendFailureMessage(500, null, null); @@ -115,35 +123,136 @@ public class ApiRestClient { } } - public void request(String method, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { + public void request(String method, String url, StringEntity postBody, RequestParams getParams, AsyncHttpResponseHandler responseHandler) { Header[] headers = new Header[]{new TokenHeader()}; Log.d("API", "Do request to: " + url); if(method == "post") { - client.post(player.getContext(), url, headers,params,"application/json", responseHandler); + client.post(player.getContext(), url, headers, postBody,"application/json", responseHandler); } else { - client.get(player.getContext(), url, headers, params, responseHandler); + client.get(player.getContext(), url, headers, getParams, responseHandler); } } public void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { String jwt = player.getJWT(); if(jwt != null) { - request("get", getAbsoluteUrl(url), params, responseHandler); + request("get", getAbsoluteUrl(url), null, params, responseHandler); } else { - requestWithJWT("get", getAbsoluteUrl(url), params, responseHandler); + requestWithJWT("get", getAbsoluteUrl(url), null, params, responseHandler); } } - public void post(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) { + public void post(String url, StringEntity postBody, AsyncHttpResponseHandler responseHandler) { String jwt = player.getJWT(); if(jwt != null) { - request("post", getAbsoluteUrl(url), params, responseHandler); + request("post", getAbsoluteUrl(url), postBody, null, responseHandler); } else { - requestWithJWT("post", getAbsoluteUrl(url), params, responseHandler); + requestWithJWT("post", getAbsoluteUrl(url), postBody, null, 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("time", game.time); + + JSONArray jHits = new JSONArray(); + j.put("hits", jHits); + + for(Hit hit: game.hits.values()) { + JSONObject jHit = new JSONObject(); + jHit.put("target_index", hit.target.index); + jHit.put("score", hit.score); + 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); + } + + } catch (JSONException e) { + e.printStackTrace(); + } + + StringEntity postBody = null; + + try { + postBody = new StringEntity(j.toString()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + this.post("/me/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); + } + } 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); + } + + }); + } + + 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); + Log.e("API", "Headers:"); + for(Header header: headers) { + Log.e("API", "\t" + header.getName() + ": " + header.getValue()); + } + Log.e("API", "Response:"); + Log.e("API", responseString); + } } diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Emotion.java b/app/src/main/java/com/rubenvandeven/emotionhero/Emotion.java index 2c9b8fb..35c8a7f 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/Emotion.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Emotion.java @@ -62,4 +62,9 @@ public enum Emotion { return Color.BLACK; } + + + public String getDbName() { + return this.name().toLowerCase(); + } } diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Expression.java b/app/src/main/java/com/rubenvandeven/emotionhero/Expression.java new file mode 100644 index 0000000..72d6499 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Expression.java @@ -0,0 +1,89 @@ +package com.rubenvandeven.emotionhero; + +import com.affectiva.android.affdex.sdk.detector.Face; + +/** + * Created by ruben on 02/09/16. + * + * Strictly not all expression variables, but they all are floats... + */ + +public enum Expression { + ROLL, + PITCH, + YAW, + INTER_OCULAR_DISTANCE, + MOUTH_OPEN, + LIP_PRESS, + BROW_RAISE, + NOSE_WRINKLER, + LIP_DEPRESSOR, + BROW_FURROW, + ATTENTION, + SMILE, + INNER_BROW_RAISER, + CHIN_RAISER, + SMIRK, + LIP_SUCK, + UPPER_LIP_RAISER, + LIP_PUCKER, + EYE_CLOSURE, + ENGAGEMENT, + VALENCE; + + public float getValueFromFace(Face face) + { + if(face == null) + return 0f; + + switch (this) { + case ROLL: + return face.measurements.orientation.getRoll(); + case PITCH: + return face.measurements.orientation.getPitch(); + case YAW: + return face.measurements.orientation.getYaw(); + case INTER_OCULAR_DISTANCE: + return face.measurements.getInterocularDistance(); + case MOUTH_OPEN: + return face.expressions.getMouthOpen(); + case LIP_PRESS: + return face.expressions.getLipPress(); + case BROW_RAISE: + return face.expressions.getBrowRaise(); + case NOSE_WRINKLER: + return face.expressions.getNoseWrinkle(); + case LIP_DEPRESSOR: + return face.expressions.getLipCornerDepressor(); + case BROW_FURROW: + return face.expressions.getBrowFurrow(); + case ATTENTION: + return face.expressions.getAttention(); + case SMILE: + return face.expressions.getSmile(); + case INNER_BROW_RAISER: + return face.expressions.getBrowRaise(); + case CHIN_RAISER: + return face.expressions.getChinRaise(); + case SMIRK: + return face.expressions.getSmirk(); + case LIP_SUCK: + return face.expressions.getLipSuck(); + case UPPER_LIP_RAISER: + return face.expressions.getUpperLipRaise(); + case LIP_PUCKER: + return face.expressions.getLipPucker(); + case EYE_CLOSURE: + return face.expressions.getEyeClosure(); + case ENGAGEMENT: + return face.emotions.getEngagement(); + case VALENCE: + return face.emotions.getValence(); + } + return 0; + } + + public String getDbName() { + return this.name().toLowerCase(); + } +} diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Game.java b/app/src/main/java/com/rubenvandeven/emotionhero/Game.java new file mode 100644 index 0000000..d9a0fe2 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Game.java @@ -0,0 +1,49 @@ +package com.rubenvandeven.emotionhero; + +import com.loopj.android.http.JsonHttpResponseHandler; +import com.loopj.android.http.RequestParams; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by ruben on 02/09/16. + * + * A play of a level by current user at a specific time + */ + +public class Game { + public Long id; + public Scenario scenario; + public float score; + public Date time; + public String remoteId; + public Map hits = new HashMap<>(); + + public Game(Long id, int lvl_id, float score, Date time, String remoteId) { + this(id, new Scenario(lvl_id, null), score, time, remoteId); + } + + public Game(Long id, Scenario scenario, float score, Date time, String remoteId) { + this.id = id; + this.scenario= scenario; + this.score = score; + this.time = time == null ? new Date() : time; + this.remoteId = remoteId; + } + + public void addHit(Hit hit) { + hits.put(hit.target.index, hit); + score = calculateScore(); + } + + private float calculateScore() { + float s = 0; + for(Hit hit: hits.values()) { + s += hit.score; + } + return s; + } +} diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/GameOpenHelper.java b/app/src/main/java/com/rubenvandeven/emotionhero/GameOpenHelper.java new file mode 100644 index 0000000..02215f7 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/emotionhero/GameOpenHelper.java @@ -0,0 +1,401 @@ +package com.rubenvandeven.emotionhero; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.PointF; + +import java.util.ArrayList; +import java.util.Date; + +/** + * Created by ruben on 02/09/16. + * + * To test data: + * adb -d shell "run-as com.rubenvandeven.emotionhero ls /data/data/com.rubenvandeven.emotionhero/databases/" + * adb -d shell "run-as com.rubenvandeven.emotionhero cp /data/data/com.rubenvandeven.emotionhero/databases/emotionhero.db /sdcard/emotionhero.db" + * adb pull /sdcard/emotionhero.db + */ +public class GameOpenHelper extends SQLiteOpenHelper { + private static final int DATABASE_VERSION = 2; + private static final String GAME_TABLE_NAME = "games"; + private static final String GAME_TABLE_CREATE = + "CREATE TABLE " + GAME_TABLE_NAME + " (" + + "id INTEGER NOT NULL," + + "lvl_id INTEGER NOT NULL," + + "score VARCHAR(255) NOT NULL," + + "time INTEGER NOT NULL," + + "remoteId VARCHAR(255) DEFAULT NULL, " + + "PRIMARY KEY(id));"; + private static final String[] GAME_PROJECTION = { + "id", + "lvl_id", + "score", + "time", + "remoteId" + }; + + private static final String HIT_TABLE_NAME = "hits"; + public static final String HIT_TABLE_CREATE = + "CREATE TABLE hits (" + + "id INTEGER NOT NULL," + + "game_id INTEGER NOT NULL," + + "target_index INTEGER NOT_NULL," + + "score VARCHAR(20)," + + "remoteId VARCHAR(20)," + + "glasses VARCHAR(20)," + + "ethnicity VARCHAR(20)," + + "age VARCHAR(20)," + + "gender VARCHAR(1)," + + "anger VARCHAR(20)," + + "contempt VARCHAR(20)," + + "disgust VARCHAR(20)," + + "fear VARCHAR(20)," + + "joy VARCHAR(20)," + + "sadness VARCHAR(20)," + + "surprise VARCHAR(20)," + + "roll VARCHAR(20)," + + "pitch VARCHAR(20)," + + "yaw VARCHAR(20)," + + "inter_ocular_distance VARCHAR(20)," + + "mouth_open VARCHAR(20)," + + "lip_press VARCHAR(20)," + + "brow_raise VARCHAR(20)," + + "nose_wrinkler VARCHAR(20)," + + "lip_depressor VARCHAR(20)," + + "brow_furrow VARCHAR(20)," + + "attention VARCHAR(20)," + + "smile VARCHAR(20)," + + "inner_brow_raiser VARCHAR(20)," + + "chin_raiser VARCHAR(20)," + + "smirk VARCHAR(20)," + + "lip_suck VARCHAR(20)," + + "upper_lip_raiser VARCHAR(20)," + + "lip_pucker VARCHAR(20)," + + "eye_closure VARCHAR(20)," + + "engagement VARCHAR(20)," + + "valence VARCHAR(20)," + + "point_0x VARCHAR(20)," + + "point_0y VARCHAR(20)," + + "point_1x VARCHAR(20)," + + "point_1y VARCHAR(20)," + + "point_2x VARCHAR(20)," + + "point_2y VARCHAR(20)," + + "point_3x VARCHAR(20)," + + "point_3y VARCHAR(20)," + + "point_4x VARCHAR(20)," + + "point_4y VARCHAR(20)," + + "point_5x VARCHAR(20)," + + "point_5y VARCHAR(20)," + + "point_6x VARCHAR(20)," + + "point_6y VARCHAR(20)," + + "point_7x VARCHAR(20)," + + "point_7y VARCHAR(20)," + + "point_8x VARCHAR(20)," + + "point_8y VARCHAR(20)," + + "point_9x VARCHAR(20)," + + "point_9y VARCHAR(20)," + + "point_10x VARCHAR(20)," + + "point_10y VARCHAR(20)," + + "point_11x VARCHAR(20)," + + "point_11y VARCHAR(20)," + + "point_12x VARCHAR(20)," + + "point_12y VARCHAR(20)," + + "point_13x VARCHAR(20)," + + "point_13y VARCHAR(20)," + + "point_14x VARCHAR(20)," + + "point_14y VARCHAR(20)," + + "point_15x VARCHAR(20)," + + "point_15y VARCHAR(20)," + + "point_16x VARCHAR(20)," + + "point_16y VARCHAR(20)," + + "point_17x VARCHAR(20)," + + "point_17y VARCHAR(20)," + + "point_18x VARCHAR(20)," + + "point_18y VARCHAR(20)," + + "point_19x VARCHAR(20)," + + "point_19y VARCHAR(20)," + + "point_20x VARCHAR(20)," + + "point_20y VARCHAR(20)," + + "point_21x VARCHAR(20)," + + "point_21y VARCHAR(20)," + + "point_22x VARCHAR(20)," + + "point_22y VARCHAR(20)," + + "point_23x VARCHAR(20)," + + "point_23y VARCHAR(20)," + + "point_24x VARCHAR(20)," + + "point_24y VARCHAR(20)," + + "point_25x VARCHAR(20)," + + "point_25y VARCHAR(20)," + + "point_26x VARCHAR(20)," + + "point_26y VARCHAR(20)," + + "point_27x VARCHAR(20)," + + "point_27y VARCHAR(20)," + + "point_28x VARCHAR(20)," + + "point_28y VARCHAR(20)," + + "point_29x VARCHAR(20)," + + "point_29y VARCHAR(20)," + + "point_30x VARCHAR(20)," + + "point_30y VARCHAR(20)," + + "point_31x VARCHAR(20)," + + "point_31y VARCHAR(20)," + + "point_32x VARCHAR(20)," + + "point_32y VARCHAR(20)," + + "point_33x VARCHAR(20)," + + "point_33y VARCHAR(20)," + + "PRIMARY KEY (id));" ; + private static final String[] HIT_PROJECTION = { + "id","game_id", "target_index","score","remoteId","glasses","ethnicity","age","gender","anger","contempt","disgust","fear","joy","sadness","surprise","roll","pitch","yaw","inter_ocular_distance","mouth_open","lip_press","brow_raise","nose_wrinkler","lip_depressor","brow_furrow","attention","smile","inner_brow_raiser","chin_raiser","smirk","lip_suck","upper_lip_raiser","lip_pucker","eye_closure","engagement","valence","point_0x","point_0y","point_1x","point_1y","point_2x","point_2y","point_3x","point_3y","point_4x","point_4y","point_5x","point_5y","point_6x","point_6y","point_7x","point_7y","point_8x","point_8y","point_9x","point_9y","point_10x","point_10y","point_11x","point_11y","point_12x","point_12y","point_13x","point_13y","point_14x","point_14y","point_15x","point_15y","point_16x","point_16y","point_17x","point_17y","point_18x","point_18y","point_19x","point_19y","point_20x","point_20y","point_21x","point_21y","point_22x","point_22y","point_23x","point_23y","point_24x","point_24y","point_25x","point_25y","point_26x","point_26y","point_27x","point_27y","point_28x","point_28y","point_29x","point_29y","point_30x","point_30y","point_31x","point_31y","point_32x","point_32y","point_33x","point_33y" + }; + + private Context context; + + GameOpenHelper(Context context) { + super(context, "emotionhero.db", null, DATABASE_VERSION); + this.context = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(GAME_TABLE_CREATE); + db.execSQL(HIT_TABLE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + // until now there is only 1 database version.. so no alter table statements + if(oldVersion == 1 && newVersion == 2) { + // run alter statements + db.execSQL("DROP TABLE hits"); + db.execSQL("DROP TABLE games"); + db.execSQL(GAME_TABLE_CREATE); + db.execSQL(HIT_TABLE_CREATE); + } + } + + public void insertGame(Game game) { + // Gets the data repository in write mode + SQLiteDatabase db = getWritableDatabase(); + + // Create a new map of values, where column names are the keys + ContentValues values = new ContentValues(); + values.put("lvl_id", game.scenario.id); + values.put("score", game.score); + values.put("time", game.time.getTime()); + if(game.remoteId == null) { + values.putNull("remoteId"); + } else { + values.put("remoteId", game.remoteId); + } + + // Insert the new row, returning the primary key value of the new row + long newRowId = db.insert(GAME_TABLE_NAME, null, values); + game.id = newRowId; + + for(Hit hit: game.hits.values()) { + insertHit(hit); + } + } + + public Game[] getGamesForLevel(int lvl_id) { + SQLiteDatabase db = getReadableDatabase(); + + String selection = "lvl_id = ?"; + String[] selectionArgs = { Integer.toString(lvl_id) }; + + String sortOrder = "score DESC"; + + Cursor c = db.query( + GAME_TABLE_NAME, // The table to query + GAME_PROJECTION, // The columns to return + selection, // The columns for the WHERE clause + selectionArgs, // The values for the WHERE clause + null, // don't group the rows + null, // don't filter by row groups + sortOrder, // The sort order + null // no limit! + ); + return cursorToGames(c, null); + } + + public Game[] getGamesForScenario(Scenario s) { + SQLiteDatabase db = getReadableDatabase(); + + String selection = "lvl_id = ?"; + String[] selectionArgs = { Integer.toString(s.id) }; + + String sortOrder = "score DESC"; + + Cursor c = db.query( + GAME_TABLE_NAME, // The table to query + GAME_PROJECTION, // The columns to return + selection, // The columns for the WHERE clause + selectionArgs, // The values for the WHERE clause + null, // don't group the rows + null, // don't filter by row groups + sortOrder, // The sort order + null // no limit! + ); + return cursorToGames(c, s); + } + + public Game[] cursorToGames(Cursor c, Scenario s) { + Game[] games = new Game[c.getCount()]; + int i = 0; + + c.moveToFirst(); + while (!c.isAfterLast()) { + Scenario scenario = s == null ? new Scenario(c.getInt(1), null) : s; + Game game = new Game(c.getLong(0), scenario, c.getFloat(2), new Date(c.getInt(3)), c.getString(4)); + games[i] = game; + this.getHitsForGame(game); // hits are appended to game object + i++; + c.moveToNext(); + } + + return games; + } + + public void saveRemoteId(Game game) { + SQLiteDatabase db = getWritableDatabase(); + + // New value for one column + ContentValues values = new ContentValues(); + values.put("remoteId", game.remoteId); + + // Which row to update, based on the title + String selection = "id = ?"; + String[] selectionArgs = { Long.toString(game.id) }; + + int count = db.update( + GAME_TABLE_NAME, + values, + selection, + selectionArgs); + } + + + public void insertHit(Hit hit) { + // Gets the data repository in write mode + SQLiteDatabase db = getWritableDatabase(); + + // Create a new map of values, where column names are the keys + ContentValues values = new ContentValues(); + + values.put("game_id", hit.game.id); + values.put("target_index", hit.target.index); + values.put("score", hit.score); + + values.put("glasses", hit.glasses); + values.put("ethnicity", hit.ethnicity); + values.put("age", hit.age); + values.put("gender", hit.gender); + + for(Emotion emotion: Emotion.values()) { + values.put(emotion.name().toLowerCase(), hit.emotions.get(emotion)); + } + for(Expression exp: Expression.values()) { + values.put(exp.getDbName(), hit.expressions.get(exp)); + } + + int i=0; + for(PointF point: hit.points) { + values.put("point_"+i+"x", point.x); + values.put("point_"+i+"y", point.y); + i++; + } + + if(hit.remoteId == null) { + values.putNull("remoteId"); + } else { + values.put("remoteId", hit.remoteId); + } + + // Insert the new row, returning the primary key value of the new row + long newRowId = db.insert(HIT_TABLE_NAME, null, values); + hit.id = newRowId; + } + + public ArrayList getHitsForGame(Game game) { + SQLiteDatabase db = getReadableDatabase(); + + String selection = "game_id = ?"; + String[] selectionArgs = { Long.toString(game.id) }; + + String sortOrder = "target_index ASC"; + + Cursor c = db.query( + HIT_TABLE_NAME, // The table to query + HIT_PROJECTION, // The columns to return + selection, // The columns for the WHERE clause + selectionArgs, // The values for the WHERE clause + null, // don't group the rows + null, // don't filter by row groups + sortOrder, // The sort order + null // no limit! + ); + return cursorToHits(game, c); + } + + public ArrayList cursorToHits(Game game, Cursor c) { + ArrayList hits = new ArrayList(c.getCount()); + c.moveToFirst(); + while (!c.isAfterLast()) { + hits.add(new Hit(game, c)); + c.moveToNext(); + } + return hits; + } + + + + public void saveRemoteId(Hit hit) { + SQLiteDatabase db = getWritableDatabase(); + + // New value for one column + ContentValues values = new ContentValues(); + values.put("remoteId", hit.remoteId); + + // Which row to update, based on the title + String selection = "id = ?"; + String[] selectionArgs = { Long.toString(hit.id) }; + + int count = db.update( + HIT_TABLE_NAME, + values, + selection, + selectionArgs); + } + + public Game getGameByid(long id) { + SQLiteDatabase db = getReadableDatabase(); + + String selection = "id = ?"; + String[] selectionArgs = { Long.toString(id) }; + + Cursor c = db.query( + GAME_TABLE_NAME, // The table to query + GAME_PROJECTION, // The columns to return + selection, // The columns for the WHERE clause + selectionArgs, // The values for the WHERE clause + null, // don't group the rows + null, // don't filter by row groups + null, // The sort order + null // no limit! + ); + + Game[] games = cursorToGames(c, null); + return games[0]; + } + + public int getLocalRankOfGame(Game game) { + String[] params = { Long.toString(game.scenario.id), Float.toString(game.score) }; + Cursor c = getReadableDatabase().rawQuery("SELECT COUNT(id)+1 FROM games WHERE lvl_id = ? AND score > ?", params); + c.moveToFirst(); + return c.getInt(0); + } + +} diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/GamingActivity.java b/app/src/main/java/com/rubenvandeven/emotionhero/GamingActivity.java index 1aac6ba..f460bfc 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/GamingActivity.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/GamingActivity.java @@ -205,7 +205,7 @@ public class GamingActivity extends AppCompatActivity implements Detector.ImageL detector.setDetectAllEmotions(true); detector.setDetectAllAppearances(false); detector.setDetectAllEmojis(false); - detector.setDetectAllExpressions(false); + detector.setDetectAllExpressions(true); detector.setMaxProcessRate(20); detector.setImageListener(this); @@ -393,8 +393,12 @@ public class GamingActivity extends AppCompatActivity implements Detector.ImageL // highscoreView.setVisibility(View.VISIBLE); finish(); + GameOpenHelper gameHelper = new GameOpenHelper(getApplicationContext()); + gameHelper.insertGame(currentScenario.game); + Intent intent = new Intent(this, HighscoreActivity.class); intent.putExtra(HighscoreActivity.INTENT_EXTRA_SCORE_ID, score.id.toString()); + intent.putExtra(HighscoreActivity.INTENT_EXTRA_GAME_ID, currentScenario.game.id.toString()); startActivity(intent); } } diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/HighscoreActivity.java b/app/src/main/java/com/rubenvandeven/emotionhero/HighscoreActivity.java index 8f6cba8..56b91f8 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/HighscoreActivity.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/HighscoreActivity.java @@ -33,6 +33,7 @@ public class HighscoreActivity extends AppCompatActivity { public final static String INTENT_EXTRA_SCORE_ID = "com.rubenvandeven.emotionhero.SCORE_ID"; + public final static String INTENT_EXTRA_GAME_ID = "com.rubenvandeven.emotionhero.GAME_ID"; /** * The {@link android.support.v4.view.PagerAdapter} that will provide @@ -67,6 +68,7 @@ public class HighscoreActivity extends AppCompatActivity { player = Player.getInstance(getApplicationContext()); String scoreIdString = getIntent().getStringExtra(INTENT_EXTRA_SCORE_ID); + String gameIdString = getIntent().getStringExtra(INTENT_EXTRA_GAME_ID); if(scoreIdString != null) { UUID score_id = UUID.fromString(scoreIdString); score = player.getPlayerInfo().getScore(score_id); @@ -74,10 +76,20 @@ public class HighscoreActivity extends AppCompatActivity { Log.e("Highscore", "CANNOT FIND SCORE!! " + scoreIdString); } } + if(gameIdString != null) { + long gameId = Long.valueOf(gameIdString); + Game game = player.getGameOpenHelper().getGameByid(gameId); + if(game == null) { + Log.e("Highscore", "CANNOT FIND GAME!! " + gameIdString); + } else { + Log.i("Highscore", "FOUND GAME" + game.id + " " + game.score); + Log.i("Highscore", "RANK " + player.getGameOpenHelper().getLocalRankOfGame(game)); + player.api.syncGame(game); + } + } Log.i("Highscore", ""+player.getPlayerInfo().reachedLevelId); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Hit.java b/app/src/main/java/com/rubenvandeven/emotionhero/Hit.java new file mode 100644 index 0000000..a9529b8 --- /dev/null +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Hit.java @@ -0,0 +1,130 @@ +package com.rubenvandeven.emotionhero; + +import android.database.Cursor; +import android.graphics.PointF; + +import com.affectiva.android.affdex.sdk.detector.Face; +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by ruben on 02/09/16. + */ + +public class Hit { + + public long id; + + public Scenario.Target target; + + @Expose(serialize = false, deserialize = false) + public Game game; + + public float score; + + public Map emotions = new HashMap<>(7); + + public boolean glasses; + public String ethnicity; + public String age; + public String gender; + + public Map expressions = new HashMap<>(21); // also includes measurements + + public PointF[] points = new PointF[34]; + + public String remoteId; + + /** + * Constructor for game moment + */ + public Hit(Scenario.Target target, Game game, Face face, float score) { + this.score = score; + this.target = target; + this.game = game; + for(Emotion emotion: Emotion.values()) { + emotions.put(emotion,emotion.getValueFromFace(face)); + } + + this.gender = face.appearance.getGender() == Face.GENDER.UNKNOWN ? "U" : (face.appearance.getGender() == Face.GENDER.MALE ? "M" : "F"); + this.glasses = face.appearance.getGlasses() == Face.GLASSES.YES ? true : false; + this.ethnicity = face.appearance.getEthnicity().name(); + this.age = face.appearance.getAge().name(); + + for(Expression exp: Expression.values()) { + this.expressions.put(exp, exp.getValueFromFace(face)); + } + + this.points = face.getFacePoints().clone(); + + game.addHit(this); + } + + /** + * Constructor from database entity + */ + public Hit(Game game, Cursor c) { + this.id = c.getLong(c.getColumnIndex("id")); + this.score = c.getFloat(c.getColumnIndex("score")); + String rId = c.getString(c.getColumnIndex("remoteId")); + this.remoteId = (rId == null || rId.length() == 0) ? null : c.getString(c.getColumnIndex("remoteId")); + this.target = game.scenario.getTargetByIndex(c.getInt(c.getColumnIndex("target_index"))); + + this.glasses = c.getInt(c.getColumnIndex("glasses")) == 1; + this.ethnicity = c.getString(c.getColumnIndex("ethnicity")); + this.age = c.getString(c.getColumnIndex("age")); + this.gender = c.getString(c.getColumnIndex("gender")); + + this.emotions.put(Emotion.ANGER, c.getFloat(c.getColumnIndex("anger"))); + this.emotions.put(Emotion.CONTEMPT, c.getFloat(c.getColumnIndex("contempt"))); + this.emotions.put(Emotion.DISGUST, c.getFloat(c.getColumnIndex("disgust"))); + this.emotions.put(Emotion.FEAR, c.getFloat(c.getColumnIndex("fear"))); + this.emotions.put(Emotion.JOY, c.getFloat(c.getColumnIndex("joy"))); + this.emotions.put(Emotion.SADNESS, c.getFloat(c.getColumnIndex("sadness"))); + this.emotions.put(Emotion.SURPRISE, c.getFloat(c.getColumnIndex("surprise"))); + + for(Expression exp: Expression.values()) { + this.expressions.put(exp, c.getFloat( c.getColumnIndex( exp.getDbName() ))); + } + + this.points[0] = new PointF(c.getFloat(c.getColumnIndex("point_0x")), c.getFloat(c.getColumnIndex("point_0y"))); + this.points[1] = new PointF(c.getFloat(c.getColumnIndex("point_1x")), c.getFloat(c.getColumnIndex("point_1y"))); + this.points[2] = new PointF(c.getFloat(c.getColumnIndex("point_2x")), c.getFloat(c.getColumnIndex("point_2y"))); + this.points[3] = new PointF(c.getFloat(c.getColumnIndex("point_3x")), c.getFloat(c.getColumnIndex("point_3y"))); + this.points[4] = new PointF(c.getFloat(c.getColumnIndex("point_4x")), c.getFloat(c.getColumnIndex("point_4y"))); + this.points[5] = new PointF(c.getFloat(c.getColumnIndex("point_5x")), c.getFloat(c.getColumnIndex("point_5y"))); + this.points[6] = new PointF(c.getFloat(c.getColumnIndex("point_6x")), c.getFloat(c.getColumnIndex("point_6y"))); + this.points[7] = new PointF(c.getFloat(c.getColumnIndex("point_7x")), c.getFloat(c.getColumnIndex("point_7y"))); + this.points[8] = new PointF(c.getFloat(c.getColumnIndex("point_8x")), c.getFloat(c.getColumnIndex("point_8y"))); + this.points[9] = new PointF(c.getFloat(c.getColumnIndex("point_9x")), c.getFloat(c.getColumnIndex("point_9y"))); + this.points[10] = new PointF(c.getFloat(c.getColumnIndex("point_10x")), c.getFloat(c.getColumnIndex("point_10y"))); + this.points[11] = new PointF(c.getFloat(c.getColumnIndex("point_11x")), c.getFloat(c.getColumnIndex("point_11y"))); + this.points[12] = new PointF(c.getFloat(c.getColumnIndex("point_12x")), c.getFloat(c.getColumnIndex("point_12y"))); + this.points[13] = new PointF(c.getFloat(c.getColumnIndex("point_13x")), c.getFloat(c.getColumnIndex("point_13y"))); + this.points[14] = new PointF(c.getFloat(c.getColumnIndex("point_14x")), c.getFloat(c.getColumnIndex("point_14y"))); + this.points[15] = new PointF(c.getFloat(c.getColumnIndex("point_15x")), c.getFloat(c.getColumnIndex("point_15y"))); + this.points[16] = new PointF(c.getFloat(c.getColumnIndex("point_16x")), c.getFloat(c.getColumnIndex("point_16y"))); + this.points[17] = new PointF(c.getFloat(c.getColumnIndex("point_17x")), c.getFloat(c.getColumnIndex("point_17y"))); + this.points[18] = new PointF(c.getFloat(c.getColumnIndex("point_18x")), c.getFloat(c.getColumnIndex("point_18y"))); + this.points[19] = new PointF(c.getFloat(c.getColumnIndex("point_19x")), c.getFloat(c.getColumnIndex("point_19y"))); + this.points[20] = new PointF(c.getFloat(c.getColumnIndex("point_20x")), c.getFloat(c.getColumnIndex("point_20y"))); + this.points[21] = new PointF(c.getFloat(c.getColumnIndex("point_21x")), c.getFloat(c.getColumnIndex("point_21y"))); + this.points[22] = new PointF(c.getFloat(c.getColumnIndex("point_22x")), c.getFloat(c.getColumnIndex("point_22y"))); + this.points[23] = new PointF(c.getFloat(c.getColumnIndex("point_23x")), c.getFloat(c.getColumnIndex("point_23y"))); + this.points[24] = new PointF(c.getFloat(c.getColumnIndex("point_24x")), c.getFloat(c.getColumnIndex("point_24y"))); + this.points[25] = new PointF(c.getFloat(c.getColumnIndex("point_25x")), c.getFloat(c.getColumnIndex("point_25y"))); + this.points[26] = new PointF(c.getFloat(c.getColumnIndex("point_26x")), c.getFloat(c.getColumnIndex("point_26y"))); + this.points[27] = new PointF(c.getFloat(c.getColumnIndex("point_27x")), c.getFloat(c.getColumnIndex("point_27y"))); + this.points[28] = new PointF(c.getFloat(c.getColumnIndex("point_28x")), c.getFloat(c.getColumnIndex("point_28y"))); + this.points[29] = new PointF(c.getFloat(c.getColumnIndex("point_29x")), c.getFloat(c.getColumnIndex("point_29y"))); + this.points[30] = new PointF(c.getFloat(c.getColumnIndex("point_30x")), c.getFloat(c.getColumnIndex("point_30y"))); + this.points[31] = new PointF(c.getFloat(c.getColumnIndex("point_31x")), c.getFloat(c.getColumnIndex("point_31y"))); + this.points[32] = new PointF(c.getFloat(c.getColumnIndex("point_32x")), c.getFloat(c.getColumnIndex("point_32y"))); + this.points[33] = new PointF(c.getFloat(c.getColumnIndex("point_33x")), c.getFloat(c.getColumnIndex("point_33y"))); + + game.addHit(this); + } +} diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Player.java b/app/src/main/java/com/rubenvandeven/emotionhero/Player.java index 46d0b9b..9cc19f7 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/Player.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Player.java @@ -91,6 +91,7 @@ public class Player { while((ch = fis.read()) != -1){ builder.append((char)ch); } + Log.d("PLAYER", builder.toString()); return PlayerInfo.fromJson(builder.toString()); } catch (IOException e) { return new PlayerInfo(); @@ -120,4 +121,12 @@ public class Player { Log.e("PlayerInfo", "Could not save player information!"); } } + + private GameOpenHelper gameOpenHelper; + public GameOpenHelper getGameOpenHelper() { + if(gameOpenHelper == null) { + gameOpenHelper = new GameOpenHelper(getContext()); + } + return gameOpenHelper; + } } diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/PlayerInfo.java b/app/src/main/java/com/rubenvandeven/emotionhero/PlayerInfo.java index 711501f..b0bcda0 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/PlayerInfo.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/PlayerInfo.java @@ -1,19 +1,13 @@ package com.rubenvandeven.emotionhero; -import android.support.annotation.NonNull; import android.util.Log; -import android.util.SparseArray; import com.google.gson.Gson; -import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.UUID; -import static com.google.gson.internal.bind.TypeAdapters.UUID; - /** * Created by ruben on 20/08/16. */ diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/Scenario.java b/app/src/main/java/com/rubenvandeven/emotionhero/Scenario.java index 92a6e8e..b1772ce 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/Scenario.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/Scenario.java @@ -6,6 +6,7 @@ import android.util.Log; import com.affectiva.android.affdex.sdk.detector.Face; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Timer; @@ -39,6 +40,11 @@ public class Scenario { float duration = 0; + /** + * If a game is beign played. + */ + Game game; + /** * @deprecated */ @@ -68,22 +74,11 @@ public class Scenario { ArrayList targets = new ArrayList<>(); class Target { + public int index; public Emotion emotion; public float value; public float timestamp; - public Hit hit; - } - - ArrayList hits = new ArrayList<>(); - - class Hit { - public float value; - /** - * Extra bonus given - */ - public boolean isSpotOn = false; -// public Bitmap image; //image at time of hit - public Face face; // face at time of hit + public boolean isHit = false; } /** @@ -115,7 +110,7 @@ public class Scenario { } }; timer.schedule(tickTask, 0, 1000/DESIRED_FPS); - + this.game = new Game(null, this, 0, new Date(), null); } /** @@ -133,30 +128,30 @@ public class Scenario { return; } - for (int i = targets.size() - 1; i >= 0; i--) { - Target target = targets.get(i); + for(Target target: targets) { // skip targets that are already scored - if(target.hit != null) { + if(target.isHit == true) { continue; } if(target.timestamp <= runningTime) { float scored_value = target.emotion.getValueFromFace(currentFace); float required_value = target.value; - Hit hit = new Hit(); - hit.value = Math.round(100 - Math.abs(scored_value-required_value)); - hit.face = currentFace; - hits.add(hit); + float score = Math.round(100 - Math.abs(scored_value-required_value)); + Hit hit = new Hit(target, game, currentFace, score); // _activity.sound.play(_activity.soundIds.get(_activity.SOUND_SCORE), 1, 1, - 1,0, hit.value / 200f + 0.5f ); // play back the sound slower + 1,0, hit.score / 200f + 0.5f ); // play back the sound slower // depending on hit value - target.hit = hit; + target.isHit = true; } } } + public Target getTargetByIndex(int target_index) { + return targets.get(target_index-1); + } /** * Add a target on given timestamp @@ -175,6 +170,7 @@ public class Scenario { target.timestamp = timestamp; target.value = value; target.emotion = emotion; + target.index = targets.size() + 1; targets.add(target); } @@ -195,32 +191,11 @@ public class Scenario { setTarget(emotion, value, timestamp); } - /** - * @deprecated use @see tick() - * @param face - * @param timestamp - */ - public void validateFaceOnTime(Face face, float timestamp) - { -// TODO: interpolation of time - for (int i = targets.size() - 1; i >= 0; i--) { - Target target = targets.get(i); - if(target.timestamp > timestamp - 0.2 && target.timestamp < timestamp + 0.2) { - float scored_value = target.emotion.getValueFromFace(face); - float required_value = target.value; - Hit hit = new Hit(); - hit.value = 100 - Math.abs(scored_value-required_value); - target.hit = hit; - hits.add(hit); - } - } - } - public float getHitTotalValue() { float value = 0; - for (Hit hit : hits) { - value += hit.value; + for (Hit hit : game.hits.values()) { + value += hit.score; } return value; } @@ -385,4 +360,8 @@ public class Scenario { return "..."; } + public Hit getHitForTarget(Target target) { + return game.hits.get(target.index); + } + } diff --git a/app/src/main/java/com/rubenvandeven/emotionhero/ScenarioView.java b/app/src/main/java/com/rubenvandeven/emotionhero/ScenarioView.java index 60ec4bd..377f68e 100644 --- a/app/src/main/java/com/rubenvandeven/emotionhero/ScenarioView.java +++ b/app/src/main/java/com/rubenvandeven/emotionhero/ScenarioView.java @@ -196,9 +196,10 @@ public class ScenarioView extends SurfaceView implements SurfaceHolder.Callback canvas.drawCircle(cx, cy, max_ball_radius * target.value/100, emoPaints.get(target.emotion)); - if(target.hit != null) { - canvas.drawText(Float.toString(target.hit.value), cx, cy, emoPaints.get(target.emotion)); - canvas.drawCircle(cx, cy, max_ball_radius * target.hit.value/100, emoScoredPaints.get(target.emotion)); + if(target.isHit) { + Hit hit = _scenario.getHitForTarget(target); + canvas.drawText(Float.toString(hit.score), cx, cy, emoPaints.get(target.emotion)); + canvas.drawCircle(cx, cy, max_ball_radius * hit.score/100, emoScoredPaints.get(target.emotion)); // TODO: Use target.hit.face to set Rect src. // if(target.hit.image != null) {