Merge pull request #3 from Affectiva/affdex_2.0_release
Publish AffdexMe 2.0
|
@ -5,7 +5,7 @@
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="distributionType" value="LOCAL" />
|
<option name="distributionType" value="LOCAL" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<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="gradleJvm" value="1.7" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
|
|
|
@ -12,12 +12,10 @@
|
||||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
<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="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
|
||||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
||||||
<afterSyncTasks>
|
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
|
||||||
<task>generateDebugAndroidTestSources</task>
|
|
||||||
<task>generateDebugSources</task>
|
|
||||||
</afterSyncTasks>
|
|
||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<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/outputs" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||||
</content>
|
</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="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="dagger-1.2.2" level="project" />
|
||||||
<orderEntry type="library" exported="" name="javax.inject-1" 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="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" />
|
<orderEntry type="library" exported="" name="flurry-analytics-4.1.0" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
|
@ -24,8 +24,8 @@ dependencies {
|
||||||
compile 'com.google.code.gson:gson:2.3'
|
compile 'com.google.code.gson:gson:2.3'
|
||||||
|
|
||||||
//include the Affdex SDK jars
|
//include the Affdex SDK jars
|
||||||
compile files('libs/Affdex-sdk-1.2-SNAPSHOT.jar')
|
compile files('libs/Affdex-sdk.jar')
|
||||||
compile files('libs/Affdex-sdk-1.2-SNAPSHOT-javadoc.jar')
|
compile files('libs/Affdex-sdk-javadoc.jar')
|
||||||
compile files('libs/dagger-1.2.2.jar')
|
compile files('libs/dagger-1.2.2.jar')
|
||||||
compile files('libs/flurry-analytics-4.1.0.jar')
|
compile files('libs/flurry-analytics-4.1.0.jar')
|
||||||
compile files('libs/javax.inject-1.jar')
|
compile files('libs/javax.inject-1.jar')
|
||||||
|
|
|
@ -7,28 +7,48 @@
|
||||||
android:targetSdkVersion="22" />
|
android:targetSdkVersion="22" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="com.affectiva.errorreporting.CustomApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:hardwareAccelerated="true"
|
||||||
android:theme="@style/AppTheme">
|
android:label="@string/app_name">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="screenSize|keyboardHidden|orientation"
|
android:configChanges="screenSize|keyboardHidden|orientation"
|
||||||
android:screenOrientation="sensorPortrait"
|
android:screenOrientation="sensorPortrait"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:theme="@style/MainActivityTheme"
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package com.affectiva.affdexme;
|
package com.affectiva.affdexme;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
@ -14,6 +16,8 @@ import android.util.Log;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.SurfaceView;
|
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.
|
* 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 DrawingViewConfig config;
|
||||||
private final long drawPeriod = 33; //draw at 30 fps
|
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) {
|
public DrawingThread(SurfaceHolder surfaceHolder, DrawingViewConfig con) {
|
||||||
mSurfaceHolder = surfaceHolder;
|
mSurfaceHolder = surfaceHolder;
|
||||||
|
|
||||||
circlePaint = new Paint();
|
circlePaint = new Paint();
|
||||||
circlePaint.setColor(Color.WHITE);
|
circlePaint.setColor(Color.WHITE);
|
||||||
|
|
||||||
boxPaint = new Paint();
|
boxPaint = new Paint();
|
||||||
boxPaint.setColor(Color.WHITE);
|
boxPaint.setColor(Color.WHITE);
|
||||||
boxPaint.setStyle(Paint.Style.STROKE);
|
boxPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
@ -44,20 +54,24 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
config = con;
|
config = con;
|
||||||
|
|
||||||
setThickness(config.drawThickness);
|
setThickness(config.drawThickness);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void setMetrics(float roll, float yaw, float pitch, float interOcDis, float valence) {
|
||||||
* Used to set the valence score, which determines the color of the bounding box.
|
//format string for our DrawingView to use when ready
|
||||||
* **/
|
this.roll = String.format("%.2f",roll);
|
||||||
void setScore(float s) {
|
this.yaw = String.format("%.2f",yaw);
|
||||||
if (s > 0) {
|
this.pitch = String.format("%.2f",pitch);
|
||||||
float colorScore = ((100f-s)/100f)*255;
|
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));
|
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
|
||||||
} else {
|
} else {
|
||||||
float colorScore = ((100f+s)/100f)*255;
|
float colorScore = ((100f+valence)/100f)*255;
|
||||||
boxPaint.setColor(Color.rgb(255, (int) colorScore, (int) colorScore));
|
boxPaint.setColor(Color.rgb(255, (int) colorScore, (int) colorScore));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopThread() {
|
public void stopThread() {
|
||||||
|
@ -100,7 +114,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
if (c!= null) {
|
if (c!= null) {
|
||||||
synchronized (mSurfaceHolder) {
|
synchronized (mSurfaceHolder) {
|
||||||
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); //clear previous dots
|
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); //clear previous dots
|
||||||
if (config.isDrawPointsEnabled && (nextPointsToDraw != null) ) {
|
if ((nextPointsToDraw != null) ) {
|
||||||
draw(c);
|
draw(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,31 +145,69 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
PointF[] points = nextPointsToDraw;
|
PointF[] points = nextPointsToDraw;
|
||||||
|
|
||||||
//Coordinates around which to draw bounding box.
|
//Coordinates around which to draw bounding box.
|
||||||
float leftBx = config.imageWidth;
|
float leftBx = config.surfaceViewWidth;
|
||||||
float rightBx = 0;
|
float rightBx = 0;
|
||||||
float topBx = config.imageHeight;
|
float topBx = config.surfaceViewHeight;
|
||||||
float botBx = 0;
|
float botBx = 0;
|
||||||
|
|
||||||
//Iterate through all the points given to us by the CameraDetector object
|
|
||||||
for (int i = 0; i < points.length; i++) {
|
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.
|
//We determine the left-most, top-most, right-most, and bottom-most points to draw the bounding box around.
|
||||||
if (points[i].x < leftBx)
|
if (x < leftBx)
|
||||||
leftBx = points[i].x;
|
leftBx = x;
|
||||||
if (points[i].x > rightBx)
|
if (x > rightBx)
|
||||||
rightBx = points[i].x;
|
rightBx = x;
|
||||||
if (points[i].y < topBx)
|
if (y < topBx)
|
||||||
topBx = points[i].y;
|
topBx = y;
|
||||||
if (points[i].y > botBx)
|
if (y > botBx)
|
||||||
botBx = points[i].y;
|
botBx = y;
|
||||||
|
|
||||||
//Draw facial tracking dots.
|
//Draw facial tracking dots.
|
||||||
//The camera preview is displayed as a mirror, so X pts have to be reversed
|
if (config.isDrawPointsEnabled) {
|
||||||
c.drawCircle((config.imageWidth - points[i].x - 1) * config.screenToImageRatio, (points[i].y)* config.screenToImageRatio, config.drawThickness, circlePaint);
|
c.drawCircle(x, y, config.drawThickness, circlePaint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Draw the bounding box.
|
//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 isImageDimensionsNeeded = true;
|
||||||
private boolean isSurfaceViewDimensionsNeeded = true;
|
private boolean isSurfaceViewDimensionsNeeded = true;
|
||||||
private boolean isDrawPointsEnabled = true; //by default, have the drawing thread draw tracking dots
|
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) {
|
public void updateImageDimensions(int w, int h) {
|
||||||
|
|
||||||
|
@ -211,6 +276,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
//Class variables of DrawingView class
|
//Class variables of DrawingView class
|
||||||
private SurfaceHolder surfaceHolder;
|
private SurfaceHolder surfaceHolder;
|
||||||
private DrawingThread drawingThread; //DrawingThread object
|
private DrawingThread drawingThread; //DrawingThread object
|
||||||
|
private Typeface typeface;
|
||||||
private DrawingViewConfig drawingViewConfig;
|
private DrawingViewConfig drawingViewConfig;
|
||||||
private static String LOG_TAG = "AffdexMe";
|
private static String LOG_TAG = "AffdexMe";
|
||||||
|
|
||||||
|
@ -218,24 +284,59 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
//three constructors required of any custom view
|
//three constructors required of any custom view
|
||||||
public DrawingView(Context context) {
|
public DrawingView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
initView();
|
initView(context, null);
|
||||||
}
|
}
|
||||||
public DrawingView(Context context, AttributeSet attrs) {
|
public DrawingView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
initView();
|
initView(context, attrs);
|
||||||
}
|
}
|
||||||
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
|
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, 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 = 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.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
|
surfaceHolder.addCallback(this); //become a Listener to the three events below that SurfaceView throws
|
||||||
|
|
||||||
drawingViewConfig = new DrawingViewConfig();
|
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);
|
drawingThread = new DrawingThread(surfaceHolder, drawingViewConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTypeface(Typeface face) {
|
||||||
|
drawingViewConfig.textPaint.setTypeface(face);
|
||||||
|
drawingViewConfig.textBorderPaint.setTypeface(face);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
@ -309,9 +410,16 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
|
||||||
return drawingViewConfig.isDrawPointsEnabled;
|
return drawingViewConfig.isDrawPointsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
//The methods below simply delegate to the drawingThread object
|
public void setDrawMeasurementsEnabled(boolean b) {
|
||||||
public void setScore(float s) {
|
drawingViewConfig.isDrawMeasurementsEnabled = b;
|
||||||
drawingThread.setScore(s);
|
}
|
||||||
|
|
||||||
|
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) {
|
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;
|
package com.affectiva.affdexme;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
@ -14,18 +15,17 @@ import android.view.SurfaceView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.widget.ImageButton;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.affectiva.android.affdex.sdk.Frame;
|
import com.affectiva.android.affdex.sdk.Frame;
|
||||||
|
@ -67,37 +67,24 @@ import com.affectiva.android.affdex.sdk.detector.Face;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class MainActivity extends Activity
|
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";
|
private static final String LOG_TAG = "Affectiva";
|
||||||
|
public static final int NUM_METRICS_DISPLAYED = 6;
|
||||||
|
|
||||||
//Affectiva SDK Object
|
//Affectiva SDK Object
|
||||||
private CameraDetector detector = null;
|
private CameraDetector detector = null;
|
||||||
|
|
||||||
//Metrics View UI Objects
|
//MetricsManager View UI Objects
|
||||||
private RelativeLayout metricViewLayout;
|
private RelativeLayout metricViewLayout;
|
||||||
private LinearLayout leftMetricsLayout;
|
private LinearLayout leftMetricsLayout;
|
||||||
private LinearLayout rightMetricsLayout;
|
private LinearLayout rightMetricsLayout;
|
||||||
private MetricView smilePct;
|
private MetricDisplay[] metricDisplays;
|
||||||
private MetricView browRaisePct;
|
private TextView[] metricNames;
|
||||||
private MetricView browFurrowPct;
|
|
||||||
private MetricView frownPct;
|
|
||||||
private MetricView valencePct;
|
|
||||||
private MetricView engagementPct;
|
|
||||||
private TextView fpsName;
|
private TextView fpsName;
|
||||||
private TextView fpsPct;
|
private TextView fpsPct;
|
||||||
private TextView smileName;
|
private TextView pleaseWaitTextView;
|
||||||
private TextView browRaiseName;
|
private ProgressBar progressBar;
|
||||||
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;
|
|
||||||
|
|
||||||
//Other UI objects
|
//Other UI objects
|
||||||
private ViewGroup activityLayout; //top-most ViewGroup in which all content resides
|
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 RelativeLayout progressBarLayout; //layout used to show progress circle while camera is starting
|
||||||
private SurfaceView cameraView; //SurfaceView used to display camera images
|
private SurfaceView cameraView; //SurfaceView used to display camera images
|
||||||
private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
|
private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
|
||||||
|
private ImageButton settingsButton;
|
||||||
//The Shared Preferences object is used to restore/save settings when activity is created/destroyed
|
|
||||||
private final String PREFS_NAME = "AffdexMe";
|
|
||||||
SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
//Application settings variables
|
//Application settings variables
|
||||||
private int detectorProcessRate = 20;
|
private int detectorProcessRate;
|
||||||
private boolean isMenuVisible = false;
|
private boolean isMenuVisible = false;
|
||||||
private boolean isFPSVisible = false;
|
private boolean isFPSVisible = false;
|
||||||
|
private boolean isMenuShowingForFirstTime = true;
|
||||||
|
|
||||||
//Frames Per Second (FPS) variables
|
//Frames Per Second (FPS) variables
|
||||||
private long firstSystemTime = 0;
|
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
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //To maximize UI space, we declare our app to be full-screen
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
initializeUI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We check to make sure the device has a front-facing camera.
|
* 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
|
* 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)) {
|
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
|
||||||
isFrontFacingCameraDetected = false;
|
isFrontFacingCameraDetected = false;
|
||||||
|
progressBar.setVisibility(View.INVISIBLE);
|
||||||
|
pleaseWaitTextView.setVisibility(View.INVISIBLE);
|
||||||
TextView notFoundTextView = (TextView) findViewById(R.id.not_found_textview);
|
TextView notFoundTextView = (TextView) findViewById(R.id.not_found_textview);
|
||||||
notFoundTextView.setVisibility(View.VISIBLE);
|
notFoundTextView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeUI();
|
|
||||||
|
|
||||||
initializeCameraDetector();
|
initializeCameraDetector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,47 +140,42 @@ public class MainActivity extends Activity
|
||||||
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
|
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
|
||||||
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
|
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
|
||||||
mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
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);
|
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);
|
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);
|
cameraView = (SurfaceView) findViewById(R.id.camera_preview);
|
||||||
drawingView = (DrawingView) findViewById(R.id.drawing_view);
|
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
|
//Load Application Font and set UI Elements to use it
|
||||||
Typeface face = Typeface.createFromAsset(getAssets(), "fonts/Square.ttf");
|
Typeface face = Typeface.createFromAsset(getAssets(), "fonts/Square.ttf");
|
||||||
smilePct.setTypeface(face);
|
for (TextView textView : metricNames) {
|
||||||
browRaisePct.setTypeface(face);
|
textView.setTypeface(face);
|
||||||
browFurrowPct.setTypeface(face);
|
}
|
||||||
frownPct.setTypeface(face);
|
for (MetricDisplay metricDisplay : metricDisplays) {
|
||||||
valencePct.setTypeface(face);
|
metricDisplay.setTypeface(face);
|
||||||
engagementPct.setTypeface(face);
|
}
|
||||||
smileName.setTypeface(face);
|
|
||||||
browRaiseName.setTypeface(face);
|
|
||||||
browFurrowName.setTypeface(face);
|
|
||||||
frownName.setTypeface(face);
|
|
||||||
valenceName.setTypeface(face);
|
|
||||||
engagementName.setTypeface(face);
|
|
||||||
fpsPct.setTypeface(face);
|
fpsPct.setTypeface(face);
|
||||||
fpsName.setTypeface(face);
|
fpsName.setTypeface(face);
|
||||||
fpsEditTextName.setTypeface(face);
|
drawingView.setTypeface(face);
|
||||||
fpsCheckbox.setTypeface(face);
|
pleaseWaitTextView.setTypeface(face);
|
||||||
trackingCheckbox.setTypeface(face);
|
|
||||||
|
|
||||||
//Hide left and right metrics by default (will be made visible when face detection starts)
|
//Hide left and right metrics by default (will be made visible when face detection starts)
|
||||||
leftMetricsLayout.setAlpha(0);
|
leftMetricsLayout.setAlpha(0);
|
||||||
|
@ -210,18 +192,6 @@ public class MainActivity extends Activity
|
||||||
|
|
||||||
//Attach event listeners to the menu and edit box
|
//Attach event listeners to the menu and edit box
|
||||||
activityLayout.setOnTouchListener(this);
|
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
|
* 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.
|
* that view will be painted with what the camera sees.
|
||||||
*/
|
*/
|
||||||
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
|
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
|
||||||
|
detector.setLicensePath("YourLicenseFile");
|
||||||
// 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.setImageListener(this);
|
detector.setImageListener(this);
|
||||||
detector.setFaceListener(this);
|
detector.setFaceListener(this);
|
||||||
detector.setCameraDetectorDimensionsListener(this);
|
detector.setCameraDetectorDimensionsListener(this);
|
||||||
|
@ -273,33 +230,75 @@ public class MainActivity extends Activity
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
restoreApplicationSettings();
|
restoreApplicationSettings();
|
||||||
setMenuVisible(false); //always make the menu invisible by default
|
setMenuVisible(true);
|
||||||
|
isMenuShowingForFirstTime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We use the Shared Preferences object to restore application settings.
|
* We use the Shared Preferences object to restore application settings.
|
||||||
*/
|
*/
|
||||||
public void restoreApplicationSettings() {
|
public void restoreApplicationSettings() {
|
||||||
sharedPreferences = getSharedPreferences(PREFS_NAME, 0);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
detectorProcessRate = sharedPreferences.getInt("rate", detectorProcessRate); //restore camera processing rate
|
//restore camera processing rate
|
||||||
fpsEditText.setText(String.valueOf(detectorProcessRate));
|
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
|
||||||
|
detector.setMaxProcessRate(detectorProcessRate);
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
|
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
|
||||||
fpsCheckbox.setChecked(true);
|
|
||||||
setFPSVisible(true);
|
setFPSVisible(true);
|
||||||
} else {
|
} else {
|
||||||
fpsCheckbox.setChecked(false);
|
|
||||||
setFPSVisible(false);
|
setFPSVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
|
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
|
||||||
setTrackPoints(true);
|
setTrackPoints(true);
|
||||||
trackingCheckbox.setChecked(true);
|
|
||||||
} else {
|
} else {
|
||||||
setTrackPoints(false);
|
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
|
* We want to start the camera as late as possible, so it does not freeze the application before it has been visually resumed.
|
||||||
* been opened or reopened. Although this can also occur when the application regains focus after a dialog box has been closed,
|
* We thus post a runnable that will take care of starting the camera.
|
||||||
* the startCamera() method will not start the camera if it is already running.
|
|
||||||
* We also reset variables used to calculate the Processed Frames Per Second.
|
* We also reset variables used to calculate the Processed Frames Per Second.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
|
||||||
if (hasFocus && isFrontFacingCameraDetected) {
|
if (hasFocus && isFrontFacingCameraDetected) {
|
||||||
|
cameraView.post(new Runnable() {
|
||||||
startCamera();
|
@Override
|
||||||
|
public void run() {
|
||||||
if (!drawingView.isSurfaceDimensionsNeeded()) {
|
mainWindowResumedTasks();
|
||||||
progressBarLayout.setVisibility(View.GONE);
|
}
|
||||||
}
|
});
|
||||||
resetFPSCalculations();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
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()) {
|
if (!detector.isRunning()) {
|
||||||
try {
|
try {
|
||||||
detector.start();
|
detector.start();
|
||||||
|
@ -347,6 +362,7 @@ public class MainActivity extends Activity
|
||||||
public void onFaceDetectionStarted() {
|
public void onFaceDetectionStarted() {
|
||||||
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
|
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
|
||||||
rightMetricsLayout.animate().alpha(1);
|
rightMetricsLayout.animate().alpha(1);
|
||||||
|
|
||||||
resetFPSCalculations(); //Since the FPS may be different whether a face is being tracked or not, reset variables.
|
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() {
|
void performFaceDetectionStoppedTasks() {
|
||||||
leftMetricsLayout.animate().alpha(0); //make left and right metrics disappear
|
leftMetricsLayout.animate().alpha(0); //make left and right metrics disappear
|
||||||
rightMetricsLayout.animate().alpha(0);
|
rightMetricsLayout.animate().alpha(0);
|
||||||
|
|
||||||
drawingView.invalidatePoints(); //inform the drawing thread that the latest facial tracking points are now invalid
|
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.
|
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);
|
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.
|
//update metrics with latest face information. The metrics are displayed on a MetricView, a custom view with a .setScore() method.
|
||||||
smilePct.setScore(face.getSmileScore());
|
for (MetricDisplay metricDisplay : metricDisplays) {
|
||||||
browRaisePct.setScore(face.getBrowRaiseScore());
|
updateMetricScore(metricDisplay,face);
|
||||||
browFurrowPct.setScore(face.getBrowFurrowScore());
|
}
|
||||||
engagementPct.setScore(face.getEngagementScore());
|
|
||||||
frownPct.setScore(face.getLipCornerDepressorScore());
|
|
||||||
float valenceScore = face.getValenceScore();
|
|
||||||
valencePct.setScore(valenceScore);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* to our drawing thread and also inform the thread what the valence score was, as that will determine the color
|
||||||
* of the bounding box.
|
* of the bounding box.
|
||||||
*/
|
*/
|
||||||
if (drawingView.getDrawPointsEnabled()) {
|
if (drawingView.getDrawPointsEnabled() || drawingView.getDrawMeasurementsEnabled()) {
|
||||||
drawingView.setScore(valenceScore);
|
drawingView.setMetrics(face.measurements.orientation.getRoll(), face.measurements.orientation.getYaw(), face.measurements.orientation.getPitch(), face.measurements.getInterocularDistance(), face.emotions.getValence());
|
||||||
drawingView.updatePoints(face.getFacePoints());
|
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
|
* Use the method that we saved in activateMetric() to get the metric score and display it
|
||||||
* 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.
|
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){
|
void calculateImageDimensions(Frame image){
|
||||||
///referenceDimension will be used to determine the size of the facial tracking dots
|
///referenceDimension will be used to determine the size of the facial tracking dots
|
||||||
|
@ -437,8 +472,8 @@ public class MainActivity extends Activity
|
||||||
referenceDimension = activityLayout.getWidth();
|
referenceDimension = activityLayout.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawingView.updateImageDimensions(imageWidth,imageHeight);
|
drawingView.updateImageDimensions(imageWidth, imageHeight);
|
||||||
drawingView.setThickness((int)(referenceDimension/160f));
|
drawingView.setThickness((int) (referenceDimension / 160f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -486,12 +521,15 @@ public class MainActivity extends Activity
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
progressBarLayout.setVisibility(View.VISIBLE);
|
progressBarLayout.setVisibility(View.VISIBLE);
|
||||||
saveApplicationSettings();
|
|
||||||
stopCamera();
|
stopCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopCamera() {
|
private void stopCamera() {
|
||||||
performFaceDetectionStoppedTasks();
|
performFaceDetectionStoppedTasks();
|
||||||
|
|
||||||
|
detector.setDetectAllEmotions(false);
|
||||||
|
detector.setDetectAllExpressions(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
detector.stop();
|
detector.stop();
|
||||||
} catch (Exception e) {
|
} 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.
|
* When the user taps the screen, hide the menu if it is visible and show it if it is hidden.
|
||||||
* **/
|
* **/
|
||||||
void setMenuVisible(boolean b){
|
void setMenuVisible(boolean b){
|
||||||
|
isMenuShowingForFirstTime = false;
|
||||||
isMenuVisible = b;
|
isMenuVisible = b;
|
||||||
if (b) {
|
if (b) {
|
||||||
menuLayout.setVisibility(View.VISIBLE);
|
settingsButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
//We display the navigation bar again
|
//We display the navigation bar again
|
||||||
getWindow().getDecorView().setSystemUiVisibility(
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
@ -558,9 +554,6 @@ public class MainActivity extends Activity
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
InputMethodManager imm = (InputMethodManager)getSystemService(
|
|
||||||
Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(fpsEditText.getWindowToken(), 0);
|
|
||||||
|
|
||||||
//We hide the navigation bar
|
//We hide the navigation bar
|
||||||
getWindow().getDecorView().setSystemUiVisibility(
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
@ -572,7 +565,7 @@ public class MainActivity extends Activity
|
||||||
| View.SYSTEM_UI_FLAG_IMMERSIVE);
|
| 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);
|
drawingView.setDrawPointsEnabled(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setShowMeasurements(boolean b) {
|
||||||
|
drawingView.setDrawMeasurementsEnabled(b);
|
||||||
|
}
|
||||||
|
|
||||||
void setFPSVisible(boolean b) {
|
void setFPSVisible(boolean b) {
|
||||||
isFPSVisible = b;
|
isFPSVisible = b;
|
||||||
if (b) {
|
if (b) {
|
||||||
|
@ -612,6 +609,10 @@ public class MainActivity extends Activity
|
||||||
}
|
}
|
||||||
return false;
|
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.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import java.lang.Math;
|
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.
|
* 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 midX = 0; //coordinates of the center of the view
|
||||||
float midY = 0;
|
float midY = 0;
|
||||||
|
@ -26,20 +30,28 @@ public class MetricView extends View {
|
||||||
float right = 0;
|
float right = 0;
|
||||||
float top = 0;
|
float top = 0;
|
||||||
float textBottom = 0; //tells our view where to draw the baseline of the font
|
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);
|
super(context);
|
||||||
initResources(context,null);
|
initResources(context,null);
|
||||||
}
|
}
|
||||||
public MetricView(Context context, AttributeSet attrs) {
|
public MetricDisplay(Context context, AttributeSet attrs) {
|
||||||
super(context,attrs);
|
super(context,attrs);
|
||||||
initResources(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);
|
super(context, attrs, styleID);
|
||||||
initResources(context,attrs);
|
initResources(context,attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setIsShadedMetricView(boolean b) {
|
||||||
|
this.isShadedMetricView = b;
|
||||||
|
if (!b) {
|
||||||
|
boxPaint.setColor(Color.GREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void initResources(Context context, AttributeSet attrs) {
|
void initResources(Context context, AttributeSet attrs) {
|
||||||
|
|
||||||
boxPaint = new Paint();
|
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) {
|
public void setTypeface(Typeface face) {
|
||||||
textPaint.setTypeface(face);
|
textPaint.setTypeface(face);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScore(float s){
|
public void setScore(float s){
|
||||||
text = String.format("%.0f%%", s); //change the text of the view
|
text = String.format("%.0f%%", s); //change the text of the view
|
||||||
left = midX - (halfWidth * (s / 100)); //change the coordinates at which the colored bar will be drawn
|
|
||||||
right = midX + (halfWidth * (s / 100));
|
//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
|
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"
|
<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:layout_height="match_parent" tools:context=".MainActivity" android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:keepScreenOn="true"
|
android:keepScreenOn="true"
|
||||||
|
@ -21,14 +23,28 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_centerInParent="true"
|
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"/>
|
android:id="@+id/drawing_view"/>
|
||||||
<include layout="@layout/metric_layout"
|
<include layout="@layout/metric_layout"
|
||||||
android:id="@+id/metric_view_group"
|
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_below="@id/metric_view_group"
|
||||||
android:layout_width="fill_parent"
|
android:id="@+id/settings_button"
|
||||||
android:layout_height="wrap_content"/>
|
android:onClick="settings_button_click"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
|
@ -36,12 +52,24 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/progress_bar_cover"
|
android:id="@+id/progress_bar_cover"
|
||||||
android:background="@color/black">
|
android:background="@color/black">
|
||||||
<ProgressBar
|
|
||||||
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@android:style/Widget.DeviceDefault.ProgressBar"
|
android:layout_centerVertical="true"
|
||||||
android:layout_centerInParent="true"
|
android:id="@+id/please_wait_textview"
|
||||||
android:indeterminate="true"/>
|
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
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:affdex="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_height="@dimen/metric_viewgroup"
|
android:layout_height="@dimen/metric_viewgroup"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -13,11 +12,12 @@
|
||||||
<!-- Logo-->
|
<!-- Logo-->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="@dimen/image_width"
|
android:layout_width="@dimen/logo_width"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:src="@drawable/affectiva_logo_clear_background"
|
android:src="@drawable/affectiva_logo_clear_background"
|
||||||
|
android:contentDescription="@string/affectiva_logo_content_description"
|
||||||
android:id="@+id/affectiva_logo" />
|
android:id="@+id/affectiva_logo" />
|
||||||
<!-- Left Metrics-->
|
<!-- Left MetricsManager-->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingLeft="@dimen/metric_panel_padding"
|
android:paddingLeft="@dimen/metric_panel_padding"
|
||||||
|
@ -27,26 +27,23 @@
|
||||||
android:id="@+id/left_metrics">
|
android:id="@+id/left_metrics">
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/smile"
|
android:id="@+id/metric_name_0"
|
||||||
android:id="@+id/smile_name"
|
|
||||||
/>
|
/>
|
||||||
<com.affectiva.affdexme.MetricView
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/smile_pct" />
|
android:id="@+id/metric_pct_0" />
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/brow_raise"
|
android:id="@+id/metric_name_1" />
|
||||||
android:id="@+id/brow_raise_name" />
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
<com.affectiva.affdexme.MetricView
|
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/brow_raise_pct" />
|
android:id="@+id/metric_pct_1" />
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/brow_furrow"
|
android:id="@+id/metric_name_2" />
|
||||||
android:id="@+id/brow_furrow_name" />
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
<com.affectiva.affdexme.MetricView
|
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/brow_furrow_pct" />
|
android:id="@+id/metric_pct_2" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<!-- FPS Counter-->
|
<!-- FPS Counter-->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -70,10 +67,11 @@
|
||||||
android:gravity="left|bottom"
|
android:gravity="left|bottom"
|
||||||
android:id="@+id/fps_value"
|
android:id="@+id/fps_value"
|
||||||
android:textSize="@dimen/pct_text_size"
|
android:textSize="@dimen/pct_text_size"
|
||||||
|
android:textColor="@color/letter_gray"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<!-- Right Metrics-->
|
<!-- Right MetricsManager-->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -83,24 +81,21 @@
|
||||||
android:id="@+id/right_metrics">
|
android:id="@+id/right_metrics">
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/frown"
|
android:id="@+id/metric_name_3" />
|
||||||
android:id="@+id/frown_name" />
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
<com.affectiva.affdexme.MetricView
|
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/frown_pct" />
|
android:id="@+id/metric_pct_3" />
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/valence"
|
android:id="@+id/metric_name_4" />
|
||||||
android:id="@+id/valence_name" />
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
<com.affectiva.affdexme.GradientMetricView
|
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/valence_pct" />
|
android:id="@+id/metric_pct_4" />
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/metricName"
|
style="@style/metricName"
|
||||||
android:text="@string/engagement"
|
android:id="@+id/metric_name_5" />
|
||||||
android:id="@+id/engagement_name" />
|
<com.affectiva.affdexme.MetricDisplay
|
||||||
<com.affectiva.affdexme.MetricView
|
|
||||||
style="@style/metricPct"
|
style="@style/metricPct"
|
||||||
android:id="@+id/engagement_pct" />
|
android:id="@+id/metric_pct_5" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</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="metric_view_bar_length">130dp</dimen>
|
||||||
<dimen name="bottom_padding">10dp</dimen>
|
<dimen name="bottom_padding">10dp</dimen>
|
||||||
<dimen name="metric_viewgroup">190dp</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="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>
|
</resources>
|
||||||
|
|
|
@ -5,6 +5,30 @@
|
||||||
<dimen name="metric_view_bar_length">80dp</dimen>
|
<dimen name="metric_view_bar_length">80dp</dimen>
|
||||||
<dimen name="bottom_padding">5dp</dimen>
|
<dimen name="bottom_padding">5dp</dimen>
|
||||||
<dimen name="metric_viewgroup">130dp</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="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>
|
</resources>
|
||||||
|
|
|
@ -5,6 +5,29 @@
|
||||||
<dimen name="metric_view_bar_length">70dp</dimen>
|
<dimen name="metric_view_bar_length">70dp</dimen>
|
||||||
<dimen name="bottom_padding">5dp</dimen>
|
<dimen name="bottom_padding">5dp</dimen>
|
||||||
<dimen name="metric_viewgroup">130dp</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="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>
|
</resources>
|
||||||
|
|
|
@ -6,6 +6,32 @@
|
||||||
<dimen name="metric_view_bar_length">140dp</dimen>
|
<dimen name="metric_view_bar_length">140dp</dimen>
|
||||||
<dimen name="bottom_padding">15dp</dimen>
|
<dimen name="bottom_padding">15dp</dimen>
|
||||||
<dimen name="metric_viewgroup">220dp</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="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>
|
</resources>
|
|
@ -6,4 +6,12 @@
|
||||||
<attr name="barLength" format="dimension"/>
|
<attr name="barLength" format="dimension"/>
|
||||||
<attr name="textDepth" format="dimension"/>
|
<attr name="textDepth" format="dimension"/>
|
||||||
</declare-styleable>
|
</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>
|
</resources>
|
|
@ -2,6 +2,12 @@
|
||||||
<resources>
|
<resources>
|
||||||
<color name="transparent_overlay">#55ffffff</color>
|
<color name="transparent_overlay">#55ffffff</color>
|
||||||
<color name="letter_gray">#514a40</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="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>
|
</resources>
|
|
@ -5,6 +5,32 @@
|
||||||
<dimen name="metric_view_bar_length">80dp</dimen>
|
<dimen name="metric_view_bar_length">80dp</dimen>
|
||||||
<dimen name="bottom_padding">5dp</dimen>
|
<dimen name="bottom_padding">5dp</dimen>
|
||||||
<dimen name="metric_viewgroup">140dp</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="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>
|
</resources>
|
||||||
|
|