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/)
- 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 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
- Build the project
- Run the app and smile!

2
app/.gitignore vendored
View File

@ -3,6 +3,6 @@
/jniLibs/armeabi-v7a
/libs/Affdex-sdk.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/v_9

View File

@ -67,6 +67,8 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<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/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
@ -80,6 +82,9 @@
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<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="Affdex-sdk" level="project" />
<orderEntry type="library" exported="" name="javax.inject-1" level="project" />

View File

@ -6,7 +6,7 @@ android {
defaultConfig {
applicationId "com.affectiva.affdexme"
minSdkVersion 14
minSdkVersion 16
targetSdkVersion 23
versionCode 15
versionName "2.1.0"
@ -41,14 +41,14 @@ dependencies {
compile files('libs/Affdex-sdk-javadoc.jar')
//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 'javax.inject:javax.inject:1'
//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
// 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.google.android.gms:play-services: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.
amreabi-v7a/libaffdexface_jni.so
armeabi-v7a/libaffdexface_jni.so

View File

@ -1,4 +1,6 @@
Place the Affdex SDK JARs here.
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.
xxxxxx.license
Classifiers
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/*
license.txt
Classifiers/v_9/*

View File

@ -1,6 +1,7 @@
package com.affectiva.affdexme;
import android.app.Activity;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -8,14 +9,20 @@ import android.graphics.Typeface;
import android.os.Bundle;
import android.os.SystemClock;
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.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@ -23,18 +30,16 @@ 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;
import com.affectiva.android.affdex.sdk.Frame.ROTATE;
import com.affectiva.android.affdex.sdk.detector.CameraDetector;
import com.affectiva.android.affdex.sdk.detector.Detector;
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
* 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.
*/
public class MainActivity extends Activity
implements Detector.FaceListener, Detector.ImageListener, View.OnTouchListener, CameraDetector.CameraEventListener {
public class MainActivity extends AppCompatActivity
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;
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
private CameraDetector detector = null;
//MetricsManager View UI Objects
private RelativeLayout metricViewLayout;
private LinearLayout leftMetricsLayout;
@ -86,34 +99,28 @@ public class MainActivity extends Activity
private TextView fpsPct;
private TextView pleaseWaitTextView;
private ProgressBar progressBar;
//Other UI objects
private ViewGroup activityLayout; //top-most ViewGroup in which all content resides
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 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 DrawingView drawingView; //SurfaceView containing its own thread, used to draw facial tracking dots
private ImageButton settingsButton;
private ImageButton cameraButton;
private Button retryPermissionsButton;
//Application settings variables
private int detectorProcessRate;
private boolean isMenuVisible = false;
private boolean isFPSVisible = false;
private boolean isMenuShowingForFirstTime = true;
//Frames Per Second (FPS) variables
private long firstSystemTime = 0;
private float numberOfFrames = 0;
private long timeToUpdate = 0;
//Camera-related variables
private boolean isFrontFacingCameraDetected = true;
private boolean isBackFacingCameraDetected = true;
int cameraPreviewWidth = 0;
int cameraPreviewHeight = 0;
CameraDetector.CameraType cameraType;
boolean mirrorPoints = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -122,12 +129,110 @@ public class MainActivity extends Activity
setContentView(R.layout.activity_main);
initializeUI();
checkForDangerousPermissions();
determineCameraAvailability();
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.
* 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
activityLayout = (ViewGroup) findViewById(android.R.id.content);
progressBarLayout = (RelativeLayout) findViewById(R.id.progress_bar_cover);
permissionsUnavailableLayout = (LinearLayout) findViewById(R.id.permissionsUnavialableLayout);
metricViewLayout = (RelativeLayout) findViewById(R.id.metric_view_group);
leftMetricsLayout = (LinearLayout) findViewById(R.id.left_metrics);
rightMetricsLayout = (LinearLayout) findViewById(R.id.right_metrics);
@ -173,6 +279,7 @@ public class MainActivity extends Activity
cameraButton = (ImageButton) findViewById(R.id.camera_button);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview);
retryPermissionsButton = (Button) findViewById(R.id.retryPermissionsButton);
//Initialize views to display metrics
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() {
@ -245,9 +359,8 @@ public class MainActivity extends Activity
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
// update the license path here if you name your file something else
// detector.setLicensePath("license.txt");
detector.setLicensePath("Affectiva.license");
// update the license path here if you name your file something else
detector.setLicensePath("license.txt");
detector.setImageListener(this);
detector.setFaceListener(this);
detector.setOnCameraEventListener(this);
@ -259,6 +372,7 @@ public class MainActivity extends Activity
@Override
public void onResume() {
super.onResume();
checkForDangerousPermissions();
restoreApplicationSettings();
setMenuVisible(true);
isMenuShowingForFirstTime = true;
@ -274,19 +388,19 @@ public class MainActivity extends Activity
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
detector.setMaxProcessRate(detectorProcessRate);
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
if (sharedPreferences.getBoolean("fps", isFPSVisible)) { //restore isFPSMetricVisible
setFPSVisible(true);
} else {
setFPSVisible(false);
}
if (sharedPreferences.getBoolean("track",drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
if (sharedPreferences.getBoolean("track", drawingView.getDrawPointsEnabled())) { //restore isTrackingDotsVisible
setTrackPoints(true);
} else {
setTrackPoints(false);
}
if (sharedPreferences.getBoolean("measurements",drawingView.getDrawMeasurementsEnabled())) { //restore show measurements
if (sharedPreferences.getBoolean("measurements", drawingView.getDrawMeasurementsEnabled())) { //restore show measurements
setShowMeasurements(true);
} else {
setShowMeasurements(false);
@ -294,15 +408,15 @@ public class MainActivity extends Activity
//populate metric displays
for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) {
activateMetric(n,PreferencesUtils.getMetricFromPrefs(sharedPreferences, 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
* -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));
@ -322,10 +436,10 @@ public class MainActivity extends Activity
metricDisplays[index].setIsShadedMetricView(false);
}
} 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) {
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);
@ -333,7 +447,7 @@ public class MainActivity extends Activity
/**
* Reset the variables used to calculate processed frames per second.
* **/
**/
public void resetFPSCalculations() {
firstSystemTime = SystemClock.elapsedRealtime();
timeToUpdate = firstSystemTime + 1000L;
@ -359,6 +473,12 @@ public class MainActivity extends Activity
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();
if (!drawingView.isDimensionsNeeded()) {
@ -390,7 +510,6 @@ public class MainActivity extends Activity
}
@Override
public void onFaceDetectionStarted() {
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.
for (MetricDisplay metricDisplay : metricDisplays) {
updateMetricScore(metricDisplay,face);
updateMetricScore(metricDisplay, face);
}
/**
@ -445,7 +564,7 @@ public class MainActivity extends Activity
*/
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(),mirrorPoints);
drawingView.updatePoints(face.getFacePoints(), mirrorPoints);
}
}
@ -460,13 +579,13 @@ public class MainActivity extends Activity
try {
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);
} 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) {
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);
}
@ -482,8 +601,8 @@ public class MainActivity extends Activity
numberOfFrames += 1;
long currentTime = SystemClock.elapsedRealtime();
if (currentTime > timeToUpdate) {
float framesPerSecond = (numberOfFrames/(float)(currentTime - firstSystemTime))*1000f;
fpsPct.setText(String.format(" %.1f",framesPerSecond));
float framesPerSecond = (numberOfFrames / (float) (currentTime - firstSystemTime)) * 1000f;
fpsPct.setText(String.format(" %.1f", framesPerSecond));
timeToUpdate = currentTime + 1000L;
}
}
@ -508,7 +627,7 @@ public class MainActivity extends Activity
try {
detector.stop();
} 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.
* **/
void setMenuVisible(boolean b){
**/
void setMenuVisible(boolean b) {
isMenuShowingForFirstTime = false;
isMenuVisible = b;
if (b) {
@ -533,8 +651,7 @@ public class MainActivity extends Activity
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
else {
} else {
//We hide the navigation bar
getWindow().getDecorView().setSystemUiVisibility(
@ -613,7 +730,7 @@ public class MainActivity extends Activity
cameraPreviewWidth = cameraWidth;
cameraPreviewHeight = cameraHeight;
}
drawingView.setThickness((int)(cameraPreviewWidth/100f));
drawingView.setThickness((int) (cameraPreviewWidth / 100f));
mainLayout.post(new Runnable() {
@Override
@ -627,21 +744,21 @@ public class MainActivity extends Activity
if (cameraPreviewWidth == 0 || cameraPreviewHeight == 0 || layoutWidth == 0 || layoutHeight == 0)
return;
float layoutAspectRatio = (float)layoutWidth/layoutHeight;
float cameraPreviewAspectRatio = (float)cameraPreviewWidth/cameraPreviewHeight;
float layoutAspectRatio = (float) layoutWidth / layoutHeight;
float cameraPreviewAspectRatio = (float) cameraPreviewWidth / cameraPreviewHeight;
int newWidth;
int newHeight;
if (cameraPreviewAspectRatio > layoutAspectRatio) {
newWidth = layoutWidth;
newHeight =(int) (layoutWidth / cameraPreviewAspectRatio);
newHeight = (int) (layoutWidth / cameraPreviewAspectRatio);
} else {
newWidth = (int) (layoutHeight * cameraPreviewAspectRatio);
newHeight = layoutHeight;
}
drawingView.updateViewDimensions(newWidth,newHeight,cameraPreviewWidth,cameraPreviewHeight);
drawingView.updateViewDimensions(newWidth, newHeight, cameraPreviewWidth, cameraPreviewHeight);
ViewGroup.LayoutParams params = mainLayout.getLayoutParams();
params.height = newHeight;
@ -662,14 +779,14 @@ public class MainActivity extends Activity
cameraType = CameraDetector.CameraType.CAMERA_BACK;
mirrorPoints = false;
} 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) {
if (isFrontFacingCameraDetected) {
cameraType = CameraDetector.CameraType.CAMERA_FRONT;
mirrorPoints = true;
} 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 {
detector.setCameraType(cameraType);
} 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));
textSize = a.getDimensionPixelSize(R.styleable.custom_attributes_textSize, 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();
} else {
textPaint.setColor(Color.BLACK);

View File

@ -1,93 +1,102 @@
<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"
android:layout_gravity="center"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
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:keepScreenOn="true"
>
tools:context=".MainActivity">
<SurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:id="@+id/camera_preview"
/>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
<com.affectiva.affdexme.DrawingView
android:id="@+id/drawing_view"
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_lower_spacing="@dimen/measurements_lower_text_spacing"
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"
custom:measurements_text_size="@dimen/measurements_text_size"
custom:measurements_upper_spacing="@dimen/measurements_upper_text_spacing" />
<include
android:id="@+id/metric_view_group"
/>
layout="@layout/metric_layout" />
<ImageButton
android:id="@+id/settings_button"
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:id="@+id/settings_button"
android:onClick="settings_button_click"/>
android:layout_margin="@dimen/settings_button_margin"
android:background="@null"
android:contentDescription="@string/settings_content_description"
android:onClick="settings_button_click"
android:scaleType="fitCenter"
android:src="@drawable/settings_button_selector" />
<ImageButton
android:id="@+id/camera_button"
android:layout_width="@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_below="@id/settings_button"
android:id="@+id/camera_button"
android:onClick="camera_button_click"/>
android:layout_margin="@dimen/settings_button_margin"
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
android:id="@+id/progress_bar_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/progress_bar_cover"
android:background="@color/black">
<TextView
style="@android:style/TextAppearance.Holo.Medium.Inverse"
android:id="@+id/please_wait_textview"
android:layout_width="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_centerVertical="true"
android:text="@string/loading"
android:textSize="@dimen/please_wait_textview_size" />
<ProgressBar
android:id="@+id/progress_bar"
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:layout_toLeftOf="@id/please_wait_textview"
android:indeterminate="true"
android:paddingRight="10dp"
android:id="@+id/progress_bar"/>
android:paddingRight="10dp" />
<TextView
style="@android:style/TextAppearance.Holo.Medium.Inverse"
android:id="@+id/not_found_textview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/not_found"
android:padding="20sp"
android:textColor="#CCCCCC"
android:background="@color/black"
android:gravity="center"
android:id="@+id/not_found_textview"
android:visibility="gone"
android:textSize="20sp"/>
android:padding="20sp"
android:text="@string/not_found"
android:textColor="#CCCCCC"
android:textSize="20sp"
android:visibility="gone" />
</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">
<attr name="textSize" format="dimension" />
<attr name="textColor" format="color"/>
<attr name="barLength" format="dimension"/>
<attr name="metricBarLength" format="dimension"/>
<attr name="textDepth" format="dimension"/>
</declare-styleable>
<declare-styleable name="drawing_view_attributes">

View File

@ -6,6 +6,6 @@
<item name="android:layout_gravity">center</item>
<item name="textColor">@color/letter_gray</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>
</resources>

View File

@ -6,6 +6,11 @@
<string name="loading">Loading&#8230;</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-->
<string name="clear_all">Clear All</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_message">Display the actual processed frames per second.</string>
<string name="retry">Retry</string>
</resources>

View File

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