Merge pull request #3 from Affectiva/affdex_2.0_release

Publish AffdexMe 2.0
This commit is contained in:
Mostafa Zaher Hosney 2015-09-11 20:19:05 +02:00
commit 99baa6cbef
105 changed files with 2001 additions and 391 deletions

View file

@ -5,7 +5,7 @@
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="C:\Program Files\Android\Android Studio\gradle\gradle-2.4" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.2.1" />
<option name="gradleJvm" value="1.7" />
<option name="modules">
<set>

View file

@ -12,12 +12,10 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@ -86,13 +84,13 @@
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform (1)" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="Affdex-sdk-1.2-SNAPSHOT-javadoc" level="project" />
<orderEntry type="library" exported="" name="dagger-1.2.2" level="project" />
<orderEntry type="library" exported="" name="javax.inject-1" level="project" />
<orderEntry type="library" exported="" name="Affdex-sdk-1.2-SNAPSHOT" level="project" />
<orderEntry type="library" exported="" name="Affdex-sdk" level="project" />
<orderEntry type="library" exported="" name="gson-2.3" level="project" />
<orderEntry type="library" exported="" name="Affdex-sdk-javadoc" level="project" />
<orderEntry type="library" exported="" name="flurry-analytics-4.1.0" level="project" />
</component>
</module>

View file

