Add the new affdexme
|
@ -5,7 +5,7 @@
|
|||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="C:\Program Files\Android\Android Studio\gradle\gradle-2.4" />
|
||||
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.2.1" />
|
||||
<option name="gradleJvm" value="1.7" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
|
|
|
@ -12,12 +12,10 @@
|
|||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
|
||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugAndroidTestSources</task>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
|
@ -86,13 +84,13 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 22 Platform (1)" jdkType="Android SDK" />
|
||||
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="Affdex-sdk-1.2-SNAPSHOT-javadoc" level="project" />
|
||||
<orderEntry type="library" exported="" name="dagger-1.2.2" level="project" />
|
||||
<orderEntry type="library" exported="" name="javax.inject-1" level="project" />
|
||||
<orderEntry type="library" exported="" name="Affdex-sdk-1.2-SNAPSHOT" level="project" />
|
||||
<orderEntry type="library" exported="" name="Affdex-sdk" level="project" />
|
||||
<orderEntry type="library" exported="" name="gson-2.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="Affdex-sdk-javadoc" level="project" />
|
||||
<orderEntry type="library" exported="" name="flurry-analytics-4.1.0" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -24,8 +24,8 @@ dependencies {
|
|||
compile 'com.google.code.gson:gson:2.3'
|
||||
|
||||
//include the Affdex SDK jars
|
||||
compile files('libs/Affdex-sdk-1.2-SNAPSHOT.jar')
|
||||
compile files('libs/Affdex-sdk-1.2-SNAPSHOT-javadoc.jar')
|
||||
compile files('libs/Affdex-sdk.jar')
|
||||
compile files('libs/Affdex-sdk-javadoc.jar')
|
||||
compile files('libs/dagger-1.2.2.jar')
|
||||
compile files('libs/flurry-analytics-4.1.0.jar')
|
||||
compile files('libs/javax.inject-1.jar')
|
||||
|
|
|
@ -14,21 +14,41 @@
|
|||
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
|
||||
|
||||
<application
|
||||
android:name="com.affectiva.errorreporting.CustomApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="@string/app_name">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:theme="@style/MainActivityTheme"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:parentActivityName="com.affectiva.affdexme.MainActivity"
|
||||
android:theme="@style/EditPreferencesTheme">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.affectiva.affdexme.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.affectiva.errorreporting.ErrorReporter"
|
||||
android:theme="@android:style/Theme.DeviceDefault"
|
||||
android:textAppearance="@android:style/TextAppearance.Large">
|
||||
<intent-filter>
|
||||
<action android:name="com.affectiva.REPORT_ERROR" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
|
@ -14,6 +16,8 @@ import android.util.Log;
|
|||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import com.affectiva.android.affdex.sdk.detector.Face;
|
||||
|
||||
|
||||
/**
|
||||
* This class contains a SurfaceView and its own thread that draws to it.
|
||||
|
@ -31,12 +35,18 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
private DrawingViewConfig config;
|
||||
private final long drawPeriod = 33; //draw at 30 fps
|
||||
|
||||
private final int TEXT_RAISE = 10;
|
||||
|
||||
String roll = "";
|
||||
String yaw = "";
|
||||
String pitch = "";
|
||||
String interOcDis = "";
|
||||
|
||||
public DrawingThread(SurfaceHolder surfaceHolder, DrawingViewConfig con) {
|
||||
mSurfaceHolder = surfaceHolder;
|
||||
|
||||
circlePaint = new Paint();
|
||||
circlePaint.setColor(Color.WHITE);
|
||||
|
||||
boxPaint = new Paint();
|
||||
boxPaint.setColor(Color.WHITE);
|
||||
boxPaint.setStyle(Paint.Style.STROKE);
|
||||
|
@ -44,20 +54,24 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
config = con;
|
||||
|
||||
setThickness(config.drawThickness);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to set the valence score, which determines the color of the bounding box.
|
||||
* **/
|
||||
void setScore(float s) {
|
||||
if (s > 0) {
|
||||
float colorScore = ((100f-s)/100f)*255;
|
||||
void setMetrics(float roll, float yaw, float pitch, float interOcDis, float valence) {
|
||||
//format string for our DrawingView to use when ready
|
||||
this.roll = String.format("%.2f",roll);
|
||||
this.yaw = String.format("%.2f",yaw);
|
||||
this.pitch = String.format("%.2f",pitch);
|
||||
this.interOcDis = String.format("%.2f",interOcDis);
|
||||
|
||||
//prepare the color of the bounding box using the valence score. Red for -100, White for 0, and Green for +100, with linear interpolation in between.
|
||||
if (valence > 0) {
|
||||
float colorScore = ((100f-valence)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
|
||||
} else {
|
||||
float colorScore = ((100f+s)/100f)*255;
|
||||
float colorScore = ((100f+valence)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb(255, (int) colorScore, (int) colorScore));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void stopThread() {
|
||||
|
@ -100,7 +114,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
if (c!= null) {
|
||||
synchronized (mSurfaceHolder) {
|
||||
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); //clear previous dots
|
||||
if (config.isDrawPointsEnabled && (nextPointsToDraw != null) ) {
|
||||
if ((nextPointsToDraw != null) ) {
|
||||
draw(c);
|
||||
}
|
||||
}
|
||||
|
@ -131,31 +145,69 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
PointF[] points = nextPointsToDraw;
|
||||
|
||||
//Coordinates around which to draw bounding box.
|
||||
float leftBx = config.imageWidth;
|
||||
float leftBx = config.surfaceViewWidth;
|
||||
float rightBx = 0;
|
||||
float topBx = config.imageHeight;
|
||||
float topBx = config.surfaceViewHeight;
|
||||
float botBx = 0;
|
||||
|
||||
//Iterate through all the points given to us by the CameraDetector object
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
|
||||
//transform from the camera coordinates to our screen coordinates
|
||||
//The camera preview is displayed as a mirror, so X pts have to be mirrored back.
|
||||
float x = (config.imageWidth - points[i].x - 1) * config.screenToImageRatio;
|
||||
float y = (points[i].y)* config.screenToImageRatio;
|
||||
|
||||
//We determine the left-most, top-most, right-most, and bottom-most points to draw the bounding box around.
|
||||
if (points[i].x < leftBx)
|
||||
leftBx = points[i].x;
|
||||
if (points[i].x > rightBx)
|
||||
rightBx = points[i].x;
|
||||
if (points[i].y < topBx)
|
||||
topBx = points[i].y;
|
||||
if (points[i].y > botBx)
|
||||
botBx = points[i].y;
|
||||
if (x < leftBx)
|
||||
leftBx = x;
|
||||
if (x > rightBx)
|
||||
rightBx = x;
|
||||
if (y < topBx)
|
||||
topBx = y;
|
||||
if (y > botBx)
|
||||
botBx = y;
|
||||
|
||||
//Draw facial tracking dots.
|
||||
//The camera preview is displayed as a mirror, so X pts have to be reversed
|
||||
c.drawCircle((config.imageWidth - points[i].x - 1) * config.screenToImageRatio, (points[i].y)* config.screenToImageRatio, config.drawThickness, circlePaint);
|
||||
if (config.isDrawPointsEnabled) {
|
||||
c.drawCircle(x, y, config.drawThickness, circlePaint);
|
||||
}
|
||||
}
|
||||
|
||||
//Draw the bounding box.
|
||||
c.drawRect((config.imageWidth - leftBx - 1) * config.screenToImageRatio, topBx * config.screenToImageRatio, (config.imageWidth - rightBx - 1) * config.screenToImageRatio, botBx * config.screenToImageRatio, boxPaint);
|
||||
if (config.isDrawPointsEnabled) {
|
||||
c.drawRect(leftBx, topBx, rightBx, botBx, boxPaint);
|
||||
}
|
||||
|
||||
//Draw the measurement metrics, with a dark border around the words to make them visible for users of all skin colors.
|
||||
if (config.isDrawMeasurementsEnabled) {
|
||||
float centerBx = (leftBx + rightBx) / 2;
|
||||
|
||||
float upperText = topBx - TEXT_RAISE;
|
||||
c.drawText("PITCH", centerBx, upperText - config.textSize,config.textBorderPaint);
|
||||
c.drawText("PITCH", centerBx, upperText - config.textSize, config.textPaint);
|
||||
c.drawText(pitch,centerBx ,upperText ,config.textBorderPaint);
|
||||
c.drawText(pitch, centerBx, upperText, config.textPaint);
|
||||
|
||||
float upperLeft = centerBx - config.upperTextSpacing;
|
||||
|
||||
c.drawText("YAW", upperLeft , upperText - config.textSize , config.textBorderPaint);
|
||||
c.drawText("YAW", upperLeft, upperText - config.textSize, config.textPaint);
|
||||
c.drawText(yaw, upperLeft , upperText , config.textBorderPaint);
|
||||
c.drawText(yaw, upperLeft, upperText, config.textPaint);
|
||||
|
||||
float upperRight = centerBx + config.upperTextSpacing;
|
||||
c.drawText("ROLL", upperRight , upperText - config.textSize , config.textBorderPaint);
|
||||
c.drawText("ROLL", upperRight, upperText - config.textSize, config.textPaint);
|
||||
c.drawText(roll, upperRight , upperText , config.textBorderPaint);
|
||||
c.drawText(roll, upperRight, upperText, config.textPaint);
|
||||
|
||||
c.drawText("INTEROCULAR DISTANCE", centerBx , botBx + config.textSize , config.textBorderPaint);
|
||||
c.drawText("INTEROCULAR DISTANCE", centerBx, botBx + config.textSize, config.textPaint);
|
||||
c.drawText(interOcDis,centerBx , botBx + config.textSize*2 , config.textBorderPaint);
|
||||
c.drawText(interOcDis, centerBx, botBx + config.textSize * 2, config.textPaint);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,6 +221,19 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
private boolean isImageDimensionsNeeded = true;
|
||||
private boolean isSurfaceViewDimensionsNeeded = true;
|
||||
private boolean isDrawPointsEnabled = true; //by default, have the drawing thread draw tracking dots
|
||||
private boolean isDrawMeasurementsEnabled = false;
|
||||
|
||||
private Paint textPaint;
|
||||
private int textSize;
|
||||
private Paint textBorderPaint;
|
||||
private int upperTextSpacing;
|
||||
|
||||
public void setMeasurementMetricConfigs(Paint textPaint, Paint dropShadowPaint, int textSize, int upperTextSpacing) {
|
||||
this.textPaint = textPaint;
|
||||
this.textSize = textSize;
|
||||
this.textBorderPaint = dropShadowPaint;
|
||||
this.upperTextSpacing = upperTextSpacing;
|
||||
}
|
||||
|
||||
public void updateImageDimensions(int w, int h) {
|
||||
|
||||
|
@ -211,6 +276,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
//Class variables of DrawingView class
|
||||
private SurfaceHolder surfaceHolder;
|
||||
private DrawingThread drawingThread; //DrawingThread object
|
||||
private Typeface typeface;
|
||||
private DrawingViewConfig drawingViewConfig;
|
||||
private static String LOG_TAG = "AffdexMe";
|
||||
|
||||
|
@ -218,25 +284,60 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
//three constructors required of any custom view
|
||||
public DrawingView(Context context) {
|
||||
super(context);
|
||||
initView();
|
||||
initView(context, null);
|
||||
}
|
||||
public DrawingView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView();
|
||||
initView(context, attrs);
|
||||
}
|
||||
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initView();
|
||||
initView(context, attrs);
|
||||
}
|
||||
|
||||
void initView(){
|
||||
void initView(Context context, AttributeSet attrs){
|
||||
surfaceHolder = getHolder(); //The SurfaceHolder object will be used by the thread to request canvas to draw on SurfaceView
|
||||
surfaceHolder.setFormat(PixelFormat.TRANSPARENT); //set to Transparent so this surfaceView does not obscure the one it is overlaying (the one displaying the camera).
|
||||
surfaceHolder.addCallback(this); //become a Listener to the three events below that SurfaceView throws
|
||||
|
||||
drawingViewConfig = new DrawingViewConfig();
|
||||
|
||||
//default values
|
||||
int upperTextSpacing = 15;
|
||||
int textSize = 15;
|
||||
|
||||
Paint measurementTextPaint = new Paint();
|
||||
measurementTextPaint.setStyle(Paint.Style.FILL);
|
||||
measurementTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
Paint dropShadow = new Paint();
|
||||
dropShadow.setColor(Color.BLACK);
|
||||
dropShadow.setStyle(Paint.Style.STROKE);
|
||||
dropShadow.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
//load and parse XML attributes
|
||||
if (attrs != null) {
|
||||
TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.drawing_view_attributes,0,0);
|
||||
upperTextSpacing = a.getDimensionPixelSize(R.styleable.drawing_view_attributes_measurements_upper_spacing,upperTextSpacing);
|
||||
measurementTextPaint.setColor(a.getColor(R.styleable.drawing_view_attributes_measurements_color,Color.WHITE));
|
||||
dropShadow.setColor(a.getColor(R.styleable.drawing_view_attributes_measurements_text_border_color,Color.BLACK));
|
||||
dropShadow.setStrokeWidth(a.getInteger(R.styleable.drawing_view_attributes_measurements_text_border_thickness,5));
|
||||
textSize = a.getDimensionPixelSize(R.styleable.drawing_view_attributes_measurements_text_size,textSize);
|
||||
measurementTextPaint.setTextSize(textSize);
|
||||
dropShadow.setTextSize(textSize);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
drawingViewConfig.setMeasurementMetricConfigs(measurementTextPaint, dropShadow, textSize, upperTextSpacing);
|
||||
|
||||
drawingThread = new DrawingThread(surfaceHolder, drawingViewConfig);
|
||||
}
|
||||
|
||||
public void setTypeface(Typeface face) {
|
||||
drawingViewConfig.textPaint.setTypeface(face);
|
||||
drawingViewConfig.textBorderPaint.setTypeface(face);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (drawingThread.isStopped()) {
|
||||
|
@ -309,9 +410,16 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
|||
return drawingViewConfig.isDrawPointsEnabled;
|
||||
}
|
||||
|
||||
//The methods below simply delegate to the drawingThread object
|
||||
public void setScore(float s) {
|
||||
drawingThread.setScore(s);
|
||||
public void setDrawMeasurementsEnabled(boolean b) {
|
||||
drawingViewConfig.isDrawMeasurementsEnabled = b;
|
||||
}
|
||||
|
||||
public boolean getDrawMeasurementsEnabled() {
|
||||
return drawingViewConfig.isDrawMeasurementsEnabled;
|
||||
}
|
||||
|
||||
public void setMetrics(float roll, float yaw, float pitch, float interOcDis, float valence) {
|
||||
drawingThread.setMetrics(roll,yaw,pitch,interOcDis,valence);
|
||||
}
|
||||
|
||||
public void updatePoints(PointF[] points) {
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* GradientMetricView is used to display the valence metric and adds functionality of allowing
|
||||
* the bar's shade of color to scale with the metric's value, rather than just being red or green.
|
||||
*/
|
||||
public class GradientMetricView extends MetricView {
|
||||
|
||||
//Three Constructors required of any custom view:
|
||||
public GradientMetricView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
public GradientMetricView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public GradientMetricView(Context context, AttributeSet attrs, int styleID){
|
||||
super(context, attrs, styleID);
|
||||
}
|
||||
|
||||
/**
|
||||
* As in MetricView, we set our text to display the score and size the colored bar appropriately.
|
||||
* In this class, however, we let the score determine the color of the bar (shades of red for negative
|
||||
* and shades of green for positive).
|
||||
*/
|
||||
@Override
|
||||
public void setScore(float s) {
|
||||
text = String.format("%d%%", (int)s);
|
||||
if (s > 0) {
|
||||
left = midX - (halfWidth * (s / 100));
|
||||
right = midX + (halfWidth * (s / 100));
|
||||
} else {
|
||||
left = midX - (halfWidth * (-s / 100));
|
||||
right = midX + (halfWidth * (-s / 100));
|
||||
}
|
||||
if (s > 0) {
|
||||
float colorScore = ((100f-s)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
|
||||
} else {
|
||||
float colorScore = ((100f+s)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb(255,(int)colorScore,(int)colorScore));
|
||||
}
|
||||
invalidate(); //instruct Android to re-draw our view, now that the text has changed
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
@ -14,18 +15,17 @@ import android.view.SurfaceView;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import com.affectiva.android.affdex.sdk.Frame;
|
||||
|
@ -67,37 +67,24 @@ import com.affectiva.android.affdex.sdk.detector.Face;
|
|||
*/
|
||||
|
||||
public class MainActivity extends Activity
|
||||
implements Detector.FaceListener, Detector.ImageListener, TextView.OnEditorActionListener, View.OnTouchListener, CameraDetector.CameraSurfaceViewListener {
|
||||
implements Detector.FaceListener, Detector.ImageListener, View.OnTouchListener, CameraDetector.CameraSurfaceViewListener {
|
||||
|
||||
private static final String LOG_TAG = "Affectiva";
|
||||
public static final int NUM_METRICS_DISPLAYED = 6;
|
||||
|
||||
//Affectiva SDK Object
|
||||
private CameraDetector detector = null;
|
||||
|
||||
//Metrics View UI Objects
|
||||
//MetricsManager View UI Objects
|
||||
private RelativeLayout metricViewLayout;
|
||||
private LinearLayout leftMetricsLayout;
|
||||
private LinearLayout rightMetricsLayout;
|
||||
private MetricView smilePct;
|
||||
private MetricView browRaisePct;
|
||||
private MetricView browFurrowPct;
|
||||
private MetricView frownPct;
|
||||
private MetricView valencePct;
|
||||
private MetricView engagementPct;
|
||||
private MetricDisplay[] metricDisplays;
|
||||
private TextView[] metricNames;
|
||||
private TextView fpsName;
|
||||
private TextView fpsPct;
|
||||
private TextView smileName;
|
||||
private TextView browRaiseName;
|
||||
private TextView browFurrowName;
|
||||
private TextView frownName;
|
||||
private TextView valenceName;
|
||||
private TextView engagementName;
|
||||
|
||||
//Menu UI Objects
|
||||
private RelativeLayout menuLayout;
|
||||
private EditText fpsEditText;
|
||||
private CheckBox fpsCheckbox;
|
||||
private CheckBox trackingCheckbox;
|
||||
private TextView fpsEditTextName;
|
||||
private TextView pleaseWaitTextView;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
//Other UI objects
|
||||
private ViewGroup activityLayout; //top-most ViewGroup in which all content resides
|
||||
|
@ -105,15 +92,13 @@ public class MainActivity extends Activity
|
|||
private RelativeLayout progressBarLayout; //layout used to show progress circle while camera is starting
|
||||
private SurfaceView cameraView; //SurfaceView used to display camera images
|
||||
private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
|
||||
|
||||
//The Shared Preferences object is used to restore/save settings when activity is created/destroyed
|
||||
private final String PREFS_NAME = "AffdexMe";
|
||||
SharedPreferences sharedPreferences;
|
||||
private ImageButton settingsButton;
|
||||
|
||||
//Application settings variables
|
||||
private int detectorProcessRate = 20;
|
||||
private int detectorProcessRate;
|
||||
private boolean isMenuVisible = false;
|
||||
private boolean isFPSVisible = false;
|
||||
private boolean isMenuShowingForFirstTime = true;
|
||||
|
||||
//Frames Per Second (FPS) variables
|
||||
private long firstSystemTime = 0;
|
||||
|
@ -128,6 +113,8 @@ public class MainActivity extends Activity
|
|||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //To maximize UI space, we declare our app to be full-screen
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
initializeUI();
|
||||
|
||||
/**
|
||||
* We check to make sure the device has a front-facing camera.
|
||||
* If it does not, we obscure the app with a notice informing the user they cannot
|
||||
|
@ -135,12 +122,12 @@ public class MainActivity extends Activity
|
|||
*/
|
||||
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
|
||||
isFrontFacingCameraDetected = false;
|
||||
progressBar.setVisibility(View.INVISIBLE);
|
||||
pleaseWaitTextView.setVisibility(View.INVISIBLE);
|
||||
TextView notFoundTextView = (TextView) findViewById(R.id.not_found_textview);
|
||||
notFoundTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
initializeUI();
|
||||
|
||||
initializeCameraDetector();
|
||||
}
|
||||
|
||||
|
@ -153,47 +140,42 @@ public class MainActivity extends Activity
|
|||
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
|
||||
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
|
||||
mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
||||
menuLayout = (RelativeLayout) findViewById(R.id.affdexme_menu);
|
||||
smilePct = (MetricView) findViewById(R.id.smile_pct);
|
||||
browRaisePct = (MetricView) findViewById(R.id.brow_raise_pct);
|
||||
browFurrowPct = (MetricView) findViewById(R.id.brow_furrow_pct);
|
||||
frownPct = (MetricView) findViewById(R.id.frown_pct);
|
||||
valencePct = (MetricView) findViewById(R.id.valence_pct);
|
||||
engagementPct = (MetricView) findViewById(R.id.engagement_pct);
|
||||
fpsPct = (TextView) findViewById(R.id.fps_value);
|
||||
smileName = (TextView) findViewById(R.id.smile_name);
|
||||
browRaiseName = (TextView) findViewById(R.id.brow_raise_name);
|
||||
browFurrowName = (TextView) findViewById(R.id.brow_furrow_name);
|
||||
frownName = (TextView) findViewById(R.id.frown_name);
|
||||
valenceName = (TextView) findViewById(R.id.valence_name);
|
||||
engagementName = (TextView) findViewById(R.id.engagement_name);
|
||||
fpsName = (TextView) findViewById(R.id.fps_name);
|
||||
fpsEditText = (EditText) findViewById(R.id.fps_edittext);
|
||||
fpsEditTextName = (TextView) findViewById(R.id.fps_edittext_name);
|
||||
fpsCheckbox = (CheckBox) findViewById(R.id.fps_checkbox);
|
||||
trackingCheckbox = (CheckBox) findViewById(R.id.tracking_checkbox);
|
||||
cameraView = (SurfaceView) findViewById(R.id.camera_preview);
|
||||
drawingView = (DrawingView) findViewById(R.id.drawing_view);
|
||||
settingsButton = (ImageButton) findViewById(R.id.settings_button);
|
||||
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
|
||||
pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview);
|
||||
|
||||
//Initialize views to display metrics
|
||||
metricNames = new TextView[NUM_METRICS_DISPLAYED];
|
||||
metricNames[0] = (TextView) findViewById(R.id.metric_name_0);
|
||||
metricNames[1] = (TextView) findViewById(R.id.metric_name_1);
|
||||
metricNames[2] = (TextView) findViewById(R.id.metric_name_2);
|
||||
metricNames[3] = (TextView) findViewById(R.id.metric_name_3);
|
||||
metricNames[4] = (TextView) findViewById(R.id.metric_name_4);
|
||||
metricNames[5] = (TextView) findViewById(R.id.metric_name_5);
|
||||
metricDisplays = new MetricDisplay[NUM_METRICS_DISPLAYED];
|
||||
metricDisplays[0] = (MetricDisplay) findViewById(R.id.metric_pct_0);
|
||||
metricDisplays[1] = (MetricDisplay) findViewById(R.id.metric_pct_1);
|
||||
metricDisplays[2] = (MetricDisplay) findViewById(R.id.metric_pct_2);
|
||||
metricDisplays[3] = (MetricDisplay) findViewById(R.id.metric_pct_3);
|
||||
metricDisplays[4] = (MetricDisplay) findViewById(R.id.metric_pct_4);
|
||||
metricDisplays[5] = (MetricDisplay) findViewById(R.id.metric_pct_5);
|
||||
|
||||
//Load Application Font and set UI Elements to use it
|
||||
Typeface face = Typeface.createFromAsset(getAssets(), "fonts/Square.ttf");
|
||||
smilePct.setTypeface(face);
|
||||
browRaisePct.setTypeface(face);
|
||||
browFurrowPct.setTypeface(face);
|
||||
frownPct.setTypeface(face);
|
||||
valencePct.setTypeface(face);
|
||||
engagementPct.setTypeface(face);
|
||||
smileName.setTypeface(face);
|
||||
browRaiseName.setTypeface(face);
|
||||
browFurrowName.setTypeface(face);
|
||||
frownName.setTypeface(face);
|
||||
valenceName.setTypeface(face);
|
||||
engagementName.setTypeface(face);
|
||||
for (TextView textView : metricNames) {
|
||||
textView.setTypeface(face);
|
||||
}
|
||||
for (MetricDisplay metricDisplay : metricDisplays) {
|
||||
metricDisplay.setTypeface(face);
|
||||
}
|
||||
fpsPct.setTypeface(face);
|
||||
fpsName.setTypeface(face);
|
||||
fpsEditTextName.setTypeface(face);
|
||||
fpsCheckbox.setTypeface(face);
|
||||
trackingCheckbox.setTypeface(face);
|
||||
drawingView.setTypeface(face);
|
||||
pleaseWaitTextView.setTypeface(face);
|
||||
|
||||
//Hide left and right metrics by default (will be made visible when face detection starts)
|
||||
leftMetricsLayout.setAlpha(0);
|
||||
|
@ -210,18 +192,6 @@ public class MainActivity extends Activity
|
|||
|
||||
//Attach event listeners to the menu and edit box
|
||||
activityLayout.setOnTouchListener(this);
|
||||
menuLayout.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/*
|
||||
* This method effectively blocks the mainLayout from receiving a touch event
|
||||
* when the menu is pressed. This is to prevent the menu from closing if the user accidentally touches it
|
||||
* when aiming for a checkbox or edit box.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
});
|
||||
fpsEditText.setOnEditorActionListener(this);
|
||||
|
||||
/*
|
||||
* This app sets the View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag. Unfortunately, this flag causes
|
||||
|
@ -247,20 +217,7 @@ public class MainActivity extends Activity
|
|||
* that view will be painted with what the camera sees.
|
||||
*/
|
||||
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
|
||||
|
||||
// NOTE: uncomment the line below and replace "YourLicenseFile" with your license file, which should be stored in /assets/Affdex/
|
||||
//detector.setLicensePath("YourLicenseFile");
|
||||
|
||||
// We want to detect all expressions, so turn on all classifiers.
|
||||
detector.setDetectSmile(true);
|
||||
detector.setDetectBrowFurrow(true);
|
||||
detector.setDetectBrowRaise(true);
|
||||
detector.setDetectEngagement(true);
|
||||
detector.setDetectValence(true);
|
||||
detector.setDetectLipCornerDepressor(true);
|
||||
|
||||
detector.setMaxProcessRate(detectorProcessRate);
|
||||
|
||||
detector.setLicensePath("YourLicenseFile");
|
||||
detector.setImageListener(this);
|
||||
detector.setFaceListener(this);
|
||||
detector.setCameraDetectorDimensionsListener(this);
|
||||
|
@ -273,33 +230,75 @@ public class MainActivity extends Activity
|
|||
public void onResume() {
|
||||
super.onResume();
|
||||
restoreApplicationSettings();
|
||||
setMenuVisible(false); //always make the menu invisible by default
|
||||
setMenuVisible(true);
|
||||
isMenuShowingForFirstTime = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We use the Shared Preferences object to restore application settings.
|
||||
*/
|
||||
public void restoreApplicationSettings() {
|
||||
sharedPreferences = getSharedPreferences(PREFS_NAME, 0);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
detectorProcessRate = sharedPreferences.getInt("rate", detectorProcessRate); //restore camera processing rate
|
||||
fpsEditText.setText(String.valueOf(detectorProcessRate));
|
||||
//restore camera processing rate
|
||||
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
|
||||
detector.setMaxProcessRate(detectorProcessRate);
|
||||
|
||||
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
|
||||
fpsCheckbox.setChecked(true);
|
||||
setFPSVisible(true);
|
||||
} else {
|
||||
fpsCheckbox.setChecked(false);
|
||||
setFPSVisible(false);
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
|
||||
setTrackPoints(true);
|
||||
trackingCheckbox.setChecked(true);
|
||||
} else {
|
||||
setTrackPoints(false);
|
||||
trackingCheckbox.setChecked(false);
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("measurements",drawingView.getDrawMeasurementsEnabled())) { //restore show measurements
|
||||
setShowMeasurements(true);
|
||||
} else {
|
||||
setShowMeasurements(false);
|
||||
}
|
||||
|
||||
//populate metric displays
|
||||
for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) {
|
||||
activateMetric(n,PreferencesUtils.getMetricFromPrefs(sharedPreferences, n));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a TextView to display a metric name and readies a MetricDisplay to display the value.
|
||||
* Uses reflection to:
|
||||
* -enable the corresponding metric in the Detector object by calling Detector.setDetect<MetricName>()
|
||||
* -save the Method object that will be invoked on the Face object received in onImageResults() to get the metric score
|
||||
*/
|
||||
void activateMetric(int index, MetricsManager.Metrics metric) {
|
||||
metricNames[index].setText(MetricsManager.getUpperCaseName(metric));
|
||||
|
||||
Method getFaceScoreMethod = null; //The method that will be used to get a metric score
|
||||
try {
|
||||
//Enable metric detection
|
||||
Detector.class.getMethod("setDetect" + MetricsManager.getCamelCase(metric), boolean.class).invoke(detector, true);
|
||||
|
||||
if (metric.getType() == MetricsManager.MetricType.Emotion) {
|
||||
getFaceScoreMethod = Face.Emotions.class.getMethod("get" + MetricsManager.getCamelCase(metric), null);
|
||||
|
||||
//The MetricDisplay for Valence is unique; it shades it color depending on the metric value
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
metricDisplays[index].setIsShadedMetricView(true);
|
||||
} else {
|
||||
metricDisplays[index].setIsShadedMetricView(false);
|
||||
}
|
||||
} else if (metric.getType() == MetricsManager.MetricType.Expression) {
|
||||
getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric),null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG,String.format("Error using reflection to generate methods for %s",metric.toString()));
|
||||
}
|
||||
|
||||
metricDisplays[index].setMetricToDisplay(metric, getFaceScoreMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -312,27 +311,43 @@ public class MainActivity extends Activity
|
|||
}
|
||||
|
||||
/**
|
||||
* We start the camera as soon as the application has been given focus, which occurs as soon as the application has
|
||||
* been opened or reopened. Although this can also occur when the application regains focus after a dialog box has been closed,
|
||||
* the startCamera() method will not start the camera if it is already running.
|
||||
* We want to start the camera as late as possible, so it does not freeze the application before it has been visually resumed.
|
||||
* We thus post a runnable that will take care of starting the camera.
|
||||
* We also reset variables used to calculate the Processed Frames Per Second.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
|
||||
if (hasFocus && isFrontFacingCameraDetected) {
|
||||
cameraView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mainWindowResumedTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void mainWindowResumedTasks() {
|
||||
startCamera();
|
||||
|
||||
if (!drawingView.isSurfaceDimensionsNeeded()) {
|
||||
progressBarLayout.setVisibility(View.GONE);
|
||||
}
|
||||
resetFPSCalculations();
|
||||
cameraView.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isMenuShowingForFirstTime) {
|
||||
setMenuVisible(false);
|
||||
}
|
||||
}
|
||||
},5000);
|
||||
}
|
||||
|
||||
void startCamera() {
|
||||
|
||||
//this app will always detect valence (it will also always detect measurements, but measurement don't need to be enabled)
|
||||
detector.setDetectValence(true);
|
||||
|
||||
if (!detector.isRunning()) {
|
||||
try {
|
||||
detector.start();
|
||||
|
@ -347,6 +362,7 @@ public class MainActivity extends Activity
|
|||
public void onFaceDetectionStarted() {
|
||||
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
|
||||
rightMetricsLayout.animate().alpha(1);
|
||||
|
||||
resetFPSCalculations(); //Since the FPS may be different whether a face is being tracked or not, reset variables.
|
||||
}
|
||||
|
||||
|
@ -358,6 +374,7 @@ public class MainActivity extends Activity
|
|||
void performFaceDetectionStoppedTasks() {
|
||||
leftMetricsLayout.animate().alpha(0); //make left and right metrics disappear
|
||||
rightMetricsLayout.animate().alpha(0);
|
||||
|
||||
drawingView.invalidatePoints(); //inform the drawing thread that the latest facial tracking points are now invalid
|
||||
resetFPSCalculations(); //Since the FPS may be different whether a face is being tracked or not, reset variables.
|
||||
}
|
||||
|
@ -393,29 +410,47 @@ public class MainActivity extends Activity
|
|||
Face face = faces.get(0);
|
||||
|
||||
//update metrics with latest face information. The metrics are displayed on a MetricView, a custom view with a .setScore() method.
|
||||
smilePct.setScore(face.getSmileScore());
|
||||
browRaisePct.setScore(face.getBrowRaiseScore());
|
||||
browFurrowPct.setScore(face.getBrowFurrowScore());
|
||||
engagementPct.setScore(face.getEngagementScore());
|
||||
frownPct.setScore(face.getLipCornerDepressorScore());
|
||||
float valenceScore = face.getValenceScore();
|
||||
valencePct.setScore(valenceScore);
|
||||
for (MetricDisplay metricDisplay : metricDisplays) {
|
||||
updateMetricScore(metricDisplay,face);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has selected to have facial tracking dots drawn, we use face.getFacePoints() to send those points
|
||||
* If the user has selected to have facial tracking dots or measurements drawn, we use face.getFacePoints() to send those points
|
||||
* to our drawing thread and also inform the thread what the valence score was, as that will determine the color
|
||||
* of the bounding box.
|
||||
*/
|
||||
if (drawingView.getDrawPointsEnabled()) {
|
||||
drawingView.setScore(valenceScore);
|
||||
if (drawingView.getDrawPointsEnabled() || drawingView.getDrawMeasurementsEnabled()) {
|
||||
drawingView.setMetrics(face.measurements.orientation.getRoll(), face.measurements.orientation.getYaw(), face.measurements.orientation.getPitch(), face.measurements.getInterocularDistance(), face.emotions.getValence());
|
||||
drawingView.updatePoints(face.getFacePoints());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In this method, we update our drawingView to contain the dimensions of the frames coming from the camera so that drawingView
|
||||
* can correctly draw the tracking dots. We also call drawingView.setThickness(), which sets the size of the tracking dots and the
|
||||
* thickness of the bounding box.
|
||||
* Use the method that we saved in activateMetric() to get the metric score and display it
|
||||
*/
|
||||
void updateMetricScore(MetricDisplay metricDisplay, Face face) {
|
||||
|
||||
MetricsManager.Metrics metric = metricDisplay.getMetricToDisplay();
|
||||
float score = Float.NaN;
|
||||
|
||||
try {
|
||||
if (metric.getType() == MetricsManager.MetricType.Emotion) {
|
||||
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emotions,null);
|
||||
metricDisplay.setScore(score);
|
||||
} else if (metric.getType() == MetricsManager.MetricType.Expression) {
|
||||
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.expressions,null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG,String.format("Error using reflecting to get %s score from face.",metric.toString()));
|
||||
}
|
||||
metricDisplay.setScore(score);
|
||||
}
|
||||
|
||||
/**
|
||||
* In this method, we inform our drawingView of the size of the incoming camera images.
|
||||
* We also set the thickness (which controls the size of the dots and bounding box) based on a reference thickness, which
|
||||
* should be the same whether the device is landscape or portrait.
|
||||
*/
|
||||
void calculateImageDimensions(Frame image){
|
||||
///referenceDimension will be used to determine the size of the facial tracking dots
|
||||
|
@ -486,12 +521,15 @@ public class MainActivity extends Activity
|
|||
public void onPause() {
|
||||
super.onPause();
|
||||
progressBarLayout.setVisibility(View.VISIBLE);
|
||||
saveApplicationSettings();
|
||||
stopCamera();
|
||||
}
|
||||
|
||||
private void stopCamera() {
|
||||
performFaceDetectionStoppedTasks();
|
||||
|
||||
detector.setDetectAllEmotions(false);
|
||||
detector.setDetectAllExpressions(false);
|
||||
|
||||
try {
|
||||
detector.stop();
|
||||
} catch (Exception e) {
|
||||
|
@ -499,57 +537,15 @@ public class MainActivity extends Activity
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use the SharedPreferences object to save application settings.
|
||||
**/
|
||||
public void saveApplicationSettings() {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putBoolean("fps", isFPSVisible);
|
||||
editor.putBoolean("track", drawingView.getDrawPointsEnabled());
|
||||
editor.putInt("rate", detectorProcessRate);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public void fps_checkbox_click(View view) {
|
||||
setFPSVisible(((CheckBox) view).isChecked());
|
||||
}
|
||||
|
||||
public void tracking_checkbox_click(View view) {
|
||||
setTrackPoints(((CheckBox) view).isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
/**
|
||||
* When a user has selected the Edit box to change the number of frames the detector processes per second
|
||||
* and presses the 'DONE' button, the below block will be executed.
|
||||
* */
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
int parsedInt = 0;
|
||||
try {
|
||||
parsedInt = Integer.parseInt(v.getText().toString());
|
||||
} catch (Exception e) {
|
||||
v.setText(String.valueOf(detectorProcessRate));
|
||||
return false;
|
||||
}
|
||||
if (parsedInt > 0) {
|
||||
detectorProcessRate = parsedInt;
|
||||
detector.setMaxProcessRate(detectorProcessRate);
|
||||
resetFPSCalculations(); //reset FPS variables, since changing the process rate should change the FPS.
|
||||
} else {
|
||||
v.setText(String.valueOf(detectorProcessRate));
|
||||
}
|
||||
}
|
||||
return false; //return false regardless, so Android closes the keyboard when user presses 'DONE'
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user taps the screen, hide the menu if it is visible and show it if it is hidden.
|
||||
* **/
|
||||
void setMenuVisible(boolean b){
|
||||
isMenuShowingForFirstTime = false;
|
||||
isMenuVisible = b;
|
||||
if (b) {
|
||||
menuLayout.setVisibility(View.VISIBLE);
|
||||
settingsButton.setVisibility(View.VISIBLE);
|
||||
|
||||
//We display the navigation bar again
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
|
@ -558,9 +554,6 @@ public class MainActivity extends Activity
|
|||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
else {
|
||||
InputMethodManager imm = (InputMethodManager)getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(fpsEditText.getWindowToken(), 0);
|
||||
|
||||
//We hide the navigation bar
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
|
@ -572,7 +565,7 @@ public class MainActivity extends Activity
|
|||
| View.SYSTEM_UI_FLAG_IMMERSIVE);
|
||||
|
||||
|
||||
menuLayout.setVisibility(View.INVISIBLE);
|
||||
settingsButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,6 +587,10 @@ public class MainActivity extends Activity
|
|||
drawingView.setDrawPointsEnabled(b);
|
||||
}
|
||||
|
||||
void setShowMeasurements(boolean b) {
|
||||
drawingView.setDrawMeasurementsEnabled(b);
|
||||
}
|
||||
|
||||
void setFPSVisible(boolean b) {
|
||||
isFPSVisible = b;
|
||||
if (b) {
|
||||
|
@ -612,6 +609,10 @@ public class MainActivity extends Activity
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void settings_button_click(View view) {
|
||||
startActivity(new Intent(this,SettingsActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,11 +9,15 @@ import android.graphics.Typeface;
|
|||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import java.lang.Math;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* The MetricView class is used to display metric scores on top of colored bars whose color depend on the score.
|
||||
*/
|
||||
public class MetricView extends View {
|
||||
public class MetricDisplay extends View {
|
||||
|
||||
MetricsManager.Metrics metricToDisplay; //indicates which of the 24 Affectiva Emotions and Expressions this view is displaying
|
||||
Method faceScoreMethod;
|
||||
|
||||
float midX = 0; //coordinates of the center of the view
|
||||
float midY = 0;
|
||||
|
@ -26,20 +30,28 @@ public class MetricView extends View {
|
|||
float right = 0;
|
||||
float top = 0;
|
||||
float textBottom = 0; //tells our view where to draw the baseline of the font
|
||||
boolean isShadedMetricView = false;
|
||||
|
||||
public MetricView(Context context) {
|
||||
public MetricDisplay(Context context) {
|
||||
super(context);
|
||||
initResources(context,null);
|
||||
}
|
||||
public MetricView(Context context, AttributeSet attrs) {
|
||||
public MetricDisplay(Context context, AttributeSet attrs) {
|
||||
super(context,attrs);
|
||||
initResources(context,attrs);
|
||||
}
|
||||
public MetricView(Context context, AttributeSet attrs, int styleID){
|
||||
public MetricDisplay(Context context, AttributeSet attrs, int styleID){
|
||||
super(context, attrs, styleID);
|
||||
initResources(context,attrs);
|
||||
}
|
||||
|
||||
void setIsShadedMetricView(boolean b) {
|
||||
this.isShadedMetricView = b;
|
||||
if (!b) {
|
||||
boxPaint.setColor(Color.GREEN);
|
||||
}
|
||||
}
|
||||
|
||||
void initResources(Context context, AttributeSet attrs) {
|
||||
|
||||
boxPaint = new Paint();
|
||||
|
@ -74,14 +86,48 @@ public class MetricView extends View {
|
|||
|
||||
}
|
||||
|
||||
public void setMetricToDisplay(MetricsManager.Metrics metricToDisplay, Method faceScoreMethod) {
|
||||
this.metricToDisplay = metricToDisplay;
|
||||
this.faceScoreMethod = faceScoreMethod;
|
||||
}
|
||||
|
||||
public MetricsManager.Metrics getMetricToDisplay() {
|
||||
return this.metricToDisplay;
|
||||
}
|
||||
|
||||
public Method getFaceScoreMethod() {
|
||||
return this.faceScoreMethod;
|
||||
}
|
||||
|
||||
public void setTypeface(Typeface face) {
|
||||
textPaint.setTypeface(face);
|
||||
}
|
||||
|
||||
public void setScore(float s){
|
||||
text = String.format("%.0f%%", s); //change the text of the view
|
||||
|
||||
//shading mode is turned on for Valence, which causes this view to shade its color according
|
||||
//to the value of 's'
|
||||
if (isShadedMetricView) {
|
||||
if (s > 0) {
|
||||
left = midX - (halfWidth * (s / 100));
|
||||
right = midX + (halfWidth * (s / 100));
|
||||
} else {
|
||||
left = midX - (halfWidth * (-s / 100));
|
||||
right = midX + (halfWidth * (-s / 100));
|
||||
}
|
||||
if (s > 0) {
|
||||
float colorScore = ((100f-s)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
|
||||
} else {
|
||||
float colorScore = ((100f+s)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb(255,(int)colorScore,(int)colorScore));
|
||||
}
|
||||
} else {
|
||||
left = midX - (halfWidth * (s / 100)); //change the coordinates at which the colored bar will be drawn
|
||||
right = midX + (halfWidth * (s / 100));
|
||||
}
|
||||
|
||||
invalidate(); //instruct Android to re-draw our view, now that the text has changed
|
||||
}
|
||||
|
|
@ -0,0 +1,599 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import static com.affectiva.affdexme.MainActivity.NUM_METRICS_DISPLAYED;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
/**
|
||||
* A fragment to display a graphical menu which allows the user to select which metrics to display.
|
||||
*
|
||||
*/
|
||||
public class MetricSelectionFragment extends Fragment implements View.OnClickListener {
|
||||
|
||||
final static String LOG_TAG = "Affectiva";
|
||||
|
||||
int numberOfSelectedItems = 0;
|
||||
|
||||
int messageAtOrUnderLimitColor;
|
||||
int messageOverLimitColor;
|
||||
|
||||
SharedPreferences sharedPreferences;
|
||||
|
||||
TextView metricChooserTextView;
|
||||
GridLayout gridLayout;
|
||||
Button clearAllButton;
|
||||
|
||||
HashMap<MetricsManager.Metrics, MetricSelector> metricSelectors = new HashMap<>();
|
||||
|
||||
//An inner class object to control video playback in the metricSelectors
|
||||
MetricSelectionFragmentMediaPlayer fragmentMediaPlayer;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View fragmentLayout = inflater.inflate(R.layout.metric_chooser, container, false);
|
||||
|
||||
initUI(fragmentLayout);
|
||||
|
||||
fragmentMediaPlayer = new MetricSelectionFragmentMediaPlayer();
|
||||
|
||||
restoreSettings(savedInstanceState);
|
||||
|
||||
//We post the methods used to populate the gridLayout view so that they run when gridLayout has been added to the layout and sized.
|
||||
gridLayout.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
populateGrid();
|
||||
updateAllGridItems();
|
||||
}
|
||||
});
|
||||
|
||||
return fragmentLayout;
|
||||
}
|
||||
|
||||
void initUI(View fragmentLayout) {
|
||||
gridLayout = (GridLayout) fragmentLayout.findViewById(R.id.metric_chooser_gridlayout);
|
||||
metricChooserTextView = (TextView) fragmentLayout.findViewById(R.id.metrics_chooser_textview);
|
||||
clearAllButton = (Button) fragmentLayout.findViewById(R.id.clear_all_button);
|
||||
|
||||
clearAllButton.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
clearItems();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Resources res = getResources();
|
||||
messageAtOrUnderLimitColor = res.getColor(R.color.white);
|
||||
messageOverLimitColor = res.getColor(R.color.red);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A method to populate the metricSelectors array using information from either a saved instance bundle (if the activity is being re-created)
|
||||
* or sharedPreferences (if the activity is being created for the first time)
|
||||
*/
|
||||
void restoreSettings(Bundle bundle) {
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
||||
Activity hostActivity = getActivity();
|
||||
LayoutInflater inflater = hostActivity.getLayoutInflater();
|
||||
Resources res = getResources();
|
||||
String packageName = hostActivity.getPackageName();
|
||||
|
||||
//populate metricSelectors list with objects
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
metricSelectors.put(metric, new MetricSelector(hostActivity, inflater, res, packageName, metric));
|
||||
}
|
||||
|
||||
if (bundle != null) { //if we were passed a bundle, use its data to configure the MetricSelectors
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
if (bundle.getBoolean(metric.toString(),false)) {
|
||||
selectItem(metricSelectors.get(metric),true,false);
|
||||
}
|
||||
}
|
||||
|
||||
} else { //otherwise, we pull the data from application preferences
|
||||
for (int i = 0; i < NUM_METRICS_DISPLAYED; i++) {
|
||||
MetricsManager.Metrics chosenMetric = PreferencesUtils.getMetricFromPrefs(sharedPreferences, i);
|
||||
selectItem(metricSelectors.get(chosenMetric),true,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle bundle) {
|
||||
//save whether each MetricSelector has been selected, using the metric name as the key
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
bundle.putBoolean(metric.toString(), metricSelectors.get(metric).getIsSelected());
|
||||
}
|
||||
super.onSaveInstanceState(bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the app is minimized, the active TextureView in fragmentMediaPlayer is destroyed, but throws an
|
||||
* exception if we try to remove it from its parent while in the destroyed state. Since fragmentMediaPlayer
|
||||
* begins assuming the TextureView is not attached to any parent, we run through all MetricSelectors and command
|
||||
* them to let go of the TextureView if they are the current parent.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
fragmentMediaPlayer.stopMetricSelectorPlayback(metricSelectors.get(metric));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
/* Our goal in this method is to ensure that 6 and only 6 metrics are saved in Preferences. We attempt to fill all 6 slots
|
||||
with metrics chosen by the user, but if the user did not choose 6 slots, we fill the remaining slots with any other metrics.
|
||||
*/
|
||||
void saveSettings() {
|
||||
|
||||
ArrayList<MetricsManager.Metrics> selectedMetrics = new ArrayList<>();
|
||||
|
||||
//Add all selected metrics
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
if (metricSelectors.get(metric).getIsSelected()) {
|
||||
selectedMetrics.add(metric);
|
||||
if (selectedMetrics.size() >= NUM_METRICS_DISPLAYED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fill remaining empty slots
|
||||
if (selectedMetrics.size() < NUM_METRICS_DISPLAYED) {
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
if (!selectedMetrics.contains(metric)) {
|
||||
selectedMetrics.add(metric);
|
||||
if (selectedMetrics.size() >= NUM_METRICS_DISPLAYED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//save list into application preferences
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
for (int n = 0; n < selectedMetrics.size(); n++) {
|
||||
PreferencesUtils.saveMetricToPrefs(editor, n, selectedMetrics.get(n));
|
||||
}
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/* While Android offers a GridView which can automatically populate a grid from an array, we wished to divide our grid items into 'Emotions' and 'Expressions'
|
||||
categories. Therefore, we implement a scrollable GridLayout.
|
||||
Furthermore, while we wished for the grid items to take up the entire grid, Android versions lower than 21 do not support the concept of weights in a GridLayout,
|
||||
so we manually size the grid items in this method.
|
||||
Note that since this method is posted as a runnable of gridLayout, it should only be run once gridLayout has been added to the layout and sized.
|
||||
*/
|
||||
void populateGrid() {
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
Resources res = getResources();
|
||||
int minColumnWidth = res.getDimensionPixelSize(R.dimen.metric_chooser_column_width);
|
||||
|
||||
//calculate number of columns
|
||||
int gridWidth = gridLayout.getWidth();
|
||||
int numColumns = gridWidth / minColumnWidth; //intentional integer division
|
||||
if (numColumns <= 0) {
|
||||
Log.e(LOG_TAG, "Desired Column Width too large! Unable to populate Grid");
|
||||
return;
|
||||
}
|
||||
int columnWidth = (int)((float) gridWidth / (float)numColumns);
|
||||
|
||||
//This integer reference will be used across methods to keep track of how many rows we have created.
|
||||
//Each method we pass it into leaves it at a value indicating the next row number that views should be added to.
|
||||
IntRef currentRow = new IntRef();
|
||||
|
||||
addHeader("Emotions", currentRow, numColumns, inflater);
|
||||
addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Emotions.values());
|
||||
addHeader("Expressions", currentRow, numColumns, inflater);
|
||||
addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Expressions.values());
|
||||
|
||||
gridLayout.setColumnCount(numColumns);
|
||||
gridLayout.setRowCount(currentRow.value);
|
||||
}
|
||||
|
||||
//adds a header (consisting of a TextView and border line) to the grid
|
||||
void addHeader(String name, IntRef currentRow, int numColumns, LayoutInflater inflater) {
|
||||
|
||||
View header = inflater.inflate(R.layout.grid_header, null);
|
||||
|
||||
//each header should take up one row and all available columns
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams(GridLayout.spec(currentRow.value, 1), GridLayout.spec(0, numColumns));
|
||||
params.width = gridLayout.getWidth();
|
||||
header.setLayoutParams(params);
|
||||
gridLayout.addView(header);
|
||||
|
||||
((TextView) header.findViewById(R.id.header_text)).setText(name);
|
||||
|
||||
currentRow.value += 1; //point currentRow to row where next views should be added
|
||||
}
|
||||
|
||||
//adds a set of metrics (using the data from the MetricsManager object from index 'start' to index 'end')
|
||||
void addGridItems(IntRef currentRow, int numColumns, LayoutInflater inflater, Resources res, int size, MetricsManager.Metrics[] metricsToAdd) {
|
||||
|
||||
//keeps track of the column we are adding to
|
||||
int col = -1; //start col at -1 so it becomes 0 during first iteration of for loop
|
||||
|
||||
for (MetricsManager.Metrics metric : metricsToAdd) {
|
||||
|
||||
col += 1;
|
||||
if (col >= numColumns) {
|
||||
col = 0;
|
||||
currentRow.value += 1;
|
||||
}
|
||||
|
||||
MetricSelector item = metricSelectors.get(metric);
|
||||
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||
params.width = size;
|
||||
params.height = size;
|
||||
params.columnSpec = GridLayout.spec(col);
|
||||
params.rowSpec = GridLayout.spec(currentRow.value);
|
||||
item.setLayoutParams(params);
|
||||
|
||||
item.setOnClickListener(this);
|
||||
gridLayout.addView(item);
|
||||
}
|
||||
currentRow.value +=1; //point currentRow to row where next views should be added
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MetricSelector item = (MetricSelector) v;
|
||||
selectItem(item, !item.getIsSelected(), true); //select item if de-selected, and vice-versa
|
||||
updateAllGridItems(); //each click will result in all items being updated
|
||||
}
|
||||
|
||||
/* Updates numberOfSelectedItems as well as the message presented by the text at the top of the activity
|
||||
*/
|
||||
void selectItem(MetricSelector metricSelector, boolean isSelected, boolean playVideo) {
|
||||
//update numberOfSelectedItems
|
||||
boolean wasSelected = metricSelector.getIsSelected();
|
||||
if (!wasSelected && isSelected) {
|
||||
numberOfSelectedItems += 1;
|
||||
|
||||
if (playVideo) {
|
||||
fragmentMediaPlayer.startMetricSelectorPlayback(metricSelector);
|
||||
}
|
||||
|
||||
} else if (wasSelected && !isSelected) {
|
||||
numberOfSelectedItems -= 1;
|
||||
if (playVideo) {
|
||||
fragmentMediaPlayer.stopMetricSelectorPlayback(metricSelector);
|
||||
}
|
||||
}
|
||||
metricSelector.setIsSelected(isSelected);
|
||||
|
||||
//Create and display message at the top
|
||||
/*String dMetricsChosen;
|
||||
if (numberOfSelectedItems == 1) {
|
||||
dMetricsChosen = "1 metric chosen.";
|
||||
} else {
|
||||
dMetricsChosen = String.format("%d metrics chosen.",numberOfSelectedItems);
|
||||
}*/
|
||||
|
||||
if (numberOfSelectedItems == 1) {
|
||||
metricChooserTextView.setText("1 metric chosen.");
|
||||
} else {
|
||||
metricChooserTextView.setText(String.format("%d metrics chosen.",numberOfSelectedItems));
|
||||
}
|
||||
|
||||
if (numberOfSelectedItems <= NUM_METRICS_DISPLAYED) {
|
||||
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||||
} else {
|
||||
metricChooserTextView.setTextColor(messageOverLimitColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*if (numberOfSelectedItems < NUM_METRICS_DISPLAYED) {
|
||||
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||||
metricChooserTextView.setText(String.format("%s Choose %d more.", dMetricsChosen, NUM_METRICS_DISPLAYED - numberOfSelectedItems));
|
||||
} else if (numberOfSelectedItems == NUM_METRICS_DISPLAYED) {
|
||||
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||||
metricChooserTextView.setText(dMetricsChosen);
|
||||
} else {
|
||||
metricChooserTextView.setTextColor(messageOverLimitColor);
|
||||
metricChooserTextView.setText(String.format("%s Please de-select %d.", dMetricsChosen, numberOfSelectedItems - NUM_METRICS_DISPLAYED));
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
void clearItems() {
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
selectItem(metricSelectors.get(metric),false,true);
|
||||
}
|
||||
updateAllGridItems();
|
||||
}
|
||||
|
||||
//loop through all grid items, and update those which are tagged with an Integer (those which represent selectable metrics).
|
||||
void updateAllGridItems() {
|
||||
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||||
metricSelectors.get(metric).setUnderOrOverLimit(numberOfSelectedItems <= NUM_METRICS_DISPLAYED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
fragmentMediaPlayer.destroy();
|
||||
}
|
||||
|
||||
//IntRef represents a reference to a mutable integer value
|
||||
//It is used to keep track of how many rows have been created in the populateGrid() method
|
||||
class IntRef {
|
||||
public int value;
|
||||
public IntRef() {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The MetricSelector objects in this fragment will play a video when selected. To keep memory usage low, we use only one MediaPlayer
|
||||
* object to control video playback. Video is rendered on a single TextureView.
|
||||
* Chain of events that lead to video playback:
|
||||
* -When a MetricSelector is clicked, the MediaPlayer.setDataSource() is called to set the video file
|
||||
* -The TextureView is added to the view hierarchy of the MetricSelector, causing the onSurfaceTextureAvailable callback to fire
|
||||
* -The TextureView is bound to the MediaPlayer through MediaPlayer.setSurface(), then MediaPlayer.prepareAsync() is called
|
||||
* -Once preparation is complete, MediaPlayer.start() is called
|
||||
* -MediaPlayer.stop() will be called when playback finishes or the item has been de-selected, at which point the TextureView will
|
||||
* be removed from the MetricSelector's view hierarchy, causing onSurfaceTextureDestroyed(), where we call MediaPlayer.setSurface(null)
|
||||
*/
|
||||
class MetricSelectionFragmentMediaPlayer {
|
||||
SafeMediaPlayer safePlayer;
|
||||
TextureView textureView;
|
||||
MetricSelector videoPlayingSelector;
|
||||
|
||||
public MetricSelectionFragmentMediaPlayer() {
|
||||
safePlayer = new SafeMediaPlayer(getActivity());
|
||||
safePlayer.setOnPreparedListener(new OnSafeMediaPlayerPreparedListener() {
|
||||
@Override
|
||||
public void onSafeMediaPlayerPrepared() {
|
||||
safePlayer.start();
|
||||
if (!greaterThanHoneyComb()) {
|
||||
safePlayer.seekTo(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Although it is best to remove the MetricSelector video cover upon reception of the
|
||||
* VIDEO_RENDERING event, this event is only available on SDK 17 and above.
|
||||
*/
|
||||
if (greaterThanHoneyComb()) {
|
||||
safePlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(MediaPlayer mp, int what, int extra) {
|
||||
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
|
||||
videoPlayingSelector.removeCover();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
safePlayer.setOnSeekListener(new MediaPlayer.OnSeekCompleteListener() {
|
||||
@Override
|
||||
public void onSeekComplete(MediaPlayer mp) {
|
||||
videoPlayingSelector.removeCover();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
safePlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
Uri nextVideoUri = videoPlayingSelector.getNextVideoResourceURI();
|
||||
if (nextVideoUri == null) {
|
||||
endVideoPlayback();
|
||||
} else {
|
||||
safePlayer.stopAndReset();
|
||||
safePlayer.setDataSource(nextVideoUri);
|
||||
safePlayer.prepareAsync();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
textureView = new TextureView(getActivity());
|
||||
textureView.setVisibility(View.GONE);
|
||||
textureView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
safePlayer.setSurface(new Surface(surface));
|
||||
safePlayer.prepareAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
safePlayer.stopAndReset();
|
||||
safePlayer.setSurface(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startVideoPlayback(MetricSelector metricSelector) {
|
||||
videoPlayingSelector = metricSelector;
|
||||
videoPlayingSelector.initIndex();
|
||||
safePlayer.setDataSource(metricSelector.getNextVideoResourceURI());
|
||||
metricSelector.displayVideo(textureView); //will cause onSurfaceTextureAvailable to fire
|
||||
}
|
||||
|
||||
private void endVideoPlayback() {
|
||||
videoPlayingSelector.displayCover();
|
||||
safePlayer.stopAndReset();
|
||||
videoPlayingSelector.removeVideo(); //will cause onSurfaceTextureDestroyed() to fire
|
||||
}
|
||||
|
||||
void startMetricSelectorPlayback(MetricSelector metricSelector) {
|
||||
if (videoPlayingSelector != null) {
|
||||
endVideoPlayback(); //stop previous video
|
||||
}
|
||||
startVideoPlayback(metricSelector);
|
||||
}
|
||||
|
||||
void stopMetricSelectorPlayback(MetricSelector metricSelector) {
|
||||
if (metricSelector == videoPlayingSelector) { //if de-selected item is a playing video, stop it
|
||||
endVideoPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
safePlayer.release(); //release resources of media player
|
||||
textureView = null;
|
||||
}
|
||||
|
||||
boolean greaterThanHoneyComb() {
|
||||
return Build.VERSION.SDK_INT >= 17;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* These are not all the MediaPlayer states defined by Android, but they are all the ones we are interested in.
|
||||
* Note that SafeMediaPlayer never stays in the STOPPED state, so we don't include it.
|
||||
*/
|
||||
enum MediaPlayerState {
|
||||
IDLE, INIT, PREPARED, PLAYING
|
||||
};
|
||||
|
||||
interface OnSafeMediaPlayerPreparedListener {
|
||||
void onSafeMediaPlayerPrepared();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Facade to ensure our MediaPlayer does not throw an error due to an invalid state change.
|
||||
*/
|
||||
class SafeMediaPlayer {
|
||||
MediaPlayerState state;
|
||||
MediaPlayer mediaPlayer;
|
||||
Activity activity;
|
||||
OnSafeMediaPlayerPreparedListener listener = null;
|
||||
|
||||
public SafeMediaPlayer(Activity activity) {
|
||||
mediaPlayer = new MediaPlayer();
|
||||
state = MediaPlayerState.IDLE;
|
||||
this.activity = activity;
|
||||
|
||||
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
state = MediaPlayerState.PREPARED;
|
||||
if (listener != null) {
|
||||
listener.onSafeMediaPlayerPrepared();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setDataSource(Uri source) {
|
||||
if (state == MediaPlayerState.IDLE) {
|
||||
try {
|
||||
mediaPlayer.setDataSource(activity, source);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, e.getMessage());
|
||||
return; //If unable to setup video, exit function
|
||||
}
|
||||
state = MediaPlayerState.INIT;
|
||||
}
|
||||
}
|
||||
|
||||
void prepareAsync() {
|
||||
if (state == MediaPlayerState.INIT) {
|
||||
mediaPlayer.prepareAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (state == MediaPlayerState.PREPARED) {
|
||||
mediaPlayer.start();
|
||||
state = MediaPlayerState.PLAYING;
|
||||
}
|
||||
}
|
||||
|
||||
void seekTo(int msec) {
|
||||
if (state == MediaPlayerState.PREPARED || state == MediaPlayerState.PLAYING) {
|
||||
mediaPlayer.seekTo(msec);
|
||||
}
|
||||
}
|
||||
|
||||
void stopAndReset() {
|
||||
if (state == MediaPlayerState.PLAYING || state == MediaPlayerState.PREPARED) {
|
||||
mediaPlayer.stop();
|
||||
}
|
||||
mediaPlayer.reset(); //can be called from any state
|
||||
state = MediaPlayerState.IDLE;
|
||||
}
|
||||
|
||||
void setOnPreparedListener(OnSafeMediaPlayerPreparedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
//The rest of the methods are delegation methods
|
||||
void setSurface(Surface surface) {
|
||||
mediaPlayer.setSurface(surface);
|
||||
}
|
||||
|
||||
void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
|
||||
mediaPlayer.setOnCompletionListener(listener);
|
||||
}
|
||||
|
||||
void setOnSeekListener(MediaPlayer.OnSeekCompleteListener listener) {
|
||||
mediaPlayer.setOnSeekCompleteListener(listener);
|
||||
}
|
||||
|
||||
void setOnInfoListener(MediaPlayer.OnInfoListener listener) {
|
||||
mediaPlayer.setOnInfoListener(listener);
|
||||
}
|
||||
|
||||
void release() {
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A view representing a metric that can be selected by the user. Meant for use by MetricSelectionFragment.
|
||||
* This view not only changes color when selected, but also plays a video. To save resources, only one MetricSelector
|
||||
* object plays a video at a time, so playback is coordinated by the MetricSelectionFragment object.
|
||||
*/
|
||||
public class MetricSelector extends FrameLayout {
|
||||
|
||||
private boolean isMetricSelected;
|
||||
private MetricsManager.Metrics metric;
|
||||
|
||||
TextureView textureView;
|
||||
|
||||
TextView gridItemTextView;
|
||||
ImageView imageView;
|
||||
ImageView imageViewBeneath;
|
||||
RelativeLayout backgroundLayout;
|
||||
|
||||
int itemNotSelectedColor;
|
||||
int itemSelectedColor;
|
||||
int itemSelectedOverLimitColor;
|
||||
|
||||
Uri[] videoResourceURIs;
|
||||
int videoResourceURIIndex;
|
||||
TextView videoOverlay;
|
||||
|
||||
int picId;
|
||||
|
||||
public MetricSelector(Activity hostActivity, LayoutInflater inflater, Resources res, String packageName, MetricsManager.Metrics metric) {
|
||||
super(hostActivity);
|
||||
|
||||
this.metric = metric;
|
||||
this.isMetricSelected = false;
|
||||
|
||||
initContent(inflater, res, packageName);
|
||||
}
|
||||
|
||||
void initContent(LayoutInflater inflater, Resources res, String packageName) {
|
||||
View content = inflater.inflate(R.layout.grid_item, this, true);
|
||||
|
||||
String resourceName = MetricsManager.getLowerCaseName(metric);
|
||||
|
||||
videoOverlay = (TextView) content.findViewById(R.id.video_overlay);
|
||||
|
||||
int videoId = res.getIdentifier(resourceName,"raw",packageName);
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
videoResourceURIs = new Uri[2];
|
||||
videoResourceURIs[0] = Uri.parse(String.format("android.resource://%s/%d", packageName, videoId ));
|
||||
videoResourceURIs[1] = Uri.parse(String.format("android.resource://%s/%d", packageName, res.getIdentifier(resourceName+"0","raw",packageName)));
|
||||
} else {
|
||||
videoResourceURIs = new Uri[1];
|
||||
videoResourceURIs[0] = Uri.parse(String.format("android.resource://%s/%d", packageName, videoId));
|
||||
}
|
||||
|
||||
videoResourceURIIndex = 0;
|
||||
|
||||
//set up image
|
||||
picId = res.getIdentifier(resourceName, "drawable", packageName);
|
||||
imageView = (ImageView) content.findViewById(R.id.grid_item_image_view);
|
||||
imageViewBeneath = (ImageView) content.findViewById(R.id.grid_item_image_view_beneath);
|
||||
imageView.setImageResource(picId);
|
||||
imageViewBeneath.setImageResource(picId);
|
||||
imageViewBeneath.setVisibility(GONE);
|
||||
|
||||
backgroundLayout = (RelativeLayout) content.findViewById(R.id.grid_item_background);
|
||||
|
||||
gridItemTextView = (TextView) content.findViewById(R.id.grid_item_text);
|
||||
gridItemTextView.setText(MetricsManager.getCapitalizedName(metric));
|
||||
|
||||
itemSelectedOverLimitColor = res.getColor(R.color.grid_item_chosen_over_limit);
|
||||
itemNotSelectedColor = res.getColor(R.color.grid_item_not_chosen);
|
||||
itemSelectedColor = res.getColor(R.color.grid_item_chosen);
|
||||
}
|
||||
|
||||
boolean getIsSelected() {
|
||||
return this.isMetricSelected;
|
||||
}
|
||||
|
||||
void setIsSelected(boolean isSelected) {
|
||||
this.isMetricSelected = isSelected;
|
||||
}
|
||||
|
||||
void removeCover() {
|
||||
imageViewBeneath.setVisibility(VISIBLE);
|
||||
imageView.setVisibility(GONE);
|
||||
}
|
||||
|
||||
void displayCover() {
|
||||
imageViewBeneath.setVisibility(GONE);
|
||||
imageView.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
void displayVideo(TextureView videoView) {
|
||||
textureView = videoView;
|
||||
backgroundLayout.addView(textureView, 1);
|
||||
textureView.setVisibility(VISIBLE);
|
||||
videoOverlay.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
void removeVideo() {
|
||||
if (textureView != null) {
|
||||
textureView.setVisibility(GONE);
|
||||
backgroundLayout.removeView(textureView);
|
||||
textureView = null;
|
||||
}
|
||||
videoOverlay.setVisibility(GONE);
|
||||
}
|
||||
|
||||
MetricsManager.Metrics getMetric() {
|
||||
return metric;
|
||||
}
|
||||
|
||||
void initIndex() {
|
||||
videoResourceURIIndex = 0;
|
||||
}
|
||||
|
||||
Uri getNextVideoResourceURI() {
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
if (videoResourceURIIndex == 0) {
|
||||
videoOverlay.setText("NEGATIVE");
|
||||
videoOverlay.setTextColor(Color.RED);
|
||||
} else {
|
||||
videoOverlay.setText("POSITIVE");
|
||||
videoOverlay.setTextColor(Color.GREEN);
|
||||
}
|
||||
}
|
||||
|
||||
videoResourceURIIndex += 1;
|
||||
if (videoResourceURIIndex > videoResourceURIs.length) {
|
||||
return null;
|
||||
} else {
|
||||
return videoResourceURIs[videoResourceURIIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the appearance of the grid item to indicate whether too many metrics have been selected
|
||||
*/
|
||||
void setUnderOrOverLimit(boolean atOrUnderLimit) {
|
||||
if (isMetricSelected) {
|
||||
if (atOrUnderLimit) {
|
||||
gridItemTextView.setBackgroundColor(itemSelectedColor);
|
||||
backgroundLayout.setBackgroundColor(itemSelectedColor);
|
||||
} else {
|
||||
gridItemTextView.setBackgroundColor(itemSelectedOverLimitColor);
|
||||
backgroundLayout.setBackgroundColor(itemSelectedOverLimitColor);
|
||||
}
|
||||
} else {
|
||||
gridItemTextView.setBackgroundColor(itemNotSelectedColor);
|
||||
backgroundLayout.setBackgroundColor(itemNotSelectedColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import java.lang.StringBuilder;
|
||||
|
||||
/**
|
||||
* A class containing:
|
||||
* -enumerations representing the Emotion and Expressions featured in the Affectiva SDK.
|
||||
* -a Metric interface to allow easy iteration through all Expressions and Emotions
|
||||
* -utility methods for converting a Metric into several types of strings
|
||||
*/
|
||||
public class MetricsManager {
|
||||
|
||||
private static Metrics[] allMetrics;
|
||||
|
||||
static {
|
||||
Emotions[] emotions = Emotions.values();
|
||||
Expressions[] expressions = Expressions.values();
|
||||
allMetrics = new Metrics[emotions.length + expressions.length];
|
||||
System.arraycopy(emotions,0,allMetrics,0,emotions.length);
|
||||
System.arraycopy(expressions,0,allMetrics,emotions.length,expressions.length);
|
||||
}
|
||||
|
||||
static Metrics[] getAllMetrics() {
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
enum MetricType {Emotion, Expression};
|
||||
|
||||
interface Metrics {
|
||||
MetricType getType();
|
||||
}
|
||||
|
||||
enum Emotions implements Metrics {
|
||||
ANGER,
|
||||
DISGUST,
|
||||
FEAR,
|
||||
JOY,
|
||||
SADNESS,
|
||||
SURPRISE,
|
||||
CONTEMPT,
|
||||
ENGAGEMENT,
|
||||
VALENCE;
|
||||
|
||||
@Override
|
||||
public MetricType getType() {
|
||||
return MetricType.Emotion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum Expressions implements Metrics {
|
||||
ATTENTION,
|
||||
BROW_FURROW,
|
||||
BROW_RAISE,
|
||||
CHIN_RAISE,
|
||||
EYE_CLOSURE,
|
||||
INNER_BROW_RAISE,
|
||||
LIP_CORNER_DEPRESSOR,
|
||||
LIP_PRESS,
|
||||
LIP_PUCKER,
|
||||
LIP_SUCK,
|
||||
MOUTH_OPEN,
|
||||
NOSE_WRINKLE,
|
||||
SMILE,
|
||||
SMIRK,
|
||||
UPPER_LIP_RAISE;
|
||||
|
||||
@Override
|
||||
public MetricType getType() {
|
||||
return MetricType.Expression;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Used for displays
|
||||
static String getUpperCaseName(Metrics metric) {
|
||||
if (metric == Expressions.LIP_CORNER_DEPRESSOR) {
|
||||
return "FROWN";
|
||||
} else {
|
||||
return metric.toString().replace("_", " ");
|
||||
}
|
||||
}
|
||||
|
||||
//Used for MetricSelectionFragment
|
||||
//This method is optimized for strings of the form SOME_METRIC_NAME, which all metric names currently are
|
||||
static String getCapitalizedName(Metrics metric) {
|
||||
if (metric == Expressions.LIP_CORNER_DEPRESSOR) {
|
||||
return "Frown";
|
||||
}
|
||||
String original = metric.toString();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean canBeLowerCase = false;
|
||||
for (int n = 0; n < original.length(); n++) {
|
||||
char c = original.charAt(n);
|
||||
if (c == '_') {
|
||||
builder.append(' ');
|
||||
canBeLowerCase = false;
|
||||
} else {
|
||||
if (canBeLowerCase) {
|
||||
builder.append(Character.toLowerCase(c));
|
||||
} else {
|
||||
builder.append(c);
|
||||
canBeLowerCase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
//Used to load resource files
|
||||
static String getLowerCaseName(Metrics metric) {
|
||||
return metric.toString().toLowerCase();
|
||||
}
|
||||
|
||||
//Used to construct method names for reflection
|
||||
static String getCamelCase(Metrics metric) {
|
||||
String metricString = metric.toString();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(Character.toUpperCase(metricString.charAt(0)));
|
||||
|
||||
if (metricString.length() > 1) {
|
||||
for (int n = 1; n < metricString.length(); n++ ){
|
||||
char c = metricString.charAt(n);
|
||||
if (c == '_') {
|
||||
n += 1;
|
||||
if (n < metricString.length()) {
|
||||
builder.append(metricString.charAt(n));
|
||||
}
|
||||
} else {
|
||||
builder.append(Character.toLowerCase(metricString.charAt(n)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
/**
|
||||
* A helper class to translate strings held in preferences into values to be used by the application.
|
||||
*/
|
||||
public class PreferencesUtils {
|
||||
|
||||
static final int DEFAULT_FPS = 20;
|
||||
|
||||
/**
|
||||
* Attempt to parse and return FPS set by user. If the FPS is invalid, we set it to be the default FPS.
|
||||
*/
|
||||
public static int getFrameProcessingRate(SharedPreferences pref) {
|
||||
String rateString = pref.getString("rate", String.valueOf(DEFAULT_FPS));
|
||||
int toReturn;
|
||||
try {
|
||||
toReturn = Integer.parseInt(rateString);
|
||||
} catch (Exception e) {
|
||||
saveFrameProcessingRate(pref,DEFAULT_FPS);
|
||||
return DEFAULT_FPS;
|
||||
}
|
||||
if (toReturn > 0) {
|
||||
return toReturn;
|
||||
} else {
|
||||
saveFrameProcessingRate(pref,DEFAULT_FPS);
|
||||
return DEFAULT_FPS;
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveFrameProcessingRate(SharedPreferences pref, int rate) {
|
||||
SharedPreferences.Editor editor = pref.edit();
|
||||
editor.putString("rate",String.valueOf(rate));
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public static MetricsManager.Metrics getMetricFromPrefs(SharedPreferences pref, int index) {
|
||||
MetricsManager.Metrics metric;
|
||||
try {
|
||||
String stringFromPref = pref.getString(String.format("metric_display_%d", index),defaultMetric(index).toString());
|
||||
metric = parseSavedMetric(stringFromPref );
|
||||
} catch (IllegalArgumentException e) {
|
||||
metric = defaultMetric(index);
|
||||
SharedPreferences.Editor editor = pref.edit();
|
||||
editor.putString(String.format("metric_display_%d", index),defaultMetric(index).toString());
|
||||
editor.commit();
|
||||
}
|
||||
return metric;
|
||||
}
|
||||
|
||||
public static void saveMetricToPrefs(SharedPreferences.Editor editor , int index, MetricsManager.Metrics metric) {
|
||||
editor.putString(String.format("metric_display_%d", index), metric.toString());
|
||||
}
|
||||
|
||||
static private MetricsManager.Metrics defaultMetric(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return MetricsManager.Emotions.ANGER;
|
||||
case 1:
|
||||
return MetricsManager.Emotions.DISGUST;
|
||||
case 2:
|
||||
return MetricsManager.Emotions.FEAR;
|
||||
case 3:
|
||||
return MetricsManager.Emotions.JOY;
|
||||
case 4:
|
||||
return MetricsManager.Emotions.SADNESS;
|
||||
case 5:
|
||||
return MetricsManager.Emotions.SURPRISE;
|
||||
}
|
||||
|
||||
return MetricsManager.Emotions.ANGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* We attempt to parse the string as an Emotion or, failing that, as an Expression.
|
||||
*/
|
||||
static MetricsManager.Metrics parseSavedMetric(String metricString) throws IllegalArgumentException{
|
||||
try {
|
||||
MetricsManager.Emotions emotion;
|
||||
emotion = MetricsManager.Emotions.valueOf(metricString);
|
||||
return emotion;
|
||||
} catch (IllegalArgumentException emotionParseFailed) {
|
||||
try {
|
||||
MetricsManager.Expressions expression;
|
||||
expression = MetricsManager.Expressions.valueOf(metricString);
|
||||
return expression;
|
||||
} catch (IllegalArgumentException expressionParseFailed) {
|
||||
throw new IllegalArgumentException("String did not match an emotion or expression");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.R.id.*;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//Activity which works with standard Android Preferences framework to display the preference headers.
|
||||
public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedBundleInstance) {
|
||||
super.onCreate(savedBundleInstance);
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setIcon(
|
||||
new ColorDrawable(getResources().getColor(android.R.color.transparent)));
|
||||
//actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
this.onBackPressed();
|
||||
return true;
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
// Respond to the action bar's Up/Home button
|
||||
case android.R.id.home:
|
||||
this.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
//Populate the activity with the top-level headers.
|
||||
@Override
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
loadHeadersFromResource(R.xml.preference_headers, target);
|
||||
}
|
||||
|
||||
//Boilerplate method, required by Android API
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
if (SettingsFragment.class.getName().equals(fragmentName) || MetricSelectionFragment.class.getName().equals(fragmentName)) {
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
//This fragment shows the preferences for the first header.
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.settings_preferences);
|
||||
}
|
||||
}
|
||||
|
||||
//The second fragment is defined in a separate file.
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.affectiva.errorreporting;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Created by Alan on 8/21/2015.
|
||||
*/
|
||||
public class CustomApplication extends Application {
|
||||
|
||||
static volatile boolean wasErrorActivityStarted = false;
|
||||
static final boolean enableCustomErrorMessage = false;
|
||||
Thread.UncaughtExceptionHandler exceptionHandler;
|
||||
|
||||
@Override
|
||||
public void onCreate ()
|
||||
{
|
||||
super.onCreate();
|
||||
exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
// Setup handler for uncaught exceptions.
|
||||
Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
|
||||
{
|
||||
@Override
|
||||
public void uncaughtException (Thread thread, Throwable e)
|
||||
{
|
||||
handleUncaughtException (thread, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void handleUncaughtException (Thread thread, Throwable e)
|
||||
{
|
||||
if (!wasErrorActivityStarted && enableCustomErrorMessage) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("com.affectiva.REPORT_ERROR"); // see step 5.
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application
|
||||
intent.putExtra("affdexme_error", e);
|
||||
startActivity(intent);
|
||||
wasErrorActivityStarted = true;
|
||||
}
|
||||
|
||||
exceptionHandler.uncaughtException(thread,e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package com.affectiva.errorreporting;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.affectiva.affdexme.R;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Created by Alan on 8/21/2015.
|
||||
*/
|
||||
public class ErrorReporter extends Activity implements View.OnClickListener {
|
||||
|
||||
String errorMessage;
|
||||
Button sendButton;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE); // make a dialog without a titlebar
|
||||
setContentView(R.layout.error_reporter);
|
||||
|
||||
Throwable error = (Throwable) getIntent().getSerializableExtra("affdexme_error");
|
||||
if (error != null) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("AffdexMe Error Report:");
|
||||
builder.append(DateFormat.getDateTimeInstance().format(new Date()));
|
||||
builder.append("\n");
|
||||
builder.append(error.getMessage());
|
||||
builder.append(("\n"));
|
||||
|
||||
StackTraceElement[] stackTraceElements = error.getStackTrace();
|
||||
for (StackTraceElement element : stackTraceElements) {
|
||||
builder.append("\n");
|
||||
builder.append(element.toString());
|
||||
}
|
||||
errorMessage = builder.toString();
|
||||
} else {
|
||||
errorMessage = "Failed to catch error.";
|
||||
}
|
||||
|
||||
sendButton = (Button) findViewById(R.id.send_button);
|
||||
sendButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent (Intent.ACTION_SEND);
|
||||
intent.setType("plain/text");
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"sdk@affectiva.com"});
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, "AffdexMe Crash Report");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, errorMessage); // do this so some email clients don't complain about empty body.
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 250 B |
BIN
AffdexMe/app/src/main/res/drawable-hdpi/settings_button.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 201 B |
BIN
AffdexMe/app/src/main/res/drawable-mdpi/settings_button.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/anger.jpg
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/attention.jpg
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/brow_furrow.jpg
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/brow_raise.jpg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/chin_raise.jpg
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/contempt.jpg
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/disgust.jpg
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/engagement.jpg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/eye_closure.jpg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/fear.jpg
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/inner_brow_raise.jpg
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/joy.jpg
Normal file
After Width: | Height: | Size: 7 KiB |
After Width: | Height: | Size: 5.7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/lip_press.jpg
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/lip_pucker.jpg
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/lip_suck.jpg
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/mouth_open.jpg
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/negative_valence.jpg
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/nose_wrinkle.jpg
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/sadness.jpg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/smile.jpg
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/smirk.jpg
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/surprise.jpg
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/upper_lip_raise.jpg
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
AffdexMe/app/src/main/res/drawable-nodpi/valence.jpg
Normal file
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 232 B |
BIN
AffdexMe/app/src/main/res/drawable-xhdpi/settings_button.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 290 B |
BIN
AffdexMe/app/src/main/res/drawable-xxhdpi/settings_button.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 13 KiB |
BIN
AffdexMe/app/src/main/res/drawable/ic_arrow_back_white_24dp.png
Normal file
After Width: | Height: | Size: 232 B |
BIN
AffdexMe/app/src/main/res/drawable/settings_button.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
AffdexMe/app/src/main/res/drawable/settings_button_pressed.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/settings_button_pressed"/>
|
||||
<item android:drawable="@drawable/settings_button" />
|
||||
</selector>
|
|
@ -1,5 +1,7 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" tools:context=".MainActivity" android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:keepScreenOn="true"
|
||||
|
@ -21,14 +23,28 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
custom:measurements_text_size="@dimen/measurements_text_size"
|
||||
custom:measurements_upper_spacing="@dimen/measurements_upper_text_spacing"
|
||||
custom:measurements_lower_spacing="@dimen/measurements_lower_text_spacing"
|
||||
custom:measurements_color="#DDDDDD"
|
||||
custom:measurements_text_border_color="@color/letter_gray"
|
||||
custom:measurements_text_border_thickness="@integer/measurements_text_border_thickness"
|
||||
android:id="@+id/drawing_view"/>
|
||||
<include layout="@layout/metric_layout"
|
||||
android:id="@+id/metric_view_group"
|
||||
/>
|
||||
<include layout="@layout/menu_layout"
|
||||
<ImageButton
|
||||
android:layout_width="@dimen/settings_button_size"
|
||||
android:layout_height="@dimen/settings_button_size"
|
||||
android:background="@null"
|
||||
android:src="@drawable/settings_button_selector"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_margin="@dimen/settings_button_margin"
|
||||
android:contentDescription="@string/settings_content_description"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@id/metric_view_group"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:id="@+id/settings_button"
|
||||
android:onClick="settings_button_click"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
|
@ -36,12 +52,24 @@
|
|||
android:layout_height="match_parent"
|
||||
android:id="@+id/progress_bar_cover"
|
||||
android:background="@color/black">
|
||||
<ProgressBar
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:style/Widget.DeviceDefault.ProgressBar"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"/>
|
||||
android:layout_centerVertical="true"
|
||||
android:id="@+id/please_wait_textview"
|
||||
android:text="@string/loading"
|
||||
android:textSize="@dimen/please_wait_textview_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
/>
|
||||
<ProgressBar
|
||||
android:layout_width="@dimen/please_wait_textview_size"
|
||||
android:layout_height="@dimen/please_wait_textview_size"
|
||||
android:layout_toLeftOf="@id/please_wait_textview"
|
||||
android:layout_centerVertical="true"
|
||||
android:indeterminate="true"
|
||||
android:paddingRight="10dp"
|
||||
android:id="@+id/progress_bar"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
23
AffdexMe/app/src/main/res/layout/error_reporter.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<Button
|
||||
android:padding="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:id="@+id/send_button"
|
||||
android:text="Send"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:gravity="center"
|
||||
android:textSize="20sp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_above="@id/send_button"
|
||||
android:text="It looks like AffdexMe has crashed! Click below to send an automated report, then try restarting the app."/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
17
AffdexMe/app/src/main/res/layout/grid_header.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/header_text"
|
||||
android:textSize="@dimen/header_text_size"
|
||||
android:layout_marginLeft="5dp"/>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/affectiva_orange"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
</LinearLayout>
|
39
AffdexMe/app/src/main/res/layout/grid_item.xml
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
android:padding="5dp">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
android:id="@+id/grid_item_background"
|
||||
android:paddingLeft="4dp" android:paddingBottom="8dp" android:paddingRight="4dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/grid_item_image_view_beneath"
|
||||
/>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/grid_item_image_view"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/grid_item_text"
|
||||
android:textSize="@dimen/grid_item_metric_name"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:layout_alignParentTop="true"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:id="@+id/video_overlay"
|
||||
android:textSize="@dimen/grid_item_chooser_text_size"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
android:padding="2dp"/>
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/affdexme_menu"
|
||||
android:background="@color/transparent_overlay"
|
||||
android:visibility="invisible"
|
||||
>
|
||||
<View
|
||||
android:id="@+id/menu_border"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginRight="30dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/letter_gray"/>
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/menu_border"
|
||||
android:id="@+id/menu_table_layout"
|
||||
android:stretchColumns="0,1">
|
||||
<TableRow>
|
||||
<CheckBox
|
||||
style="@style/optionsStyle"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/show_fps"
|
||||
android:id="@+id/fps_checkbox"
|
||||
android:onClick="fps_checkbox_click"
|
||||
/>
|
||||
<CheckBox
|
||||
style="@style/optionsStyle"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/show_tracking"
|
||||
android:id="@+id/tracking_checkbox"
|
||||
android:onClick="tracking_checkbox_click"
|
||||
/>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_below="@id/menu_table_layout">
|
||||
<TextView
|
||||
style="@style/optionsStyle"
|
||||
android:text="@string/processed_frames_per_second"
|
||||
android:id="@+id/fps_edittext_name"
|
||||
/>
|
||||
<EditText
|
||||
style="@style/optionsStyle"
|
||||
android:inputType="number"
|
||||
android:ems="3"
|
||||
android:maxLength="2"
|
||||
android:id="@+id/fps_edittext"
|
||||
android:imeOptions="actionDone" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
47
AffdexMe/app/src/main/res/layout/metric_chooser.xml
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/metric_chooser_root_layout"
|
||||
android:soundEffectsEnabled="true">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textSize="@dimen/metric_chooser_text_size"
|
||||
android:text="@string/choose_six_message"/>
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
>
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/clear_all"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:id="@+id/clear_all_button"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@id/clear_all_button"
|
||||
android:textSize="@dimen/metric_chooser_text_size"
|
||||
android:id="@+id/metrics_chooser_textview"
|
||||
android:text="@string/metric_chooser_default_message"
|
||||
android:textColor="@color/white"
|
||||
android:layout_centerVertical="true"/>
|
||||
</RelativeLayout>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/metric_chooser_gridlayout"
|
||||
android:orientation="horizontal"
|
||||
android:columnOrderPreserved="true"
|
||||
/>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:affdex="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="@dimen/metric_viewgroup"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -13,11 +12,12 @@
|
|||
<!-- Logo-->
|
||||
<ImageView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="@dimen/image_width"
|
||||
android:layout_width="@dimen/logo_width"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/affectiva_logo_clear_background"
|
||||
android:contentDescription="@string/affectiva_logo_content_description"
|
||||
android:id="@+id/affectiva_logo" />
|
||||
<!-- Left Metrics-->
|
||||
<!-- Left MetricsManager-->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/metric_panel_padding"
|
||||
|
@ -27,26 +27,23 @@
|
|||
android:id="@+id/left_metrics">
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/smile"
|
||||
android:id="@+id/smile_name"
|
||||
android:id="@+id/metric_name_0"
|
||||
/>
|
||||
<com.affectiva.affdexme.MetricView
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/smile_pct" />
|
||||
android:id="@+id/metric_pct_0" />
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/brow_raise"
|
||||
android:id="@+id/brow_raise_name" />
|
||||
<com.affectiva.affdexme.MetricView
|
||||
android:id="@+id/metric_name_1" />
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/brow_raise_pct" />
|
||||
android:id="@+id/metric_pct_1" />
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/brow_furrow"
|
||||
android:id="@+id/brow_furrow_name" />
|
||||
<com.affectiva.affdexme.MetricView
|
||||
android:id="@+id/metric_name_2" />
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/brow_furrow_pct" />
|
||||
android:id="@+id/metric_pct_2" />
|
||||
</LinearLayout>
|
||||
<!-- FPS Counter-->
|
||||
<LinearLayout
|
||||
|
@ -70,10 +67,11 @@
|
|||
android:gravity="left|bottom"
|
||||
android:id="@+id/fps_value"
|
||||
android:textSize="@dimen/pct_text_size"
|
||||
android:textColor="@color/letter_gray"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<!-- Right Metrics-->
|
||||
<!-- Right MetricsManager-->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -83,24 +81,21 @@
|
|||
android:id="@+id/right_metrics">
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/frown"
|
||||
android:id="@+id/frown_name" />
|
||||
<com.affectiva.affdexme.MetricView
|
||||
android:id="@+id/metric_name_3" />
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/frown_pct" />
|
||||
android:id="@+id/metric_pct_3" />
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/valence"
|
||||
android:id="@+id/valence_name" />
|
||||
<com.affectiva.affdexme.GradientMetricView
|
||||
android:id="@+id/metric_name_4" />
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/valence_pct" />
|
||||
android:id="@+id/metric_pct_4" />
|
||||
<TextView
|
||||
style="@style/metricName"
|
||||
android:text="@string/engagement"
|
||||
android:id="@+id/engagement_name" />
|
||||
<com.affectiva.affdexme.MetricView
|
||||
android:id="@+id/metric_name_5" />
|
||||
<com.affectiva.affdexme.MetricDisplay
|
||||
style="@style/metricPct"
|
||||
android:id="@+id/engagement_pct" />
|
||||
android:id="@+id/metric_pct_5" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.3 KiB |
BIN
AffdexMe/app/src/main/res/raw/anger.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/attention.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/brow_furrow.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/brow_raise.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/chin_raise.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/contempt.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/disgust.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/engagement.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/eye_closure.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/fear.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/inner_brow_raise.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/joy.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/lip_corner_depressor.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/lip_press.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/lip_pucker.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/lip_suck.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/mouth_open.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/nose_wrinkle.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/sadness.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/smile.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/smirk.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/surprise.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/upper_lip_raise.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/valence.mp4
Normal file
BIN
AffdexMe/app/src/main/res/raw/valence0.mp4
Normal file
|
@ -5,6 +5,35 @@
|
|||
<dimen name="metric_view_bar_length">130dp</dimen>
|
||||
<dimen name="bottom_padding">10dp</dimen>
|
||||
<dimen name="metric_viewgroup">190dp</dimen>
|
||||
<dimen name="image_width">180dp</dimen>
|
||||
<dimen name="logo_width">180dp</dimen>
|
||||
<dimen name="metric_panel_padding">15dp</dimen>
|
||||
|
||||
<dimen name="please_wait_textview_size">28sp</dimen>
|
||||
|
||||
<dimen name="settings_button_size">64dp</dimen>
|
||||
<dimen name="settings_button_margin">12dp</dimen>
|
||||
|
||||
<dimen name="measurements_text_size">21sp</dimen>
|
||||
<dimen name="measurements_upper_text_spacing">70dp</dimen>
|
||||
<dimen name="measurements_lower_text_spacing">100dp</dimen>
|
||||
<integer name="measurements_text_border_thickness">5</integer>
|
||||
|
||||
<dimen name="metric_chooser_text_size">24dp</dimen>
|
||||
|
||||
<dimen name="header_text_size">26dp</dimen>
|
||||
|
||||
<dimen name="grid_item_checkbox_size">40dp</dimen>
|
||||
<dimen name="grid_item_chooser_text_size">19sp</dimen>
|
||||
|
||||
<dimen name="grid_item_left_padding">5dp</dimen>
|
||||
<dimen name="grid_item_bottom_padding">10dp</dimen>
|
||||
|
||||
<dimen name="grid_item_metric_name">18sp</dimen>
|
||||
<dimen name="metric_chooser_column_width">180dp</dimen>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -5,6 +5,30 @@
|
|||
<dimen name="metric_view_bar_length">80dp</dimen>
|
||||
<dimen name="bottom_padding">5dp</dimen>
|
||||
<dimen name="metric_viewgroup">130dp</dimen>
|
||||
<dimen name="image_width">120dp</dimen>
|
||||
<dimen name="logo_width">120dp</dimen>
|
||||
<dimen name="metric_panel_padding">10dp</dimen>
|
||||
|
||||
<dimen name="please_wait_textview_size">20sp</dimen>
|
||||
|
||||
<dimen name="settings_button_size">48dp</dimen>
|
||||
<dimen name="settings_button_margin">10dp</dimen>
|
||||
|
||||
<dimen name="measurements_text_size">17sp</dimen>
|
||||
<dimen name="measurements_upper_text_spacing">60dp</dimen>
|
||||
<dimen name="measurements_lower_text_spacing">80dp</dimen>
|
||||
<integer name="measurements_text_border_thickness">3</integer>
|
||||
|
||||
<dimen name="grid_item_chooser_text_size">15sp</dimen>
|
||||
<dimen name="grid_item_checkbox_size">30dp</dimen>
|
||||
|
||||
<dimen name="header_text_size">21dp</dimen>
|
||||
|
||||
<dimen name="metric_chooser_text_size">18dp</dimen>
|
||||
|
||||
<dimen name="grid_item_left_padding">4dp</dimen>
|
||||
<dimen name="grid_item_bottom_padding">8dp</dimen>
|
||||
|
||||
<dimen name="grid_item_metric_name">15sp</dimen>
|
||||
<dimen name="metric_chooser_column_width">140dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
<dimen name="metric_view_bar_length">70dp</dimen>
|
||||
<dimen name="bottom_padding">5dp</dimen>
|
||||
<dimen name="metric_viewgroup">130dp</dimen>
|
||||
<dimen name="image_width">100dp</dimen>
|
||||
<dimen name="logo_width">100dp</dimen>
|
||||
<dimen name="metric_panel_padding">8dp</dimen>
|
||||
|
||||
<dimen name="please_wait_textview_size">17sp</dimen>
|
||||
|
||||
<dimen name="settings_button_size">36dp</dimen>
|
||||
<dimen name="settings_button_margin">7dp</dimen>
|
||||
|
||||
<dimen name="grid_item_chooser_text_size">12sp</dimen>
|
||||
<dimen name="grid_item_checkbox_size">50dp</dimen>
|
||||
|
||||
<dimen name="measurements_text_size">13sp</dimen>
|
||||
<dimen name="measurements_upper_text_spacing">50dp</dimen>
|
||||
<dimen name="measurements_lower_text_spacing">70dp</dimen>
|
||||
<integer name="measurements_text_border_thickness">3</integer>
|
||||
|
||||
<dimen name="header_text_size">17dp</dimen>
|
||||
|
||||
<dimen name="metric_chooser_text_size">13dp</dimen>
|
||||
|
||||
<dimen name="grid_item_left_padding">3dp</dimen>
|
||||
<dimen name="grid_item_bottom_padding">6dp</dimen>
|
||||
|
||||
<dimen name="grid_item_metric_name">11sp</dimen>
|
||||
<dimen name="metric_chooser_column_width">120dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,32 @@
|
|||
<dimen name="metric_view_bar_length">140dp</dimen>
|
||||
<dimen name="bottom_padding">15dp</dimen>
|
||||
<dimen name="metric_viewgroup">220dp</dimen>
|
||||
<dimen name="image_width">250dp</dimen>
|
||||
<dimen name="logo_width">250dp</dimen>
|
||||
<dimen name="metric_panel_padding">25dp</dimen>
|
||||
|
||||
<dimen name="please_wait_textview_size">35sp</dimen>
|
||||
|
||||
<dimen name="settings_size">72dp</dimen>
|
||||
<dimen name="settings_button_margin">15dp</dimen>
|
||||
|
||||
|
||||
|
||||
<dimen name="measurements_text_size">30sp</dimen>
|
||||
<dimen name="measurements_upper_text_spacing">110dp</dimen>
|
||||
<dimen name="measurements_lower_text_spacing">130dp</dimen>
|
||||
<integer name="measurements_text_border_thickness">7</integer>
|
||||
|
||||
<dimen name="metric_chooser_text_size">26dp</dimen>
|
||||
|
||||
<dimen name="header_text_size">30dp</dimen>
|
||||
|
||||
<dimen name="grid_item_checkbox_size">60dp</dimen>
|
||||
<dimen name="grid_item_chooser_text_size">24sp</dimen>
|
||||
|
||||
<dimen name="grid_item_left_padding">6dp</dimen>
|
||||
<dimen name="grid_item_bottom_padding">12dp</dimen>
|
||||
|
||||
<dimen name="grid_item_metric_name">22sp</dimen>
|
||||
<dimen name="metric_chooser_column_width">200dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -6,4 +6,12 @@
|
|||
<attr name="barLength" format="dimension"/>
|
||||
<attr name="textDepth" format="dimension"/>
|
||||
</declare-styleable>
|
||||
<declare-styleable name="drawing_view_attributes">
|
||||
<attr name="measurements_text_size" format="dimension" />
|
||||
<attr name="measurements_upper_spacing" format="dimension"/>
|
||||
<attr name="measurements_lower_spacing" format="dimension"/>
|
||||
<attr name="measurements_color" format="color"/>
|
||||
<attr name="measurements_text_border_color" format="color"/>
|
||||
<attr name="measurements_text_border_thickness" format="float"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -2,6 +2,12 @@
|
|||
<resources>
|
||||
<color name="transparent_overlay">#55ffffff</color>
|
||||
<color name="letter_gray">#514a40</color>
|
||||
<color name="letter_orange">#ff8000</color>
|
||||
<color name="affectiva_orange">#ff8000</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="white">#ffffff</color>
|
||||
<color name="red">#cf1a0b</color>
|
||||
|
||||
<color name="grid_item_not_chosen">#646464</color>
|
||||
<color name="grid_item_chosen">#009600</color>
|
||||
<color name="grid_item_chosen_over_limit">#cf1a0b</color>
|
||||
</resources>
|
|
@ -5,6 +5,32 @@
|
|||
<dimen name="metric_view_bar_length">80dp</dimen>
|
||||
<dimen name="bottom_padding">5dp</dimen>
|
||||
<dimen name="metric_viewgroup">140dp</dimen>
|
||||
<dimen name="image_width">120dp</dimen>
|
||||
<dimen name="logo_width">120dp</dimen>
|
||||
<dimen name="metric_panel_padding">10dp</dimen>
|
||||
|
||||
<dimen name="please_wait_textview_size">20sp</dimen>
|
||||
|
||||
<!--TODO: create dimensions for other screen sizes -->
|
||||
<dimen name="settings_button_size">48dp</dimen>
|
||||
<dimen name="settings_button_margin">10dp</dimen>
|
||||
|
||||
<dimen name="measurements_text_size">17sp</dimen>
|
||||
<dimen name="measurements_upper_text_spacing">60dp</dimen>
|
||||
<dimen name="measurements_lower_text_spacing">80dp</dimen>
|
||||
<integer name="measurements_text_border_thickness">3</integer>
|
||||
|
||||
<dimen name="grid_item_chooser_text_size">15sp</dimen>
|
||||
<dimen name="grid_item_checkbox_size">30dp</dimen>
|
||||
|
||||
<dimen name="header_text_size">21dp</dimen>
|
||||
|
||||
|
||||
<dimen name="metric_chooser_text_size">18dp</dimen>
|
||||
|
||||
<dimen name="grid_item_left_padding">4dp</dimen>
|
||||
<dimen name="grid_item_bottom_padding">8dp</dimen>
|
||||
|
||||
<dimen name="grid_item_metric_name">15sp</dimen>
|
||||
<dimen name="metric_chooser_column_width">140dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|