Add runtime permission checking to support new Dangerous Permissions rules in api23 and bring dependency files up to date.

Fixes and tweaks to clean up api23 compatability.
This commit is contained in:
Abraham Hedtke 2015-12-18 22:17:20 -05:00 committed by toby cabot
parent 07a596c0d6
commit bf440ea9c0
15 changed files with 290 additions and 120 deletions

View file

@ -8,7 +8,7 @@ In order to use this project, you will need to:
- Obtain the Affectiva Android SDK (visit http://www.affectiva.com/solutions/apis-sdks/) - Obtain the Affectiva Android SDK (visit http://www.affectiva.com/solutions/apis-sdks/)
- Copy the contents of the SDK's assets folder into this project's app/src/main/assets folder - Copy the contents of the SDK's assets folder into this project's app/src/main/assets folder
- Copy the contents of the SDK's libs folder into this project's app/libs folder - Copy the contents of the SDK's libs folder into this project's app/libs folder
- Copy the armeabi-v7a folder (found in the SDK libs folder) into this project's app/native-libs folder - Copy the armeabi-v7a folder (found in the SDK libs folder) into this project's app/jniLibs folder
- Copy your license file to this project's app/src/main/assets/Affdex folder and rename to license.txt - Copy your license file to this project's app/src/main/assets/Affdex folder and rename to license.txt
- Build the project - Build the project
- Run the app and smile! - Run the app and smile!

2
app/.gitignore vendored
View file

@ -3,6 +3,6 @@
/jniLibs/armeabi-v7a /jniLibs/armeabi-v7a
/libs/Affdex-sdk.jar /libs/Affdex-sdk.jar
/libs/Affdex-sdk-javadoc.jar /libs/Affdex-sdk-javadoc.jar
/src/main/assets/Affdex/*.license /src/main/assets/Affdex/*license*
/src/main/assets/Affdex/Classifiers /src/main/assets/Affdex/Classifiers
/src/main/assets/Affdex/Classifiers/v_9 /src/main/assets/Affdex/Classifiers/v_9

View file

@ -67,6 +67,8 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
@ -80,6 +82,9 @@
</content> </content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-annotations-23.1.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.1.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.1.1" level="project" />
<orderEntry type="library" exported="" name="dagger-1.2.2" level="project" /> <orderEntry type="library" exported="" name="dagger-1.2.2" level="project" />
<orderEntry type="library" exported="" name="Affdex-sdk" level="project" /> <orderEntry type="library" exported="" name="Affdex-sdk" level="project" />
<orderEntry type="library" exported="" name="javax.inject-1" level="project" /> <orderEntry type="library" exported="" name="javax.inject-1" level="project" />

View file

@ -6,7 +6,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.affectiva.affdexme" applicationId "com.affectiva.affdexme"
minSdkVersion 14 minSdkVersion 16
targetSdkVersion 23 targetSdkVersion 23
versionCode 15 versionCode 15
versionName "2.1.0" versionName "2.1.0"
@ -41,14 +41,14 @@ dependencies {
compile files('libs/Affdex-sdk-javadoc.jar') compile files('libs/Affdex-sdk-javadoc.jar')
//include dependencies //include dependencies
compile 'com.android.support:support-v4:23.1.1'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.squareup.dagger:dagger:1.2.2' compile 'com.squareup.dagger:dagger:1.2.2'
compile 'javax.inject:javax.inject:1' compile 'javax.inject:javax.inject:1'
//although the use of the CameraDetector class in this project does not require it, you may have to include //although the use of the CameraDetector class in this project does not require it, you may have to include
//the following dependencies if you use other features of the Affdex SDK //the following dependencies if you use other features of the Affdex SDK
// compile 'com.google.code.gson:gson:2.4' // compile 'com.google.code.gson:gson:2.4'
// compile 'com.android.support:support-v4:23.1.1'
// compile 'com.android.support:appcompat-v7:23.1.1'
// compile 'com.android.support:support-v13:23.1.1' // compile 'com.android.support:support-v13:23.1.1'
// compile 'com.google.android.gms:play-services:8.4.0' // compile 'com.google.android.gms:play-services:8.4.0'
// compile 'com.google.android.gms:play-services-ads:8.4.0' // compile 'com.google.android.gms:play-services-ads:8.4.0'

View file

@ -1,3 +1,3 @@
Place the native library files here. Place the native library files here.
amreabi-v7a/libaffdexface_jni.so armeabi-v7a/libaffdexface_jni.so

View file

@ -2,3 +2,5 @@ Place the Affdex SDK JARs here.
Affdex-sdk.jar Affdex-sdk.jar
Affdex-sdk-javadoc.jar Affdex-sdk-javadoc.jar
dagger-*.java
javax.inject-1.jar

View file

@ -1,12 +1,4 @@
Place license file and Classifiers folder here. Place license file and Classifiers folder here.
xxxxxx.license license.txt
Classifiers Classifiers/v_9/*
Classifiers/v_9
Classifiers/v_9/face_model_3d.bin
Classifiers/v_9/CT_pack/*
Classifiers/v_9/haarcascade/*
Classifiers/v_9/new_pack/*
Classifiers/v_9/noctali0.83/*
Classifiers/v_9/PA_pack/*
Classifiers/v_9/production_config/*

View file

@ -1,6 +1,7 @@
package com.affectiva.affdexme; package com.affectiva.affdexme;
import android.app.Activity; import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -8,14 +9,20 @@ import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -23,18 +30,16 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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; import com.affectiva.android.affdex.sdk.Frame;
import com.affectiva.android.affdex.sdk.Frame.ROTATE; import com.affectiva.android.affdex.sdk.Frame.ROTATE;
import com.affectiva.android.affdex.sdk.detector.CameraDetector; import com.affectiva.android.affdex.sdk.detector.CameraDetector;
import com.affectiva.android.affdex.sdk.detector.Detector; import com.affectiva.android.affdex.sdk.detector.Detector;
import com.affectiva.android.affdex.sdk.detector.Face; import com.affectiva.android.affdex.sdk.detector.Face;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/* /*
* AffdexMe is an app that demonstrates the use of the Affectiva Android SDK. It uses the * AffdexMe is an app that demonstrates the use of the Affectiva Android SDK. It uses the
* front-facing camera on your Android device to view, process and analyze live video of your face. * front-facing camera on your Android device to view, process and analyze live video of your face.
@ -67,15 +72,23 @@ import com.affectiva.android.affdex.sdk.detector.Face;
* Copyright (c) 2014 Affectiva. All rights reserved. * Copyright (c) 2014 Affectiva. All rights reserved.
*/ */
public class MainActivity extends Activity public class MainActivity extends AppCompatActivity
implements Detector.FaceListener, Detector.ImageListener, View.OnTouchListener, CameraDetector.CameraEventListener { implements Detector.FaceListener, Detector.ImageListener, CameraDetector.CameraEventListener,
View.OnTouchListener, ActivityCompat.OnRequestPermissionsResultCallback {
private static final String LOG_TAG = "Affectiva";
public static final int NUM_METRICS_DISPLAYED = 6; public static final int NUM_METRICS_DISPLAYED = 6;
private static final String LOG_TAG = "Affectiva";
//Permission-related constants and variables
private static final int AFFDEXME_PERMISSIONS_REQUEST = 42; //value is arbitrary (between 0 and 255)
private boolean cameraPermissionsAvailable = false;
private boolean storagePermissionsAvailable = false;
//Camera variables
int cameraPreviewWidth = 0;
int cameraPreviewHeight = 0;
CameraDetector.CameraType cameraType;
boolean mirrorPoints = false;
//Affectiva SDK Object //Affectiva SDK Object
private CameraDetector detector = null; private CameraDetector detector = null;
//MetricsManager View UI Objects //MetricsManager View UI Objects
private RelativeLayout metricViewLayout; private RelativeLayout metricViewLayout;
private LinearLayout leftMetricsLayout; private LinearLayout leftMetricsLayout;
@ -86,34 +99,28 @@ public class MainActivity extends Activity
private TextView fpsPct; private TextView fpsPct;
private TextView pleaseWaitTextView; private TextView pleaseWaitTextView;
private ProgressBar progressBar; private ProgressBar progressBar;
//Other UI objects //Other UI objects
private ViewGroup activityLayout; //top-most ViewGroup in which all content resides private ViewGroup activityLayout; //top-most ViewGroup in which all content resides
private RelativeLayout mainLayout; //layout, to be resized, containing all UI elements private RelativeLayout mainLayout; //layout, to be resized, containing all UI elements
private RelativeLayout progressBarLayout; //layout used to show progress circle while camera is starting private RelativeLayout progressBarLayout; //layout used to show progress circle while camera is starting
private LinearLayout permissionsUnavailableLayout; //layout used to notify the user that not enough permissions have been granted to use the app
private SurfaceView cameraView; //SurfaceView used to display camera images private SurfaceView cameraView; //SurfaceView used to display camera images
private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots private DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
private ImageButton settingsButton; private ImageButton settingsButton;
private ImageButton cameraButton; private ImageButton cameraButton;
private Button retryPermissionsButton;
//Application settings variables //Application settings variables
private int detectorProcessRate; private int detectorProcessRate;
private boolean isMenuVisible = false; private boolean isMenuVisible = false;
private boolean isFPSVisible = false; private boolean isFPSVisible = false;
private boolean isMenuShowingForFirstTime = true; private boolean isMenuShowingForFirstTime = true;
//Frames Per Second (FPS) variables //Frames Per Second (FPS) variables
private long firstSystemTime = 0; private long firstSystemTime = 0;
private float numberOfFrames = 0; private float numberOfFrames = 0;
private long timeToUpdate = 0; private long timeToUpdate = 0;
//Camera-related variables //Camera-related variables
private boolean isFrontFacingCameraDetected = true; private boolean isFrontFacingCameraDetected = true;
private boolean isBackFacingCameraDetected = true; private boolean isBackFacingCameraDetected = true;
int cameraPreviewWidth = 0;
int cameraPreviewHeight = 0;
CameraDetector.CameraType cameraType;
boolean mirrorPoints = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -122,12 +129,110 @@ public class MainActivity extends Activity
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
initializeUI(); initializeUI();
checkForDangerousPermissions();
determineCameraAvailability(); determineCameraAvailability();
initializeCameraDetector(); initializeCameraDetector();
} }
private void checkForDangerousPermissions() {
cameraPermissionsAvailable =
ContextCompat.checkSelfPermission(
getApplicationContext(),
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
storagePermissionsAvailable =
ContextCompat.checkSelfPermission(
getBaseContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
if (!cameraPermissionsAvailable || !storagePermissionsAvailable) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
showPermissionExplanationDialog();
} else {
// No explanation needed, we can request the permission.
requestNeededPermissions();
}
}
}
private void requestNeededPermissions() {
List<String> neededPermissions = new ArrayList<>();
if (!cameraPermissionsAvailable) {
neededPermissions.add(Manifest.permission.CAMERA);
}
if (!storagePermissionsAvailable) {
neededPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
ActivityCompat.requestPermissions(
this,
neededPermissions.toArray(new String[neededPermissions.size()]),
AFFDEXME_PERMISSIONS_REQUEST);
// AFFDEXME_PERMISSIONS_REQUEST is an app-defined int constant that must be between 0 and 255.
// The callback method gets the result of the request.
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == AFFDEXME_PERMISSIONS_REQUEST) {
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.CAMERA)) {
cameraPermissionsAvailable = (grantResult == PackageManager.PERMISSION_GRANTED);
}
if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
storagePermissionsAvailable = (grantResult == PackageManager.PERMISSION_GRANTED);
}
}
}
if (!cameraPermissionsAvailable || !storagePermissionsAvailable) {
permissionsUnavailableLayout.setVisibility(View.VISIBLE);
} else {
permissionsUnavailableLayout.setVisibility(View.GONE);
}
}
private void showPermissionExplanationDialog() {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
getApplicationContext());
// set title
alertDialogBuilder.setTitle(getResources().getString(R.string.insufficient_permissions));
// set dialog message
alertDialogBuilder
.setMessage(getResources().getString(R.string.permissions_needed_explanation))
.setCancelable(false)
.setPositiveButton(getResources().getString(R.string.understood), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
requestNeededPermissions();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
/** /**
* We check to make sure the device has a front-facing camera. * We check to make sure the device has a front-facing camera.
* If it does not, we obscure the app with a notice informing the user they cannot * If it does not, we obscure the app with a notice informing the user they cannot
@ -161,6 +266,7 @@ public class MainActivity extends Activity
//Get handles to UI objects //Get handles to UI objects
activityLayout = (ViewGroup) findViewById(android.R.id.content); activityLayout = (ViewGroup) findViewById(android.R.id.content);
progressBarLayout = (RelativeLayout) findViewById(R.id.progress_bar_cover); progressBarLayout = (RelativeLayout) findViewById(R.id.progress_bar_cover);
permissionsUnavailableLayout = (LinearLayout) findViewById(R.id.permissionsUnavialableLayout);
metricViewLayout = (RelativeLayout) findViewById(R.id.metric_view_group); metricViewLayout = (RelativeLayout) findViewById(R.id.metric_view_group);
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics); leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics); rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
@ -173,6 +279,7 @@ public class MainActivity extends Activity
cameraButton = (ImageButton) findViewById(R.id.camera_button); cameraButton = (ImageButton) findViewById(R.id.camera_button);
progressBar = (ProgressBar) findViewById(R.id.progress_bar); progressBar = (ProgressBar) findViewById(R.id.progress_bar);
pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview); pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview);
retryPermissionsButton = (Button) findViewById(R.id.retryPermissionsButton);
//Initialize views to display metrics //Initialize views to display metrics
metricNames = new TextView[NUM_METRICS_DISPLAYED]; metricNames = new TextView[NUM_METRICS_DISPLAYED];
@ -235,6 +342,13 @@ public class MainActivity extends Activity
} }
}); });
retryPermissionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
requestNeededPermissions();
}
});
} }
void initializeCameraDetector() { void initializeCameraDetector() {
@ -246,8 +360,7 @@ public class MainActivity extends Activity
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView); detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
// update the license path here if you name your file something else // update the license path here if you name your file something else
// detector.setLicensePath("license.txt"); detector.setLicensePath("license.txt");
detector.setLicensePath("Affectiva.license");
detector.setImageListener(this); detector.setImageListener(this);
detector.setFaceListener(this); detector.setFaceListener(this);
detector.setOnCameraEventListener(this); detector.setOnCameraEventListener(this);
@ -259,6 +372,7 @@ public class MainActivity extends Activity
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
checkForDangerousPermissions();
restoreApplicationSettings(); restoreApplicationSettings();
setMenuVisible(true); setMenuVisible(true);
isMenuShowingForFirstTime = true; isMenuShowingForFirstTime = true;
@ -274,19 +388,19 @@ public class MainActivity extends Activity
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences); detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
detector.setMaxProcessRate(detectorProcessRate); detector.setMaxProcessRate(detectorProcessRate);
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible if (sharedPreferences.getBoolean("fps", isFPSVisible)) { //restore isFPSMetricVisible
setFPSVisible(true); setFPSVisible(true);
} else { } else {
setFPSVisible(false); setFPSVisible(false);
} }
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible if (sharedPreferences.getBoolean("track", drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
setTrackPoints(true); setTrackPoints(true);
} else { } else {
setTrackPoints(false); setTrackPoints(false);
} }
if (sharedPreferences.getBoolean("measurements",drawingView.getDrawMeasurementsEnabled())) { //restore show measurements if (sharedPreferences.getBoolean("measurements", drawingView.getDrawMeasurementsEnabled())) { //restore show measurements
setShowMeasurements(true); setShowMeasurements(true);
} else { } else {
setShowMeasurements(false); setShowMeasurements(false);
@ -294,7 +408,7 @@ public class MainActivity extends Activity
//populate metric displays //populate metric displays
for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) { for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) {
activateMetric(n,PreferencesUtils.getMetricFromPrefs(sharedPreferences, n)); activateMetric(n, PreferencesUtils.getMetricFromPrefs(sharedPreferences, n));
} }
} }
@ -322,10 +436,10 @@ public class MainActivity extends Activity
metricDisplays[index].setIsShadedMetricView(false); metricDisplays[index].setIsShadedMetricView(false);
} }
} else if (metric.getType() == MetricsManager.MetricType.Expression) { } else if (metric.getType() == MetricsManager.MetricType.Expression) {
getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric),null); getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric), null);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG,String.format("Error using reflection to generate methods for %s",metric.toString())); Log.e(LOG_TAG, String.format("Error using reflection to generate methods for %s", metric.toString()));
} }
metricDisplays[index].setMetricToDisplay(metric, getFaceScoreMethod); metricDisplays[index].setMetricToDisplay(metric, getFaceScoreMethod);
@ -333,7 +447,7 @@ public class MainActivity extends Activity
/** /**
* Reset the variables used to calculate processed frames per second. * Reset the variables used to calculate processed frames per second.
* **/ **/
public void resetFPSCalculations() { public void resetFPSCalculations() {
firstSystemTime = SystemClock.elapsedRealtime(); firstSystemTime = SystemClock.elapsedRealtime();
timeToUpdate = firstSystemTime + 1000L; timeToUpdate = firstSystemTime + 1000L;
@ -359,6 +473,12 @@ public class MainActivity extends Activity
void mainWindowResumedTasks() { void mainWindowResumedTasks() {
//Notify the user that they can't use the app without authorizing these permissions.
if (!cameraPermissionsAvailable || !storagePermissionsAvailable) {
permissionsUnavailableLayout.setVisibility(View.VISIBLE);
return;
}
startDetector(); startDetector();
if (!drawingView.isDimensionsNeeded()) { if (!drawingView.isDimensionsNeeded()) {
@ -390,7 +510,6 @@ public class MainActivity extends Activity
} }
@Override @Override
public void onFaceDetectionStarted() { public void onFaceDetectionStarted() {
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
@ -435,7 +554,7 @@ public class MainActivity extends Activity
//update metrics with latest face information. The metrics are displayed on a MetricView, a custom view with a .setScore() method. //update metrics with latest face information. The metrics are displayed on a MetricView, a custom view with a .setScore() method.
for (MetricDisplay metricDisplay : metricDisplays) { for (MetricDisplay metricDisplay : metricDisplays) {
updateMetricScore(metricDisplay,face); updateMetricScore(metricDisplay, face);
} }
/** /**
@ -445,7 +564,7 @@ public class MainActivity extends Activity
*/ */
if (drawingView.getDrawPointsEnabled() || drawingView.getDrawMeasurementsEnabled()) { 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.setMetrics(face.measurements.orientation.getRoll(), face.measurements.orientation.getYaw(), face.measurements.orientation.getPitch(), face.measurements.getInterocularDistance(), face.emotions.getValence());
drawingView.updatePoints(face.getFacePoints(),mirrorPoints); drawingView.updatePoints(face.getFacePoints(), mirrorPoints);
} }
} }
@ -460,13 +579,13 @@ public class MainActivity extends Activity
try { try {
if (metric.getType() == MetricsManager.MetricType.Emotion) { if (metric.getType() == MetricsManager.MetricType.Emotion) {
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emotions,null); score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emotions, null);
metricDisplay.setScore(score); metricDisplay.setScore(score);
} else if (metric.getType() == MetricsManager.MetricType.Expression) { } else if (metric.getType() == MetricsManager.MetricType.Expression) {
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.expressions,null); score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.expressions, null);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG,String.format("Error using reflecting to get %s score from face.",metric.toString())); Log.e(LOG_TAG, String.format("Error using reflecting to get %s score from face.", metric.toString()));
} }
metricDisplay.setScore(score); metricDisplay.setScore(score);
} }
@ -482,8 +601,8 @@ public class MainActivity extends Activity
numberOfFrames += 1; numberOfFrames += 1;
long currentTime = SystemClock.elapsedRealtime(); long currentTime = SystemClock.elapsedRealtime();
if (currentTime > timeToUpdate) { if (currentTime > timeToUpdate) {
float framesPerSecond = (numberOfFrames/(float)(currentTime - firstSystemTime))*1000f; float framesPerSecond = (numberOfFrames / (float) (currentTime - firstSystemTime)) * 1000f;
fpsPct.setText(String.format(" %.1f",framesPerSecond)); fpsPct.setText(String.format(" %.1f", framesPerSecond));
timeToUpdate = currentTime + 1000L; timeToUpdate = currentTime + 1000L;
} }
} }
@ -508,7 +627,7 @@ public class MainActivity extends Activity
try { try {
detector.stop(); detector.stop();
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG,e.getMessage()); Log.e(LOG_TAG, e.getMessage());
} }
} }
@ -517,11 +636,10 @@ public class MainActivity extends Activity
} }
/** /**
* When the user taps the screen, hide the menu if it is visible and show it if it is hidden. * When the user taps the screen, hide the menu if it is visible and show it if it is hidden.
* **/ **/
void setMenuVisible(boolean b){ void setMenuVisible(boolean b) {
isMenuShowingForFirstTime = false; isMenuShowingForFirstTime = false;
isMenuVisible = b; isMenuVisible = b;
if (b) { if (b) {
@ -533,8 +651,7 @@ public class MainActivity extends Activity
View.SYSTEM_UI_FLAG_LAYOUT_STABLE View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} } else {
else {
//We hide the navigation bar //We hide the navigation bar
getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().setSystemUiVisibility(
@ -613,7 +730,7 @@ public class MainActivity extends Activity
cameraPreviewWidth = cameraWidth; cameraPreviewWidth = cameraWidth;
cameraPreviewHeight = cameraHeight; cameraPreviewHeight = cameraHeight;
} }
drawingView.setThickness((int)(cameraPreviewWidth/100f)); drawingView.setThickness((int) (cameraPreviewWidth / 100f));
mainLayout.post(new Runnable() { mainLayout.post(new Runnable() {
@Override @Override
@ -627,21 +744,21 @@ public class MainActivity extends Activity
if (cameraPreviewWidth == 0 || cameraPreviewHeight == 0 || layoutWidth == 0 || layoutHeight == 0) if (cameraPreviewWidth == 0 || cameraPreviewHeight == 0 || layoutWidth == 0 || layoutHeight == 0)
return; return;
float layoutAspectRatio = (float)layoutWidth/layoutHeight; float layoutAspectRatio = (float) layoutWidth / layoutHeight;
float cameraPreviewAspectRatio = (float)cameraPreviewWidth/cameraPreviewHeight; float cameraPreviewAspectRatio = (float) cameraPreviewWidth / cameraPreviewHeight;
int newWidth; int newWidth;
int newHeight; int newHeight;
if (cameraPreviewAspectRatio > layoutAspectRatio) { if (cameraPreviewAspectRatio > layoutAspectRatio) {
newWidth = layoutWidth; newWidth = layoutWidth;
newHeight =(int) (layoutWidth / cameraPreviewAspectRatio); newHeight = (int) (layoutWidth / cameraPreviewAspectRatio);
} else { } else {
newWidth = (int) (layoutHeight * cameraPreviewAspectRatio); newWidth = (int) (layoutHeight * cameraPreviewAspectRatio);
newHeight = layoutHeight; newHeight = layoutHeight;
} }
drawingView.updateViewDimensions(newWidth,newHeight,cameraPreviewWidth,cameraPreviewHeight); drawingView.updateViewDimensions(newWidth, newHeight, cameraPreviewWidth, cameraPreviewHeight);
ViewGroup.LayoutParams params = mainLayout.getLayoutParams(); ViewGroup.LayoutParams params = mainLayout.getLayoutParams();
params.height = newHeight; params.height = newHeight;
@ -662,14 +779,14 @@ public class MainActivity extends Activity
cameraType = CameraDetector.CameraType.CAMERA_BACK; cameraType = CameraDetector.CameraType.CAMERA_BACK;
mirrorPoints = false; mirrorPoints = false;
} else { } else {
Toast.makeText(this,"No back-facing camera found",Toast.LENGTH_LONG).show(); Toast.makeText(this, "No back-facing camera found", Toast.LENGTH_LONG).show();
} }
} else if (cameraType == CameraDetector.CameraType.CAMERA_BACK) { } else if (cameraType == CameraDetector.CameraType.CAMERA_BACK) {
if (isFrontFacingCameraDetected) { if (isFrontFacingCameraDetected) {
cameraType = CameraDetector.CameraType.CAMERA_FRONT; cameraType = CameraDetector.CameraType.CAMERA_FRONT;
mirrorPoints = true; mirrorPoints = true;
} else { } else {
Toast.makeText(this,"No front-facing camera found",Toast.LENGTH_LONG).show(); Toast.makeText(this, "No front-facing camera found", Toast.LENGTH_LONG).show();
} }
} }
@ -678,7 +795,7 @@ public class MainActivity extends Activity
try { try {
detector.setCameraType(cameraType); detector.setCameraType(cameraType);
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG,e.getMessage()); Log.e(LOG_TAG, e.getMessage());
} }
} }
} }