@ -24,8 +24,8 @@ dependencies {
compile 'com.google.code.gson:gson:2.3'
//include the Affdex SDK jars
compile files('libs/Affdex-sdk-1.2-SNAPSHOT.jar')
compile files('libs/Affdex-sdk-1.2-SNAPSHOT-javadoc.jar')
compile files('libs/Affdex-sdk.jar')
compile files('libs/Affdex-sdk-javadoc.jar')
compile files('libs/dagger-1.2.2.jar')
compile files('libs/flurry-analytics-4.1.0.jar')
compile files('libs/javax.inject-1.jar')

View file

@ -14,21 +14,41 @@
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
<application
android:name="com.affectiva.errorreporting.CustomApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:hardwareAccelerated="true"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:configChanges="screenSize|keyboardHidden|orientation"
android:screenOrientation="sensorPortrait"
android:windowSoftInputMode="stateHidden"
android:theme="@style/MainActivityTheme"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:parentActivityName="com.affectiva.affdexme.MainActivity"
android:theme="@style/EditPreferencesTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.affectiva.affdexme.MainActivity" />
</activity>
<activity
android:name="com.affectiva.errorreporting.ErrorReporter"
android:theme="@android:style/Theme.DeviceDefault"
android:textAppearance="@android:style/TextAppearance.Large">
<intent-filter>
<action android:name="com.affectiva.REPORT_ERROR" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,12 +1,14 @@
package com.affectiva.affdexme;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
@ -14,6 +16,8 @@ import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.affectiva.android.affdex.sdk.detector.Face;
/**
* This class contains a SurfaceView and its own thread that draws to it.
@ -31,12 +35,18 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
private DrawingViewConfig config;
private final long drawPeriod = 33; //draw at 30 fps
private final int TEXT_RAISE = 10;
String roll = "";
String yaw = "";
String pitch = "";
String interOcDis = "";
public DrawingThread(SurfaceHolder surfaceHolder, DrawingViewConfig con) {
mSurfaceHolder = surfaceHolder;
circlePaint = new Paint();
circlePaint.setColor(Color.WHITE);
boxPaint = new Paint();
boxPaint.setColor(Color.WHITE);
boxPaint.setStyle(Paint.Style.STROKE);
@ -44,20 +54,24 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
config = con;
setThickness(config.drawThickness);
}
/**
* Used to set the valence score, which determines the color of the bounding box.
* **/
void setScore(float s) {
if (s > 0) {
float colorScore = ((100f-s)/100f)*255;
void setMetrics(float roll, float yaw, float pitch, float interOcDis, float valence) {
//format string for our DrawingView to use when ready
this.roll = String.format("%.2f",roll);
this.yaw = String.format("%.2f",yaw);
this.pitch = String.format("%.2f",pitch);
this.interOcDis = String.format("%.2f",interOcDis);
//prepare the color of the bounding box using the valence score. Red for -100, White for 0, and Green for +100, with linear interpolation in between.
if (valence > 0) {
float colorScore = ((100f-valence)/100f)*255;
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
} else {
float colorScore = ((100f+s)/100f)*255;
float colorScore = ((100f+valence)/100f)*255;
boxPaint.setColor(Color.rgb(255, (int) colorScore, (int) colorScore));
}
}
public void stopThread() {
@ -100,7 +114,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
if (c!= null) {
synchronized (mSurfaceHolder) {
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); //clear previous dots
if (config.isDrawPointsEnabled && (nextPointsToDraw != null) ) {
if ((nextPointsToDraw != null) ) {
draw(c);
}
}
@ -131,31 +145,69 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
PointF[] points = nextPointsToDraw;
//Coordinates around which to draw bounding box.
float leftBx = config.imageWidth;
float leftBx = config.surfaceViewWidth;
float rightBx = 0;
float topBx = config.imageHeight;
float topBx = config.surfaceViewHeight;
float botBx = 0;
//Iterate through all the points given to us by the CameraDetector object
for (int i = 0; i < points.length; i++) {
//transform from the camera coordinates to our screen coordinates
//The camera preview is displayed as a mirror, so X pts have to be mirrored back.
float x = (config.imageWidth - points[i].x - 1) * config.screenToImageRatio;
float y = (points[i].y)* config.screenToImageRatio;
//We determine the left-most, top-most, right-most, and bottom-most points to draw the bounding box around.
if (points[i].x < leftBx)
leftBx = points[i].x;
if (points[i].x > rightBx)
rightBx = points[i].x;
if (points[i].y < topBx)
topBx = points[i].y;
if (points[i].y > botBx)
botBx = points[i].y;
if (x < leftBx)
leftBx = x;
if (x > rightBx)
rightBx = x;
if (y < topBx)
topBx = y;
if (y > botBx)
botBx = y;
//Draw facial tracking dots.
//The camera preview is displayed as a mirror, so X pts have to be reversed
c.drawCircle((config.imageWidth - points[i].x - 1) * config.screenToImageRatio, (points[i].y)* config.screenToImageRatio, config.drawThickness, circlePaint);
if (config.isDrawPointsEnabled) {
c.drawCircle(x, y, config.drawThickness, circlePaint);
}
}
//Draw the bounding box.
c.drawRect((config.imageWidth - leftBx - 1) * config.screenToImageRatio, topBx * config.screenToImageRatio, (config.imageWidth - rightBx - 1) * config.screenToImageRatio, botBx * config.screenToImageRatio, boxPaint);
if (config.isDrawPointsEnabled) {
c.drawRect(leftBx, topBx, rightBx, botBx, boxPaint);
}
//Draw the measurement metrics, with a dark border around the words to make them visible for users of all skin colors.
if (config.isDrawMeasurementsEnabled) {
float centerBx = (leftBx + rightBx) / 2;
float upperText = topBx - TEXT_RAISE;
c.drawText("PITCH", centerBx, upperText - config.textSize,config.textBorderPaint);
c.drawText("PITCH", centerBx, upperText - config.textSize, config.textPaint);
c.drawText(pitch,centerBx ,upperText ,config.textBorderPaint);
c.drawText(pitch, centerBx, upperText, config.textPaint);
float upperLeft = centerBx - config.upperTextSpacing;
c.drawText("YAW", upperLeft , upperText - config.textSize , config.textBorderPaint);
c.drawText("YAW", upperLeft, upperText - config.textSize, config.textPaint);
c.drawText(yaw, upperLeft , upperText , config.textBorderPaint);
c.drawText(yaw, upperLeft, upperText, config.textPaint);
float upperRight = centerBx + config.upperTextSpacing;
c.drawText("ROLL", upperRight , upperText - config.textSize , config.textBorderPaint);
c.drawText("ROLL", upperRight, upperText - config.textSize, config.textPaint);
c.drawText(roll, upperRight , upperText , config.textBorderPaint);
c.drawText(roll, upperRight, upperText, config.textPaint);
c.drawText("INTEROCULAR DISTANCE", centerBx , botBx + config.textSize , config.textBorderPaint);
c.drawText("INTEROCULAR DISTANCE", centerBx, botBx + config.textSize, config.textPaint);
c.drawText(interOcDis,centerBx , botBx + config.textSize*2 , config.textBorderPaint);
c.drawText(interOcDis, centerBx, botBx + config.textSize * 2, config.textPaint);
}
}
}
@ -169,6 +221,19 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
private boolean isImageDimensionsNeeded = true;
private boolean isSurfaceViewDimensionsNeeded = true;
private boolean isDrawPointsEnabled = true; //by default, have the drawing thread draw tracking dots
private boolean isDrawMeasurementsEnabled = false;
private Paint textPaint;
private int textSize;
private Paint textBorderPaint;
private int upperTextSpacing;
public void setMeasurementMetricConfigs(Paint textPaint, Paint dropShadowPaint, int textSize, int upperTextSpacing) {
this.textPaint = textPaint;
this.textSize = textSize;
this.textBorderPaint = dropShadowPaint;
this.upperTextSpacing = upperTextSpacing;
}
public void updateImageDimensions(int w, int h) {
@ -211,6 +276,7 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
//Class variables of DrawingView class
private SurfaceHolder surfaceHolder;
private DrawingThread drawingThread; //DrawingThread object
private Typeface typeface;
private DrawingViewConfig drawingViewConfig;
private static String LOG_TAG = "AffdexMe";
@ -218,25 +284,60 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
//three constructors required of any custom view
public DrawingView(Context context) {
super(context);
initView();
initView(context, null);
}
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
initView(context, attrs);
}
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
initView(context, attrs);
}
void initView(){
void initView(Context context, AttributeSet attrs){
surfaceHolder = getHolder(); //The SurfaceHolder object will be used by the thread to request canvas to draw on SurfaceView
surfaceHolder.setFormat(PixelFormat.TRANSPARENT); //set to Transparent so this surfaceView does not obscure the one it is overlaying (the one displaying the camera).
surfaceHolder.addCallback(this); //become a Listener to the three events below that SurfaceView throws
drawingViewConfig = new DrawingViewConfig();
//default values
int upperTextSpacing = 15;
int textSize = 15;
Paint measurementTextPaint = new Paint();
measurementTextPaint.setStyle(Paint.Style.FILL);
measurementTextPaint.setTextAlign(Paint.Align.CENTER);
Paint dropShadow = new Paint();
dropShadow.setColor(Color.BLACK);
dropShadow.setStyle(Paint.Style.STROKE);
dropShadow.setTextAlign(Paint.Align.CENTER);
//load and parse XML attributes
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.drawing_view_attributes,0,0);
upperTextSpacing = a.getDimensionPixelSize(R.styleable.drawing_view_attributes_measurements_upper_spacing,upperTextSpacing);
measurementTextPaint.setColor(a.getColor(R.styleable.drawing_view_attributes_measurements_color,Color.WHITE));
dropShadow.setColor(a.getColor(R.styleable.drawing_view_attributes_measurements_text_border_color,Color.BLACK));
dropShadow.setStrokeWidth(a.getInteger(R.styleable.drawing_view_attributes_measurements_text_border_thickness,5));
textSize = a.getDimensionPixelSize(R.styleable.drawing_view_attributes_measurements_text_size,textSize);
measurementTextPaint.setTextSize(textSize);
dropShadow.setTextSize(textSize);
a.recycle();
}
drawingViewConfig.setMeasurementMetricConfigs(measurementTextPaint, dropShadow, textSize, upperTextSpacing);
drawingThread = new DrawingThread(surfaceHolder, drawingViewConfig);
}
public void setTypeface(Typeface face) {
drawingViewConfig.textPaint.setTypeface(face);
drawingViewConfig.textBorderPaint.setTypeface(face);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (drawingThread.isStopped()) {
@ -309,9 +410,16 @@ public class DrawingView extends SurfaceView implements SurfaceHolder.Callback {
return drawingViewConfig.isDrawPointsEnabled;
}
//The methods below simply delegate to the drawingThread object
public void setScore(float s) {
drawingThread.setScore(s);
public void setDrawMeasurementsEnabled(boolean b) {
drawingViewConfig.isDrawMeasurementsEnabled = b;
}
public boolean getDrawMeasurementsEnabled() {
return drawingViewConfig.isDrawMeasurementsEnabled;
}
public void setMetrics(float roll, float yaw, float pitch, float interOcDis, float valence) {
drawingThread.setMetrics(roll,yaw,pitch,interOcDis,valence);
}
public void updatePoints(PointF[] points) {

View file

@ -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
}
}

View file

@ -1,12 +1,13 @@
package com.affectiva.affdexme;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@ -14,18 +15,17 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.List;
import com.affectiva.android.affdex.sdk.Frame;
@ -67,37 +67,24 @@ import com.affectiva.android.affdex.sdk.detector.Face;
*/
public class MainActivity extends Activity
implements Detector.FaceListener, Detector.ImageListener, TextView.OnEditorActionListener, View.OnTouchListener, CameraDetector.CameraSurfaceViewListener {
implements Detector.FaceListener, Detector.ImageListener, View.OnTouchListener, CameraDetector.CameraSurfaceViewListener {
private static final String LOG_TAG = "Affectiva";
public static final int NUM_METRICS_DISPLAYED = 6;
//Affectiva SDK Object
private CameraDetector detector = null;
//Metrics View UI Objects
//MetricsManager View UI Objects
private RelativeLayout metricViewLayout;
private LinearLayout leftMetricsLayout;
private LinearLayout rightMetricsLayout;
private MetricView smilePct;
private MetricView browRaisePct;
private MetricView browFurrowPct;
private MetricView frownPct;
private MetricView valencePct;
private MetricView engagementPct;
private MetricDisplay[] metricDisplays;
private TextView[] metricNames;
private TextView fpsName;
private TextView fpsPct;
private TextView smileName;
private TextView browRaiseName;
private TextView browFurrowName;
private TextView frownName;
private TextView valenceName;
private TextView engagementName;
//Menu UI Objects
private RelativeLayout menuLayout;
private EditText fpsEditText;
private CheckBox fpsCheckbox;
private CheckBox trackingCheckbox;
private TextView fpsEditTextName;
private TextView pleaseWaitTextView;
private ProgressBar progressBar;
//Other UI objects
private ViewGroup activityLayout; //top-most ViewGroup in which all content resides
@ -105,15 +92,13 @@ public class MainActivity extends Activity
private RelativeLayout progressBarLayout; //layout used to show progress circle while camera is starting
private SurfaceView cameraView; //SurfaceView used to display camera images
private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
//The Shared Preferences object is used to restore/save settings when activity is created/destroyed
private final String PREFS_NAME = "AffdexMe";
SharedPreferences sharedPreferences;
private ImageButton settingsButton;
//Application settings variables
private int detectorProcessRate = 20;
private int detectorProcessRate;
private boolean isMenuVisible = false;
private boolean isFPSVisible = false;
private boolean isMenuShowingForFirstTime = true;
//Frames Per Second (FPS) variables
private long firstSystemTime = 0;
@ -128,6 +113,8 @@ public class MainActivity extends Activity
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //To maximize UI space, we declare our app to be full-screen
setContentView(R.layout.activity_main);
initializeUI();
/**
* We check to make sure the device has a front-facing camera.
* If it does not, we obscure the app with a notice informing the user they cannot
@ -135,12 +122,12 @@ public class MainActivity extends Activity
*/
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
isFrontFacingCameraDetected = false;
progressBar.setVisibility(View.INVISIBLE);
pleaseWaitTextView.setVisibility(View.INVISIBLE);
TextView notFoundTextView = (TextView) findViewById(R.id.not_found_textview);
notFoundTextView.setVisibility(View.VISIBLE);
}
initializeUI();
initializeCameraDetector();
}
@ -153,47 +140,42 @@ public class MainActivity extends Activity
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
menuLayout = (RelativeLayout) findViewById(R.id.affdexme_menu);
smilePct = (MetricView) findViewById(R.id.smile_pct);
browRaisePct = (MetricView) findViewById(R.id.brow_raise_pct);
browFurrowPct = (MetricView) findViewById(R.id.brow_furrow_pct);
frownPct = (MetricView) findViewById(R.id.frown_pct);
valencePct = (MetricView) findViewById(R.id.valence_pct);
engagementPct = (MetricView) findViewById(R.id.engagement_pct);
fpsPct = (TextView) findViewById(R.id.fps_value);
smileName = (TextView) findViewById(R.id.smile_name);
browRaiseName = (TextView) findViewById(R.id.brow_raise_name);
browFurrowName = (TextView) findViewById(R.id.brow_furrow_name);
frownName = (TextView) findViewById(R.id.frown_name);
valenceName = (TextView) findViewById(R.id.valence_name);
engagementName = (TextView) findViewById(R.id.engagement_name);
fpsName = (TextView) findViewById(R.id.fps_name);
fpsEditText = (EditText) findViewById(R.id.fps_edittext);
fpsEditTextName = (TextView) findViewById(R.id.fps_edittext_name);
fpsCheckbox = (CheckBox) findViewById(R.id.fps_checkbox);
trackingCheckbox = (CheckBox) findViewById(R.id.tracking_checkbox);
cameraView = (SurfaceView) findViewById(R.id.camera_preview);
drawingView = (DrawingView) findViewById(R.id.drawing_view);
settingsButton = (ImageButton) findViewById(R.id.settings_button);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview);
//Initialize views to display metrics
metricNames = new TextView[NUM_METRICS_DISPLAYED];
metricNames[0] = (TextView) findViewById(R.id.metric_name_0);
metricNames[1] = (TextView) findViewById(R.id.metric_name_1);
metricNames[2] = (TextView) findViewById(R.id.metric_name_2);
metricNames[3] = (TextView) findViewById(R.id.metric_name_3);
metricNames[4] = (TextView) findViewById(R.id.metric_name_4);
metricNames[5] = (TextView) findViewById(R.id.metric_name_5);
metricDisplays = new MetricDisplay[NUM_METRICS_DISPLAYED];
metricDisplays[0] = (MetricDisplay) findViewById(R.id.metric_pct_0);
metricDisplays[1] = (MetricDisplay) findViewById(R.id.metric_pct_1);
metricDisplays[2] = (MetricDisplay) findViewById(R.id.metric_pct_2);
metricDisplays[3] = (MetricDisplay) findViewById(R.id.metric_pct_3);
metricDisplays[4] = (MetricDisplay) findViewById(R.id.metric_pct_4);
metricDisplays[5] = (MetricDisplay) findViewById(R.id.metric_pct_5);
//Load Application Font and set UI Elements to use it
Typeface face = Typeface.createFromAsset(getAssets(), "fonts/Square.ttf");
smilePct.setTypeface(face);
browRaisePct.setTypeface(face);
browFurrowPct.setTypeface(face);
frownPct.setTypeface(face);
valencePct.setTypeface(face);
engagementPct.setTypeface(face);
smileName.setTypeface(face);
browRaiseName.setTypeface(face);
browFurrowName.setTypeface(face);
frownName.setTypeface(face);
valenceName.setTypeface(face);
engagementName.setTypeface(face);
for (TextView textView : metricNames) {
textView.setTypeface(face);
}
for (MetricDisplay metricDisplay : metricDisplays) {
metricDisplay.setTypeface(face);
}
fpsPct.setTypeface(face);
fpsName.setTypeface(face);
fpsEditTextName.setTypeface(face);
fpsCheckbox.setTypeface(face);
trackingCheckbox.setTypeface(face);
drawingView.setTypeface(face);
pleaseWaitTextView.setTypeface(face);
//Hide left and right metrics by default (will be made visible when face detection starts)
leftMetricsLayout.setAlpha(0);
@ -210,18 +192,6 @@ public class MainActivity extends Activity
//Attach event listeners to the menu and edit box
activityLayout.setOnTouchListener(this);
menuLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
/*
* This method effectively blocks the mainLayout from receiving a touch event
* when the menu is pressed. This is to prevent the menu from closing if the user accidentally touches it
* when aiming for a checkbox or edit box.
*/
return true;
}
});
fpsEditText.setOnEditorActionListener(this);
/*
* This app sets the View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag. Unfortunately, this flag causes
@ -247,20 +217,7 @@ public class MainActivity extends Activity
* that view will be painted with what the camera sees.
*/
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
// NOTE: uncomment the line below and replace "YourLicenseFile" with your license file, which should be stored in /assets/Affdex/
//detector.setLicensePath("YourLicenseFile");
// We want to detect all expressions, so turn on all classifiers.
detector.setDetectSmile(true);
detector.setDetectBrowFurrow(true);
detector.setDetectBrowRaise(true);
detector.setDetectEngagement(true);
detector.setDetectValence(true);
detector.setDetectLipCornerDepressor(true);
detector.setMaxProcessRate(detectorProcessRate);
detector.setLicensePath("YourLicenseFile");
detector.setImageListener(this);
detector.setFaceListener(this);
detector.setCameraDetectorDimensionsListener(this);
@ -273,33 +230,75 @@ public class MainActivity extends Activity
public void onResume() {
super.onResume();
restoreApplicationSettings();
setMenuVisible(false); //always make the menu invisible by default
setMenuVisible(true);
isMenuShowingForFirstTime = true;
}
/*
* We use the Shared Preferences object to restore application settings.
*/
public void restoreApplicationSettings() {
sharedPreferences = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
detectorProcessRate = sharedPreferences.getInt("rate", detectorProcessRate); //restore camera processing rate
fpsEditText.setText(String.valueOf(detectorProcessRate));
//restore camera processing rate
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
detector.setMaxProcessRate(detectorProcessRate);
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
fpsCheckbox.setChecked(true);
setFPSVisible(true);
} else {
fpsCheckbox.setChecked(false);
setFPSVisible(false);
}
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
setTrackPoints(true);
trackingCheckbox.setChecked(true);
} else {
setTrackPoints(false);
trackingCheckbox.setChecked(false);
}
if (sharedPreferences.getBoolean("measurements",drawingView.getDrawMeasurementsEnabled())) { //restore show measurements
setShowMeasurements(true);
} else {
setShowMeasurements(false);
}
//populate metric displays
for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) {
activateMetric(n,PreferencesUtils.getMetricFromPrefs(sharedPreferences, n));
}
}
/**
* Populates a TextView to display a metric name and readies a MetricDisplay to display the value.
* Uses reflection to:
* -enable the corresponding metric in the Detector object by calling Detector.setDetect<MetricName>()
* -save the Method object that will be invoked on the Face object received in onImageResults() to get the metric score
*/
void activateMetric(int index, MetricsManager.Metrics metric) {
metricNames[index].setText(MetricsManager.getUpperCaseName(metric));
Method getFaceScoreMethod = null; //The method that will be used to get a metric score
try {
//Enable metric detection
Detector.class.getMethod("setDetect" + MetricsManager.getCamelCase(metric), boolean.class).invoke(detector, true);
if (metric.getType() == MetricsManager.MetricType.Emotion) {
getFaceScoreMethod = Face.Emotions.class.getMethod("get" + MetricsManager.getCamelCase(metric), null);
//The MetricDisplay for Valence is unique; it shades it color depending on the metric value
if (metric == MetricsManager.Emotions.VALENCE) {
metricDisplays[index].setIsShadedMetricView(true);
} else {
metricDisplays[index].setIsShadedMetricView(false);
}
} else if (metric.getType() == MetricsManager.MetricType.Expression) {
getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric),null);
}
} catch (Exception e) {
Log.e(LOG_TAG,String.format("Error using reflection to generate methods for %s",metric.toString()));
}
metricDisplays[index].setMetricToDisplay(metric, getFaceScoreMethod);
}
/**
@ -312,27 +311,43 @@ public class MainActivity extends Activity
}
/**
* We start the camera as soon as the application has been given focus, which occurs as soon as the application has
* been opened or reopened. Although this can also occur when the application regains focus after a dialog box has been closed,
* the startCamera() method will not start the camera if it is already running.
* We want to start the camera as late as possible, so it does not freeze the application before it has been visually resumed.
* We thus post a runnable that will take care of starting the camera.
* We also reset variables used to calculate the Processed Frames Per Second.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus && isFrontFacingCameraDetected) {
cameraView.post(new Runnable() {
@Override
public void run() {
mainWindowResumedTasks();
}
});
}
}
void mainWindowResumedTasks() {
startCamera();
if (!drawingView.isSurfaceDimensionsNeeded()) {
progressBarLayout.setVisibility(View.GONE);
}
resetFPSCalculations();
cameraView.postDelayed(new Runnable() {
@Override
public void run() {
if (isMenuShowingForFirstTime) {
setMenuVisible(false);
}
}
},5000);
}
void startCamera() {
//this app will always detect valence (it will also always detect measurements, but measurement don't need to be enabled)
detector.setDetectValence(true);
if (!detector.isRunning()) {
try {
detector.start();
@ -347,6 +362,7 @@ public class MainActivity extends Activity
public void onFaceDetectionStarted() {
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
rightMetricsLayout.animate().alpha(1);
resetFPSCalculations(); //Since the FPS may be different whether a face is being tracked or not, reset variables.
}
@ -358,6 +374,7 @@ public class MainActivity extends Activity
void performFaceDetectionStoppedTasks() {
leftMetricsLayout.animate().alpha(0); //make left and right metrics disappear
rightMetricsLayout.animate().alpha(0);
drawingView.invalidatePoints(); //inform the drawing thread that the latest facial tracking points are now invalid
resetFPSCalculations(); //Since the FPS may be different whether a face is being tracked or not, reset variables.
}
@ -393,29 +410,47 @@ public class MainActivity extends Activity
Face face = faces.get(0);
//update metrics with latest face information. The metrics are displayed on a MetricView, a custom view with a .setScore() method.
smilePct.setScore(face.getSmileScore());
browRaisePct.setScore(face.getBrowRaiseScore());
browFurrowPct.setScore(face.getBrowFurrowScore());
engagementPct.setScore(face.getEngagementScore());
frownPct.setScore(face.getLipCornerDepressorScore());
float valenceScore = face.getValenceScore();
valencePct.setScore(valenceScore);
for (MetricDisplay metricDisplay : metricDisplays) {
updateMetricScore(metricDisplay,face);
}
/**
* If the user has selected to have facial tracking dots drawn, we use face.getFacePoints() to send those points
* If the user has selected to have facial tracking dots or measurements drawn, we use face.getFacePoints() to send those points
* to our drawing thread and also inform the thread what the valence score was, as that will determine the color
* of the bounding box.
*/
if (drawingView.getDrawPointsEnabled()) {
drawingView.setScore(valenceScore);
if (drawingView.getDrawPointsEnabled() || drawingView.getDrawMeasurementsEnabled()) {
drawingView.setMetrics(face.measurements.orientation.getRoll(), face.measurements.orientation.getYaw(), face.measurements.orientation.getPitch(), face.measurements.getInterocularDistance(), face.emotions.getValence());
drawingView.updatePoints(face.getFacePoints());
}
}
/**
* In this method, we update our drawingView to contain the dimensions of the frames coming from the camera so that drawingView
* can correctly draw the tracking dots. We also call drawingView.setThickness(), which sets the size of the tracking dots and the
* thickness of the bounding box.
* Use the method that we saved in activateMetric() to get the metric score and display it
*/
void updateMetricScore(MetricDisplay metricDisplay, Face face) {
MetricsManager.Metrics metric = metricDisplay.getMetricToDisplay();
float score = Float.NaN;
try {
if (metric.getType() == MetricsManager.MetricType.Emotion) {
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emotions,null);
metricDisplay.setScore(score);
} else if (metric.getType() == MetricsManager.MetricType.Expression) {
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.expressions,null);
}
} catch (Exception e) {
Log.e(LOG_TAG,String.format("Error using reflecting to get %s score from face.",metric.toString()));
}
metricDisplay.setScore(score);
}
/**
* In this method, we inform our drawingView of the size of the incoming camera images.
* We also set the thickness (which controls the size of the dots and bounding box) based on a reference thickness, which
* should be the same whether the device is landscape or portrait.
*/
void calculateImageDimensions(Frame image){
///referenceDimension will be used to determine the size of the facial tracking dots
@ -486,12 +521,15 @@ public class MainActivity extends Activity
public void onPause() {
super.onPause();
progressBarLayout.setVisibility(View.VISIBLE);
saveApplicationSettings();
stopCamera();
}
private void stopCamera() {
performFaceDetectionStoppedTasks();
detector.setDetectAllEmotions(false);
detector.setDetectAllExpressions(false);
try {
detector.stop();
} catch (Exception e) {
@ -499,57 +537,15 @@ public class MainActivity extends Activity
}
}
/**
* We use the SharedPreferences object to save application settings.
**/
public void saveApplicationSettings() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("fps", isFPSVisible);
editor.putBoolean("track", drawingView.getDrawPointsEnabled());
editor.putInt("rate", detectorProcessRate);
editor.commit();
}
public void fps_checkbox_click(View view) {
setFPSVisible(((CheckBox) view).isChecked());
}
public void tracking_checkbox_click(View view) {
setTrackPoints(((CheckBox) view).isChecked());
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
/**
* When a user has selected the Edit box to change the number of frames the detector processes per second
* and presses the 'DONE' button, the below block will be executed.
* */
if (actionId == EditorInfo.IME_ACTION_DONE) {
int parsedInt = 0;
try {
parsedInt = Integer.parseInt(v.getText().toString());
} catch (Exception e) {
v.setText(String.valueOf(detectorProcessRate));
return false;
}
if (parsedInt > 0) {
detectorProcessRate = parsedInt;
detector.setMaxProcessRate(detectorProcessRate);
resetFPSCalculations(); //reset FPS variables, since changing the process rate should change the FPS.
} else {
v.setText(String.valueOf(detectorProcessRate));
}
}
return false; //return false regardless, so Android closes the keyboard when user presses 'DONE'
}
/**
* When the user taps the screen, hide the menu if it is visible and show it if it is hidden.
* **/
void setMenuVisible(boolean b){
isMenuShowingForFirstTime = false;
isMenuVisible = b;
if (b) {
menuLayout.setVisibility(View.VISIBLE);
settingsButton.setVisibility(View.VISIBLE);
//We display the navigation bar again
getWindow().getDecorView().setSystemUiVisibility(
@ -558,9 +554,6 @@ public class MainActivity extends Activity
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
else {
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(fpsEditText.getWindowToken(), 0);
//We hide the navigation bar
getWindow().getDecorView().setSystemUiVisibility(
@ -572,7 +565,7 @@ public class MainActivity extends Activity
| View.SYSTEM_UI_FLAG_IMMERSIVE);
menuLayout.setVisibility(View.INVISIBLE);
settingsButton.setVisibility(View.INVISIBLE);
}
}
@ -594,6 +587,10 @@ public class MainActivity extends Activity
drawingView.setDrawPointsEnabled(b);
}
void setShowMeasurements(boolean b) {
drawingView.setDrawMeasurementsEnabled(b);
}
void setFPSVisible(boolean b) {
isFPSVisible = b;
if (b) {
@ -612,6 +609,10 @@ public class MainActivity extends Activity
}
return false;
}
public void settings_button_click(View view) {
startActivity(new Intent(this,SettingsActivity.class));
}
}

