Merge pull request #14 from Affectiva/merge-3.0
3.0 - emojis, multiface, screenshots
2
.gitignore
vendored
|
@ -18,6 +18,8 @@ build/
|
|||
/*/build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
affdexme-android.iml
|
||||
gradle.properties
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
|
|
12
README.md
|
@ -1,3 +1,9 @@
|
|||
![Affectiva Logo](http://developer.affectiva.com/images/logo.png)
|
||||
|
||||
###Copyright (c) 2016 Affectiva Inc. <br> See the file [license.txt](license.txt) for copying permission.
|
||||
|
||||
*****************************
|
||||
|
||||
**AffdexMe** is an app that demonstrates the use of the Affectiva Android SDK. It uses the camera on your Android device to view, process and analyze live video of your face. Start the app and you will see your face on the screen and metrics describing your expressions. Tapping the screen will bring up a menu with options to display the Processed Frames Per Second metric, display facial tracking points, and control the rate at which frames are processed by the SDK.
|
||||
|
||||
Most of the methods in this file control the application's UI. Therefore, if you are just interested in learning how the Affectiva SDK works, you will find the calls relevant to the use of the SDK in the initializeCameraDetector(), startCamera(), stopCamera(), and onImageResults() methods.
|
||||
|
@ -8,11 +14,13 @@ 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!
|
||||
|
||||
See the comment section at the top of the MainActivity.java file for more information.
|
||||
|
||||
Copyright (c) 2014-2015 Affectiva. All rights reserved.
|
||||
***
|
||||
|
||||
This app uses some of the excellent [Emoji One emojis](http://emojione.com).
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="affdexme-android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
9
app/.gitignore
vendored
|
@ -1 +1,10 @@
|
|||
/app.iml
|
||||
/build
|
||||
/jniLibs/armeabi-v7a
|
||||
/libs/Affdex-sdk.jar
|
||||
/libs/Affdex-sdk-javadoc.jar
|
||||
/libs/javax.inject-1.jar
|
||||
/libs/dagger-1.2.2.jar
|
||||
/src/main/assets/Affdex/*license*
|
||||
/src/main/assets/Affdex/Classifiers
|
||||
/src/main/assets/Affdex/Classifiers/v_9
|
||||
|
|
97
app/app.iml
|
@ -1,97 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="affdexme-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<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="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="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" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<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" 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>
|
|
@ -1,15 +1,15 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion '22.0.1'
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.affectiva.affdexme"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 14
|
||||
versionName "2.0.0"
|
||||
targetSdkVersion 23
|
||||
versionCode 492
|
||||
versionName "3.0.0"
|
||||
setProperty("archivesBaseName", "AffdexMe-$versionName-$versionCode")
|
||||
}
|
||||
buildTypes {
|
||||
|
@ -23,30 +23,25 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['native-libs']
|
||||
jniLibs.srcDirs = ['jniLibs']
|
||||
jni.srcDirs = [] //disable automatic ndk-build
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//gson is necessary for the license to be parsed
|
||||
compile 'com.google.code.gson:gson:2.3'
|
||||
|
||||
//include the Affdex SDK jars
|
||||
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')
|
||||
|
||||
//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.android.support:support-v4:22.2.0'
|
||||
//compile 'com.google.android.gms:play-services:7.5.0'
|
||||
//include the Affdex SDK jars and its dependencies
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
//include project dependencies
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
compile 'com.android.support:appcompat-v7:23.1.1'
|
||||
}
|
||||
|
||||
// build a signed release apk only if the environment is configured
|
||||
|
|
3
app/jniLibs/READ.ME
Normal file
|
@ -0,0 +1,3 @@
|
|||
Place the native library files here.
|
||||
|
||||
armeabi-v7a/libaffdexface_jni.so
|
6
app/libs/READ.ME
Normal file
|
@ -0,0 +1,6 @@
|
|||
Place the Affdex SDK JARs here.
|
||||
|
||||
Affdex-sdk.jar
|
||||
Affdex-sdk-javadoc.jar
|
||||
dagger-*.java
|
||||
javax.inject-1.jar
|
|
@ -1,30 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.affectiva.affdexme" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
android:targetSdkVersion="22" />
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.affectiva.affdexme">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.front"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name="com.affectiva.errorreporting.CustomApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/MainActivityTheme"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MainActivityTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
@ -38,17 +45,14 @@
|
|||
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">
|
||||
android:textAppearance="@android:style/TextAppearance.Large"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<intent-filter>
|
||||
<action android:name="com.affectiva.REPORT_ERROR" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
4
app/src/main/assets/Affdex/READ.ME
Normal file
|
@ -0,0 +1,4 @@
|
|||
Place license file and Classifiers folder here.
|
||||
|
||||
license.txt
|
||||
Classifiers/v_9/*
|
306
app/src/main/java/com/affectiva/affdexme/ImageHelper.java
Normal file
|
@ -0,0 +1,306 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.YuvImage;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.affectiva.android.affdex.sdk.Frame;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ImageHelper {
|
||||
|
||||
private static final String LOG_TAG = "AffdexMe";
|
||||
|
||||
// Prevent instantiation of this object
|
||||
private ImageHelper() {
|
||||
}
|
||||
|
||||
public static boolean checkIfImageFileExists(@NonNull final Context context, @NonNull final String fileName) {
|
||||
|
||||
// path to /data/data/yourapp/app_data/images
|
||||
File directory = context.getDir("images", Context.MODE_PRIVATE);
|
||||
|
||||
// File location to save image
|
||||
File imagePath = new File(directory, fileName);
|
||||
|
||||
return imagePath.exists();
|
||||
}
|
||||
|
||||
public static boolean deleteImageFile(@NonNull final Context context, @NonNull final String fileName) {
|
||||
// path to /data/data/yourapp/app_data/images
|
||||
File directory = context.getDir("images", Context.MODE_PRIVATE);
|
||||
|
||||
// File location to save image
|
||||
File imagePath = new File(directory, fileName);
|
||||
|
||||
return imagePath.delete();
|
||||
}
|
||||
|
||||
public static void resizeAndSaveResourceImageToInternalStorage(@NonNull final Context context, @NonNull final String fileName, @NonNull final String resourceName) throws FileNotFoundException {
|
||||
final int resourceId = context.getResources().getIdentifier(resourceName, "drawable", context.getPackageName());
|
||||
|
||||
if (resourceId == 0) {
|
||||
//unrecognised resource
|
||||
throw new FileNotFoundException("Resource not found for file named: " + resourceName);
|
||||
}
|
||||
resizeAndSaveResourceImageToInternalStorage(context, fileName, resourceId);
|
||||
}
|
||||
|
||||
public static void resizeAndSaveResourceImageToInternalStorage(@NonNull final Context context, @NonNull final String fileName, final int resourceId) {
|
||||
Resources resources = context.getResources();
|
||||
Bitmap sourceBitmap = BitmapFactory.decodeResource(resources, resourceId);
|
||||
Bitmap resizedBitmap = resizeBitmapForDeviceDensity(context, sourceBitmap);
|
||||
saveBitmapToInternalStorage(context, resizedBitmap, fileName);
|
||||
sourceBitmap.recycle();
|
||||
resizedBitmap.recycle();
|
||||
}
|
||||
|
||||
public static Bitmap resizeBitmapForDeviceDensity(@NonNull final Context context, @NonNull final Bitmap sourceBitmap) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
|
||||
int targetWidth = Math.round(sourceBitmap.getWidth() * metrics.density);
|
||||
int targetHeight = Math.round(sourceBitmap.getHeight() * metrics.density);
|
||||
|
||||
return Bitmap.createScaledBitmap(sourceBitmap, targetWidth, targetHeight, false);
|
||||
}
|
||||
|
||||
public static void saveBitmapToInternalStorage(@NonNull final Context context, @NonNull final Bitmap bitmapImage, @NonNull final String fileName) {
|
||||
|
||||
// path to /data/data/yourapp/app_data/images
|
||||
File directory = context.getDir("images", Context.MODE_PRIVATE);
|
||||
|
||||
// File location to save image
|
||||
File imagePath = new File(directory, fileName);
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(imagePath);
|
||||
|
||||
// Use the compress method on the BitMap object to write image to the OutputStream
|
||||
bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.flush();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Exception while trying to save file to internal storage: " + imagePath, e);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Exception while trying to flush the output stream", e);
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Exception wile trying to close file output stream.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap loadBitmapFromInternalStorage(@NonNull final Context applicationContext, @NonNull final String fileName) {
|
||||
|
||||
// path to /data/data/yourapp/app_data/images
|
||||
File directory = applicationContext.getDir("images", Context.MODE_PRIVATE);
|
||||
|
||||
// File location to save image
|
||||
File imagePath = new File(directory, fileName);
|
||||
|
||||
try {
|
||||
return BitmapFactory.decodeStream(new FileInputStream(imagePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Exception wile trying to load image: " + imagePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void preproccessImageIfNecessary(@NonNull final Context context, @NonNull final String fileName, @NonNull final String resourceName) {
|
||||
// Set this to true to force the app to always load the images for debugging purposes
|
||||
final boolean DEBUG = false;
|
||||
|
||||
if (ImageHelper.checkIfImageFileExists(context, fileName)) {
|
||||
// Image file already exists, no need to load the file again.
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "DEBUG: Deleting: " + fileName);
|
||||
ImageHelper.deleteImageFile(context, fileName);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ImageHelper.resizeAndSaveResourceImageToInternalStorage(context, fileName, resourceName);
|
||||
Log.d(LOG_TAG, "Resized and saved image: " + fileName);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Unable to process image: " + fileName, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitmap position inside an imageView.
|
||||
* Source: http://stackoverflow.com/a/26930938
|
||||
* Author: http://stackoverflow.com/users/1474079/chteuchteu
|
||||
*
|
||||
* @param imageView source ImageView
|
||||
* @return 0: left, 1: top, 2: width, 3: height
|
||||
*/
|
||||
public static int[] getBitmapPositionInsideImageView(@NonNull final ImageView imageView) {
|
||||
int[] ret = new int[4];
|
||||
|
||||
if (imageView.getDrawable() == null)
|
||||
return ret;
|
||||
|
||||
// Get image dimensions
|
||||
// Get image matrix values and place them in an array
|
||||
float[] f = new float[9];
|
||||
imageView.getImageMatrix().getValues(f);
|
||||
|
||||
// Extract the scale values using the constants (if aspect ratio maintained, scaleX == scaleY)
|
||||
final float scaleX = f[Matrix.MSCALE_X];
|
||||
final float scaleY = f[Matrix.MSCALE_Y];
|
||||
|
||||
// Get the drawable (could also get the bitmap behind the drawable and getWidth/getHeight)
|
||||
final Drawable d = imageView.getDrawable();
|
||||
final int origW = d.getIntrinsicWidth();
|
||||
final int origH = d.getIntrinsicHeight();
|
||||
|
||||
// Calculate the actual dimensions
|
||||
final int actW = Math.round(origW * scaleX);
|
||||
final int actH = Math.round(origH * scaleY);
|
||||
|
||||
ret[2] = actW;
|
||||
ret[3] = actH;
|
||||
|
||||
// Get image position
|
||||
// We assume that the image is centered into ImageView
|
||||
int imgViewW = imageView.getWidth();
|
||||
int imgViewH = imageView.getHeight();
|
||||
|
||||
int top = (imgViewH - actH) / 2;
|
||||
int left = (imgViewW - actW) / 2;
|
||||
|
||||
ret[0] = left;
|
||||
ret[1] = top;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a HACK.
|
||||
* We need to update the Android SDK to make this process cleaner.
|
||||
* We should just be able to call frame.getBitmap() and have it return a bitmap no matter what type
|
||||
* of frame it is. If any conversion between file types needs to take place, it needs to happen
|
||||
* inside the SDK layer and put the onus on the developer to know how to convert between YUV and ARGB.
|
||||
* TODO: See above
|
||||
*
|
||||
* @param frame - The Frame containing the desired image
|
||||
* @return - The Bitmap representation of the image
|
||||
*/
|
||||
public static Bitmap getBitmapFromFrame(@NonNull final Frame frame) {
|
||||
Bitmap bitmap;
|
||||
|
||||
if (frame instanceof Frame.BitmapFrame) {
|
||||
bitmap = ((Frame.BitmapFrame) frame).getBitmap();
|
||||
} else { //frame is ByteArrayFrame
|
||||
switch (frame.getColorFormat()) {
|
||||
case RGBA:
|
||||
bitmap = getBitmapFromRGBFrame(frame);
|
||||
break;
|
||||
case YUV_NV21:
|
||||
bitmap = getBitmapFromYuvFrame(frame);
|
||||
break;
|
||||
case UNKNOWN_TYPE:
|
||||
default:
|
||||
Log.e(LOG_TAG, "Unable to get bitmap from unknown frame type");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitmap == null || frame.getTargetRotation().toDouble() == 0.0) {
|
||||
return bitmap;
|
||||
} else {
|
||||
return rotateBitmap(bitmap, (float) frame.getTargetRotation().toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap getBitmapFromRGBFrame(@NonNull final Frame frame) {
|
||||
byte[] pixels = ((Frame.ByteArrayFrame) frame).getByteArray();
|
||||
Bitmap bitmap = Bitmap.createBitmap(frame.getWidth(), frame.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(pixels));
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static Bitmap getBitmapFromYuvFrame(@NonNull final Frame frame) {
|
||||
byte[] pixels = ((Frame.ByteArrayFrame) frame).getByteArray();
|
||||
YuvImage yuvImage = new YuvImage(pixels, ImageFormat.NV21, frame.getWidth(), frame.getHeight(), null);
|
||||
return convertYuvImageToBitmap(yuvImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This conversion procedure is sloppy and may result in JPEG compression artifacts
|
||||
*
|
||||
* @param yuvImage - The YuvImage to convert
|
||||
* @return - The converted Bitmap
|
||||
*/
|
||||
public static Bitmap convertYuvImageToBitmap(@NonNull final YuvImage yuvImage) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, out);
|
||||
byte[] imageBytes = out.toByteArray();
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Exception while closing output stream", e);
|
||||
}
|
||||
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||
}
|
||||
|
||||
public static Bitmap rotateBitmap(@NonNull final Bitmap source, final float angle) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(angle);
|
||||
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
public static void saveBitmapToFileAsPng(@NonNull final Bitmap bitmap, @NonNull final File file) throws IOException {
|
||||
try {
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
|
||||
bitmap.recycle();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Unable to save bitmap to file: " + file.getPath() + "\n" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void addPngToGallery(@NonNull final Context context, @NonNull final File imageFile) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
|
||||
values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
|
||||
|
||||
context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,40 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
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.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.format.DateFormat;
|
||||
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 +42,20 @@ 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
@ -56,27 +77,32 @@ import com.affectiva.android.affdex.sdk.detector.Face;
|
|||
*
|
||||
* In order to use this project, you will need to:
|
||||
* - Obtain the SDK from Affectiva (visit http://www.affdex.com/mobile-sdk)
|
||||
* - Copy the SDK assets folder contents into this project's assets folder
|
||||
* - Copy the SDK assets folder contents into this project's assets folder under AffdexMe/app/src/main/assets
|
||||
* - Copy the contents of the SDK's libs folder into this project's libs folder under AffdexMe/app/lib
|
||||
* - Copy the armeabi-v7a folder (found in the SDK libs folder) into this project's jniLibs folder under AffdexMe/app/src/main/jniLibs
|
||||
* - Add your license file to the /assets/Affdex folder and rename to license.txt.
|
||||
* - Add your license file to the assets/Affdex folder and rename to license.txt.
|
||||
* (Note: if you name the license file something else you will need to update the licensePath in the initializeCameraDetector() method in MainActivity)
|
||||
* - Build the project
|
||||
* - Run the app on an Android device with a front-facing camera
|
||||
*
|
||||
* 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, DrawingView.DrawingThreadEventListener {
|
||||
|
||||
private static final String LOG_TAG = "Affectiva";
|
||||
public static final int MAX_SUPPORTED_FACES = 3;
|
||||
public static final boolean STORE_RAW_SCREENSHOTS = false; // setting to enable saving the raw images when taking screenshots
|
||||
public static final int NUM_METRICS_DISPLAYED = 6;
|
||||
|
||||
//Affectiva SDK Object
|
||||
private static final String LOG_TAG = "AffdexMe";
|
||||
private static final int CAMERA_PERMISSIONS_REQUEST = 42; //value is arbitrary (between 0 and 255)
|
||||
private static final int EXTERNAL_STORAGE_PERMISSIONS_REQUEST = 73;
|
||||
int cameraPreviewWidth = 0;
|
||||
int cameraPreviewHeight = 0;
|
||||
CameraDetector.CameraType cameraType;
|
||||
boolean mirrorPoints = false;
|
||||
private boolean cameraPermissionsAvailable = false;
|
||||
private boolean storagePermissionsAvailable = false;
|
||||
private CameraDetector detector = null;
|
||||
|
||||
//MetricsManager View UI Objects
|
||||
private RelativeLayout metricViewLayout;
|
||||
private LinearLayout leftMetricsLayout;
|
||||
private LinearLayout rightMetricsLayout;
|
||||
|
@ -86,48 +112,206 @@ 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;
|
||||
|
||||
//Application settings variables
|
||||
private int detectorProcessRate;
|
||||
private ImageButton screenshotButton;
|
||||
private Frame mostRecentFrame;
|
||||
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;
|
||||
private boolean multiFaceModeEnabled = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //To maximize UI space, we declare our app to be full-screen
|
||||
preproccessMetricImages();
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
initializeUI();
|
||||
|
||||
checkForCameraPermissions();
|
||||
determineCameraAvailability();
|
||||
|
||||
initializeCameraDetector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only load the files onto disk the first time the app opens
|
||||
*/
|
||||
private void preproccessMetricImages() {
|
||||
Context context = getBaseContext();
|
||||
|
||||
for (Face.EMOJI emoji : Face.EMOJI.values()) {
|
||||
if (emoji.equals(Face.EMOJI.UNKNOWN)) {
|
||||
continue;
|
||||
}
|
||||
String emojiResourceName = emoji.name().trim().replace(' ', '_').toLowerCase(Locale.US).concat("_emoji");
|
||||
String emojiFileName = emojiResourceName + ".png";
|
||||
ImageHelper.preproccessImageIfNecessary(context, emojiFileName, emojiResourceName);
|
||||
}
|
||||
|
||||
ImageHelper.preproccessImageIfNecessary(context, "female_glasses.png", "female_glasses");
|
||||
ImageHelper.preproccessImageIfNecessary(context, "female_noglasses.png", "female_noglasses");
|
||||
ImageHelper.preproccessImageIfNecessary(context, "male_glasses.png", "male_glasses");
|
||||
ImageHelper.preproccessImageIfNecessary(context, "male_noglasses.png", "male_noglasses");
|
||||
ImageHelper.preproccessImageIfNecessary(context, "unknown_glasses.png", "unknown_glasses");
|
||||
ImageHelper.preproccessImageIfNecessary(context, "unknown_noglasses.png", "unknown_noglasses");
|
||||
}
|
||||
|
||||
private void checkForCameraPermissions() {
|
||||
cameraPermissionsAvailable =
|
||||
ContextCompat.checkSelfPermission(
|
||||
getApplicationContext(),
|
||||
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
if (!cameraPermissionsAvailable) {
|
||||
|
||||
// Should we show an explanation?
|
||||
if (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(CAMERA_PERMISSIONS_REQUEST);
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestCameraPermissions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForStoragePermissions() {
|
||||
storagePermissionsAvailable =
|
||||
ContextCompat.checkSelfPermission(
|
||||
getApplicationContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
if (!storagePermissionsAvailable) {
|
||||
|
||||
// Should we show an explanation?
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
|
||||
// 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(EXTERNAL_STORAGE_PERMISSIONS_REQUEST);
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestStoragePermissions();
|
||||
}
|
||||
} else {
|
||||
takeScreenshot(screenshotButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestStoragePermissions() {
|
||||
if (!storagePermissionsAvailable) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
EXTERNAL_STORAGE_PERMISSIONS_REQUEST);
|
||||
|
||||
// EXTERNAL_STORAGE_PERMISSIONS_REQUEST is an app-defined int constant that must be between 0 and 255.
|
||||
// The callback method gets the result of the request.
|
||||
}
|
||||
}
|
||||
|
||||
private void requestCameraPermissions() {
|
||||
if (!cameraPermissionsAvailable) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
new String[]{Manifest.permission.CAMERA},
|
||||
CAMERA_PERMISSIONS_REQUEST);
|
||||
|
||||
// CAMERA_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 == CAMERA_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 (!cameraPermissionsAvailable) {
|
||||
permissionsUnavailableLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
permissionsUnavailableLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (requestCode == EXTERNAL_STORAGE_PERMISSIONS_REQUEST) {
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
String permission = permissions[i];
|
||||
int grantResult = grantResults[i];
|
||||
|
||||
if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
storagePermissionsAvailable = (grantResult == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
}
|
||||
|
||||
if (storagePermissionsAvailable) {
|
||||
// resume taking the screenshot
|
||||
takeScreenshot(screenshotButton);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showPermissionExplanationDialog(int requestCode) {
|
||||
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
|
||||
MainActivity.this);
|
||||
|
||||
// set title
|
||||
alertDialogBuilder.setTitle(getResources().getString(R.string.insufficient_permissions));
|
||||
|
||||
// set dialog message
|
||||
if (requestCode == CAMERA_PERMISSIONS_REQUEST) {
|
||||
alertDialogBuilder
|
||||
.setMessage(getResources().getString(R.string.permissions_camera_needed_explanation))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getResources().getString(R.string.understood), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
requestCameraPermissions();
|
||||
}
|
||||
});
|
||||
} else if (requestCode == EXTERNAL_STORAGE_PERMISSIONS_REQUEST) {
|
||||
alertDialogBuilder
|
||||
.setMessage(getResources().getString(R.string.permissions_storage_needed_explanation))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getResources().getString(R.string.understood), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
requestStoragePermissions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -145,22 +329,26 @@ public class MainActivity extends Activity
|
|||
notFoundTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
//TODO: change this to be taken from settings
|
||||
if (isBackFacingCameraDetected) {
|
||||
cameraType = CameraDetector.CameraType.CAMERA_BACK;
|
||||
mirrorPoints = false;
|
||||
}
|
||||
if (isFrontFacingCameraDetected) {
|
||||
//set default camera settings
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
//restore the camera type settings
|
||||
String cameraTypeName = sharedPreferences.getString("cameraType", CameraDetector.CameraType.CAMERA_FRONT.name());
|
||||
if (cameraTypeName.equals(CameraDetector.CameraType.CAMERA_FRONT.name())) {
|
||||
cameraType = CameraDetector.CameraType.CAMERA_FRONT;
|
||||
mirrorPoints = true;
|
||||
} else {
|
||||
cameraType = CameraDetector.CameraType.CAMERA_BACK;
|
||||
mirrorPoints = false;
|
||||
}
|
||||
}
|
||||
|
||||
void initializeUI() {
|
||||
|
||||
//Get handles to UI objects
|
||||
activityLayout = (ViewGroup) findViewById(android.R.id.content);
|
||||
ViewGroup 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);
|
||||
|
@ -171,8 +359,10 @@ public class MainActivity extends Activity
|
|||
drawingView = (DrawingView) findViewById(R.id.drawing_view);
|
||||
settingsButton = (ImageButton) findViewById(R.id.settings_button);
|
||||
cameraButton = (ImageButton) findViewById(R.id.camera_button);
|
||||
screenshotButton = (ImageButton) findViewById(R.id.screenshot_button);
|
||||
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
|
||||
pleaseWaitTextView = (TextView) findViewById(R.id.please_wait_textview);
|
||||
Button retryPermissionsButton = (Button) findViewById(R.id.retryPermissionsButton);
|
||||
|
||||
//Initialize views to display metrics
|
||||
metricNames = new TextView[NUM_METRICS_DISPLAYED];
|
||||
|
@ -219,6 +409,9 @@ public class MainActivity extends Activity
|
|||
//Attach event listeners to the menu and edit box
|
||||
activityLayout.setOnTouchListener(this);
|
||||
|
||||
//Attach event listerner to drawing view
|
||||
drawingView.setEventListener(this);
|
||||
|
||||
/*
|
||||
* This app sets the View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag. Unfortunately, this flag causes
|
||||
* Android to steal the first touch event after the navigation bar has been hidden, a touch event
|
||||
|
@ -229,12 +422,19 @@ public class MainActivity extends Activity
|
|||
activityLayout.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int uiCode) {
|
||||
if ((uiCode == 0) && (isMenuVisible == false)) {
|
||||
if ((uiCode == 0) && (!isMenuVisible)) {
|
||||
setMenuVisible(true);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
retryPermissionsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
requestCameraPermissions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initializeCameraDetector() {
|
||||
|
@ -242,8 +442,7 @@ public class MainActivity extends Activity
|
|||
* the camera. If a SurfaceView is passed in as the last argument to the constructor,
|
||||
* that view will be painted with what the camera sees.
|
||||
*/
|
||||
|
||||
detector = new CameraDetector(this, CameraDetector.CameraType.CAMERA_FRONT, cameraView);
|
||||
detector = new CameraDetector(this, cameraType, cameraView, (multiFaceModeEnabled ? MAX_SUPPORTED_FACES : 1), Detector.FaceDetectorMode.LARGE_FACES);
|
||||
|
||||
// update the license path here if you name your file something else
|
||||
detector.setLicensePath("license.txt");
|
||||
|
@ -258,42 +457,85 @@ public class MainActivity extends Activity
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
checkForCameraPermissions();
|
||||
restoreApplicationSettings();
|
||||
setMenuVisible(true);
|
||||
isMenuShowingForFirstTime = true;
|
||||
}
|
||||
|
||||
private void setMultiFaceModeEnabled(boolean isEnabled) {
|
||||
|
||||
//if setting change is necessary
|
||||
if (isEnabled != multiFaceModeEnabled) {
|
||||
// change the setting, stop the detector, and reinitialize it to change the setting
|
||||
multiFaceModeEnabled = isEnabled;
|
||||
stopDetector();
|
||||
initializeCameraDetector();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We use the Shared Preferences object to restore application settings.
|
||||
*/
|
||||
public void restoreApplicationSettings() {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
//restore camera processing rate
|
||||
detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
|
||||
detector.setMaxProcessRate(detectorProcessRate);
|
||||
//restore the camera type settings
|
||||
String cameraTypeName = sharedPreferences.getString("cameraType", CameraDetector.CameraType.CAMERA_FRONT.name());
|
||||
if (cameraTypeName.equals(CameraDetector.CameraType.CAMERA_FRONT.name())) {
|
||||
setCameraType(CameraDetector.CameraType.CAMERA_FRONT);
|
||||
} else {
|
||||
setCameraType(CameraDetector.CameraType.CAMERA_BACK);
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("fps",isFPSVisible)) { //restore isFPSMetricVisible
|
||||
//restore the multiface mode setting to reset the detector if necessary
|
||||
if (sharedPreferences.getBoolean("multiface", false)) { // default to false
|
||||
setMultiFaceModeEnabled(true);
|
||||
} else {
|
||||
setMultiFaceModeEnabled(false);
|
||||
}
|
||||
|
||||
//restore camera processing rate
|
||||
int detectorProcessRate = PreferencesUtils.getFrameProcessingRate(sharedPreferences);
|
||||
detector.setMaxProcessRate(detectorProcessRate);
|
||||
drawingView.invalidateDimensions();
|
||||
|
||||
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
|
||||
setShowMeasurements(true);
|
||||
if (sharedPreferences.getBoolean("appearance", drawingView.getDrawAppearanceMarkersEnabled())) {
|
||||
detector.setDetectAllAppearance(true);
|
||||
setShowAppearance(true);
|
||||
} else {
|
||||
setShowMeasurements(false);
|
||||
detector.setDetectAllAppearance(false);
|
||||
setShowAppearance(false);
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("emoji", drawingView.getDrawEmojiMarkersEnabled())) {
|
||||
detector.setDetectAllEmojis(true);
|
||||
setShowEmoji(true);
|
||||
} else {
|
||||
detector.setDetectAllEmojis(false);
|
||||
setShowEmoji(false);
|
||||
}
|
||||
|
||||
//populate metric displays
|
||||
for (int n = 0; n < NUM_METRICS_DISPLAYED; n++) {
|
||||
activateMetric(n,PreferencesUtils.getMetricFromPrefs(sharedPreferences, n));
|
||||
activateMetric(n, PreferencesUtils.getMetricFromPrefs(sharedPreferences, n));
|
||||
}
|
||||
|
||||
//if we are in multiface mode, we need to enable the detection of all emotions
|
||||
if (multiFaceModeEnabled) {
|
||||
detector.setDetectAllEmotions(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,15 +546,15 @@ public class MainActivity extends Activity
|
|||
* -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);
|
||||
try {
|
||||
switch (metric.getType()) {
|
||||
case Emotion:
|
||||
Detector.class.getMethod("setDetect" + MetricsManager.getCamelCase(metric), boolean.class).invoke(detector, true);
|
||||
metricNames[index].setText(MetricsManager.getUpperCaseName(metric));
|
||||
getFaceScoreMethod = Face.Emotions.class.getMethod("get" + MetricsManager.getCamelCase(metric));
|
||||
|
||||
//The MetricDisplay for Valence is unique; it shades it color depending on the metric value
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
|
@ -320,11 +562,27 @@ public class MainActivity extends Activity
|
|||
} else {
|
||||
metricDisplays[index].setIsShadedMetricView(false);
|
||||
}
|
||||
} else if (metric.getType() == MetricsManager.MetricType.Expression) {
|
||||
getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric),null);
|
||||
break;
|
||||
case Expression:
|
||||
Detector.class.getMethod("setDetect" + MetricsManager.getCamelCase(metric), boolean.class).invoke(detector, true);
|
||||
metricNames[index].setText(MetricsManager.getUpperCaseName(metric));
|
||||
getFaceScoreMethod = Face.Expressions.class.getMethod("get" + MetricsManager.getCamelCase(metric));
|
||||
break;
|
||||
case Emoji:
|
||||
detector.setDetectAllEmojis(true);
|
||||
MetricsManager.Emojis emoji = ((MetricsManager.Emojis) metric);
|
||||
String metricTitle = emoji.getDisplayName(); // + " " + emoji.getUnicodeForEmoji();
|
||||
metricNames[index].setText(metricTitle);
|
||||
Log.d(LOG_TAG, "Getter Method: " + "get" + MetricsManager.getCamelCase(metric));
|
||||
getFaceScoreMethod = Face.Emojis.class.getMethod("get" + MetricsManager.getCamelCase(metric));
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG,String.format("Error using reflection to generate methods for %s",metric.toString()));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(LOG_TAG, String.format("No such method while using reflection to generate methods for %s", metric.toString()), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(LOG_TAG, String.format("Invocation error while using reflection to generate methods for %s", metric.toString()), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(LOG_TAG, String.format("Illegal access error while using reflection to generate methods for %s", metric.toString()), e);
|
||||
}
|
||||
|
||||
metricDisplays[index].setMetricToDisplay(metric, getFaceScoreMethod);
|
||||
|
@ -332,7 +590,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;
|
||||
|
@ -358,6 +616,12 @@ public class MainActivity extends Activity
|
|||
|
||||
void mainWindowResumedTasks() {
|
||||
|
||||
//Notify the user that they can't use the app without authorizing these permissions.
|
||||
if (!cameraPermissionsAvailable) {
|
||||
permissionsUnavailableLayout.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
startDetector();
|
||||
|
||||
if (!drawingView.isDimensionsNeeded()) {
|
||||
|
@ -388,8 +652,6 @@ public class MainActivity extends Activity
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onFaceDetectionStarted() {
|
||||
leftMetricsLayout.animate().alpha(1); //make left and right metrics appear
|
||||
|
@ -414,6 +676,8 @@ public class MainActivity extends Activity
|
|||
*/
|
||||
@Override
|
||||
public void onImageResults(List<Face> faces, Frame image, float timeStamp) {
|
||||
mostRecentFrame = image;
|
||||
|
||||
//If the faces object is null, we received an unprocessed frame
|
||||
if (faces == null) {
|
||||
return;
|
||||
|
@ -423,31 +687,131 @@ public class MainActivity extends Activity
|
|||
performFPSCalculations();
|
||||
|
||||
//If faces.size() is 0, we received a frame in which no face was detected
|
||||
if (faces.size() == 0) {
|
||||
drawingView.updatePoints(null, mirrorPoints); //the drawingView takes null points to mean it doesn't have to draw anything
|
||||
return;
|
||||
}
|
||||
|
||||
//The SDK currently detects one face at a time, so we recover it using .get(0).
|
||||
//'0' indicates we are recovering the first face.
|
||||
Face face = faces.get(0);
|
||||
if (faces.size() <= 0) {
|
||||
drawingView.invalidatePoints();
|
||||
} else if (faces.size() == 1) {
|
||||
metricViewLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
//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, faces.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has selected to have facial tracking dots or measurements drawn, we use face.getFacePoints() to send those points
|
||||
* If the user has selected to have any facial attributes 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.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);
|
||||
if (drawingView.getDrawPointsEnabled() || drawingView.getDrawAppearanceMarkersEnabled() || drawingView.getDrawEmojiMarkersEnabled()) {
|
||||
drawingView.updatePoints(faces, mirrorPoints);
|
||||
}
|
||||
|
||||
} else {
|
||||
// metrics overlay is hidden in multi face mode
|
||||
metricViewLayout.setVisibility(View.GONE);
|
||||
|
||||
// always update points in multi face mode
|
||||
drawingView.updatePoints(faces, mirrorPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public void takeScreenshot(View view) {
|
||||
// Check the permissions to see if we are allowed to save the screenshot
|
||||
if (!storagePermissionsAvailable) {
|
||||
checkForStoragePermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
drawingView.requestBitmap();
|
||||
|
||||
/**
|
||||
* A screenshot of the drawing view is generated and processing continues via the callback
|
||||
* onBitmapGenerated() which calls processScreenshot().
|
||||
*/
|
||||
}
|
||||
|
||||
private void processScreenshot(Bitmap drawingViewBitmap, boolean alsoSaveRaw) {
|
||||
if (mostRecentFrame == null) {
|
||||
Toast.makeText(getApplicationContext(), "No frame detected, aborting screenshot", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!storagePermissionsAvailable) {
|
||||
checkForStoragePermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap faceBitmap = ImageHelper.getBitmapFromFrame(mostRecentFrame);
|
||||
|
||||
if (faceBitmap == null) {
|
||||
Log.e(LOG_TAG, "Unable to generate bitmap for frame, aborting screenshot");
|
||||
return;
|
||||
}
|
||||
|
||||
metricViewLayout.setDrawingCacheEnabled(true);
|
||||
Bitmap metricsBitmap = Bitmap.createBitmap(metricViewLayout.getDrawingCache());
|
||||
metricViewLayout.setDrawingCacheEnabled(false);
|
||||
|
||||
Bitmap finalScreenshot = Bitmap.createBitmap(faceBitmap.getWidth(), faceBitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(finalScreenshot);
|
||||
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
canvas.drawBitmap(faceBitmap, 0, 0, paint);
|
||||
|
||||
float scaleFactor = ((float) faceBitmap.getWidth()) / ((float) drawingViewBitmap.getWidth());
|
||||
int scaledHeight = Math.round(drawingViewBitmap.getHeight() * scaleFactor);
|
||||
canvas.drawBitmap(drawingViewBitmap, null, new Rect(0, 0, faceBitmap.getWidth(), scaledHeight), paint);
|
||||
|
||||
scaleFactor = ((float) faceBitmap.getWidth()) / ((float) metricsBitmap.getWidth());
|
||||
scaledHeight = Math.round(metricsBitmap.getHeight() * scaleFactor);
|
||||
canvas.drawBitmap(metricsBitmap, null, new Rect(0, 0, faceBitmap.getWidth(), scaledHeight), paint);
|
||||
|
||||
metricsBitmap.recycle();
|
||||
drawingViewBitmap.recycle();
|
||||
|
||||
Date now = new Date();
|
||||
String timestamp = DateFormat.format("yyyy-MM-dd_hh-mm-ss", now).toString();
|
||||
File pictureFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "AffdexMe");
|
||||
if (!pictureFolder.exists()) {
|
||||
if (!pictureFolder.mkdir()) {
|
||||
Log.e(LOG_TAG, "Unable to create directory: " + pictureFolder.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String screenshotFileName = timestamp + ".png";
|
||||
File screenshotFile = new File(pictureFolder, screenshotFileName);
|
||||
|
||||
try {
|
||||
ImageHelper.saveBitmapToFileAsPng(finalScreenshot, screenshotFile);
|
||||
} catch (IOException e) {
|
||||
String msg = "Unable to save screenshot";
|
||||
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
Log.e(LOG_TAG, msg, e);
|
||||
return;
|
||||
}
|
||||
ImageHelper.addPngToGallery(getApplicationContext(), screenshotFile);
|
||||
|
||||
if (alsoSaveRaw) {
|
||||
String rawScreenshotFileName = timestamp + "_raw.png";
|
||||
File rawScreenshotFile = new File(pictureFolder, rawScreenshotFileName);
|
||||
|
||||
try {
|
||||
ImageHelper.saveBitmapToFileAsPng(faceBitmap, rawScreenshotFile);
|
||||
} catch (IOException e) {
|
||||
String msg = "Unable to save screenshot";
|
||||
Log.e(LOG_TAG, msg, e);
|
||||
}
|
||||
ImageHelper.addPngToGallery(getApplicationContext(), rawScreenshotFile);
|
||||
}
|
||||
|
||||
faceBitmap.recycle();
|
||||
finalScreenshot.recycle();
|
||||
|
||||
String fileSavedMessage = "Screenshot saved to: " + screenshotFile.getPath();
|
||||
Toast.makeText(getApplicationContext(), fileSavedMessage, Toast.LENGTH_SHORT).show();
|
||||
Log.d(LOG_TAG, fileSavedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the method that we saved in activateMetric() to get the metric score and display it
|
||||
|
@ -458,14 +822,21 @@ public class MainActivity extends Activity
|
|||
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);
|
||||
switch (metric.getType()) {
|
||||
case Emotion:
|
||||
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emotions);
|
||||
break;
|
||||
case Expression:
|
||||
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.expressions);
|
||||
break;
|
||||
case Emoji:
|
||||
score = (Float) metricDisplay.getFaceScoreMethod().invoke(face.emojis);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown Metric Type: " + metric.getType().toString());
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
@ -481,8 +852,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;
|
||||
}
|
||||
}
|
||||
|
@ -507,33 +878,34 @@ public class MainActivity extends Activity
|
|||
try {
|
||||
detector.stop();
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG,e.getMessage());
|
||||
Log.e(LOG_TAG, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
detector.setDetectAllEmotions(false);
|
||||
detector.setDetectAllExpressions(false);
|
||||
detector.setDetectAllAppearance(false);
|
||||
detector.setDetectAllEmojis(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
settingsButton.setVisibility(View.VISIBLE);
|
||||
cameraButton.setVisibility(View.VISIBLE);
|
||||
screenshotButton.setVisibility(View.VISIBLE);
|
||||
|
||||
//We display the navigation bar again
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
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(
|
||||
|
@ -541,12 +913,10 @@ public class MainActivity extends Activity
|
|||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE);
|
||||
|
||||
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN);
|
||||
settingsButton.setVisibility(View.INVISIBLE);
|
||||
cameraButton.setVisibility(View.INVISIBLE);
|
||||
screenshotButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,10 +938,15 @@ public class MainActivity extends Activity
|
|||
drawingView.setDrawPointsEnabled(b);
|
||||
}
|
||||
|
||||
void setShowMeasurements(boolean b) {
|
||||
drawingView.setDrawMeasurementsEnabled(b);
|
||||
void setShowAppearance(boolean b) {
|
||||
drawingView.setDrawAppearanceMarkersEnabled(b);
|
||||
}
|
||||
|
||||
void setShowEmoji(boolean b) {
|
||||
drawingView.setDrawEmojiMarkersEnabled(b);
|
||||
}
|
||||
|
||||
|
||||
void setFPSVisible(boolean b) {
|
||||
isFPSVisible = b;
|
||||
if (b) {
|
||||
|
@ -595,14 +970,7 @@ public class MainActivity extends Activity
|
|||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
}
|
||||
|
||||
/* onCameraStarted is a feature of SDK 2.02, commenting out for 2.01
|
||||
@Override
|
||||
public void onCameraStarted(boolean b, Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
Toast.makeText(this,"Failed to start camera.",Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}*/
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
@Override
|
||||
public void onCameraSizeSelected(int cameraWidth, int cameraHeight, ROTATE rotation) {
|
||||
if (rotation == ROTATE.BY_90_CCW || rotation == ROTATE.BY_90_CW) {
|
||||
|
@ -612,7 +980,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
|
||||
|
@ -626,21 +994,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;
|
||||
|
@ -654,32 +1022,54 @@ public class MainActivity extends Activity
|
|||
|
||||
}
|
||||
|
||||
|
||||
public void camera_button_click(View view) {
|
||||
if (cameraType == CameraDetector.CameraType.CAMERA_FRONT) {
|
||||
//Toggle the camera setting
|
||||
setCameraType(cameraType == CameraDetector.CameraType.CAMERA_FRONT ? CameraDetector.CameraType.CAMERA_BACK : CameraDetector.CameraType.CAMERA_FRONT);
|
||||
}
|
||||
|
||||
private void setCameraType(CameraDetector.CameraType type) {
|
||||
SharedPreferences.Editor preferencesEditor = PreferenceManager.getDefaultSharedPreferences(this).edit();
|
||||
|
||||
//If a settings change is necessary
|
||||
if (cameraType != type) {
|
||||
switch (type) {
|
||||
case CAMERA_BACK:
|
||||
if (isBackFacingCameraDetected) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
} else if (cameraType == CameraDetector.CameraType.CAMERA_BACK) {
|
||||
break;
|
||||
case CAMERA_FRONT:
|
||||
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();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.e(LOG_TAG, "Unknown camera type selected");
|
||||
}
|
||||
|
||||
performFaceDetectionStoppedTasks();
|
||||
|
||||
try {
|
||||
detector.setCameraType(cameraType);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG,e.getMessage());
|
||||
preferencesEditor.putString("cameraType", cameraType.name());
|
||||
preferencesEditor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapGenerated(@NonNull final Bitmap bitmap) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
processScreenshot(bitmap, STORE_RAW_SCREENSHOTS);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -34,15 +39,17 @@ public class MetricDisplay extends View {
|
|||
|
||||
public MetricDisplay(Context context) {
|
||||
super(context);
|
||||
initResources(context,null);
|
||||
initResources(context, null);
|
||||
}
|
||||
|
||||
public MetricDisplay(Context context, AttributeSet attrs) {
|
||||
super(context,attrs);
|
||||
initResources(context,attrs);
|
||||
super(context, attrs);
|
||||
initResources(context, attrs);
|
||||
}
|
||||
public MetricDisplay(Context context, AttributeSet attrs, int styleID){
|
||||
|
||||
public MetricDisplay(Context context, AttributeSet attrs, int styleID) {
|
||||
super(context, attrs, styleID);
|
||||
initResources(context,attrs);
|
||||
initResources(context, attrs);
|
||||
}
|
||||
|
||||
void setIsShadedMetricView(boolean b) {
|
||||
|
@ -65,11 +72,11 @@ public class MetricDisplay extends View {
|
|||
|
||||
//load and parse XML attributes
|
||||
if (attrs != null) {
|
||||
TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.custom_attributes,0,0);
|
||||
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.custom_attributes, 0, 0);
|
||||
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);
|
||||
|
@ -83,7 +90,6 @@ public class MetricDisplay extends View {
|
|||
*/
|
||||
height = textSize;
|
||||
textBottom = height - 5;
|
||||
|
||||
}
|
||||
|
||||
public void setMetricToDisplay(MetricsManager.Metrics metricToDisplay, Method faceScoreMethod) {
|
||||
|
@ -103,7 +109,7 @@ public class MetricDisplay extends View {
|
|||
textPaint.setTypeface(face);
|
||||
}
|
||||
|
||||
public void setScore(float s){
|
||||
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
|
||||
|
@ -117,11 +123,11 @@ public class MetricDisplay extends View {
|
|||
right = midX + (halfWidth * (-s / 100));
|
||||
}
|
||||
if (s > 0) {
|
||||
float colorScore = ((100f-s)/100f)*255;
|
||||
boxPaint.setColor(Color.rgb((int)colorScore,255,(int)colorScore));
|
||||
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));
|
||||
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
|
||||
|
@ -133,29 +139,27 @@ public class MetricDisplay extends View {
|
|||
|
||||
/**
|
||||
* set our view to be the minimum of the sizes that Android will allow and our desired sizes
|
||||
* **/
|
||||
**/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension((int)Math.min(MeasureSpec.getSize(widthMeasureSpec), halfWidth *2), (int)Math.min(MeasureSpec.getSize(heightMeasureSpec),height));
|
||||
setMeasuredDimension((int) Math.min(MeasureSpec.getSize(widthMeasureSpec), halfWidth * 2), (int) Math.min(MeasureSpec.getSize(heightMeasureSpec), height));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w,h,oldW,oldH);
|
||||
midX = w/2;
|
||||
midY = h/2;
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
midX = w / 2;
|
||||
midY = h / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
//draws the colored bar that appears behind our score
|
||||
canvas.drawRect(left,top,right,height, boxPaint);
|
||||
canvas.drawRect(left, top, right, height, boxPaint);
|
||||
//draws the score
|
||||
canvas.drawText(text,midX , textBottom, textPaint);
|
||||
canvas.drawText(text, midX, textBottom, textPaint);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
|
@ -10,6 +15,7 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
|
@ -24,13 +30,13 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
|
||||
import static com.affectiva.affdexme.MainActivity.NUM_METRICS_DISPLAYED;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
final static String LOG_TAG = "AffdexMe";
|
||||
|
||||
int numberOfSelectedItems = 0;
|
||||
|
||||
|
@ -84,13 +90,11 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
}
|
||||
);
|
||||
|
||||
Resources res = getResources();
|
||||
messageAtOrUnderLimitColor = res.getColor(R.color.white);
|
||||
messageOverLimitColor = res.getColor(R.color.red);
|
||||
messageAtOrUnderLimitColor = ContextCompat.getColor(getActivity(), R.color.white);
|
||||
messageOverLimitColor = ContextCompat.getColor(getActivity(), 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)
|
||||
|
@ -110,15 +114,15 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
|
||||
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);
|
||||
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);
|
||||
selectItem(metricSelectors.get(chosenMetric), true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +211,7 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
Log.e(LOG_TAG, "Desired Column Width too large! Unable to populate Grid");
|
||||
return;
|
||||
}
|
||||
int columnWidth = (int)((float) gridWidth / (float)numColumns);
|
||||
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.
|
||||
|
@ -218,6 +222,10 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
addHeader("Expressions", currentRow, numColumns, inflater);
|
||||
addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Expressions.values());
|
||||
|
||||
// If you wanted to add Emoji as selectable metrics, you would uncomment the two lines below
|
||||
// addHeader("Emoji", currentRow, numColumns, inflater);
|
||||
// addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Emojis.values());
|
||||
|
||||
gridLayout.setColumnCount(numColumns);
|
||||
gridLayout.setRowCount(currentRow.value);
|
||||
}
|
||||
|
@ -253,7 +261,7 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
}
|
||||
|
||||
MetricSelector item = metricSelectors.get(metric);
|
||||
|
||||
if (item != null) {
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||
params.width = size;
|
||||
params.height = size;
|
||||
|
@ -263,8 +271,11 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
|
||||
item.setOnClickListener(this);
|
||||
gridLayout.addView(item);
|
||||
} else {
|
||||
Log.e(LOG_TAG, "Unknown MetricSelector item for Metric: " + metric.toString());
|
||||
}
|
||||
currentRow.value +=1; //point currentRow to row where next views should be added
|
||||
}
|
||||
currentRow.value += 1; //point currentRow to row where next views should be added
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -294,18 +305,10 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
}
|
||||
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));
|
||||
metricChooserTextView.setText(String.format("%d metrics chosen.", numberOfSelectedItems));
|
||||
}
|
||||
|
||||
if (numberOfSelectedItems <= NUM_METRICS_DISPLAYED) {
|
||||
|
@ -313,25 +316,11 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
} 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);
|
||||
selectItem(metricSelectors.get(metric), false, true);
|
||||
}
|
||||
updateAllGridItems();
|
||||
}
|
||||
|
@ -349,18 +338,29 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
fragmentMediaPlayer.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
//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.
|
||||
|
@ -429,7 +429,7 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
|
||||
textureView = new TextureView(getActivity());
|
||||
textureView.setVisibility(View.GONE);
|
||||
textureView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
textureView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
|
@ -457,9 +457,12 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
private void startVideoPlayback(MetricSelector metricSelector) {
|
||||
videoPlayingSelector = metricSelector;
|
||||
videoPlayingSelector.initIndex();
|
||||
safePlayer.setDataSource(metricSelector.getNextVideoResourceURI());
|
||||
Uri videoUri = metricSelector.getNextVideoResourceURI();
|
||||
if (videoUri != null) {
|
||||
safePlayer.setDataSource(videoUri);
|
||||
metricSelector.displayVideo(textureView); //will cause onSurfaceTextureAvailable to fire
|
||||
}
|
||||
}
|
||||
|
||||
private void endVideoPlayback() {
|
||||
videoPlayingSelector.displayCover();
|
||||
|
@ -491,18 +494,6 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -595,5 +586,3 @@ public class MetricSelectionFragment extends Fragment implements View.OnClickLis
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
|
@ -19,25 +28,35 @@ import android.widget.TextView;
|
|||
*/
|
||||
public class MetricSelector extends FrameLayout {
|
||||
|
||||
private boolean isMetricSelected;
|
||||
private MetricsManager.Metrics metric;
|
||||
|
||||
TextureView textureView;
|
||||
|
||||
TextView gridItemTextView;
|
||||
ImageView imageView;
|
||||
ImageView imageViewBeneath;
|
||||
FrameLayout videoHolder;
|
||||
RelativeLayout backgroundLayout;
|
||||
|
||||
int itemNotSelectedColor;
|
||||
int itemSelectedColor;
|
||||
int itemSelectedOverLimitColor;
|
||||
|
||||
Uri[] videoResourceURIs;
|
||||
int videoResourceURIIndex;
|
||||
TextView videoOverlay;
|
||||
|
||||
int picId;
|
||||
private boolean isMetricSelected;
|
||||
private boolean isEmoji;
|
||||
private MetricsManager.Metrics metric;
|
||||
|
||||
// These three constructors only provided to allow the UI Editor to properly render this element
|
||||
public MetricSelector(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MetricSelector(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public MetricSelector(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public MetricSelector(Activity hostActivity, LayoutInflater inflater, Resources res, String packageName, MetricsManager.Metrics metric) {
|
||||
super(hostActivity);
|
||||
|
@ -45,6 +64,10 @@ public class MetricSelector extends FrameLayout {
|
|||
this.metric = metric;
|
||||
this.isMetricSelected = false;
|
||||
|
||||
if (metric.getType().equals(MetricsManager.MetricType.Emoji)) {
|
||||
this.isEmoji = true;
|
||||
}
|
||||
|
||||
initContent(inflater, res, packageName);
|
||||
}
|
||||
|
||||
|
@ -55,11 +78,13 @@ public class MetricSelector extends FrameLayout {
|
|||
|
||||
videoOverlay = (TextView) content.findViewById(R.id.video_overlay);
|
||||
|
||||
int videoId = res.getIdentifier(resourceName,"raw",packageName);
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
int videoId = res.getIdentifier(resourceName, "raw", packageName);
|
||||
if (isEmoji) {
|
||||
videoResourceURIs = null;
|
||||
} else 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)));
|
||||
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));
|
||||
|
@ -68,6 +93,9 @@ public class MetricSelector extends FrameLayout {
|
|||
videoResourceURIIndex = 0;
|
||||
|
||||
//set up image
|
||||
if (isEmoji) {
|
||||
resourceName += "_emoji";
|
||||
}
|
||||
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);
|
||||
|
@ -75,14 +103,15 @@ public class MetricSelector extends FrameLayout {
|
|||
imageViewBeneath.setImageResource(picId);
|
||||
imageViewBeneath.setVisibility(GONE);
|
||||
|
||||
videoHolder = (FrameLayout) content.findViewById(R.id.video_holder);
|
||||
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);
|
||||
itemSelectedOverLimitColor = ContextCompat.getColor(getContext(), R.color.grid_item_chosen_over_limit);
|
||||
itemNotSelectedColor = ContextCompat.getColor(getContext(), R.color.grid_item_not_chosen);
|
||||
itemSelectedColor = ContextCompat.getColor(getContext(), R.color.grid_item_chosen);
|
||||
}
|
||||
|
||||
boolean getIsSelected() {
|
||||
|
@ -105,7 +134,15 @@ public class MetricSelector extends FrameLayout {
|
|||
|
||||
void displayVideo(TextureView videoView) {
|
||||
textureView = videoView;
|
||||
backgroundLayout.addView(textureView, 1);
|
||||
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(textureView.getLayoutParams());
|
||||
|
||||
// set the video to the same height and width of the actual bitmap inside the imageview
|
||||
int[] imageAttr = ImageHelper.getBitmapPositionInsideImageView(imageView);
|
||||
params.width = imageAttr[2]; //width
|
||||
params.height = imageAttr[3]; //height
|
||||
|
||||
textureView.setLayoutParams(params);
|
||||
videoHolder.addView(textureView);
|
||||
textureView.setVisibility(VISIBLE);
|
||||
videoOverlay.setVisibility(VISIBLE);
|
||||
}
|
||||
|
@ -113,7 +150,7 @@ public class MetricSelector extends FrameLayout {
|
|||
void removeVideo() {
|
||||
if (textureView != null) {
|
||||
textureView.setVisibility(GONE);
|
||||
backgroundLayout.removeView(textureView);
|
||||
videoHolder.removeView(textureView);
|
||||
textureView = null;
|
||||
}
|
||||
videoOverlay.setVisibility(GONE);
|
||||
|
@ -128,12 +165,15 @@ public class MetricSelector extends FrameLayout {
|
|||
}
|
||||
|
||||
Uri getNextVideoResourceURI() {
|
||||
if (isEmoji) {
|
||||
return null;
|
||||
}
|
||||
if (metric == MetricsManager.Emotions.VALENCE) {
|
||||
if (videoResourceURIIndex == 0) {
|
||||
videoOverlay.setText("NEGATIVE");
|
||||
videoOverlay.setText(R.string.negative);
|
||||
videoOverlay.setTextColor(Color.RED);
|
||||
} else {
|
||||
videoOverlay.setText("POSITIVE");
|
||||
videoOverlay.setText(R.string.positive);
|
||||
videoOverlay.setTextColor(Color.GREEN);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +203,4 @@ public class MetricSelector extends FrameLayout {
|
|||
backgroundLayout.setBackgroundColor(itemNotSelectedColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import com.affectiva.android.affdex.sdk.detector.Face;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class containing:
|
||||
* -enumerations representing the Emotion and Expressions featured in the Affectiva SDK.
|
||||
|
@ -13,67 +22,23 @@ public class MetricsManager {
|
|||
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);
|
||||
Emojis[] emojis = Emojis.values();
|
||||
allMetrics = new Metrics[emotions.length + expressions.length + emojis.length];
|
||||
System.arraycopy(emotions, 0, allMetrics, 0, emotions.length);
|
||||
System.arraycopy(expressions, 0, allMetrics, emotions.length, expressions.length);
|
||||
System.arraycopy(emojis, 0, allMetrics, emotions.length + expressions.length, emojis.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 if (metric.getType().equals(MetricType.Emoji)) {
|
||||
return ((Emojis) metric).getDisplayName().toUpperCase(Locale.US);
|
||||
} else {
|
||||
return metric.toString().replace("_", " ");
|
||||
}
|
||||
|
@ -82,6 +47,9 @@ public class MetricsManager {
|
|||
//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.getType().equals(MetricType.Emoji)) {
|
||||
return ((Emojis) metric).getDisplayName();
|
||||
}
|
||||
if (metric == Expressions.LIP_CORNER_DEPRESSOR) {
|
||||
return "Frown";
|
||||
}
|
||||
|
@ -118,7 +86,7 @@ public class MetricsManager {
|
|||
builder.append(Character.toUpperCase(metricString.charAt(0)));
|
||||
|
||||
if (metricString.length() > 1) {
|
||||
for (int n = 1; n < metricString.length(); n++ ){
|
||||
for (int n = 1; n < metricString.length(); n++) {
|
||||
char c = metricString.charAt(n);
|
||||
if (c == '_') {
|
||||
n += 1;
|
||||
|
@ -134,5 +102,116 @@ public class MetricsManager {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
public enum MetricType {Emotion, Expression, Emoji}
|
||||
|
||||
public enum Emotions implements Metrics {
|
||||
ANGER,
|
||||
DISGUST,
|
||||
FEAR,
|
||||
JOY,
|
||||
SADNESS,
|
||||
SURPRISE,
|
||||
CONTEMPT,
|
||||
ENGAGEMENT,
|
||||
VALENCE;
|
||||
|
||||
@Override
|
||||
public MetricType getType() {
|
||||
return MetricType.Emotion;
|
||||
}
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Emojis implements Metrics {
|
||||
RELAXED("Relaxed"),
|
||||
SMILEY("Smiley"),
|
||||
LAUGHING("Laughing"),
|
||||
KISSING("Kiss"),
|
||||
DISAPPOINTED("Disappointed"),
|
||||
RAGE("Rage"),
|
||||
SMIRK("Smirk Emoji"),
|
||||
WINK("Wink"),
|
||||
STUCK_OUT_TONGUE_WINKING_EYE("Tongue Wink"),
|
||||
STUCK_OUT_TONGUE("Tongue Out"),
|
||||
FLUSHED("Flushed"),
|
||||
SCREAM("Scream");
|
||||
|
||||
private String displayName;
|
||||
|
||||
Emojis(String name) {
|
||||
displayName = name;
|
||||
}
|
||||
|
||||
public static Emojis getEnum(String value) {
|
||||
for (Emojis v : values())
|
||||
if (v.displayName.equalsIgnoreCase(value)) return v;
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricType getType() {
|
||||
return MetricType.Emoji;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getUnicodeForEmoji() {
|
||||
switch (this) {
|
||||
case RELAXED:
|
||||
return Face.EMOJI.RELAXED.getUnicode();
|
||||
case SMILEY:
|
||||
return Face.EMOJI.SMILEY.getUnicode();
|
||||
case LAUGHING:
|
||||
return Face.EMOJI.LAUGHING.getUnicode();
|
||||
case KISSING:
|
||||
return Face.EMOJI.KISSING.getUnicode();
|
||||
case DISAPPOINTED:
|
||||
return Face.EMOJI.DISAPPOINTED.getUnicode();
|
||||
case RAGE:
|
||||
return Face.EMOJI.RAGE.getUnicode();
|
||||
case SMIRK:
|
||||
return Face.EMOJI.SMIRK.getUnicode();
|
||||
case WINK:
|
||||
return Face.EMOJI.WINK.getUnicode();
|
||||
case STUCK_OUT_TONGUE_WINKING_EYE:
|
||||
return Face.EMOJI.STUCK_OUT_TONGUE_WINKING_EYE.getUnicode();
|
||||
case STUCK_OUT_TONGUE:
|
||||
return Face.EMOJI.STUCK_OUT_TONGUE.getUnicode();
|
||||
case FLUSHED:
|
||||
return Face.EMOJI.FLUSHED.getUnicode();
|
||||
case SCREAM:
|
||||
return Face.EMOJI.SCREAM.getUnicode();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Metrics {
|
||||
MetricType getType();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A helper class to translate strings held in preferences into values to be used by the application.
|
||||
|
@ -8,6 +14,7 @@ import android.content.SharedPreferences;
|
|||
public class PreferencesUtils {
|
||||
|
||||
static final int DEFAULT_FPS = 20;
|
||||
private final static String LOG_TAG = "AffdexMe";
|
||||
|
||||
/**
|
||||
* Attempt to parse and return FPS set by user. If the FPS is invalid, we set it to be the default FPS.
|
||||
|
@ -18,40 +25,45 @@ public class PreferencesUtils {
|
|||
try {
|
||||
toReturn = Integer.parseInt(rateString);
|
||||
} catch (Exception e) {
|
||||
saveFrameProcessingRate(pref,DEFAULT_FPS);
|
||||
saveFrameProcessingRate(pref, DEFAULT_FPS);
|
||||
return DEFAULT_FPS;
|
||||
}
|
||||
if (toReturn > 0) {
|
||||
return toReturn;
|
||||
} else {
|
||||
saveFrameProcessingRate(pref,DEFAULT_FPS);
|
||||
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.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 );
|
||||
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.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) {
|
||||
public static void saveMetricToPrefs(SharedPreferences.Editor editor, int index, MetricsManager.Metrics metric) {
|
||||
if (metric.getType().equals(MetricsManager.MetricType.Emoji)) {
|
||||
MetricsManager.Emojis emoji = (MetricsManager.Emojis) metric;
|
||||
editor.putString(String.format("metric_display_%d", index), emoji.getDisplayName());
|
||||
} else {
|
||||
editor.putString(String.format("metric_display_%d", index), metric.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static private MetricsManager.Metrics defaultMetric(int index) {
|
||||
switch (index) {
|
||||
|
@ -73,23 +85,30 @@ public class PreferencesUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* We attempt to parse the string as an Emotion or, failing that, as an Expression.
|
||||
* We attempt to parse the string as any known metric.
|
||||
*/
|
||||
static MetricsManager.Metrics parseSavedMetric(String metricString) throws IllegalArgumentException{
|
||||
static MetricsManager.Metrics parseSavedMetric(String metricString) throws IllegalArgumentException {
|
||||
try {
|
||||
MetricsManager.Emotions emotion;
|
||||
emotion = MetricsManager.Emotions.valueOf(metricString);
|
||||
return emotion;
|
||||
} catch (IllegalArgumentException emotionParseFailed) {
|
||||
Log.v(LOG_TAG, "Not an Emotion...");
|
||||
}
|
||||
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");
|
||||
Log.v(LOG_TAG, "Not an Expression...");
|
||||
}
|
||||
try {
|
||||
MetricsManager.Emojis emoji;
|
||||
emoji = MetricsManager.Emojis.getEnum(metricString);
|
||||
return emoji;
|
||||
} catch (IllegalArgumentException expressionParseFailed) {
|
||||
Log.v(LOG_TAG, "Not an Emoji...");
|
||||
}
|
||||
throw new IllegalArgumentException("String did not match any known metric");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.affdexme;
|
||||
|
||||
import android.app.ActionBar;
|
||||
|
@ -5,6 +10,7 @@ import android.graphics.drawable.ColorDrawable;
|
|||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -16,18 +22,10 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
public void onCreate(Bundle savedBundleInstance) {
|
||||
super.onCreate(savedBundleInstance);
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setIcon(
|
||||
new ColorDrawable(getResources().getColor(android.R.color.transparent)));
|
||||
//actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (actionBar != null) {
|
||||
actionBar.setIcon(new ColorDrawable(ContextCompat.getColor(getApplicationContext(), R.color.transparent_overlay)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
this.onBackPressed();
|
||||
return true;
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
@ -49,10 +47,7 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
//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);
|
||||
return SettingsFragment.class.getName().equals(fragmentName) || MetricSelectionFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
//This fragment shows the preferences for the first header.
|
||||
|
@ -65,7 +60,4 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
addPreferencesFromResource(R.xml.settings_preferences);
|
||||
}
|
||||
}
|
||||
|
||||
//The second fragment is defined in a separate file.
|
||||
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.errorreporting;
|
||||
|
||||
import android.app.Application;
|
||||
|
@ -5,28 +10,24 @@ import android.content.Intent;
|
|||
|
||||
public class CustomApplication extends Application {
|
||||
|
||||
static volatile boolean wasErrorActivityStarted = false;
|
||||
static final boolean enableCustomErrorMessage = false;
|
||||
static volatile boolean wasErrorActivityStarted = false;
|
||||
Thread.UncaughtExceptionHandler exceptionHandler;
|
||||
|
||||
@Override
|
||||
public void onCreate ()
|
||||
{
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
// Setup handler for uncaught exceptions.
|
||||
Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
|
||||
{
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException (Thread thread, Throwable e)
|
||||
{
|
||||
handleUncaughtException (thread, e);
|
||||
public void uncaughtException(Thread thread, Throwable e) {
|
||||
handleUncaughtException(thread, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void handleUncaughtException (Thread thread, Throwable e)
|
||||
{
|
||||
public void handleUncaughtException(Thread thread, Throwable e) {
|
||||
if (!wasErrorActivityStarted && enableCustomErrorMessage) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("com.affectiva.REPORT_ERROR"); // see step 5.
|
||||
|
@ -36,6 +37,6 @@ public class CustomApplication extends Application {
|
|||
wasErrorActivityStarted = true;
|
||||
}
|
||||
|
||||
exceptionHandler.uncaughtException(thread,e);
|
||||
exceptionHandler.uncaughtException(thread, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Copyright (c) 2016 Affectiva Inc.
|
||||
* See the file license.txt for copying permission.
|
||||
*/
|
||||
|
||||
package com.affectiva.errorreporting;
|
||||
|
||||
import android.app.Activity;
|
||||
|
@ -19,8 +24,7 @@ public class ErrorReporter extends Activity implements View.OnClickListener {
|
|||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE); // make a dialog without a titlebar
|
||||
setContentView(R.layout.error_reporter);
|
||||
|
@ -51,7 +55,7 @@ public class ErrorReporter extends Activity implements View.OnClickListener {
|
|||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent (Intent.ACTION_SEND);
|
||||
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");
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 370 B |
BIN
app/src/main/res/drawable-hdpi/screenshot_button.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/drawable-hdpi/screenshot_button_pressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 308 B |
BIN
app/src/main/res/drawable-mdpi/screenshot_button.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-mdpi/screenshot_button_pressed.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 861 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 882 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1,018 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1 KiB |
BIN
app/src/main/res/drawable-nodpi/disappointed_emoji.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/main/res/drawable-nodpi/female_glasses.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/drawable-nodpi/female_noglasses.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable-nodpi/flushed_emoji.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/drawable-nodpi/kissing_emoji.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/drawable-nodpi/laughing_emoji.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable-nodpi/male_glasses.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
app/src/main/res/drawable-nodpi/male_noglasses.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/drawable-nodpi/rage_emoji.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/drawable-nodpi/relaxed_emoji.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/drawable-nodpi/scream_emoji.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/res/drawable-nodpi/smiley_emoji.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-nodpi/smirk_emoji.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-nodpi/stuck_out_tongue_emoji.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/drawable-nodpi/unknown_glasses.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/drawable-nodpi/unknown_noglasses.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-nodpi/wink_emoji.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 378 B |
BIN
app/src/main/res/drawable-xhdpi/screenshot_button.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/drawable-xhdpi/screenshot_button_pressed.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 446 B |
BIN
app/src/main/res/drawable-xxhdpi/screenshot_button.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable-xxhdpi/screenshot_button_pressed.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -1,5 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/switch_camera_button"/>
|
||||
<item android:drawable="@drawable/switch_camera_button" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/switch_camera_button_pressed" />
|
||||
</selector>
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 378 B |
11
app/src/main/res/drawable/screenshot_button_selector.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/screenshot_button_pressed" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/screenshot_button" />
|
||||
</selector>
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<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_pressed" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/settings_button" />
|
||||
</selector>
|
|
@ -1,93 +1,115 @@
|
|||
<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" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/screenshot_button"
|
||||
android:layout_width="@dimen/settings_button_size"
|
||||
android:layout_height="@dimen/settings_button_size"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@id/camera_button"
|
||||
android:layout_margin="@dimen/settings_button_margin"
|
||||
android:background="@null"
|
||||
android:contentDescription="Take screenshot"
|
||||
android:onClick="takeScreenshot"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/screenshot_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>
|
||||
|
||||
|
|
|
@ -1,39 +1,59 @@
|
|||
<?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: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">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="4dp"
|
||||
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:layout_below="@+id/grid_item_text"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/grid_item_image_view"
|
||||
/>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/grid_item_text"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/video_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/grid_item_text"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/grid_item_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/grid_item_text"
|
||||
android:textSize="@dimen/grid_item_metric_name"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@color/transparent_overlay"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:layout_alignParentTop="true"
|
||||
/>
|
||||
android:textSize="@dimen/grid_item_metric_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_overlay"
|
||||
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:layout_below="@+id/grid_item_text"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
android:padding="2dp"/>
|
||||
android:padding="2dp"
|
||||
android:textSize="@dimen/grid_item_chooser_text_size"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
39
app/src/main/res/layout/insufficent_permissions_panel.xml
Normal 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_camera_needed_explanation" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/retryPermissionsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/retry" />
|
||||
|
||||
</LinearLayout>
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.7 KiB |
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="name_text_size">24sp</dimen>
|
||||
<dimen name="pct_text_size">14sp</dimen>
|
||||
|
@ -7,33 +14,19 @@
|
|||
<dimen name="metric_viewgroup">190dp</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>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="name_text_size">14sp</dimen>
|
||||
<dimen name="pct_text_size">11sp</dimen>
|
||||
|
@ -7,28 +14,19 @@
|
|||
<dimen name="metric_viewgroup">130dp</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>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="name_text_size">12sp</dimen>
|
||||
<dimen name="pct_text_size">9sp</dimen>
|
||||
|
@ -7,27 +14,19 @@
|
|||
<dimen name="metric_viewgroup">130dp</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>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="name_text_size">28sp</dimen>
|
||||
|
@ -8,30 +15,19 @@
|
|||
<dimen name="metric_viewgroup">220dp</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>
|
|
@ -1,17 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<declare-styleable name="custom_attributes">
|
||||
<attr name="textSize" format="dimension" />
|
||||
<attr name="textColor" format="color"/>
|
||||
<attr name="barLength" format="dimension"/>
|
||||
<attr name="textDepth" format="dimension"/>
|
||||
<attr name="textColor" format="color" />
|
||||
<attr name="metricBarLength" 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"/>
|
||||
<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>
|
|
@ -1,4 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="transparent_overlay">#55ffffff</color>
|
||||
<color name="letter_gray">#514a40</color>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="name_text_size">14sp</dimen>
|
||||
<dimen name="pct_text_size">11sp</dimen>
|
||||
|
@ -7,30 +14,19 @@
|
|||
<dimen name="metric_viewgroup">140dp</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>
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="metricName" >
|
||||
|
||||
<style name="metricName">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:gravity">center</item>
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="metricPct">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<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>
|
|
@ -1,6 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="optionsStyle" >
|
||||
|
||||
<style name="optionsStyle">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textColor">@color/letter_gray</item>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!--MainActivity strings-->
|
||||
<string name="not_found">Sorry, AffdexMe requires the use of a front-facing camera, which was not found on your device.</string>
|
||||
|
@ -6,6 +13,13 @@
|
|||
<string name="loading">Loading…</string>
|
||||
<string name="settings_content_description">Settings</string>
|
||||
|
||||
<string name="insufficient_permissions">Insufficient Permissions</string>
|
||||
<string name="permissions_camera_needed_explanation">This app requires the permission to access your camera to be able to gather facial images for processing. No video is saved or used other than for immediate emotion processing.</string>
|
||||
<string name="permissions_storage_needed_explanation">This app requires the permission to access your external storage to save screenshots.</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="understood">Understood</string>
|
||||
<string name="retry">Retry</string>
|
||||
|
||||
<!--MetricSelectionFragment strings-->
|
||||
<string name="clear_all">Clear All</string>
|
||||
<string name="metric_chooser_default_message">0 metrics chosen.</string>
|
||||
|
@ -14,7 +28,6 @@
|
|||
<!--SettingsActivity strings-->
|
||||
<string name="select_metrics_title">Select Metrics</string>
|
||||
<string name="select_metrics_message">Select emotions and expressions to display.</string>
|
||||
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="settings_message">Change application settings.</string>
|
||||
|
||||
|
@ -22,13 +35,16 @@
|
|||
<string name="set_fps_title">Set Target FPS</string>
|
||||
<string name="set_fps_message">Set the desired processed frames per second.</string>
|
||||
<string name="fps_edittext_title">Processed Frames Per Second</string>
|
||||
|
||||
<string name="show_tracking_title">Show Tracking Dots</string>
|
||||
<string name="show_tracking_message">Display tracking dots and bounding box.</string>
|
||||
|
||||
<string name="show_measurements_title">Show Measurements</string>
|
||||
<string name="show_measurements_message">Display roll, yaw, pitch, and interocular distance.</string>
|
||||
|
||||
<string name="show_appearance_title">Show Appearance Indicators</string>
|
||||
<string name="show_appearance_message">Display appearance markers adjacent to the bounding box.</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_emoji_title">Show Emoji Indicators</string>
|
||||
<string name="show_emoji_message">Display emoji markers adjacent to the bounding box.</string>
|
||||
<string name="show_multiface_message">Track multiple people. A beta feature that is CPU intensive, and works only on newer devices.</string>
|
||||
<string name="show_multiface_title">Enable Multi-face mode</string>
|
||||
<string name="negative">NEGATIVE</string>
|
||||
<string name="positive">POSITIVE</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<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>
|
||||
|
||||
|
@ -7,6 +15,7 @@
|
|||
<item name="android:homeAsUpIndicator">@drawable/ic_arrow_back_white_24dp</item>
|
||||
<item name="android:actionBarStyle">@style/CustomActionBarStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="CustomActionBarStyle" parent="android:Widget.Holo.ActionBar">
|
||||
<item name="android:displayOptions">useLogo|showHome</item>
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<header android:fragment="com.affectiva.affdexme.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_title"
|
||||
android:summary="@string/settings_message" />
|
||||
<header android:fragment="com.affectiva.affdexme.MetricSelectionFragment"
|
||||
android:title="@string/select_metrics_title"
|
||||
android:summary="@string/select_metrics_message" />
|
||||
<header
|
||||
android:fragment="com.affectiva.affdexme.SettingsActivity$SettingsFragment"
|
||||
android:summary="@string/settings_message"
|
||||
android:title="@string/settings_title" />
|
||||
<header
|
||||
android:fragment="com.affectiva.affdexme.MetricSelectionFragment"
|
||||
android:summary="@string/select_metrics_message"
|
||||
android:title="@string/select_metrics_title" />
|
||||
</preference-headers>
|
|
@ -1,27 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
- Copyright (c) 2016 Affectiva Inc.
|
||||
- See the file license.txt for copying permission.
|
||||
-->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="fps"
|
||||
android:title="@string/show_fps_title"
|
||||
android:summary="@string/show_fps_message"
|
||||
android:defaultValue="false"/>
|
||||
<CheckBoxPreference
|
||||
android:key="measurements"
|
||||
android:title="@string/show_measurements_title"
|
||||
android:summary="@string/show_measurements_message"
|
||||
android:defaultValue="false"/>
|
||||
<CheckBoxPreference
|
||||
android:key="track"
|
||||
android:title="@string/show_tracking_title"
|
||||
android:summary="@string/show_tracking_message"
|
||||
android:defaultValue="true"/>
|
||||
android:title="@string/show_fps_title" />
|
||||
<EditTextPreference
|
||||
android:key="rate"
|
||||
android:inputType="number"
|
||||
android:ems="3"
|
||||
android:maxLength="2"
|
||||
android:title="@string/set_fps_title"
|
||||
android:summary="@string/set_fps_message"
|
||||
android:defaultValue="20"
|
||||
android:dialogTitle="@string/fps_edittext_title"
|
||||
android:defaultValue="20"/>
|
||||
android:ems="3"
|
||||
android:inputType="number"
|
||||
android:key="rate"
|
||||
android:maxLength="2"
|
||||
android:summary="@string/set_fps_message"
|
||||
android:title="@string/set_fps_title" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="track"
|
||||
android:summary="@string/show_tracking_message"
|
||||
android:title="@string/show_tracking_title" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="appearance"
|
||||
android:summary="@string/show_appearance_message"
|
||||
android:title="@string/show_appearance_title" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="emoji"
|
||||
android:summary="@string/show_emoji_message"
|
||||
android:title="@string/show_emoji_title" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="multiface"
|
||||
android:summary="@string/show_multiface_message"
|
||||
android:title="@string/show_multiface_title" />
|
||||
|
||||
</PreferenceScreen>
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.android.tools.build:gradle:1.5.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|