View file

@ -69,7 +69,7 @@ public class MetricDisplay extends View {
textPaint.setColor(a.getColor(R.styleable.custom_attributes_textColor, Color.BLACK)); textPaint.setColor(a.getColor(R.styleable.custom_attributes_textColor, Color.BLACK));
textSize = a.getDimensionPixelSize(R.styleable.custom_attributes_textSize, textSize); textSize = a.getDimensionPixelSize(R.styleable.custom_attributes_textSize, textSize);
textPaint.setTextSize(textSize); textPaint.setTextSize(textSize);
halfWidth = a.getDimensionPixelSize(R.styleable.custom_attributes_barLength,100)/2; halfWidth = a.getDimensionPixelSize(R.styleable.custom_attributes_metricBarLength,100)/2;
a.recycle(); a.recycle();
} else { } else {
textPaint.setColor(Color.BLACK); textPaint.setColor(Color.BLACK);

View file

@ -1,93 +1,102 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/main_layout" android:id="@+id/main_layout"
android:layout_height="match_parent" tools:context=".MainActivity" android:focusable="true" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:keepScreenOn="true" android:keepScreenOn="true"
> tools:context=".MainActivity">
<SurfaceView <SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:id="@+id/camera_preview" android:id="@+id/camera_preview"
/> android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
<com.affectiva.affdexme.DrawingView <com.affectiva.affdexme.DrawingView
android:id="@+id/drawing_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true" android:layout_centerInParent="true"
custom:measurements_text_size="@dimen/measurements_text_size"
custom:measurements_upper_spacing="@dimen/measurements_upper_text_spacing"
custom:measurements_lower_spacing="@dimen/measurements_lower_text_spacing"
custom:measurements_color="#DDDDDD" custom:measurements_color="#DDDDDD"
custom:measurements_lower_spacing="@dimen/measurements_lower_text_spacing"
custom:measurements_text_border_color="@color/letter_gray" custom:measurements_text_border_color="@color/letter_gray"
custom:measurements_text_border_thickness="@integer/measurements_text_border_thickness" custom:measurements_text_border_thickness="@integer/measurements_text_border_thickness"
android:id="@+id/drawing_view"/> custom:measurements_text_size="@dimen/measurements_text_size"
<include layout="@layout/metric_layout" custom:measurements_upper_spacing="@dimen/measurements_upper_text_spacing" />
<include
android:id="@+id/metric_view_group" android:id="@+id/metric_view_group"
/> layout="@layout/metric_layout" />
<ImageButton <ImageButton
android:id="@+id/settings_button"
android:layout_width="@dimen/settings_button_size" android:layout_width="@dimen/settings_button_size"
android:layout_height="@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_alignParentRight="true"
android:layout_below="@id/metric_view_group" android:layout_below="@id/metric_view_group"
android:id="@+id/settings_button" android:layout_margin="@dimen/settings_button_margin"
android:onClick="settings_button_click"/> android:background="@null"
android:contentDescription="@string/settings_content_description"
android:onClick="settings_button_click"
android:scaleType="fitCenter"
android:src="@drawable/settings_button_selector" />
<ImageButton <ImageButton
android:id="@+id/camera_button"
android:layout_width="@dimen/settings_button_size" android:layout_width="@dimen/settings_button_size"
android:layout_height="@dimen/settings_button_size" android:layout_height="@dimen/settings_button_size"
android:background="@null"
android:src="@drawable/camera_button_selector"
android:scaleType="fitCenter"
android:layout_margin="@dimen/settings_button_margin"
android:contentDescription="Switch camera button"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_below="@id/settings_button" android:layout_below="@id/settings_button"
android:id="@+id/camera_button" android:layout_margin="@dimen/settings_button_margin"
android:onClick="camera_button_click"/> android:background="@null"
android:contentDescription="Switch camera button"
android:onClick="camera_button_click"
android:scaleType="fitCenter"
android:src="@drawable/camera_button_selector" />
<include layout="@layout/insufficent_permissions_panel" />
<RelativeLayout <RelativeLayout
android:id="@+id/progress_bar_cover"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/progress_bar_cover"
android:background="@color/black"> android:background="@color/black">
<TextView <TextView
style="@android:style/TextAppearance.Holo.Medium.Inverse"
android:id="@+id/please_wait_textview"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
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" android:layout_centerHorizontal="true"
/> android:layout_centerVertical="true"
android:text="@string/loading"
android:textSize="@dimen/please_wait_textview_size" />
<ProgressBar <ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/please_wait_textview_size" android:layout_width="@dimen/please_wait_textview_size"
android:layout_height="@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:layout_centerVertical="true"
android:layout_toLeftOf="@id/please_wait_textview"
android:indeterminate="true" android:indeterminate="true"
android:paddingRight="10dp" android:paddingRight="10dp" />
android:id="@+id/progress_bar"/>
<TextView <TextView
style="@android:style/TextAppearance.Holo.Medium.Inverse"
android:id="@+id/not_found_textview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="@string/not_found"
android:padding="20sp"
android:textColor="#CCCCCC"
android:background="@color/black" android:background="@color/black"
android:gravity="center" android:gravity="center"
android:id="@+id/not_found_textview" android:padding="20sp"
android:visibility="gone" android:text="@string/not_found"
android:textSize="20sp"/> android:textColor="#CCCCCC"
android:textSize="20sp"
android:visibility="gone" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/permissionsUnavialableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone">
<TextView
style="@android:style/TextAppearance.Holo.Large.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/bottom_padding"
android:text="@string/insufficient_permissions" />
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="@dimen/bottom_padding"
android:contentDescription="@string/error"
android:src="@android:drawable/presence_busy" />
<TextView
style="@android:style/TextAppearance.Holo.Small.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/bottom_padding"
android:gravity="center"
android:text="@string/permissions_needed_explanation" />
<Button
android:id="@+id/retryPermissionsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/retry" />
</LinearLayout>

View file

@ -3,7 +3,7 @@
<declare-styleable name="custom_attributes"> <declare-styleable name="custom_attributes">
<attr name="textSize" format="dimension" /> <attr name="textSize" format="dimension" />
<attr name="textColor" format="color"/> <attr name="textColor" format="color"/>
<attr name="barLength" format="dimension"/> <attr name="metricBarLength" format="dimension"/>
<attr name="textDepth" format="dimension"/> <attr name="textDepth" format="dimension"/>
</declare-styleable> </declare-styleable>
<declare-styleable name="drawing_view_attributes"> <declare-styleable name="drawing_view_attributes">

View file

@ -6,6 +6,6 @@
<item name="android:layout_gravity">center</item> <item name="android:layout_gravity">center</item>
<item name="textColor">@color/letter_gray</item> <item name="textColor">@color/letter_gray</item>
<item name="textSize">@dimen/pct_text_size</item> <item name="textSize">@dimen/pct_text_size</item>
<item name="barLength">@dimen/metric_view_bar_length</item> <item name="metricBarLength">@dimen/metric_view_bar_length</item>
</style> </style>
</resources> </resources>

View file

@ -6,6 +6,11 @@
<string name="loading">Loading&#8230;</string> <string name="loading">Loading&#8230;</string>
<string name="settings_content_description">Settings</string> <string name="settings_content_description">Settings</string>
<string name="insufficient_permissions">Insufficient Permissions</string>
<string name="permissions_needed_explanation">This app requires the permission to access your camera to be able to gather facial images to process, and permission to write to the storage on your device so that we can temporarily store video data there while we process it.</string>
<string name="error">Error</string>
<string name="understood">Understood</string>
<!--MetricSelectionFragment strings--> <!--MetricSelectionFragment strings-->
<string name="clear_all">Clear All</string> <string name="clear_all">Clear All</string>
<string name="metric_chooser_default_message">0 metrics chosen.</string> <string name="metric_chooser_default_message">0 metrics chosen.</string>
@ -31,4 +36,5 @@
<string name="show_fps_title">Show FPS</string> <string name="show_fps_title">Show FPS</string>
<string name="show_fps_message">Display the actual processed frames per second.</string> <string name="show_fps_message">Display the actual processed frames per second.</string>
<string name="retry">Retry</string>
</resources> </resources>

View file

@ -1,5 +1,5 @@
<resources> <resources>
<style name="MainActivityTheme" parent="android:Theme.DeviceDefault.NoActionBar"> <style name="MainActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@color/black</item> <item name="android:windowBackground">@color/black</item>
</style> </style>