View file

@ -9,11 +9,15 @@ import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import java.lang.Math;
import java.lang.reflect.Method;
/**
* The MetricView class is used to display metric scores on top of colored bars whose color depend on the score.
*/
public class MetricView extends View {
public class MetricDisplay extends View {
MetricsManager.Metrics metricToDisplay; //indicates which of the 24 Affectiva Emotions and Expressions this view is displaying
Method faceScoreMethod;
float midX = 0; //coordinates of the center of the view
float midY = 0;
@ -26,20 +30,28 @@ public class MetricView extends View {
float right = 0;
float top = 0;
float textBottom = 0; //tells our view where to draw the baseline of the font
boolean isShadedMetricView = false;
public MetricView(Context context) {
public MetricDisplay(Context context) {
super(context);
initResources(context,null);
}
public MetricView(Context context, AttributeSet attrs) {
public MetricDisplay(Context context, AttributeSet attrs) {
super(context,attrs);
initResources(context,attrs);
}
public MetricView(Context context, AttributeSet attrs, int styleID){
public MetricDisplay(Context context, AttributeSet attrs, int styleID){
super(context, attrs, styleID);
initResources(context,attrs);
}
void setIsShadedMetricView(boolean b) {
this.isShadedMetricView = b;
if (!b) {
boxPaint.setColor(Color.GREEN);
}
}
void initResources(Context context, AttributeSet attrs) {
boxPaint = new Paint();
@ -74,14 +86,48 @@ public class MetricView extends View {
}
public void setMetricToDisplay(MetricsManager.Metrics metricToDisplay, Method faceScoreMethod) {
this.metricToDisplay = metricToDisplay;
this.faceScoreMethod = faceScoreMethod;
}
public MetricsManager.Metrics getMetricToDisplay() {
return this.metricToDisplay;
}
public Method getFaceScoreMethod() {
return this.faceScoreMethod;
}
public void setTypeface(Typeface face) {
textPaint.setTypeface(face);
}
public void setScore(float s){
text = String.format("%.0f%%", s); //change the text of the view
//shading mode is turned on for Valence, which causes this view to shade its color according
//to the value of 's'
if (isShadedMetricView) {
if (s > 0) {
left = midX - (halfWidth * (s / 100));
right = midX + (halfWidth * (s / 100));
} else {
left = midX - (halfWidth * (-s / 100));
right = midX + (halfWidth * (-s / 100));
}
if (s > 0) {
float colorScore = ((100f-s)/100f)*255;
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
} else {
float colorScore = ((100f+s)/100f)*255;
boxPaint.setColor(Color.rgb(255,(int)colorScore,(int)colorScore));
}
} else {
left = midX - (halfWidth * (s / 100)); //change the coordinates at which the colored bar will be drawn
right = midX + (halfWidth * (s / 100));
}
invalidate(); //instruct Android to re-draw our view, now that the text has changed
}

View file

@ -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;
}
}
}

View file

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

View file

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

View file

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

View file

@ -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.
}

View 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);
}
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -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>

View file

@ -1,5 +1,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity" android:focusable="true"
android:focusableInTouchMode="true"
android:keepScreenOn="true"
@ -21,14 +23,28 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
custom:measurements_text_size="@dimen/measurements_text_size"
custom:measurements_upper_spacing="@dimen/measurements_upper_text_spacing"
custom:measurements_lower_spacing="@dimen/measurements_lower_text_spacing"
custom:measurements_color="#DDDDDD"
custom:measurements_text_border_color="@color/letter_gray"
custom:measurements_text_border_thickness="@integer/measurements_text_border_thickness"
android:id="@+id/drawing_view"/>
<include layout="@layout/metric_layout"
android:id="@+id/metric_view_group"
/>
<include layout="@layout/menu_layout"
<ImageButton
android:layout_width="@dimen/settings_button_size"
android:layout_height="@dimen/settings_button_size"
android:background="@null"
android:src="@drawable/settings_button_selector"
android:scaleType="fitCenter"
android:layout_margin="@dimen/settings_button_margin"
android:contentDescription="@string/settings_content_description"
android:layout_alignParentRight="true"
android:layout_below="@id/metric_view_group"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
android:id="@+id/settings_button"
android:onClick="settings_button_click"/>
</RelativeLayout>
<RelativeLayout
@ -36,12 +52,24 @@
android:layout_height="match_parent"
android:id="@+id/progress_bar_cover"
android:background="@color/black">
<ProgressBar
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/Widget.DeviceDefault.ProgressBar"
android:layout_centerInParent="true"
android:indeterminate="true"/>
android:layout_centerVertical="true"
android:id="@+id/please_wait_textview"
android:text="@string/loading"
android:textSize="@dimen/please_wait_textview_size"
android:layout_centerHorizontal="true"
/>
<ProgressBar
android:layout_width="@dimen/please_wait_textview_size"
android:layout_height="@dimen/please_wait_textview_size"
android:layout_toLeftOf="@id/please_wait_textview"
android:layout_centerVertical="true"
android:indeterminate="true"
android:paddingRight="10dp"
android:id="@+id/progress_bar"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"

View 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>

View 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>

View 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>

View file

@ -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>

View 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>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:affdex="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_height="@dimen/metric_viewgroup"
android:layout_width="match_parent"
@ -13,11 +12,12 @@
<!-- Logo-->
<ImageView
android:layout_height="wrap_content"
android:layout_width="@dimen/image_width"
android:layout_width="@dimen/logo_width"
android:layout_centerInParent="true"
android:src="@drawable/affectiva_logo_clear_background"
android:contentDescription="@string/affectiva_logo_content_description"
android:id="@+id/affectiva_logo" />
<!-- Left Metrics-->
<!-- Left MetricsManager-->
<LinearLayout
android:orientation="vertical"
android:paddingLeft="@dimen/metric_panel_padding"
@ -27,26 +27,23 @@
android:id="@+id/left_metrics">
<TextView
style="@style/metricName"
android:text="@string/smile"
android:id="@+id/smile_name"
android:id="@+id/metric_name_0"
/>
<com.affectiva.affdexme.MetricView
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/smile_pct" />
android:id="@+id/metric_pct_0" />
<TextView
style="@style/metricName"
android:text="@string/brow_raise"
android:id="@+id/brow_raise_name" />
<com.affectiva.affdexme.MetricView
android:id="@+id/metric_name_1" />
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/brow_raise_pct" />
android:id="@+id/metric_pct_1" />
<TextView
style="@style/metricName"
android:text="@string/brow_furrow"
android:id="@+id/brow_furrow_name" />
<com.affectiva.affdexme.MetricView
android:id="@+id/metric_name_2" />
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/brow_furrow_pct" />
android:id="@+id/metric_pct_2" />
</LinearLayout>
<!-- FPS Counter-->
<LinearLayout
@ -70,10 +67,11 @@
android:gravity="left|bottom"
android:id="@+id/fps_value"
android:textSize="@dimen/pct_text_size"
android:textColor="@color/letter_gray"
android:layout_weight="1"
/>
</LinearLayout>
<!-- Right Metrics-->
<!-- Right MetricsManager-->
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
@ -83,24 +81,21 @@
android:id="@+id/right_metrics">
<TextView
style="@style/metricName"
android:text="@string/frown"
android:id="@+id/frown_name" />
<com.affectiva.affdexme.MetricView
android:id="@+id/metric_name_3" />
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/frown_pct" />
android:id="@+id/metric_pct_3" />
<TextView
style="@style/metricName"
android:text="@string/valence"
android:id="@+id/valence_name" />
<com.affectiva.affdexme.GradientMetricView
android:id="@+id/metric_name_4" />
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/valence_pct" />
android:id="@+id/metric_pct_4" />
<TextView
style="@style/metricName"
android:text="@string/engagement"
android:id="@+id/engagement_name" />
<com.affectiva.affdexme.MetricView
android:id="@+id/metric_name_5" />
<com.affectiva.affdexme.MetricDisplay
style="@style/metricPct"
android:id="@+id/engagement_pct" />
android:id="@+id/metric_pct_5" />
</LinearLayout>
</RelativeLayout>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5,6 +5,35 @@
<dimen name="metric_view_bar_length">130dp</dimen>
<dimen name="bottom_padding">10dp</dimen>
<dimen name="metric_viewgroup">190dp</dimen>
<dimen name="image_width">180dp</dimen>
<dimen name="logo_width">180dp</dimen>
<dimen name="metric_panel_padding">15dp</dimen>
<dimen name="please_wait_textview_size">28sp</dimen>
<dimen name="settings_button_size">64dp</dimen>
<dimen name="settings_button_margin">12dp</dimen>
<dimen name="measurements_text_size">21sp</dimen>
<dimen name="measurements_upper_text_spacing">70dp</dimen>
<dimen name="measurements_lower_text_spacing">100dp</dimen>
<integer name="measurements_text_border_thickness">5</integer>
<dimen name="metric_chooser_text_size">24dp</dimen>
<dimen name="header_text_size">26dp</dimen>
<dimen name="grid_item_checkbox_size">40dp</dimen>
<dimen name="grid_item_chooser_text_size">19sp</dimen>
<dimen name="grid_item_left_padding">5dp</dimen>
<dimen name="grid_item_bottom_padding">10dp</dimen>
<dimen name="grid_item_metric_name">18sp</dimen>
<dimen name="metric_chooser_column_width">180dp</dimen>
</resources>

View file

@ -5,6 +5,30 @@
<dimen name="metric_view_bar_length">80dp</dimen>
<dimen name="bottom_padding">5dp</dimen>
<dimen name="metric_viewgroup">130dp</dimen>
<dimen name="image_width">120dp</dimen>
<dimen name="logo_width">120dp</dimen>
<dimen name="metric_panel_padding">10dp</dimen>
<dimen name="please_wait_textview_size">20sp</dimen>
<dimen name="settings_button_size">48dp</dimen>
<dimen name="settings_button_margin">10dp</dimen>
<dimen name="measurements_text_size">17sp</dimen>
<dimen name="measurements_upper_text_spacing">60dp</dimen>
<dimen name="measurements_lower_text_spacing">80dp</dimen>
<integer name="measurements_text_border_thickness">3</integer>
<dimen name="grid_item_chooser_text_size">15sp</dimen>
<dimen name="grid_item_checkbox_size">30dp</dimen>
<dimen name="header_text_size">21dp</dimen>
<dimen name="metric_chooser_text_size">18dp</dimen>
<dimen name="grid_item_left_padding">4dp</dimen>
<dimen name="grid_item_bottom_padding">8dp</dimen>
<dimen name="grid_item_metric_name">15sp</dimen>
<dimen name="metric_chooser_column_width">140dp</dimen>
</resources>

View file

@ -5,6 +5,29 @@
<dimen name="metric_view_bar_length">70dp</dimen>
<dimen name="bottom_padding">5dp</dimen>
<dimen name="metric_viewgroup">130dp</dimen>
<dimen name="image_width">100dp</dimen>
<dimen name="logo_width">100dp</dimen>
<dimen name="metric_panel_padding">8dp</dimen>
<dimen name="please_wait_textview_size">17sp</dimen>
<dimen name="settings_button_size">36dp</dimen>
<dimen name="settings_button_margin">7dp</dimen>
<dimen name="grid_item_chooser_text_size">12sp</dimen>
<dimen name="grid_item_checkbox_size">50dp</dimen>
<dimen name="measurements_text_size">13sp</dimen>
<dimen name="measurements_upper_text_spacing">50dp</dimen>
<dimen name="measurements_lower_text_spacing">70dp</dimen>
<integer name="measurements_text_border_thickness">3</integer>
<dimen name="header_text_size">17dp</dimen>
<dimen name="metric_chooser_text_size">13dp</dimen>
<dimen name="grid_item_left_padding">3dp</dimen>
<dimen name="grid_item_bottom_padding">6dp</dimen>
<dimen name="grid_item_metric_name">11sp</dimen>
<dimen name="metric_chooser_column_width">120dp</dimen>
</resources>

View file

@ -6,6 +6,32 @@
<dimen name="metric_view_bar_length">140dp</dimen>
<dimen name="bottom_padding">15dp</dimen>
<dimen name="metric_viewgroup">220dp</dimen>
<dimen name="image_width">250dp</dimen>
<dimen name="logo_width">250dp</dimen>
<dimen name="metric_panel_padding">25dp</dimen>
<dimen name="please_wait_textview_size">35sp</dimen>
<dimen name="settings_size">72dp</dimen>
<dimen name="settings_button_margin">15dp</dimen>
<dimen name="measurements_text_size">30sp</dimen>
<dimen name="measurements_upper_text_spacing">110dp</dimen>
<dimen name="measurements_lower_text_spacing">130dp</dimen>
<integer name="measurements_text_border_thickness">7</integer>
<dimen name="metric_chooser_text_size">26dp</dimen>
<dimen name="header_text_size">30dp</dimen>
<dimen name="grid_item_checkbox_size">60dp</dimen>
<dimen name="grid_item_chooser_text_size">24sp</dimen>
<dimen name="grid_item_left_padding">6dp</dimen>
<dimen name="grid_item_bottom_padding">12dp</dimen>
<dimen name="grid_item_metric_name">22sp</dimen>
<dimen name="metric_chooser_column_width">200dp</dimen>
</resources>

View file

@ -6,4 +6,12 @@
<attr name="barLength" format="dimension"/>
<attr name="textDepth" format="dimension"/>
</declare-styleable>
<declare-styleable name="drawing_view_attributes">
<attr name="measurements_text_size" format="dimension" />
<attr name="measurements_upper_spacing" format="dimension"/>
<attr name="measurements_lower_spacing" format="dimension"/>
<attr name="measurements_color" format="color"/>
<attr name="measurements_text_border_color" format="color"/>
<attr name="measurements_text_border_thickness" format="float"/>
</declare-styleable>
</resources>

View file

@ -2,6 +2,12 @@
<resources>
<color name="transparent_overlay">#55ffffff</color>
<color name="letter_gray">#514a40</color>
<color name="letter_orange">#ff8000</color>
<color name="affectiva_orange">#ff8000</color>
<color name="black">#000000</color>
<color name="white">#ffffff</color>
<color name="red">#cf1a0b</color>
<color name="grid_item_not_chosen">#646464</color>
<color name="grid_item_chosen">#009600</color>
<color name="grid_item_chosen_over_limit">#cf1a0b</color>
</resources>

View file

@ -5,6 +5,32 @@
<dimen name="metric_view_bar_length">80dp</dimen>
<dimen name="bottom_padding">5dp</dimen>
<dimen name="metric_viewgroup">140dp</dimen>
<dimen name="image_width">120dp</dimen>
<dimen name="logo_width">120dp</dimen>
<dimen name="metric_panel_padding">10dp</dimen>
<dimen name="please_wait_textview_size">20sp</dimen>
<!--TODO: create dimensions for other screen sizes -->
<dimen name="settings_button_size">48dp</dimen>
<dimen name="settings_button_margin">10dp</dimen>
<dimen name="measurements_text_size">17sp</dimen>
<dimen name="measurements_upper_text_spacing">60dp</dimen>
<dimen name="measurements_lower_text_spacing">80dp</dimen>
<integer name="measurements_text_border_thickness">3</integer>
<dimen name="grid_item_chooser_text_size">15sp</dimen>
<dimen name="grid_item_checkbox_size">30dp</dimen>
<dimen name="header_text_size">21dp</dimen>
<dimen name="metric_chooser_text_size">18dp</dimen>
<dimen name="grid_item_left_padding">4dp</dimen>
<dimen name="grid_item_bottom_padding">8dp</dimen>
<dimen name="grid_item_metric_name">15sp</dimen>
<dimen name="metric_chooser_column_width">140dp</dimen>
</resources>

Some files were not shown because too many files have changed in this diff Show more