作者 张卫卫

第一次提交

正在显示 55 个修改的文件 包含 1816 行增加0 行删除
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
\ No newline at end of file
... ...
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/sdk" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
... ...
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
... ...
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.70'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
... ...
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
... ...
不能预览此文件类型
#Tue Mar 17 15:17:02 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
... ...
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
... ...
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
... ...
/build
... ...
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
buildToolsVersion "29.0.3"
defaultConfig {
// applicationId "com.yhkj.rebotsdk"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.apkfuns.logutils:library:1.4.0'
}
... ...
不能预览此文件类型
不能预览此文件类型
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
... ...
package com.yhkj.rebotsdk
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yhkj.rebotsdk", appContext.packageName)
}
}
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yhkj.rebotsdk">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme" />
</manifest>
... ...
package com.yhkj.rebotsdk.engine
/**
* 状态码
*/
internal class EngineStatusCode {
companion object {
const val SUCCEED = 1000 //操作成功
const val FAILED = -1000 //操作失败
}
}
/**
* 状态
* @param code 状态码
* @param msg 状态消息
*/
internal data class EngineStatusData(val code: Int, val msg: String)
/**
* 人脸识别结果
* @param faceId 人脸记录Id
* @param score 比对分数
*/
internal data class IdentifyData(val faceId: String, val score: Float)
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.engine
import android.content.Context
internal interface EngineInterface {
/**
* 激活人脸license
*/
fun activeFace(
context: Context, active_key: String, appId: String,
sdkKey: String
): Int
/**
* 初始化
* @param context 上下文对象
* @return 是否初始化成功
*/
fun init(context: Context?): EngineStatusData
/**
* 销毁
*/
fun unInit()
/**
* 获取人脸数量
*/
fun getFaceNumber(context: Context?): Int
/**
* 清除人脸
*/
fun clearAllFaces(context: Context?): Int
/**
* 用于预览时注册人脸
*
* @param context 上下文对象
* @param nv21 NV21数据
* @param width NV21宽度
* @param height NV21高度
* * @param faceInfo {@link FaceEngine#detectFaces(byte[], int, int, int, List)}获取的人脸信息
* @param faceId 保存的名字,若为空则使用时间戳
* @return 是否注册成功
*/
fun registerNv21(
context: Context?, nv21: ByteArray?, width: Int, height: Int,
faceId: String?
): Boolean
/**
* 用于注册照片人脸
*
* @param context 上下文对象
* @param bgr24 bgr24数据
* @param width bgr24宽度
* @param height bgr24高度
* @param faceId 保存的名字,若为空则使用时间戳
* @return 是否注册成功
*/
fun registerBgr24(
context: Context?, bgr24: ByteArray?, width: Int, height: Int, faceId: String?
): Boolean
/**
* 人脸检索
* @return 符合条件的人脸信息
*/
fun identify(sourceData: ByteArray): IdentifyData
}
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.engine
import com.yhkj.rebotsdk.engine.arcface.ArcFaceEngine
internal class FaceEngineFactory {
fun createEngine() : EngineInterface = ArcFaceEngine()
}
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.engine.arcface
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import com.apkfuns.logutils.LogUtils
import com.arcsoft.face.ErrorInfo
import com.arcsoft.face.FaceEngine
import com.arcsoft.face.FaceFeature
import com.arcsoft.face.FaceInfo
import com.arcsoft.face.enums.DetectFaceOrientPriority
import com.arcsoft.face.enums.DetectMode
import com.arcsoft.imageutil.ArcSoftImageFormat
import com.arcsoft.imageutil.ArcSoftImageUtil
import com.arcsoft.imageutil.ArcSoftImageUtilError
import com.arcsoft.imageutil.ArcSoftRotateDegree
import com.yhkj.rebotsdk.engine.EngineInterface
import com.yhkj.rebotsdk.engine.EngineStatusCode
import com.yhkj.rebotsdk.engine.EngineStatusData
import com.yhkj.rebotsdk.engine.IdentifyData
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import kotlin.math.max
import kotlin.math.min
/**
* Created by zhangweiwei on 2020/3/16.
*/
internal class ArcFaceEngine : EngineInterface {
private var faceEngine: FaceEngine? = null
private var faceRegisterInfoList: ArrayList<FaceRegisterInfo>? = null
private var ROOT_PATH: String? = null
private val IMG_SUFFIX = ".jpg"
/**
* 存放注册图的目录
*/
private val SAVE_IMG_DIR = "register" + File.separator + "imgs"
/**
* 存放特征的目录
*/
private val SAVE_FEATURE_DIR = "register" + File.separator + "features"
override fun activeFace(
context: Context,
active_key: String,
appId: String,
sdkKey: String
): Int {
return FaceEngine.activeOnline(
context, active_key, appId, sdkKey
)
}
override fun init(context: Context?): EngineStatusData {
synchronized(this) {
if (faceEngine == null && context != null) {
faceEngine = FaceEngine()
val engineCode: Int = faceEngine!!.init(
context,
DetectMode.ASF_DETECT_MODE_IMAGE,
DetectFaceOrientPriority.ASF_OP_0_ONLY,
16,
1,
FaceEngine.ASF_FACE_RECOGNITION or FaceEngine.ASF_FACE_DETECT
)
return if (engineCode == ErrorInfo.MOK) {
initFaceList(context)
EngineStatusData(
EngineStatusCode.SUCCEED,
"init succeed"
)
} else {
faceEngine = null
EngineStatusData(
EngineStatusCode.FAILED,
"init failed"
)
}
}
return EngineStatusData(
EngineStatusCode.FAILED,
"init failed"
)
}
}
override fun unInit() {
synchronized(this) {
faceRegisterInfoList?.clear()
faceRegisterInfoList = null
faceEngine?.unInit()
faceEngine = null
}
}
/**
* 初始化人脸特征数据以及人脸特征数据对应的注册图
*
* @param context 上下文对象
*/
private fun initFaceList(context: Context) {
synchronized(this) {
if (ROOT_PATH == null) {
ROOT_PATH = context.filesDir.absolutePath
}
val featureDir =
File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR)
if (!featureDir.exists() || !featureDir.isDirectory) {
return
}
val featureFiles: Array<File> = featureDir.listFiles()
if (featureFiles.isEmpty()) {
return
}
faceRegisterInfoList = java.util.ArrayList<FaceRegisterInfo>()
for (featureFile in featureFiles) {
try {
val fis = FileInputStream(featureFile)
val feature = ByteArray(FaceFeature.FEATURE_SIZE)
fis.read(feature)
fis.close()
faceRegisterInfoList!!.add(
FaceRegisterInfo(feature, featureFile.name)
)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
override fun getFaceNumber(context: Context?): Int {
synchronized(this) {
if (context == null) {
return 0
}
if (ROOT_PATH == null) {
ROOT_PATH = context.filesDir.absolutePath
}
val featureFileDir =
File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR)
var featureCount = 0
if (featureFileDir.exists() && featureFileDir.isDirectory) {
val featureFiles: Array<String> = featureFileDir.list()
featureCount = featureFiles.size
}
var imageCount = 0
val imgFileDir =
File(ROOT_PATH + File.separator + SAVE_IMG_DIR)
if (imgFileDir.exists() && imgFileDir.isDirectory) {
val imageFiles: Array<String> = imgFileDir.list()
imageCount = imageFiles.size
}
return if (featureCount > imageCount) imageCount else featureCount
}
}
override fun clearAllFaces(context: Context?): Int {
synchronized(this) {
if (context == null) {
return 0
}
if (ROOT_PATH == null) {
ROOT_PATH = context.filesDir.absolutePath
}
faceRegisterInfoList?.clear()
val featureFileDir =
File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR)
var deletedFeatureCount = 0
if (featureFileDir.exists() && featureFileDir.isDirectory) {
val featureFiles: Array<File> = featureFileDir.listFiles()
if (featureFiles.isNotEmpty()) {
for (featureFile in featureFiles) {
if (featureFile.delete()) {
deletedFeatureCount++
}
}
}
}
var deletedImageCount = 0
val imgFileDir =
File(ROOT_PATH + File.separator + SAVE_IMG_DIR)
if (imgFileDir.exists() && imgFileDir.isDirectory) {
val imgFiles: Array<File> = imgFileDir.listFiles()
if (imgFiles.isNotEmpty()) {
for (imgFile in imgFiles) {
if (imgFile.delete()) {
deletedImageCount++
}
}
}
}
return if (deletedFeatureCount > deletedImageCount) deletedImageCount else deletedFeatureCount
}
}
override fun registerNv21(
context: Context?,
nv21: ByteArray?,
width: Int,
height: Int,
faceId: String?
): Boolean {
synchronized(this) {
if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.size != width * height * 3 / 2) {
LogUtils.e("registerNv21: invalid params")
return false
}
val faceInfoList: List<FaceInfo>? = null
val faceInfoCode: Int = faceEngine!!.detectFaces(
nv21,
width,
height,
FaceEngine.CP_PAF_NV21,
faceInfoList
)
if(faceInfoCode != ErrorInfo.MOK){
return false
}
TrackUtil.keepMaxFace(faceInfoList)
val faceInfo=faceInfoList?.get(0)
if (ROOT_PATH == null) {
ROOT_PATH = context.filesDir.absolutePath
}
//特征存储的文件夹
val featureDir =
File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR)
if (!featureDir.exists() && !featureDir.mkdirs()) {
LogUtils.e("registerNv21: can not create feature directory")
return false
}
//图片存储的文件夹
val imgDir =
File(ROOT_PATH + File.separator + SAVE_IMG_DIR)
if (!imgDir.exists() && !imgDir.mkdirs()) {
LogUtils.e("registerNv21: can not create image directory")
return false
}
val faceFeature = FaceFeature()
//特征提取
val code: Int =
faceEngine!!.extractFaceFeature(
nv21,
width,
height,
FaceEngine.CP_PAF_NV21,
faceInfo,
faceFeature
)
return if (code != ErrorInfo.MOK) {
LogUtils.e("registerNv21: extractFaceFeature failed , code is $code")
false
} else {
val registerFaceId =
faceId ?: System.currentTimeMillis().toString()
try {
// 保存注册结果(注册图、特征数据)
// 为了美观,扩大rect截取注册图
val cropRect: Rect? =
getBestRect(width, height, faceInfo!!.rect)
if (cropRect == null) {
LogUtils.e("registerNv21: cropRect is null!")
return false
}
cropRect.left = cropRect.left and 3.inv()
cropRect.top = cropRect.top and 3.inv()
cropRect.right = cropRect.right and 3.inv()
cropRect.bottom = cropRect.bottom and 3.inv()
val file =
File(imgDir.toString() + File.separator + registerFaceId + IMG_SUFFIX)
// 创建一个头像的Bitmap,存放旋转结果图
val headBmp: Bitmap? = getHeadImage(
nv21,
width,
height,
faceInfo.orient,
cropRect,
ArcSoftImageFormat.NV21
)
val fosImage = FileOutputStream(file)
headBmp?.compress(Bitmap.CompressFormat.JPEG, 100, fosImage)
fosImage.close()
val fosFeature =
FileOutputStream(featureDir.toString() + File.separator + registerFaceId)
fosFeature.write(faceFeature.featureData)
fosFeature.close()
//内存中的数据同步
if (faceRegisterInfoList == null) {
faceRegisterInfoList =
java.util.ArrayList<FaceRegisterInfo>()
}
faceRegisterInfoList!!.add(
FaceRegisterInfo(faceFeature.featureData, registerFaceId)
)
true
} catch (e: IOException) {
e.printStackTrace()
false
}
}
}
}
override fun registerBgr24(
context: Context?,
bgr24: ByteArray?,
width: Int,
height: Int,
faceId: String?
): Boolean {
synchronized(this) {
if (faceEngine == null || context == null || bgr24 == null || width % 4 != 0 || bgr24.size != width * height * 3) {
LogUtils.e("registerBgr24: invalid params")
return false
}
if (ROOT_PATH == null) {
ROOT_PATH = context.filesDir.absolutePath
}
//特征存储的文件夹
val featureDir =
File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR)
if (!featureDir.exists() && !featureDir.mkdirs()) {
LogUtils.e("registerBgr24: can not create feature directory")
return false
}
//图片存储的文件夹
val imgDir =
File(ROOT_PATH + File.separator + SAVE_IMG_DIR)
if (!imgDir.exists() && !imgDir.mkdirs()) {
LogUtils.e("registerBgr24: can not create image directory")
return false
}
//人脸检测
val faceInfoList: List<FaceInfo> = java.util.ArrayList()
var code: Int = faceEngine!!.detectFaces(
bgr24,
width,
height,
FaceEngine.CP_PAF_BGR24,
faceInfoList
)
return if (code == ErrorInfo.MOK && faceInfoList.size > 0) {
val faceFeature = FaceFeature()
//特征提取
code = faceEngine!!.extractFaceFeature(
bgr24,
width,
height,
FaceEngine.CP_PAF_BGR24,
faceInfoList[0],
faceFeature
)
val registerFaceId =
faceId ?: System.currentTimeMillis().toString()
try {
//保存注册结果(注册图、特征数据)
if (code == ErrorInfo.MOK) {
//为了美观,扩大rect截取注册图
val cropRect: Rect? =
getBestRect(
width,
height,
faceInfoList[0].rect
)
if (cropRect == null) {
LogUtils.e("registerBgr24: cropRect is null")
return false
}
cropRect.left = cropRect.left and 3.inv()
cropRect.top = cropRect.top and 3.inv()
cropRect.right = cropRect.right and 3.inv()
cropRect.bottom = cropRect.bottom and 3.inv()
val file =
File(imgDir.toString() + File.separator + registerFaceId + IMG_SUFFIX)
val fosImage = FileOutputStream(file)
// 创建一个头像的Bitmap,存放旋转结果图
val headBmp = getHeadImage(
bgr24,
width,
height,
faceInfoList[0].orient,
cropRect,
ArcSoftImageFormat.BGR24
)
// 保存到本地
headBmp!!.compress(Bitmap.CompressFormat.JPEG, 100, fosImage)
fosImage.close()
// 保存特征数据
val fosFeature =
FileOutputStream(featureDir.toString() + File.separator + registerFaceId)
fosFeature.write(faceFeature.featureData)
fosFeature.close()
// 内存中的数据同步
if (faceRegisterInfoList == null) {
faceRegisterInfoList =
java.util.ArrayList<FaceRegisterInfo>()
}
faceRegisterInfoList?.apply {
this.add(
FaceRegisterInfo(faceFeature.featureData, registerFaceId)
)
}
true
} else {
LogUtils.e("registerBgr24: extract face feature failed, code is $code")
false
}
} catch (e: IOException) {
e.printStackTrace()
false
}
} else {
LogUtils.e("registerBgr24: no face detected, code is $code")
false
}
}
}
override fun identify(sourceData: ByteArray): IdentifyData {
TODO("Not yet implemented")
}
/**
* 截取合适的头像并旋转,保存为注册头像
*
* @param originImageData 原始的BGR24数据
* @param width BGR24图像宽度
* @param height BGR24图像高度
* @param orient 人脸角度
* @param cropRect 裁剪的位置
* @param imageFormat 图像格式
* @return 头像的图像数据
*/
private fun getHeadImage(
originImageData: ByteArray,
width: Int,
height: Int,
orient: Int,
cropRect: Rect,
imageFormat: ArcSoftImageFormat
): Bitmap? {
val headImageData =
ArcSoftImageUtil.createImageData(cropRect.width(), cropRect.height(), imageFormat)
val cropCode = ArcSoftImageUtil.cropImage(
originImageData,
headImageData,
width,
height,
cropRect,
imageFormat
)
if (cropCode != ArcSoftImageUtilError.CODE_SUCCESS) {
throw RuntimeException("crop image failed, code is $cropCode")
}
//判断人脸旋转角度,若不为0度则旋转注册图
var rotateHeadImageData: ByteArray? = null
val rotateCode: Int
val cropImageWidth: Int
val cropImageHeight: Int
// 90度或270度的情况,需要宽高互换
if (orient == FaceEngine.ASF_OC_90 || orient == FaceEngine.ASF_OC_270) {
cropImageWidth = cropRect.height()
cropImageHeight = cropRect.width()
} else {
cropImageWidth = cropRect.width()
cropImageHeight = cropRect.height()
}
var rotateDegree: ArcSoftRotateDegree? = null
when (orient) {
FaceEngine.ASF_OC_90 -> rotateDegree = ArcSoftRotateDegree.DEGREE_270
FaceEngine.ASF_OC_180 -> rotateDegree = ArcSoftRotateDegree.DEGREE_180
FaceEngine.ASF_OC_270 -> rotateDegree = ArcSoftRotateDegree.DEGREE_90
FaceEngine.ASF_OC_0 -> rotateHeadImageData = headImageData
else -> rotateHeadImageData = headImageData
}
// 非0度的情况,旋转图像
if (rotateDegree != null) {
rotateHeadImageData = ByteArray(headImageData.size)
rotateCode = ArcSoftImageUtil.rotateImage(
headImageData,
rotateHeadImageData,
cropRect.width(),
cropRect.height(),
rotateDegree,
imageFormat
)
if (rotateCode != ArcSoftImageUtilError.CODE_SUCCESS) {
throw RuntimeException("rotate image failed, code is $rotateCode")
}
}
// 将创建一个Bitmap,并将图像数据存放到Bitmap中
val headBmp =
Bitmap.createBitmap(cropImageWidth, cropImageHeight, Bitmap.Config.RGB_565)
if (ArcSoftImageUtil.imageDataToBitmap(
rotateHeadImageData,
headBmp,
imageFormat
) != ArcSoftImageUtilError.CODE_SUCCESS
) {
throw RuntimeException("failed to transform image data to bitmap")
}
return headBmp
}
/**
* 将图像中需要截取的Rect向外扩张一倍,若扩张一倍会溢出,则扩张到边界,若Rect已溢出,则收缩到边界
*
* @param width 图像宽度
* @param height 图像高度
* @param srcRect 原Rect
* @return 调整后的Rect
*/
private fun getBestRect(width: Int, height: Int, srcRect: Rect?): Rect? {
if (srcRect == null) {
return null
}
val rect = Rect(srcRect)
// 原rect边界已溢出宽高的情况
val maxOverFlow = max(
-rect.left,
max(
-rect.top,
max(rect.right - width, rect.bottom - height)
)
)
if (maxOverFlow >= 0) {
rect.inset(maxOverFlow, maxOverFlow)
return rect
}
// 原rect边界未溢出宽高的情况
var padding = rect.height() / 2
// 若以此padding扩张rect会溢出,取最大padding为四个边距的最小值
if (!(rect.left - padding > 0 && rect.right + padding < width && rect.top - padding > 0 && rect.bottom + padding < height)) {
padding = min(
min(
min(
rect.left,
width - rect.right
), height - rect.bottom
), rect.top
)
}
rect.inset(-padding, -padding)
return rect
}
}
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.engine.arcface;
public class FaceRegisterInfo {
private byte[] featureData;
private String name;
public FaceRegisterInfo(byte[] faceFeature, String name) {
this.featureData = faceFeature;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getFeatureData() {
return featureData;
}
public void setFeatureData(byte[] featureData) {
this.featureData = featureData;
}
}
... ...
package com.yhkj.rebotsdk.engine.arcface;
import com.arcsoft.face.FaceInfo;
import java.util.List;
public class TrackUtil {
public static boolean isSameFace(FaceInfo faceInfo1, FaceInfo faceInfo2) {
return faceInfo1.getFaceId() == faceInfo2.getFaceId();
}
public static void keepMaxFace(List<FaceInfo> ftFaceList) {
if (ftFaceList == null || ftFaceList.size() <= 1) {
return;
}
FaceInfo maxFaceInfo = ftFaceList.get(0);
for (FaceInfo faceInfo : ftFaceList) {
if (faceInfo.getRect().width() > maxFaceInfo.getRect().width()) {
maxFaceInfo = faceInfo;
}
}
ftFaceList.clear();
ftFaceList.add(maxFaceInfo);
}
}
... ...
package com.yhkj.rebotsdk.face
/**
* 状态码
*/
class StatusCode {
companion object {
const val SUCCEED = 1000 //操作成功
const val FAILED = -1000 //操作失败
const val CAPTURE_FAILED = -1001 //操作失败
const val INVALID_DATA = -1002 //数据异常
const val REGISTER_FAILED = -1003 //注册失败
}
}
/**
* 状态
* @param code 状态码
* @param msg 状态消息
*/
data class StatusData(val code: Int, val msg: String)
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.face
import android.content.Context
interface FaceInterface {
/**
* 录入模式监听
*/
interface OnEnrollListener {
/**
* 录入结束(并未注册到人脸引擎中,仅获取到符合录入的人脸数据)
*/
fun onEnrollFinished(fingerData : ByteArray)
/**
* 录入异常
* @param statusData 提示信息
*/
fun onEnrollException(statusData: StatusData)
}
/**
* 识别模式监听
*/
interface OnVerifyListener {
/**
* 人脸识别成功
* @param faceId 人脸Id
*/
fun onVerifySucceed(faceId: String)
/**
* 人脸识别失败
* @param msg 提示消息
*/
fun onVerifyFailed(msg: String)
}
/**
* 激活人脸license
*/
fun activeFace(context:Context,active_key:String,appId:String,
sdkKey:String): Int
/**
* 初始化人脸模块
* @param appId
* @param appKey
*/
fun initFace(context:Context): StatusData
/**
* 设置录入模式监听
* @param listener 录入模式监听
*/
fun setEnrollListener(listener: OnEnrollListener)
/**
* 设置识别模式监听
* @param listener 识别模式监听
*/
fun setVerifyListener(listener: OnVerifyListener)
/**
* 释放(仅需调用一次)
*/
fun release()
/**
* 注册人脸
* @param faceId 人脸Id
* @param faceData 人脸数据
* @return 注册结果
*/
fun registerFace(context: Context?, nv21: ByteArray?, width: Int, height: Int,
faceData: ByteArray,faceId: String) : Boolean
/**
* 删除指定人脸
* @param faceId 人脸Id
* @return 删除结果
*/
fun deleteFace(faceId: String): Boolean
/**
* 清除人脸
*/
fun clearFace(): Boolean
}
\ No newline at end of file
... ...
package com.yhkj.rebotsdk.face
import android.content.Context
import com.yhkj.rebotsdk.engine.EngineInterface
import com.yhkj.rebotsdk.engine.EngineStatusCode
import com.yhkj.rebotsdk.engine.FaceEngineFactory
internal class FaceManager private constructor() : FaceInterface {
private lateinit var mFaceEngine: EngineInterface
private var mOnEnrollListener: FaceInterface.OnEnrollListener? = null
private var mOnVerifyListener: FaceInterface.OnVerifyListener? = null
companion object {
val instance: FaceManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
FaceManager()
}
}
override fun activeFace(
context: Context,
active_key: String,
appId: String,
sdkKey: String
): Int {
mFaceEngine = FaceEngineFactory().createEngine()
return mFaceEngine.activeFace(context, active_key, appId, sdkKey)
}
override fun initFace(context: Context): StatusData {
return mFaceEngine.init(context).let {
when (it.code) {
EngineStatusCode.SUCCEED -> {
StatusData(StatusCode.SUCCEED, it.msg)
}
else -> {
StatusData(StatusCode.FAILED, it.msg)
}
}
}
}
override fun setEnrollListener(listener: FaceInterface.OnEnrollListener) {
mOnEnrollListener=listener
}
override fun setVerifyListener(listener: FaceInterface.OnVerifyListener) {
mOnVerifyListener=listener
}
override fun release() {
mFaceEngine.unInit()
}
override fun registerFace(context: Context?, nv21: ByteArray?, width: Int, height: Int,
faceData: ByteArray,faceId: String): Boolean {
TODO("Not yet implemented")
}
override fun deleteFace(faceId: String): Boolean {
TODO("Not yet implemented")
}
override fun clearFace(): Boolean {
TODO("Not yet implemented")
}
}
\ No newline at end of file
... ...
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
... ...
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
... ...
<resources>
<string name="app_name">rebot_sdk</string>
</resources>
... ...
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
... ...
package com.yhkj.rebotsdk
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
... ...
rootProject.name='rebot_sdk'
include ':sdk'
... ...