diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6454ec5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7cd12d --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# FaceLocker +A machine learning android application that helps to lock your android phone using face recognition. + +*If you find this package useful, don't forget to star this repo* + +### Download App + + +### Features +- Automatic Lock +- Face Unlock +- Pattern Unlock +- Pincode Unlock +- Multiple User Lock and Unlock + +## Screenshots + + + + + +## Getting Started +### Libraries/Tools +- Java +- [Android Studio](ttps://developer.android.com/studio/) +- [OpenCv](https://github.com/bytedeco/javacv) +- [PatternLockView](https://github.com/aritraroy/PatternLockView) +- [TapTargetView](https://github.com/KeepSafe/TapTargetView) + +## let's connect to learn together + +- [Twitter](https://twitter.com/israelalagbe) +- [Github](https://github.com/israelalagbe) +- [LinkedIn](https://www.linkedin.com/in/israel-alagbe-2b823a138/) +- [Facebook](https://www.facebook.com/alagbeisrael) + +### License + + Copyright 2018 Israel Alagbe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..23305c5 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +app/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..773a139 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,74 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "wavetech.facelocker" + minSdkVersion 26 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } + + configurations.all{ + resolutionStrategy.force group:'org.opencv.android', name:'opencv', version:'3.4.3' + + //exclude group: 'org.opencv.android', module: 'library' + } + +} + +dependencies { + implementation fileTree(include: ['*.jar', '*.so'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + 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.android.support:recyclerview-v7:28.0.0' + implementation 'com.android.support:cardview-v7:28.0.0' + implementation 'com.kaopiz:kprogresshud:1.2.0' + //implementation project(':openCVLibrary343') + implementation files('libs/javacv.jar') + implementation files('libs/javacpp.jar') + implementation files('libs/ffmpeg.jar') + implementation files('libs/opencv.jar') + implementation files('libs/json-simple-1.1.1.jar') + /*{ + exclude group: 'org.opencv.android', module: 'library' + }*/ + implementation 'com.eftimoff:android-patternview:1.0.6@aar' + implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' + implementation 'com.github.markushi:circlebutton:1.1' + implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0' + //configurations.all {exclude group: 'org.opencv.android', module: 'library'} +} + + +task findDuplicates { + doLast { + String findMe = 'javax/annotation/CheckForNull.class' + configurations.compile.files.each { file -> + if (file.name.endsWith('.jar')) { + def classMatches = zipTree(file).matching { + include findMe + }.files + if (!classMatches.empty) { + println "Found $findMe in $file ${classMatches.size()} time(s)" + } + } + } + } +} diff --git a/app/libs/ffmpeg.jar b/app/libs/ffmpeg.jar new file mode 100644 index 0000000..a63824f Binary files /dev/null and b/app/libs/ffmpeg.jar differ diff --git a/app/libs/javacpp.jar b/app/libs/javacpp.jar new file mode 100644 index 0000000..23bb57a Binary files /dev/null and b/app/libs/javacpp.jar differ diff --git a/app/libs/javacv.jar b/app/libs/javacv.jar new file mode 100644 index 0000000..6763e3f Binary files /dev/null and b/app/libs/javacv.jar differ diff --git a/app/libs/json-simple-1.1.1.jar b/app/libs/json-simple-1.1.1.jar new file mode 100644 index 0000000..66347a6 Binary files /dev/null and b/app/libs/json-simple-1.1.1.jar differ diff --git a/app/libs/opencv.jar b/app/libs/opencv.jar new file mode 100644 index 0000000..89d49fa Binary files /dev/null and b/app/libs/opencv.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 diff --git a/app/src/androidTest/java/wavetech/facelocker/ExampleInstrumentedTest.java b/app/src/androidTest/java/wavetech/facelocker/ExampleInstrumentedTest.java new file mode 100644 index 0000000..d9c1e4f --- /dev/null +++ b/app/src/androidTest/java/wavetech/facelocker/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package wavetech.facelocker; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("wavetech.facelocker", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a2de0a6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/wavetech/facelocker/AbstractCameraPreviewActivity.java b/app/src/main/java/wavetech/facelocker/AbstractCameraPreviewActivity.java new file mode 100644 index 0000000..0a8b78e --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/AbstractCameraPreviewActivity.java @@ -0,0 +1,37 @@ +package wavetech.facelocker; + +import android.support.v7.app.AppCompatActivity; + +import org.bytedeco.javacpp.opencv_core; +import org.bytedeco.javacpp.opencv_objdetect; + +import wavetech.facelocker.utils.FaceRegister; + +import static org.bytedeco.javacpp.opencv_core.LINE_8; +import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; +import static org.bytedeco.javacpp.opencv_imgproc.rectangle; + +abstract public class AbstractCameraPreviewActivity + extends AppCompatActivity + implements CvCameraPreview.CvCameraViewListener +{ + public CvCameraPreview cameraView; + public int absoluteFaceSize = 0; + public opencv_objdetect.CascadeClassifier faceDetector; + FaceRegister faceRegister=new FaceRegister(); + + @Override + public void onCameraViewStarted(int width, int height) { + absoluteFaceSize = (int) (width * 0.32f); + } + + @Override + public void onCameraViewStopped() { + + } + + + + +} diff --git a/app/src/main/java/wavetech/facelocker/CameraActivity.java b/app/src/main/java/wavetech/facelocker/CameraActivity.java new file mode 100644 index 0000000..262bb5c --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/CameraActivity.java @@ -0,0 +1,190 @@ +package wavetech.facelocker; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.MenuItem; +import android.view.SurfaceView; +import android.view.WindowManager; +import android.widget.Toast; +//OpenCV Java Classes +import com.kaopiz.kprogresshud.KProgressHUD; + +import org.bytedeco.javacpp.opencv_core; +import org.bytedeco.javacpp.opencv_objdetect; +import org.bytedeco.javacv.FrameFilter; + +import org.opencv.android.BaseLoaderCallback; + +import org.opencv.android.LoaderCallbackInterface; + +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.MatOfRect; +import org.opencv.core.Rect; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import wavetech.facelocker.utils.FaceRegister; +import wavetech.facelocker.utils.LockscreenService; +import wavetech.facelocker.utils.PasswordStore; +import wavetech.facelocker.utils.StorageHelper; + +import static org.bytedeco.javacpp.opencv_core.LINE_8; +import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; +import static org.bytedeco.javacpp.opencv_imgproc.rectangle; +import org.opencv.core.Rect; + +public class CameraActivity extends AbstractCameraPreviewActivity { + public static String TAG="facelocker.camera"; + CvCameraPreview mOpenCvCameraView; + protected KProgressHUD progressLoader; + boolean isRecognizing=false; + private void startScreenLock(){ + startService(new Intent(this, LockscreenService.class)); + } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_camera); + //Hide the action bar + getSupportActionBar().hide(); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + //askForPermissions(); + mOpenCvCameraView = (CvCameraPreview) findViewById(R.id.camera_view); + mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); + mOpenCvCameraView.setCvCameraViewListener(this); + + progressLoader = KProgressHUD.create(CameraActivity.this) + .setStyle(KProgressHUD.Style.BAR_DETERMINATE) + .setLabel("Registering Face") + .setMaxProgress(100) + .setCancellable(false) + .show(); + + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + faceDetector = StorageHelper.loadClassifierCascade(CameraActivity.this, R.raw.frontalface); + return null; + } + }.execute(); + } + + + @Override + public opencv_core.Mat onCameraFrame(opencv_core.Mat rgbaMat) { + if (faceDetector != null) { + opencv_core.Mat grayMat = new opencv_core.Mat(rgbaMat.rows(), rgbaMat.cols()); + + cvtColor(rgbaMat, grayMat, CV_BGR2GRAY); + + opencv_core.RectVector faces = new opencv_core.RectVector(); + faceDetector.detectMultiScale(grayMat, faces, 1.25f, 3, 1, + new opencv_core.Size(absoluteFaceSize, absoluteFaceSize), + new opencv_core.Size(4 * absoluteFaceSize, 4 * absoluteFaceSize)); + if (faces.size() == 1) { + int x = faces.get(0).x(); + int y = faces.get(0).y(); + int w = faces.get(0).width(); + int h = faces.get(0).height(); + opencv_core.Mat duplicateMat=rgbaMat.clone(); + rectangle(rgbaMat, new opencv_core.Point(x, y), new opencv_core.Point(x + w, y + h), opencv_core.Scalar.GREEN, 2, LINE_8, 0); + + if(isRecognizing) + return rgbaMat; + opencv_core.Mat croppedMat=new opencv_core.Mat(duplicateMat,faces.get(0)); + + try { + isRecognizing=true; + faceRegister.debounceImageSaveCall(this,duplicateMat, 300); + croppedMat.release(); + duplicateMat.release(); + progressLoader.setProgress(faceRegister.getSavedImagesCount()*100/FaceRegister.getMaxImages()); + if(faceRegister.getSavedImagesCount()>=FaceRegister.getMaxImages() ){ + //faceRegister.trainModels(); + //Toast.makeText(getApplicationContext(),"",Toast.LENGTH_LONG).show(); + //finish(); + + this.runOnUiThread(new Runnable() { + public void run() { + isRecognizing=true; + try{ + faceRegister.trainModels(CameraActivity.this.getApplicationContext()); + Log.v(TAG,"Finish training models"); + } + catch (IOException e){ + e.printStackTrace(); + Log.e(TAG,"IO Exception: "+ e.getMessage()); + } + catch (Exception e){ + e.printStackTrace(); + Log.e(TAG,"IO Exception: "+ e.getMessage()); + } + + AlertDialog alertDialog = new AlertDialog.Builder(CameraActivity.this).create(); + alertDialog.setTitle("Success"); + alertDialog.setMessage("Face registered successfully!"); + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + PasswordStore passwordStore = new PasswordStore(getApplicationContext()); + passwordStore.setIsScreenLockEnabled(true); + passwordStore.save(); + + startScreenLock(); + + Intent intent = new Intent(CameraActivity.this, LockScreen.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + dialog.dismiss(); + }catch (Exception e){ + + } + } + }); + alertDialog.show(); + } + }); + + + + + + } + }catch (IOException e){ + Log.e(TAG,"IO Error: "+ e.getMessage()); + } + catch (Exception e){ + Log.e(TAG,"Exception: "+ e.getMessage()); + } + finally { + isRecognizing=false; + } + // + duplicateMat.release(); + + } + + + + grayMat.release(); + } + + return rgbaMat; + } +} diff --git a/app/src/main/java/wavetech/facelocker/CvCameraPreview.java b/app/src/main/java/wavetech/facelocker/CvCameraPreview.java new file mode 100644 index 0000000..ed1abfb --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/CvCameraPreview.java @@ -0,0 +1,849 @@ +package wavetech.facelocker; + + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.hardware.Camera.PictureCallback; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import org.bytedeco.javacpp.opencv_core.Mat; +import org.bytedeco.javacv.AndroidFrameConverter; +import org.bytedeco.javacv.FFmpegFrameFilter; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.FrameFilter; +import org.bytedeco.javacv.OpenCVFrameConverter; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +import static org.bytedeco.javacpp.avutil.AV_PIX_FMT_NV21; + +/** + * This is the graphical object used to display a real-time preview of the Camera. + * It MUST be an extension of the {@link SurfaceView} class.
+ * It also needs to implement some other interfaces like {@link SurfaceHolder.Callback} + * (to react to SurfaceView events) + * + * @author alessandrofrancesconi, hunghd + */ +public class CvCameraPreview extends SurfaceView implements SurfaceHolder.Callback, PreviewCallback { + + private final String LOG_TAG = "CvCameraPreview"; + + private static final int STOPPED = 0; + private static final int STARTED = 1; + + public static final int CAMERA_BACK = 99; + public static final int CAMERA_FRONT = 98; + + public static final int SCALE_FIT = 1; + public static final int SCALE_FULL = 2; + + private static final int MAGIC_TEXTURE_ID = 10; + + /** + * ASPECT_RATIO_W and ASPECT_RATIO_H define the aspect ratio + * of the Surface. They are used when {@link #onMeasure(int, int)} + * is called. + */ + private final float ASPECT_RATIO_W = 4.0f; + private final float ASPECT_RATIO_H = 3.0f; + + /** + * The maximum dimension (in pixels) of the preview frames that are produced + * by the Camera object. Note that this should not be intended as + * the final, exact, dimension because the device could not support + * it and a lower value is required (but the aspect ratio should remain the same).
+ * See {@link CvCameraPreview#getBestSize(List, int)} for more information. + */ + private final int PREVIEW_MAX_WIDTH = 640; + + /** + * The maximum dimension (in pixels) of the images produced when a + * {@link PictureCallback#onPictureTaken(byte[], Camera)} event is + * fired. Again, this is a maximum value and could not be the + * real one implemented by the device. + */ + private final int PICTURE_MAX_WIDTH = 1280; + + /** + * In this example we look at camera preview buffer functionality too.
+ * This is the array that will be filled everytime a single preview frame is + * ready to be processed (for example when we want to show to the user + * a transformed preview instead of the original one, or when we want to + * make some image analysis in real-time without taking full-sized pictures). + */ + private byte[] previewBuffer; + + /** + * The "holder" is the underlying surface. + */ + private SurfaceHolder surfaceHolder; + + private FFmpegFrameFilter filter; + private int chainIdx = 0; + private boolean stopThread = false; + private boolean cameraFrameReady = false; + protected boolean enabled = true; + private boolean surfaceExist; + private Thread thread; + private CvCameraViewListener listener; + private AndroidFrameConverter converterToBitmap = new AndroidFrameConverter(); + private OpenCVFrameConverter.ToMat converterToMat = new OpenCVFrameConverter.ToMat(); + private Bitmap cacheBitmap; + protected Frame[] cameraFrame; + private int state = STOPPED; + private final Object syncObject = new Object(); + private int cameraId = -1; + private int cameraType = Camera.CameraInfo.CAMERA_FACING_BACK; + private Camera cameraDevice; + private SurfaceTexture surfaceTexture; + private int frameWidth, frameHeight; + private int scaleType = SCALE_FIT; + + public CvCameraPreview(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.CvCameraPreview); + int camType = array.getInt(R.styleable.CvCameraPreview_camera_type, CAMERA_BACK); + int scaleType = array.getInt(R.styleable.CvCameraPreview_scale_type, SCALE_FIT); + array.recycle(); + + initializer(camType == CAMERA_BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, scaleType); + + } + + public CvCameraPreview(Context context, int camType, int scaleType) { + super(context); + + initializer(camType == CAMERA_BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, scaleType); + } + + private void initializer(int camType, int scaleType) { + + this.surfaceHolder = this.getHolder(); + this.surfaceHolder.addCallback(this); + + this.scaleType = scaleType; + this.cameraType = camType; + + // deprecated setting, but required on Android versions prior to API 11 + if (Build.VERSION.SDK_INT < 11) { + this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + } + } + + public void setCvCameraViewListener(CvCameraViewListener listener) { + this.listener = listener; + } + + public int getCameraId() { + return cameraId; + } + + /** + * Called when the surface is created for the first time. It sets all the + * required {@link #cameraDevice}'s parameters and starts the preview stream. + * + * @param holder + */ + @Override + public void surfaceCreated(SurfaceHolder holder) { + /* Do nothing. Wait until surfaceChanged delivered */ + } + + /** + * [IMPORTANT!] A SurfaceChanged event means that the parent graphic has changed its layout + * (for example when the orientation changes). It's necessary to update the {@link CvCameraPreview} + * orientation, so the preview is stopped, then updated, then re-activated. + * + * @param holder The SurfaceHolder whose surface has changed + * @param format The new PixelFormat of the surface + * @param w The new width of the surface + * @param h The new height of the surface + */ + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + if (this.surfaceHolder.getSurface() == null) { + Log.e(LOG_TAG, "surfaceChanged(): surfaceHolder is null, nothing to do."); + return; + } + + synchronized (syncObject) { + if (!surfaceExist) { + surfaceExist = true; + checkCurrentState(); + } else { + /** Surface changed. We need to stop camera and restart with new parameters */ + /* Pretend that old surface has been destroyed */ + surfaceExist = false; + checkCurrentState(); + /* Now use new surface. Say we have it now */ + surfaceExist = true; + checkCurrentState(); + } + } + } + + /** + * Called when syncObject lock is held + */ + private void checkCurrentState() { + Log.d(LOG_TAG, "call checkCurrentState"); + int targetState; + + if (enabled && surfaceExist && getVisibility() == VISIBLE) { + targetState = STARTED; + } else { + targetState = STOPPED; + } + + if (targetState != state) { + /* The state change detected. Need to exit the current state and enter target state */ + processExitState(state); + state = targetState; + processEnterState(state); + } + } + + private void processExitState(int state) { + Log.d(LOG_TAG, "call processExitState: " + state); + switch (state) { + case STARTED: + onExitStartedState(); + break; + case STOPPED: + onExitStoppedState(); + break; + } + ; + } + + private void processEnterState(int state) { + Log.d(LOG_TAG, "call processEnterState: " + state); + switch (state) { + case STARTED: + onEnterStartedState(); + if (listener != null) { + listener.onCameraViewStarted(frameWidth, frameHeight); + } + break; + case STOPPED: + onEnterStoppedState(); + if (listener != null) { + listener.onCameraViewStopped(); + } + break; + } + } + + private void onEnterStoppedState() { + /* nothing to do */ + } + + private void onExitStoppedState() { + /* nothing to do */ + } + + // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x + // Bitmap must be constructed before surface + private void onEnterStartedState() { + Log.d(LOG_TAG, "call onEnterStartedState"); + /* Connect camera */ + if (!connectCamera()) { + AlertDialog ad = new AlertDialog.Builder(getContext()).create(); + ad.setCancelable(false); // This blocks the 'BACK' button + ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed."); + ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + ((Activity) getContext()).finish(); + } + }); + ad.show(); + + } + } + + private void onExitStartedState() { + disconnectCamera(); + if (cacheBitmap != null) { + cacheBitmap.recycle(); + } + if (filter != null) { + try { + filter.release(); + } catch (FrameFilter.Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceDestroyed"); + synchronized (syncObject) { + surfaceExist = false; + checkCurrentState(); + } + } + + /** + * [IMPORTANT!] Probably the most important method here. Lots of users experience bad + * camera behaviors because they don't override this guy. + * In fact, some Android devices are very strict about the size of the surface + * where the preview is printed: if its ratio is different from the + * original one, it results in errors like "startPreview failed".
+ * This methods takes care on this and applies the right size to the + * {@link CvCameraPreview}. + * + * @param widthMeasureSpec horizontal space requirements as imposed by the parent. + * @param heightMeasureSpec vertical space requirements as imposed by the parent. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + // do some ultra high precision math... + float expectedRatio = height > width ? ASPECT_RATIO_H / ASPECT_RATIO_W : ASPECT_RATIO_W / ASPECT_RATIO_H; + float screenRatio = width * 1f / height; + if (screenRatio > expectedRatio) { + if (scaleType == SCALE_FULL) { + height = (int) (width / expectedRatio + .5); + } else { + width = (int) (height * expectedRatio + .5); + } + } else { + if (scaleType == SCALE_FULL) { + width = (int) (height * expectedRatio + .5); + } else { + height = (int) (width / expectedRatio + .5); + } + } + + setMeasuredDimension(width, height); + Log.i(LOG_TAG, "onMeasure(): set surface dimension to " + width + "x" + height); + } + + private boolean connectCamera() { + /* 1. We need to instantiate camera + * 2. We need to start thread which will be getting frames + */ + /* First step - initialize camera connection */ + Log.d(LOG_TAG, "Connecting to camera"); + if (!initializeCamera()) + return false; + + /* now we can start update thread */ + Log.d(LOG_TAG, "Starting processing thread"); + stopThread = false; + thread = new Thread(new CameraWorker()); + thread.start(); + + return true; + } + + private void disconnectCamera() { + /* 1. We need to stop thread which updating the frames + * 2. Stop camera and release it + */ + Log.d(LOG_TAG, "Disconnecting from camera"); + try { + stopThread = true; + Log.d(LOG_TAG, "Notify thread"); + synchronized (this) { + this.notify(); + } + Log.d(LOG_TAG, "Wating for thread"); + if (thread != null) + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + thread = null; + } + + stopCameraPreview(); + + /* Now release camera */ + releaseCamera(); + + cameraFrameReady = false; + } + + private boolean initializeCamera() { + synchronized (this) { + if (this.cameraDevice != null) { + // do the job only if the camera is not already set + Log.i(LOG_TAG, "initializeCamera(): camera is already set, nothing to do"); + return true; + } + + // warning here! starting from API 9, we can retrieve one from the multiple + // hardware cameras (ex. front/back) + if (Build.VERSION.SDK_INT >= 9) { + + if (this.cameraId < 0) { + // at this point, it's the first time we request for a camera + Camera.CameraInfo camInfo = new Camera.CameraInfo(); + for (int i = 0; i < Camera.getNumberOfCameras(); i++) { + Camera.getCameraInfo(i, camInfo); + + if (camInfo.facing == cameraType) { + // in this example we'll request specifically the back camera + try { + this.cameraDevice = Camera.open(i); + this.cameraId = i; // assign to cameraId this camera's ID (O RLY?) + break; + } catch (RuntimeException e) { + // something bad happened! this camera could be locked by other apps + Log.e(LOG_TAG, "initializeCamera(): trying to open camera #" + i + " but it's locked", e); + } + } + } + } else { + // at this point, a previous camera was set, we try to re-instantiate it + try { + this.cameraDevice = Camera.open(this.cameraId); + } catch (RuntimeException e) { + Log.e(LOG_TAG, "initializeCamera(): trying to re-open camera #" + this.cameraId + " but it's locked", e); + } + } + } + + // we could reach this point in two cases: + // - the API is lower than 9 + // - previous code block failed + // hence, we try the classic method, that doesn't ask for a particular camera + if (this.cameraDevice == null) { + try { + this.cameraDevice = Camera.open(); + this.cameraId = 0; + } catch (RuntimeException e) { + // this is REALLY bad, the camera is definitely locked by the system. + Log.e(LOG_TAG, + "initializeCamera(): trying to open default camera but it's locked. " + + "The camera is not available for this app at the moment.", e + ); + return false; + } + } + + // here, the open() went good and the camera is available + Log.i(LOG_TAG, "initializeCamera(): successfully set camera #" + this.cameraId); + + setupCamera(); + + updateCameraDisplayOrientation(); + + initFilter(frameWidth, frameHeight); + + startCameraPreview(this.surfaceHolder); + } + + return true; + } + + /** + * It sets all the required parameters for the Camera object, like preview + * and picture size, format, flash modes and so on. + * In this particular example it initializes the {@link #previewBuffer} too. + */ + private boolean setupCamera() { + if (cameraDevice == null) { + Log.e(LOG_TAG, "setupCamera(): warning, camera is null"); + return false; + } + try { + Camera.Parameters parameters = cameraDevice.getParameters(); + List sizes = parameters.getSupportedPreviewSizes(); + if (sizes != null) { + Size bestPreviewSize = getBestSize(sizes, PREVIEW_MAX_WIDTH); + + frameWidth = bestPreviewSize.width; + frameHeight = bestPreviewSize.height; + + parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height); + + parameters.setPreviewFormat(ImageFormat.NV21); // NV21 is the most supported format for preview frames + + List FocusModes = parameters.getSupportedFocusModes(); + if (FocusModes != null && FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); + } + + cameraDevice.setParameters(parameters); // save everything + + // print saved parameters + int prevWidth = cameraDevice.getParameters().getPreviewSize().width; + int prevHeight = cameraDevice.getParameters().getPreviewSize().height; + int picWidth = cameraDevice.getParameters().getPictureSize().width; + int picHeight = cameraDevice.getParameters().getPictureSize().height; + + Log.d(LOG_TAG, "setupCamera(): settings applied:\n\t" + + "preview size: " + prevWidth + "x" + prevHeight + "\n\t" + + "picture size: " + picWidth + "x" + picHeight + ); + + // here: previewBuffer initialization. It will host every frame that comes out + // from the preview, so it must be big enough. + // After that, it's linked to the camera with the setCameraCallback() method. + try { + this.previewBuffer = new byte[prevWidth * prevHeight * ImageFormat.getBitsPerPixel(cameraDevice.getParameters().getPreviewFormat()) / 8]; + setCameraCallback(); + } catch (IOException e) { + Log.e(LOG_TAG, "setupCamera(): error setting camera callback.", e); + } + + return true; + } else { + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + protected void releaseCamera() { + synchronized (this) { + if (cameraDevice != null) { + cameraDevice.release(); + } + cameraDevice = null; + } + } + + /** + * [IMPORTANT!] Sets the {@link #previewBuffer} to be the default buffer where the + * preview frames will be copied. Also sets the callback function + * when a frame is ready. + * + * @throws IOException + */ + private void setCameraCallback() throws IOException { + Log.d(LOG_TAG, "setCameraCallback()"); + cameraDevice.addCallbackBuffer(this.previewBuffer); + cameraDevice.setPreviewCallbackWithBuffer(this); + } + + /** + * [IMPORTANT!] This is a convenient function to determine what's the proper + * preview/picture size to be assigned to the camera, by looking at + * the list of supported sizes and the maximum value given + * + * @param sizes sizes that are currently supported by the camera hardware, + * retrived with {@link Camera.Parameters#getSupportedPictureSizes()} or {@link Camera.Parameters#getSupportedPreviewSizes()} + * @param widthThreshold the maximum value we want to apply + * @return an optimal size <= widthThreshold + */ + private Size getBestSize(List sizes, int widthThreshold) { + Size bestSize = null; + + for (Size currentSize : sizes) { + boolean isDesiredRatio = ((currentSize.width / ASPECT_RATIO_W) == (currentSize.height / ASPECT_RATIO_H)); + boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width); + boolean isInBounds = currentSize.width <= widthThreshold; + + if (isDesiredRatio && isInBounds && isBetterSize) { + bestSize = currentSize; + } + } + + if (bestSize == null) { + bestSize = sizes.get(0); + Log.e(LOG_TAG, "determineBestSize(): can't find a good size. Setting to the very first..."); + } + + Log.i(LOG_TAG, "determineBestSize(): bestSize is " + bestSize.width + "x" + bestSize.height); + return bestSize; + } + + /** + * In addition to calling {@link Camera#startPreview()}, it also + * updates the preview display that could be changed in some situations + * + * @param holder the current {@link SurfaceHolder} + */ + private synchronized void startCameraPreview(SurfaceHolder holder) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + surfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID); + cameraDevice.setPreviewTexture(surfaceTexture); + } else + cameraDevice.setPreviewDisplay(holder); + cameraDevice.startPreview(); +// filter.start(); + } catch (Exception e) { + Log.e(LOG_TAG, "startCameraPreview(): error starting camera preview", e); + } + } + + /** + * It "simply" calls {@link Camera#stopPreview()} and checks + * for errors + */ + private synchronized void stopCameraPreview() { + try { + cameraDevice.stopPreview(); + cameraDevice.setPreviewCallback(null); +// filter.stop(); + } catch (Exception e) { + // ignored: tried to stop a non-existent preview + Log.i(LOG_TAG, "stopCameraPreview(): tried to stop a non-running preview, this is not an error"); + } + } + + /** + * Gets the current screen rotation in order to understand how much + * the surface needs to be rotated + */ + private void updateCameraDisplayOrientation() { + if (cameraDevice == null) { + Log.e(LOG_TAG, "updateCameraDisplayOrientation(): warning, camera is null"); + return; + } + + int degree = getRotationDegree(); + + cameraDevice.setDisplayOrientation(degree); // save settings + } + + private int getRotationDegree() { + int result = 0; + Activity parentActivity = (Activity) this.getContext(); + + int rotation = parentActivity.getWindowManager().getDefaultDisplay().getRotation(); + int degrees = 0; + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + } + + if (Build.VERSION.SDK_INT >= 9) { + // on >= API 9 we can proceed with the CameraInfo method + // and also we have to keep in mind that the camera could be the front one + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraId, info); + + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { + // back-facing + result = (info.orientation - degrees + 360) % 360; + } + } else { + // TODO: on the majority of API 8 devices, this trick works good + // and doesn't produce an upside-down preview. + // ... but there is a small amount of devices that don't like it! + result = Math.abs(degrees - 90); + } + return result; + } + + private void initFilter(int width, int height) { + int degree = getRotationDegree(); + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraId, info); + boolean isFrontFaceCamera = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT; + Log.i(LOG_TAG, "init filter with width = " + width + " and height = " + height + " and degree = " + + degree + " and isFrontFaceCamera = " + isFrontFaceCamera); + String transposeCode; + String formatCode = "format=pix_fmts=rgba"; + /* + 0 = 90CounterCLockwise and Vertical Flip (default) + 1 = 90Clockwise + 2 = 90CounterClockwise + 3 = 90Clockwise and Vertical Flip + */ + switch (degree) { + case 0: + transposeCode = isFrontFaceCamera ? "transpose=3,transpose=2" : "transpose=1,transpose=2"; + break; + case 90: + transposeCode = isFrontFaceCamera ? "transpose=3" : "transpose=1"; + break; + case 180: + transposeCode = isFrontFaceCamera ? "transpose=0,transpose=2" : "transpose=2,transpose=2"; + break; + case 270: + transposeCode = isFrontFaceCamera ? "transpose=0" : "transpose=2"; + break; + default: + transposeCode = isFrontFaceCamera ? "transpose=3,transpose=2" : "transpose=1,transpose=2"; + } + + if (cameraFrame == null) { + cameraFrame = new Frame[2]; + cameraFrame[0] = new Frame(width, height, Frame.DEPTH_UBYTE, 2); + cameraFrame[1] = new Frame(width, height, Frame.DEPTH_UBYTE, 2); + } + + filter = new FFmpegFrameFilter(transposeCode + "," + formatCode, width, height); + filter.setPixelFormat(AV_PIX_FMT_NV21); + + Log.i(LOG_TAG, "filter initialize success"); + } + + @Override + public void onPreviewFrame(byte[] raw, Camera cam) { + processFrame(previewBuffer, cam); + // [IMPORTANT!] remember to reset the CallbackBuffer at the end of every onPreviewFrame event. + // Seems weird, but it works + cam.addCallbackBuffer(previewBuffer); + } + + /** + * [IMPORTANT!] It's the callback that's fired when a preview frame is ready. Here + * we can do some real-time analysis of the preview's contents. + * Just remember that the buffer array is a list of pixels represented in + * Y'UV420sp (NV21) format, so you could have to convert it to RGB before. + * + * @param raw the preview buffer + * @param cam the camera that filled the buffer + * @see YUV Conversion - Wikipedia + */ + private void processFrame(byte[] raw, Camera cam) { + if (cameraFrame != null) { + synchronized (this) { + ((ByteBuffer) cameraFrame[chainIdx].image[0].position(0)).put(raw); + cameraFrameReady = true; + this.notify(); + } + } + } + + private class CameraWorker implements Runnable { + public void run() { + do { + boolean hasFrame = false; + synchronized (CvCameraPreview.this) { + try { + while (!cameraFrameReady && !stopThread) { + CvCameraPreview.this.wait(); + } + } catch (InterruptedException e) { + Log.e(LOG_TAG, "CameraWorker interrupted", e); + } + if (cameraFrameReady) { + chainIdx = 1 - chainIdx; + cameraFrameReady = false; + hasFrame = true; + } + } + + if (!stopThread && hasFrame) { + if (cameraFrame[1 - chainIdx] != null) { + try { + Frame frame; + filter.start(); + filter.push(cameraFrame[1 - chainIdx]); + while ((frame = filter.pull()) != null) { + deliverAndDrawFrame(frame); + } + filter.stop(); + } catch (FrameFilter.Exception e) { + e.printStackTrace(); + } + } + } + } while (!stopThread); + Log.d(LOG_TAG, "Finish processing thread"); + } + } + + /** + * This method shall be called by the subclasses when they have valid + * object and want it to be delivered to external client (via callback) and + * then displayed on the screen. + * + * @param frame - the current frame to be delivered + */ + protected void deliverAndDrawFrame(Frame frame) { + Mat processedMat = null; + + if (listener != null) { + Mat mat = converterToMat.convert(frame); + processedMat = listener.onCameraFrame(mat); + frame = converterToMat.convert(processedMat); + if (mat != null) { + mat.release(); + } + } + cacheBitmap = converterToBitmap.convert(frame); + if (cacheBitmap != null) { + int width, height; + Canvas canvas = getHolder().lockCanvas(); + if (canvas != null) { + width = canvas.getWidth(); + height = cacheBitmap.getHeight() * canvas.getWidth() / cacheBitmap.getWidth(); + canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR); + canvas.drawBitmap(cacheBitmap, + new Rect(0, 0, cacheBitmap.getWidth(), cacheBitmap.getHeight()), + new Rect(0, + (canvas.getHeight() - height) / 2, + width, + (canvas.getHeight() - height) / 2 + height), null); + getHolder().unlockCanvasAndPost(canvas); + } + } + + if (processedMat != null) { + processedMat.release(); + } + } + + public interface CvCameraViewListener { + /** + * This method is invoked when camera preview has started. After this method is invoked + * the frames will start to be delivered to client via the onCameraFrame() callback. + * + * @param width - the width of the frames that will be delivered + * @param height - the height of the frames that will be delivered + */ + public void onCameraViewStarted(int width, int height); + + /** + * This method is invoked when camera preview has been stopped for some reason. + * No frames will be delivered via onCameraFrame() callback after this method is called. + */ + public void onCameraViewStopped(); + + /** + * This method is invoked when delivery of the frame needs to be done. + * The returned values - is a modified frame which needs to be displayed on the screen. + * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc) + */ + public Mat onCameraFrame(Mat mat); + } + +} diff --git a/app/src/main/java/wavetech/facelocker/LockScreen.java b/app/src/main/java/wavetech/facelocker/LockScreen.java new file mode 100644 index 0000000..94e97db --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/LockScreen.java @@ -0,0 +1,413 @@ +package wavetech.facelocker; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Intent; +import android.os.Bundle; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.KeyEvent; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.andrognito.patternlockview.PatternLockView; +import com.andrognito.patternlockview.listener.PatternLockViewListener; +import com.andrognito.patternlockview.utils.PatternLockUtils; + +import org.bytedeco.javacpp.opencv_core; +//import org.opencv.android.CameraBridgeViewBase; +//import org.opencv.android.JavaCameraView; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfRect; +import org.opencv.core.Rect; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +import java.io.IOException; +import java.util.List; + +import wavetech.facelocker.utils.LockscreenIntentReceiver; +import wavetech.facelocker.utils.LockscreenService; +import wavetech.facelocker.utils.LockscreenUtils; +import wavetech.facelocker.utils.PasswordStore; +import wavetech.facelocker.utils.StorageHelper; + +import static org.bytedeco.javacpp.opencv_core.LINE_8; +import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; +import static org.bytedeco.javacpp.opencv_imgproc.rectangle; + +public class LockScreen extends AbstractCameraPreviewActivity + implements + LockscreenUtils.OnLockStatusChangedListener +{ + + // User-interface + private Button btnUnlock; + + // Member variables + private LockscreenUtils mLockscreenUtils; + + private PatternLockViewListener patternLockViewListener; + private PatternLockView mPatternLockView; + private PasswordStore passwordStore; + private EditText pinCodeInput; + boolean isPredicting=false; + @Override + public opencv_core.Mat onCameraFrame(opencv_core.Mat rgbaMat) { + if (faceDetector != null) { + opencv_core.Mat grayMat = new opencv_core.Mat(rgbaMat.rows(), rgbaMat.cols()); + + cvtColor(rgbaMat, grayMat, CV_BGR2GRAY); + + opencv_core.RectVector faces = new opencv_core.RectVector(); + faceDetector.detectMultiScale(grayMat, faces, 1.1, 2, 2, + new opencv_core.Size(absoluteFaceSize, absoluteFaceSize), + new opencv_core.Size(4 * absoluteFaceSize, 4 * absoluteFaceSize)); + opencv_core.Mat duplicateMat=rgbaMat.clone(); + if (faces.size() == 1) { + int x = faces.get(0).x(); + int y = faces.get(0).y(); + int w = faces.get(0).width(); + int h = faces.get(0).height(); + rectangle(rgbaMat, new opencv_core.Point(x, y), new opencv_core.Point(x + w, y + h), opencv_core.Scalar.GREEN, 2, LINE_8, 0); + } + try { + isPredicting=true; + //Mat faceMat = new Mat(duplicateMat,facesArray[0]); + boolean recognizedFace=faceRegister.predict(this,duplicateMat); + //faceMat.release(); + if(recognizedFace) { + this.runOnUiThread(new Runnable() { + public void run() { + isPredicting = true; + unlockDevice(); + } + }); + } + + }catch (IOException e){ + Log.e(CameraActivity.TAG,"IO Error: "+ e.getMessage()); + } + catch (Exception e){ + Log.e(CameraActivity.TAG,"Exception: "+ e.getMessage()); + } + finally { + isPredicting=false; + duplicateMat.release(); + } + grayMat.release(); + } + + return rgbaMat; + } + + + private void initializeListeners(){ + patternLockViewListener=new PatternLockViewListener() { + @Override + public void onStarted() { + /*Log.v(getClass().getName(), "Pattern drawing started"); + showToastMessage("Pattern drawing has started");*/ + + } + + @Override + public void onProgress(List progressPattern) { + /*Log.v(getClass().getName(), "Pattern progress: " + + PatternLockUtils.patternToString(mPatternLockView, progressPattern)); + showToastMessage("Pattern progress: " + + PatternLockUtils.patternToString(mPatternLockView, progressPattern));*/ + } + + @Override + public void onComplete(List pattern) { +// showToastMessage("Pattern complete: " + +// PatternLockUtils.patternToString(mPatternLockView, pattern)); + //Log.v(getClass().getName(), "Pattern complete: " + + //PatternLockUtils.patternToString(mPatternLockView, pattern)); + String patternInput=PatternLockUtils.patternToString(mPatternLockView, pattern); + if(passwordStore.getPatternCode().equals(patternInput)) + unlockDevice(); + else{ + Toast.makeText(getApplicationContext(),"Invalid pattern!",Toast.LENGTH_SHORT).show(); + mPatternLockView.clearPattern(); + } + } + + @Override + public void onCleared() { + Log.v(getClass().getName(), "Pattern has been cleared"); + //showToastMessage("pattern cleard"); + } + }; + mPatternLockView.addPatternLockListener(patternLockViewListener); + + pinCodeInput.setOnEditorActionListener(new EditText.OnEditorActionListener() { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + String pinCode=passwordStore.getPinCode(); + //Toast.makeText(getApplicationContext(),"Saved: " + pinCode+" Current: "+pinCodeInput.getText().toString(),Toast.LENGTH_SHORT).show(); + if(pinCode.equals(pinCodeInput.getText().toString())) + unlockDevice(); + else{ + Toast.makeText(getApplicationContext(),"Incorrect Password!",Toast.LENGTH_SHORT).show(); + pinCodeInput.setText(""); + } + return true; + } + return false; + } + }); + } + + public void showPatternView(View v){ + findViewById(R.id.patternLayout).setVisibility(View.VISIBLE); + findViewById(R.id.pinCodeLayout).setVisibility(View.GONE); + findViewById(R.id.cameraLayout).setVisibility(View.GONE); + } + public void showPinCodeView(View v){ + findViewById(R.id.pinCodeLayout).setVisibility(View.VISIBLE); + findViewById(R.id.patternLayout).setVisibility(View.GONE); + + findViewById(R.id.cameraLayout).setVisibility(View.GONE); + //mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); + //mOpenCvCameraView.setCvCameraViewListener(null); + } + public void showCameraView(View v){ + findViewById(R.id.pinCodeLayout).setVisibility(View.GONE); + findViewById(R.id.patternLayout).setVisibility(View.GONE); + + findViewById(R.id.cameraLayout).setVisibility(View.VISIBLE); + cameraView.setVisibility(SurfaceView.VISIBLE); + cameraView.setCvCameraViewListener(this); + } + + // Set appropriate flags to make the screen appear over the keyguard + @Override + public void onAttachedToWindow() { + /*this.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + this.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + );*/ + + super.onAttachedToWindow(); + //this.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); + } + @Override + public void onCreate(Bundle savedInstanceState) { + this.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + this.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + ); + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_lock_screen); + passwordStore= new PasswordStore(getApplicationContext()); + mPatternLockView=findViewById(R.id.pattern_lock_view); + pinCodeInput = findViewById(R.id.passwordEditText); + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + faceDetector = StorageHelper.loadClassifierCascade(LockScreen.this, R.raw.frontalface); + return null; + } + }.execute(); + //OpenCV initializations + cameraView = findViewById(R.id.camera_view); + cameraView.setVisibility(SurfaceView.VISIBLE); + cameraView.setCvCameraViewListener(this); + + //Hide the action bar + getSupportActionBar().hide(); + initializeListeners(); + init(); + + // unlock screen in case of app get killed by system + if (getIntent() != null && getIntent().hasExtra("kill") + && getIntent().getExtras().getInt("kill") == 1) { + enableKeyguard(); + unlockHomeButton(); + } else { + + try { + // disable keyguard + disableKeyguard(); + + // lock home button + lockHomeButton(); + + // start service for observing intents + startService(new Intent(this, LockscreenService.class)); + + // listen the events get fired during the call + StateListener phoneStateListener = new StateListener(); + TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + telephonyManager.listen(phoneStateListener, + PhoneStateListener.LISTEN_CALL_STATE); + + initializeListeners(); + + } catch (Exception e) { + Log.e(CameraActivity.TAG, e.getMessage()); + } + + } + } + + @Override + public void onPause() { + super.onPause(); + + //The following are used to disable minimize button + ActivityManager activityManager = (ActivityManager) getApplicationContext() + .getSystemService(Context.ACTIVITY_SERVICE); + + activityManager.moveTaskToFront(getTaskId(), 0); + } + + private void init() { + mLockscreenUtils = new LockscreenUtils(); +// btnUnlock = (Button) findViewById(R.id.btnUnlock); +// btnUnlock.setOnClickListener(new View.OnClickListener() { +// +// @Override +// public void onClick(View v) { +// // unlock home button and then screen on button press +// unlockDevice(); +// } +// }); + } + + // Handle events of calls and unlock screen if necessary + private class StateListener extends PhoneStateListener { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + + super.onCallStateChanged(state, incomingNumber); + switch (state) { + case TelephonyManager.CALL_STATE_RINGING: + unlockHomeButton(); + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + break; + case TelephonyManager.CALL_STATE_IDLE: + break; + } + } + }; + + // Don't finish Activity on Back press + @Override + public void onBackPressed() { + return; + } + + // Handle button clicks + @Override + public boolean onKeyDown(int keyCode, android.view.KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) + || (keyCode == KeyEvent.KEYCODE_POWER) + || (keyCode == KeyEvent.KEYCODE_VOLUME_UP) + || (keyCode == KeyEvent.KEYCODE_CAMERA)) { + return true; + } + if ((keyCode == KeyEvent.KEYCODE_HOME)) { + Toast.makeText(getApplicationContext(),"Home button pressed",Toast.LENGTH_LONG).show(); + return true; + } + + return false; + + } + + // handle the key press events here itself + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP + || (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) + || (event.getKeyCode() == KeyEvent.KEYCODE_POWER)) { + return false; + } + if ((event.getKeyCode() == KeyEvent.KEYCODE_HOME)) { + Toast.makeText(getApplicationContext(),"Home button pressed",Toast.LENGTH_LONG).show(); + return false; + } + + //Let the number keys work for the password text input + if(event.getKeyCode() >= KeyEvent.KEYCODE_1 && event.getKeyCode() <= KeyEvent.KEYCODE_9) + { + return super.dispatchKeyEvent(event); + } + return false; + } + + // Lock home button + public void lockHomeButton() { + mLockscreenUtils.lock(LockScreen.this); + } + + // Unlock home button and wait for its callback + public void unlockHomeButton() { + mLockscreenUtils.unlock(); + } + + // Simply unlock device when home button is successfully unlocked + @Override + public void onLockStatusChanged(boolean isLocked) { + if (!isLocked) { + unlockDevice(); + } + } + + @Override + protected void onStop() { + super.onStop(); + unlockHomeButton(); + } + + @SuppressWarnings("deprecation") + private void disableKeyguard() { + KeyguardManager mKM = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + KeyguardManager.KeyguardLock mKL = mKM.newKeyguardLock("IN"); + mKL.disableKeyguard(); + } + + @SuppressWarnings("deprecation") + private void enableKeyguard() { + KeyguardManager mKM = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + KeyguardManager.KeyguardLock mKL = mKM.newKeyguardLock("IN"); + mKL.reenableKeyguard(); + } + + //Simply unlock device by finishing the activity + private void unlockDevice() + { + finish(); + } +} diff --git a/app/src/main/java/wavetech/facelocker/MainActivity.java b/app/src/main/java/wavetech/facelocker/MainActivity.java new file mode 100644 index 0000000..3c5716b --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/MainActivity.java @@ -0,0 +1,222 @@ +package wavetech.facelocker; + +import android.Manifest; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.widget.CardView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Switch; +import android.widget.Toast; + +import com.getkeepsafe.taptargetview.TapTargetView; + +import java.util.ArrayList; + +import at.markushi.ui.CircleButton; +import wavetech.facelocker.utils.FaceRegister; +import wavetech.facelocker.utils.LockscreenService; +import wavetech.facelocker.utils.PasswordStore; +import wavetech.facelocker.utils.TourHelper; + +public class MainActivity extends AppCompatActivity { + private Switch enableLockSwitch; + private PasswordStore passwordStore; + private FaceRegister faceRegister; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + final int cardMinimizeHeight=200; + final float scale = getApplicationContext().getResources().getDisplayMetrics().density; + final int cardMinimizeHeightPixel = (int) (cardMinimizeHeight * scale + 0.5f); + faceRegister=new FaceRegister(); + passwordStore=new PasswordStore(getApplicationContext()); + final CircleButton clearFacesButton= findViewById(R.id.clearFacesButton); + final CircleButton addFaceButton = findViewById(R.id.addFaceButton); + final ListView listView = findViewById(R.id.faces); + final CardView cardView=findViewById(R.id.cardView); + + final ArrayList faces = new ArrayList(); + + for(String faceName: passwordStore.getFaces().keySet()){ + faces.add(faceName); + } + + if(faces.isEmpty()||!passwordStore.getIsScreenLockEnabled()){ + clearFacesButton.setVisibility(View.GONE); + listView.setVisibility(View.GONE); + addFaceButton.setVisibility(View.GONE); + ViewGroup.LayoutParams params = cardView.getLayoutParams(); + params.height = cardMinimizeHeightPixel; + cardView.setLayoutParams(params); + } + else { + TourHelper.showTourForView(this,addFaceButton,"Add new Face","Tap this button to add a new face to the database",new TapTargetView.Listener() { // The listener can listen for regular clicks, long clicks or cancels + @Override + public void onTargetClick(TapTargetView view) { + super.onTargetClick(view); // This call is optional + TourHelper.showTourForView(MainActivity.this,clearFacesButton,"Delete Faces","Tap this button to delete existing faces from the database"); + } + }); + } + + final ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.activity_listview, faces); + + listView.setAdapter(adapter); + + + clearFacesButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + faces.clear(); + adapter.notifyDataSetChanged(); + addFaceButton.setVisibility(View.GONE); + clearFacesButton.setVisibility(View.GONE); + listView.setVisibility(View.GONE); + ViewGroup.LayoutParams params = cardView.getLayoutParams(); + params.height = cardMinimizeHeightPixel; + cardView.setLayoutParams(params); + + passwordStore.reset(); + stopScreenLock(); + + Log.v(CameraActivity.TAG,"Clearing face database: "+faceRegister.clearFaceDatabase(MainActivity.this)); + + enableLockSwitch.setChecked(passwordStore.getIsScreenLockEnabled()); + } + }); + + + + enableLockSwitch=findViewById(R.id.enableLockSwitch); + enableLockSwitch.setChecked(passwordStore.getIsScreenLockEnabled()); + if(passwordStore.getIsScreenLockEnabled()) + startScreenLock(); + else + TourHelper.showTourForView(this,enableLockSwitch,"Enable Switch","Tap this switch now to enable face locker"); + + askForPermissions(); + enableLockSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + if(checked){ + askForPermissions(); + askForFaceName(); + //launchPinCodeActivity(); + } + else{ + passwordStore.reset(); + Log.v(CameraActivity.TAG,"Clearing face database: "+faceRegister.clearFaceDatabase(MainActivity.this)); + stopScreenLock(); + } + } + }); + addFaceButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + askForFaceName(); + } + }); + + } + private void stopScreenLock(){ + stopService(new Intent(this, LockscreenService.class)); + } + private void startScreenLock(){ + startService(new Intent(this, LockscreenService.class)); + } + + private void askForFaceName(){ + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(""); + builder.setCancelable(false); + + +// Set up the input + LayoutInflater layoutInflater = LayoutInflater.from(MainActivity.this); + View promptView = layoutInflater.inflate(R.layout.dialog_text_input, null); + builder.setView(promptView); + final EditText input = promptView.findViewById(R.id.dialog_input); //new EditText(this); + builder.setPositiveButton("Save", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + //resultText.setText("Hello, " + editText.getText()); + String faceName = input.getText().toString().trim(); + if(faceName.length()<1) { + Toast.makeText(MainActivity.this, "Please type in something!", Toast.LENGTH_SHORT).show(); + return; + } + else if(passwordStore.hasFace(faceName)){ + Toast.makeText(MainActivity.this, "Face already exists!, enter another name", Toast.LENGTH_SHORT).show(); + return; + } + passwordStore.setCurrentFaceName(faceName); + if(passwordStore.getFaces().size()>0) + launchCameraActivity(); + else + launchPinCodeActivity(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + private void launchPinCodeActivity(){ + Intent intent=new Intent(MainActivity.this,PatternActivity.class); + startActivity(intent); + } + private void launchCameraActivity(){ + Intent intent=new Intent(MainActivity.this,CameraActivity.class); + startActivity(intent); + } + private void askForPermissions(){ + askPermissionWithCode(Manifest.permission.CAMERA); + askPermissionWithCode(Manifest.permission.READ_EXTERNAL_STORAGE); + askPermissionWithCode(Manifest.permission.WRITE_EXTERNAL_STORAGE); + askPermissionWithCode(Manifest.permission.DISABLE_KEYGUARD); + askPermissionWithCode(Manifest.permission.WAKE_LOCK); + askPermissionWithCode(Manifest.permission.RECEIVE_BOOT_COMPLETED); + askPermissionWithCode(Manifest.permission.READ_PHONE_STATE); + askPermissionWithCode(Manifest.permission.SYSTEM_ALERT_WINDOW); + askPermissionWithCode(Manifest.permission.REORDER_TASKS); + askPermissionWithCode(Manifest.permission.FOREGROUND_SERVICE); + } + private void askPermissionWithCode(String code){ + // Here, thisActivity is the current activity + if (ContextCompat.checkSelfPermission(this, + code) + != PackageManager.PERMISSION_GRANTED) { + + // Permission is not granted + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + code)) { + // 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. + Toast.makeText(MainActivity.this, "Permission explanation should show", Toast.LENGTH_SHORT).show(); + } else { + // No explanation needed; request the permission + ActivityCompat.requestPermissions(this,new String[]{code},80); + + + // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an + // app-defined int constant. The callback method gets the + // result of the request. + } + } + } +} diff --git a/app/src/main/java/wavetech/facelocker/PatternActivity.java b/app/src/main/java/wavetech/facelocker/PatternActivity.java new file mode 100644 index 0000000..aebb8ae --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/PatternActivity.java @@ -0,0 +1,96 @@ +package wavetech.facelocker; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import com.andrognito.patternlockview.PatternLockView; +import com.andrognito.patternlockview.listener.PatternLockViewListener; +import com.andrognito.patternlockview.utils.PatternLockUtils; +import com.getkeepsafe.taptargetview.TapTargetView; + +import java.util.List; + +import wavetech.facelocker.utils.PasswordStore; +import wavetech.facelocker.utils.TourHelper; + +public class PatternActivity extends AppCompatActivity { + private PatternLockView mPatternLockView; + private Button continueButton; + private PasswordStore passwordStore; + private PatternLockViewListener patternLockViewListener; + private void showToastMessage(String msg){ + Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_SHORT).show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pin_code); + passwordStore= new PasswordStore(getApplicationContext()); + continueButton=findViewById(R.id.continueBtn); + mPatternLockView = findViewById(R.id.pattern_lock_view); + initializeListeners(); + + + } + private void launchAlternativePincodeActivity(){ + Intent intent=new Intent(PatternActivity.this,PincodeActivity.class); + startActivity(intent); + } + private void initializeListeners(){ + continueButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + passwordStore.save(); + launchAlternativePincodeActivity(); + } + }); + patternLockViewListener=new PatternLockViewListener() { + @Override + public void onStarted() { + /*Log.v(getClass().getName(), "Pattern drawing started"); + showToastMessage("Pattern drawing has started");*/ + continueButton.setVisibility(View.INVISIBLE); + } + + @Override + public void onProgress(List progressPattern) { + /*Log.v(getClass().getName(), "Pattern progress: " + + PatternLockUtils.patternToString(mPatternLockView, progressPattern)); + showToastMessage("Pattern progress: " + + PatternLockUtils.patternToString(mPatternLockView, progressPattern));*/ + } + + @Override + public void onComplete(List pattern) { +// showToastMessage("Pattern complete: " + +// PatternLockUtils.patternToString(mPatternLockView, pattern)); + Log.v(getClass().getName(), "Pattern complete: " + + PatternLockUtils.patternToString(mPatternLockView, pattern)); + passwordStore.setPatternCode(PatternLockUtils.patternToString(mPatternLockView, pattern)); + continueButton.setVisibility(View.VISIBLE); + TourHelper.showTourForView(PatternActivity.this,continueButton,"Save button","Click this button now to go to the next stage" ,new TapTargetView.Listener() { // The listener can listen for regular clicks, long clicks or cancels + @Override + public void onTargetClick(TapTargetView view) { + super.onTargetClick(view); // This call is optional + launchAlternativePincodeActivity(); + } + }); + } + + @Override + public void onCleared() { + Log.v(getClass().getName(), "Pattern has been cleared"); + //showToastMessage("pattern cleard"); + + continueButton.setVisibility(View.INVISIBLE); + } + }; + mPatternLockView.addPatternLockListener(patternLockViewListener); + } +} diff --git a/app/src/main/java/wavetech/facelocker/PincodeActivity.java b/app/src/main/java/wavetech/facelocker/PincodeActivity.java new file mode 100644 index 0000000..e13f906 --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/PincodeActivity.java @@ -0,0 +1,76 @@ +package wavetech.facelocker; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import com.getkeepsafe.taptargetview.TapTargetView; + +import wavetech.facelocker.utils.PasswordStore; +import wavetech.facelocker.utils.TourHelper; + +public class PincodeActivity extends AppCompatActivity { + private EditText pinCodeInput; + private Button btnContinue; + String pinCodeText=""; + private PasswordStore passwordStore; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pin_code_alternative); + passwordStore= new PasswordStore(getApplicationContext()); + pinCodeInput = findViewById(R.id.pinCodeInput); + btnContinue=findViewById(R.id.btnContinue); + TourHelper.showTourForView(this,pinCodeInput,"Password","Please enter your password not less than 4 characters"); + initializeListeners(); + } + private void initializeListeners(){ + pinCodeInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + pinCodeText=charSequence.toString(); + if(pinCodeText.length()>=4){ + btnContinue.setVisibility(View.VISIBLE); + TourHelper.showTourForView(PincodeActivity.this,btnContinue,"Save button","Click this button now to go to the next stage" ,new TapTargetView.Listener() { // The listener can listen for regular clicks, long clicks or cancels + @Override + public void onTargetClick(TapTargetView view) { + super.onTargetClick(view); // This call is optional + continueButtonClick(); + } + }); + } + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + btnContinue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + continueButtonClick(); + } + }); + } + private void launchCameraActivity(){ + Intent intent=new Intent(PincodeActivity.this,CameraActivity.class); + startActivity(intent); + } + private void continueButtonClick(){ + passwordStore.setPinCode(pinCodeText); + passwordStore.save(); + launchCameraActivity(); + } +} diff --git a/app/src/main/java/wavetech/facelocker/utils/FaceRegister.java b/app/src/main/java/wavetech/facelocker/utils/FaceRegister.java new file mode 100644 index 0000000..44fb33a --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/utils/FaceRegister.java @@ -0,0 +1,204 @@ +package wavetech.facelocker.utils; + + +import java.io.File; + +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.IntBuffer; + +import org.bytedeco.javacpp.DoublePointer; +import org.bytedeco.javacpp.IntPointer; +import org.bytedeco.javacpp.opencv_core; +import android.content.Context; +import android.os.Environment; +import android.util.Log; +import static org.bytedeco.javacpp.opencv_imgcodecs.CV_LOAD_IMAGE_GRAYSCALE; +import static org.bytedeco.javacpp.opencv_imgcodecs.imwrite; +import static org.bytedeco.javacpp.opencv_imgcodecs.imread; +import static org.bytedeco.javacpp.opencv_imgproc.resize; + + + + + +//New imports +import org.bytedeco.javacpp.opencv_face.FaceRecognizer; +import org.bytedeco.javacpp.opencv_face.FisherFaceRecognizer; +import org.bytedeco.javacpp.opencv_face.LBPHFaceRecognizer; +//import org.bytedeco.javacpp.opencv_core.Mat; +import org.bytedeco.javacpp.opencv_core.MatVector; +import wavetech.facelocker.CameraActivity; + +public class FaceRegister{ + private static final int maxImages=30; + private int savedImagesCount=0; + private static String TAG = CameraActivity.TAG; + private final String imgPath = Environment.DIRECTORY_PICTURES; + FaceRecognizer faceRecognizer; + static{ + //System.loadLibrary("tbb"); + //System.loadLibrary("opencv_core"); + + } + public FaceRegister(){ + //faceRecognizer = FisherFaceRecognizer.create();//com.googlecode.javacv.cpp.opencv_contrib.createLBPHFaceRecognizer(2,8,8,8,200); + } + + public static int getMaxImages() { + return maxImages; + } + + private long lastPredictTime; + public boolean predict(Context context,opencv_core.Mat mat) throws IOException{ + + //Debounce + long lastClickTime = lastPredictTime; + long now = System.currentTimeMillis(); + if (now - lastClickTime < 500) { + //Log.d(TAG, "Too much predict call ignored"); + return false; + } + lastPredictTime=now; + //end Debounce + + PasswordStore passwordStore=new PasswordStore(context); + File path = context.getFilesDir(); + File file = new File(path, "current.jpg"); + + + + + opencv_core.Mat resizeimage=new opencv_core.Mat(); + opencv_core.Size size = new opencv_core.Size(200, 200); + resize(mat,resizeimage,size); + imwrite(file.getAbsolutePath(),resizeimage); + + opencv_core.Mat image=imread(file.getAbsolutePath(), CV_LOAD_IMAGE_GRAYSCALE); + + IntPointer label = new IntPointer(1); + DoublePointer confidence = new DoublePointer(1); + + + if(faceRecognizer==null){ + File trainingFile=new File(path, "train.xml"); + faceRecognizer = LBPHFaceRecognizer.create(2,8,8,8,200); + faceRecognizer.read(trainingFile.getAbsolutePath()); + } + faceRecognizer.predict(image, label, confidence); + int predictedLabel = label.get(0); + + + Log.v(TAG,"Predicted Label: "+predictedLabel); + Log.v(TAG,"Predicted Confidence: "+confidence.get(0)); + + image.release(); + resizeimage.release(); + if(passwordStore.hasFaceLabel(predictedLabel) && confidence.get(0)<80) + return true; + return false; + } + public boolean clearFaceDatabase(Context context){ + File path = context.getFilesDir(); + File targetFile=new File(path, "train.xml"); + return targetFile.delete(); + } + public void trainModels(Context context) throws IOException{ + FilenameFilter fileNameFilter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return /*name.toLowerCase().endsWith(".jpg")||*/name.toLowerCase().endsWith(".jpeg"); + + }; + }; + File path = context.getFilesDir(); + File[] capturedImages=path.listFiles(fileNameFilter); + opencv_core.Mat labels = new opencv_core.Mat(capturedImages.length, 1, opencv_core.CV_32SC1); + PasswordStore passwordStore=new PasswordStore(context); + int label=passwordStore.getIncrementFaceLabel(); + //int[] labels = new int[capturedImages.length]; + MatVector images = new MatVector(capturedImages.length); + opencv_core.Mat mats[] =new opencv_core.Mat[capturedImages.length]; + IntBuffer labelsBuf = labels.createBuffer(); + for (int i=0;i= Build.VERSION_CODES.O) + startMyOwnForeground(); + else + startForeground(1, new Notification()); + +======= + startForeground(); +>>>>>>> 2d682c4ac1e52fb4fc61f89c509d443f38173743 + return START_STICKY; + } + + // Run service in foreground so it is less likely to be killed by system + private void startMyOwnForeground() { +// Notification notification = new NotificationCompat.Builder(this) +// .setContentTitle(getResources().getString(R.string.app_name)) +// .setTicker(getResources().getString(R.string.app_name)) +// .setContentText("Running") +// .setSmallIcon(R.drawable.face_icon_logo) +// .setContentIntent(null) +// .setOngoing(true) +// .build(); +// startForeground(9999,notification); + + String NOTIFICATION_CHANNEL_ID = "wavetech.facelocker"; + String channelName = "LockScreenService"; + NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE); + chan.setLightColor(Color.BLUE); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + assert manager != null; + manager.createNotificationChannel(chan); + + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); + Notification notification = notificationBuilder.setOngoing(true) + .setSmallIcon(R.drawable.face_icon_logo) + .setContentTitle("FaceLocker is currently protecting you") + .setPriority(NotificationManager.IMPORTANCE_MAX) +// .setCategory(Notification.CATEGORY_SERVICE) + .setOngoing(true) + .build(); + startForeground(2, notification); + } + + // Unregister receiver + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + } +} diff --git a/app/src/main/java/wavetech/facelocker/utils/LockscreenUtils.java b/app/src/main/java/wavetech/facelocker/utils/LockscreenUtils.java new file mode 100644 index 0000000..ec03180 --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/utils/LockscreenUtils.java @@ -0,0 +1,84 @@ +package wavetech.facelocker.utils; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.KeyguardManager; +import android.content.Context; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import wavetech.facelocker.R; + +public class LockscreenUtils { + + // Member variables + private OverlayDialog mOverlayDialog; + private OnLockStatusChangedListener mLockStatusChangedListener; + + // Interface to communicate with owner activity + public interface OnLockStatusChangedListener + { + public void onLockStatusChanged(boolean isLocked); + } + + // Reset the variables + public LockscreenUtils() { + reset(); + } + + // Display overlay dialog with a view to prevent home button click + public void lock(Activity activity) { + if (mOverlayDialog == null) { + mOverlayDialog = new OverlayDialog(activity); + mOverlayDialog.show(); + mLockStatusChangedListener = (OnLockStatusChangedListener) activity; + } + } + + // Reset variables + public void reset() { + if (mOverlayDialog != null) { + mOverlayDialog.dismiss(); + mOverlayDialog = null; + } + } + + // Unlock the home button and give callback to unlock the screen + public void unlock() { + if (mOverlayDialog != null) { + mOverlayDialog.dismiss(); + mOverlayDialog = null; + if(mLockStatusChangedListener!=null) + { + mLockStatusChangedListener.onLockStatusChanged(false); + } + } + } + + // Create overlay dialog for lockedscreen to disable hardware buttons + private static class OverlayDialog extends AlertDialog { + + public OverlayDialog(Activity activity) { + super(activity, R.style.OverlayDialog); + LayoutParams params = getWindow().getAttributes(); + params.type = LayoutParams.TYPE_SYSTEM_ERROR; + params.dimAmount = 0.0F; + params.width = 0; + params.height = 0; + params.gravity = Gravity.BOTTOM; + getWindow().setAttributes(params); + getWindow().setFlags(LayoutParams.FLAG_SHOW_WHEN_LOCKED | LayoutParams.FLAG_NOT_TOUCH_MODAL, + 0xffffff); + setOwnerActivity(activity); + setCancelable(false); + } + + // consume touch events + public final boolean dispatchTouchEvent(MotionEvent motionevent) { + return true; + } + + } +} diff --git a/app/src/main/java/wavetech/facelocker/utils/PasswordStore.java b/app/src/main/java/wavetech/facelocker/utils/PasswordStore.java new file mode 100644 index 0000000..7b4c9f3 --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/utils/PasswordStore.java @@ -0,0 +1,116 @@ +package wavetech.facelocker.utils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import java.util.HashMap; +import java.util.Map; + +import wavetech.facelocker.CameraActivity; + +public class PasswordStore { + private static final String StorageKey = "Passwords" ; + private SharedPreferences sharedpreferences; + private String patternCode; + private String pinCode; + private JSONObject faces; + private boolean isScreenLockEnabled; + private static String currentFaceName; + + public PasswordStore(Context context){ + sharedpreferences = context.getSharedPreferences(StorageKey, Context.MODE_PRIVATE); + isScreenLockEnabled = sharedpreferences.getBoolean("isScreenLockEnabled",false); + patternCode = sharedpreferences.getString("patternCode",null); + pinCode = sharedpreferences.getString("pinCode",null); + JSONParser parser=new JSONParser(); + try { + faces= (JSONObject)parser.parse(sharedpreferences.getString("faces","{}")); //sharedpreferences.get + } + catch (Exception e){ + faces=new JSONObject(); + Log.e(CameraActivity.TAG,"JSON object faces error: "+e.getMessage()); + } + //faces.put("Israel",1); + } + + public String getCurrentFaceName() { + return currentFaceName; + } + + public void setCurrentFaceName(String currentFaceName) { + this.currentFaceName = currentFaceName; + Log.v(CameraActivity.TAG,"Setting Current face: "+ getCurrentFaceName()); + } + + public Map getFaces() { + return faces; + } + public int getIncrementFaceLabel(){ + int label=0; + for ( Long faceLabel: getFaces().values()){ + if(faceLabel.intValue() > label) + label=faceLabel.intValue(); + } + label++; + return label; + } + public void clearFaces(){ + faces.clear(); + } + + public void addFace(String name,int label){ + faces.put(name,label); + } + + public boolean hasFace(String name){ + return faces.containsKey(name); + } + public boolean hasFaceLabel(int label){ + return getFaces().values().contains(new Long(label)); + } + + public String getPatternCode() { + return patternCode; + } + + public void setPatternCode(String patternCode) { + this.patternCode = patternCode; + } + + public String getPinCode() { + return pinCode; + } + + public void setPinCode(String pinCode) { + this.pinCode = pinCode; + } + + public void setIsScreenLockEnabled(boolean isScreenLockEnabled) { + this.isScreenLockEnabled = isScreenLockEnabled; + } + + public boolean getIsScreenLockEnabled() { + return isScreenLockEnabled; + } + + public void save(){ + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putBoolean("isScreenLockEnabled",isScreenLockEnabled); + editor.putString("patternCode", patternCode); + editor.putString("pinCode", pinCode); + editor.putString("faces", (faces).toJSONString()); + editor.apply(); + } + public void reset(){ + setPatternCode(null); + setPinCode(null); + setIsScreenLockEnabled(false); + faces.clear(); + save(); + } + +} diff --git a/app/src/main/java/wavetech/facelocker/utils/StorageHelper.java b/app/src/main/java/wavetech/facelocker/utils/StorageHelper.java new file mode 100644 index 0000000..f74dd94 --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/utils/StorageHelper.java @@ -0,0 +1,157 @@ +package wavetech.facelocker.utils; + +// +// Copyright (c) nganluong. All rights reserved. +// Licensed under the MIT license. +// +// nganluong Cognitive Services (formerly Project FaceApi): https://www.nganluong.com/cognitive-services +// +// nganluong Cognitive Services (formerly Project FaceApi) GitHub: +// https://github.com/nganluong/Cognitive-Face-Android +// +// Copyright (c) nganluong Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +import android.content.Context; +import android.util.Log; + +import org.bytedeco.javacpp.opencv_core.Mat; +import org.bytedeco.javacpp.opencv_objdetect.CascadeClassifier; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.bytedeco.javacpp.opencv_imgcodecs.imwrite; +import static org.bytedeco.javacpp.opencv_imgproc.COLOR_RGB2BGR; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; + +/** + * Defined several functions to manage local storage. + */ +public class StorageHelper { + final static String TAG = "StorageHelper"; + + public static CascadeClassifier loadClassifierCascade(Context context, int resId) { + FileOutputStream fos = null; + InputStream inputStream; + + inputStream = context.getResources().openRawResource(resId); + File xmlDir = context.getDir("xml", Context.MODE_PRIVATE); + File cascadeFile = new File(xmlDir, "temp.xml"); + try { + fos = new FileOutputStream(cascadeFile); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + Log.d(TAG, "Can\'t load the cascade file"); + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + CascadeClassifier detector = new CascadeClassifier(cascadeFile.getAbsolutePath()); + if (detector.isNull()) { + Log.e(TAG, "Failed to load cascade classifier"); + detector = null; + } else { + Log.i(TAG, "Loaded cascade classifier from " + cascadeFile.getAbsolutePath()); + } + // delete the temporary directory + cascadeFile.delete(); + + return detector; + } + + public static File loadXmlFromRes2File(Context context, int resId, String filename) { + FileOutputStream fos = null; + InputStream inputStream; + + inputStream = context.getResources().openRawResource(resId); + File trainDir = context.getDir("xml", Context.MODE_PRIVATE); + File trainFile = new File(trainDir, filename); + try { + fos = new FileOutputStream(trainFile); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + Log.d(TAG, "Can\'t load the train file"); + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + return trainFile; + } + + public static File saveMat2File(Mat mat, String filePath, String fileName) { + File path = new File(filePath); + if (!path.exists()) { + path.mkdir(); + } + File file = new File(path, fileName); + Mat mat2Save = new Mat(); + cvtColor(mat, mat2Save, COLOR_RGB2BGR); + boolean result = imwrite(file.toString(), mat2Save); + mat2Save.release(); + if (result) + return file; + else + return null; + } +} diff --git a/app/src/main/java/wavetech/facelocker/utils/TourHelper.java b/app/src/main/java/wavetech/facelocker/utils/TourHelper.java new file mode 100644 index 0000000..ee53ff9 --- /dev/null +++ b/app/src/main/java/wavetech/facelocker/utils/TourHelper.java @@ -0,0 +1,23 @@ +package wavetech.facelocker.utils; + +import android.app.Activity; +import android.content.Context; +import android.view.View; + +import com.getkeepsafe.taptargetview.TapTarget; +import com.getkeepsafe.taptargetview.TapTargetView; + +public class TourHelper { + public static void showTourForView(Activity context, View targetView, String title, String description, TapTargetView.Listener listener){ + TapTargetView.showFor(context, + TapTarget.forView(targetView,title,description) + .transparentTarget(true) + ,listener); + } + public static void showTourForView(Activity context, View targetView, String title, String description){ + TapTargetView.showFor(context, + TapTarget.forView(targetView,title,description) + .transparentTarget(true) + ); + } +} diff --git a/app/src/main/jniLibs/armeabi/libavcodec.so b/app/src/main/jniLibs/armeabi/libavcodec.so new file mode 100644 index 0000000..20f6363 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libavcodec.so differ diff --git a/app/src/main/jniLibs/armeabi/libavdevice.so b/app/src/main/jniLibs/armeabi/libavdevice.so new file mode 100644 index 0000000..902029d Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libavdevice.so differ diff --git a/app/src/main/jniLibs/armeabi/libavfilter.so b/app/src/main/jniLibs/armeabi/libavfilter.so new file mode 100644 index 0000000..cbb9c5e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libavfilter.so differ diff --git a/app/src/main/jniLibs/armeabi/libavformat.so b/app/src/main/jniLibs/armeabi/libavformat.so new file mode 100644 index 0000000..5f68828 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libavformat.so differ diff --git a/app/src/main/jniLibs/armeabi/libavutil.so b/app/src/main/jniLibs/armeabi/libavutil.so new file mode 100644 index 0000000..050aff0 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libavutil.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniavcodec.so b/app/src/main/jniLibs/armeabi/libjniavcodec.so new file mode 100644 index 0000000..bc7856f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniavcodec.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniavdevice.so b/app/src/main/jniLibs/armeabi/libjniavdevice.so new file mode 100644 index 0000000..c896375 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniavdevice.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniavfilter.so b/app/src/main/jniLibs/armeabi/libjniavfilter.so new file mode 100644 index 0000000..684ea21 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniavfilter.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniavformat.so b/app/src/main/jniLibs/armeabi/libjniavformat.so new file mode 100644 index 0000000..769484f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniavformat.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniavutil.so b/app/src/main/jniLibs/armeabi/libjniavutil.so new file mode 100644 index 0000000..f012471 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniavutil.so differ diff --git a/app/src/main/jniLibs/armeabi/libjnicvkernels.so b/app/src/main/jniLibs/armeabi/libjnicvkernels.so new file mode 100644 index 0000000..c7bb53c Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjnicvkernels.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_aruco.so b/app/src/main/jniLibs/armeabi/libjniopencv_aruco.so new file mode 100644 index 0000000..457d344 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_aruco.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_bgsegm.so b/app/src/main/jniLibs/armeabi/libjniopencv_bgsegm.so new file mode 100644 index 0000000..01bbd84 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_bgsegm.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_bioinspired.so b/app/src/main/jniLibs/armeabi/libjniopencv_bioinspired.so new file mode 100644 index 0000000..9f39bbe Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_bioinspired.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_calib3d.so b/app/src/main/jniLibs/armeabi/libjniopencv_calib3d.so new file mode 100644 index 0000000..34c4cf1 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_calib3d.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_core.so b/app/src/main/jniLibs/armeabi/libjniopencv_core.so new file mode 100644 index 0000000..38d5ed6 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_core.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_dnn.so b/app/src/main/jniLibs/armeabi/libjniopencv_dnn.so new file mode 100644 index 0000000..7c10570 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_dnn.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_face.so b/app/src/main/jniLibs/armeabi/libjniopencv_face.so new file mode 100644 index 0000000..2914466 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_face.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_features2d.so b/app/src/main/jniLibs/armeabi/libjniopencv_features2d.so new file mode 100644 index 0000000..78731f7 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_features2d.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_flann.so b/app/src/main/jniLibs/armeabi/libjniopencv_flann.so new file mode 100644 index 0000000..3f57d4e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_flann.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_highgui.so b/app/src/main/jniLibs/armeabi/libjniopencv_highgui.so new file mode 100644 index 0000000..a46cd7e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_highgui.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_img_hash.so b/app/src/main/jniLibs/armeabi/libjniopencv_img_hash.so new file mode 100644 index 0000000..53ceb09 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_img_hash.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_imgcodecs.so b/app/src/main/jniLibs/armeabi/libjniopencv_imgcodecs.so new file mode 100644 index 0000000..1ae4e4f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_imgcodecs.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_imgproc.so b/app/src/main/jniLibs/armeabi/libjniopencv_imgproc.so new file mode 100644 index 0000000..1bfaaf5 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_imgproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_java.so b/app/src/main/jniLibs/armeabi/libjniopencv_java.so new file mode 100644 index 0000000..ec5e716 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_java.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_ml.so b/app/src/main/jniLibs/armeabi/libjniopencv_ml.so new file mode 100644 index 0000000..00523c6 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_ml.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_objdetect.so b/app/src/main/jniLibs/armeabi/libjniopencv_objdetect.so new file mode 100644 index 0000000..6890a4e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_objdetect.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_optflow.so b/app/src/main/jniLibs/armeabi/libjniopencv_optflow.so new file mode 100644 index 0000000..591f7e3 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_optflow.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_phase_unwrapping.so b/app/src/main/jniLibs/armeabi/libjniopencv_phase_unwrapping.so new file mode 100644 index 0000000..6705a3b Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_phase_unwrapping.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_photo.so b/app/src/main/jniLibs/armeabi/libjniopencv_photo.so new file mode 100644 index 0000000..b087422 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_photo.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_plot.so b/app/src/main/jniLibs/armeabi/libjniopencv_plot.so new file mode 100644 index 0000000..8b8f77c Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_plot.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_saliency.so b/app/src/main/jniLibs/armeabi/libjniopencv_saliency.so new file mode 100644 index 0000000..d2151c5 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_saliency.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_shape.so b/app/src/main/jniLibs/armeabi/libjniopencv_shape.so new file mode 100644 index 0000000..b0a50dd Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_shape.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_stitching.so b/app/src/main/jniLibs/armeabi/libjniopencv_stitching.so new file mode 100644 index 0000000..625a2e0 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_stitching.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_structured_light.so b/app/src/main/jniLibs/armeabi/libjniopencv_structured_light.so new file mode 100644 index 0000000..785ec3f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_structured_light.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_superres.so b/app/src/main/jniLibs/armeabi/libjniopencv_superres.so new file mode 100644 index 0000000..2005d47 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_superres.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_text.so b/app/src/main/jniLibs/armeabi/libjniopencv_text.so new file mode 100644 index 0000000..7c5a26d Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_text.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_tracking.so b/app/src/main/jniLibs/armeabi/libjniopencv_tracking.so new file mode 100644 index 0000000..0158b65 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_tracking.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_video.so b/app/src/main/jniLibs/armeabi/libjniopencv_video.so new file mode 100644 index 0000000..2e5e297 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_video.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_videoio.so b/app/src/main/jniLibs/armeabi/libjniopencv_videoio.so new file mode 100644 index 0000000..2a81eb1 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_videoio.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_videostab.so b/app/src/main/jniLibs/armeabi/libjniopencv_videostab.so new file mode 100644 index 0000000..6526037 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_videostab.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_xfeatures2d.so b/app/src/main/jniLibs/armeabi/libjniopencv_xfeatures2d.so new file mode 100644 index 0000000..dabe07a Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_xfeatures2d.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_ximgproc.so b/app/src/main/jniLibs/armeabi/libjniopencv_ximgproc.so new file mode 100644 index 0000000..610f74a Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_ximgproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniopencv_xphoto.so b/app/src/main/jniLibs/armeabi/libjniopencv_xphoto.so new file mode 100644 index 0000000..db5ded7 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniopencv_xphoto.so differ diff --git a/app/src/main/jniLibs/armeabi/libjnipostproc.so b/app/src/main/jniLibs/armeabi/libjnipostproc.so new file mode 100644 index 0000000..d34e48e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjnipostproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniswresample.so b/app/src/main/jniLibs/armeabi/libjniswresample.so new file mode 100644 index 0000000..16f8f83 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniswresample.so differ diff --git a/app/src/main/jniLibs/armeabi/libjniswscale.so b/app/src/main/jniLibs/armeabi/libjniswscale.so new file mode 100644 index 0000000..d9d63fa Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libjniswscale.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_aruco.so b/app/src/main/jniLibs/armeabi/libopencv_aruco.so new file mode 100644 index 0000000..cc6a2ff Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_aruco.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_bgsegm.so b/app/src/main/jniLibs/armeabi/libopencv_bgsegm.so new file mode 100644 index 0000000..4ca5f9f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_bgsegm.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_bioinspired.so b/app/src/main/jniLibs/armeabi/libopencv_bioinspired.so new file mode 100644 index 0000000..44a6403 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_bioinspired.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_calib3d.so b/app/src/main/jniLibs/armeabi/libopencv_calib3d.so new file mode 100644 index 0000000..77b4a86 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_calib3d.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_core.so b/app/src/main/jniLibs/armeabi/libopencv_core.so new file mode 100644 index 0000000..5d653a2 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_core.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_dnn.so b/app/src/main/jniLibs/armeabi/libopencv_dnn.so new file mode 100644 index 0000000..ad982cb Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_dnn.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_face.so b/app/src/main/jniLibs/armeabi/libopencv_face.so new file mode 100644 index 0000000..1d31c2a Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_face.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_features2d.so b/app/src/main/jniLibs/armeabi/libopencv_features2d.so new file mode 100644 index 0000000..4b94d75 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_features2d.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_flann.so b/app/src/main/jniLibs/armeabi/libopencv_flann.so new file mode 100644 index 0000000..899eec4 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_flann.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_highgui.so b/app/src/main/jniLibs/armeabi/libopencv_highgui.so new file mode 100644 index 0000000..0901d73 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_highgui.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_img_hash.so b/app/src/main/jniLibs/armeabi/libopencv_img_hash.so new file mode 100644 index 0000000..e1ea8d2 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_img_hash.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_imgcodecs.so b/app/src/main/jniLibs/armeabi/libopencv_imgcodecs.so new file mode 100644 index 0000000..5735da3 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_imgcodecs.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_imgproc.so b/app/src/main/jniLibs/armeabi/libopencv_imgproc.so new file mode 100644 index 0000000..9028744 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_imgproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_java.so b/app/src/main/jniLibs/armeabi/libopencv_java.so new file mode 100644 index 0000000..b0cc539 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_java.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_java3.so b/app/src/main/jniLibs/armeabi/libopencv_java3.so new file mode 100644 index 0000000..4d61c60 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_java3.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_ml.so b/app/src/main/jniLibs/armeabi/libopencv_ml.so new file mode 100644 index 0000000..62a61c3 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_ml.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_objdetect.so b/app/src/main/jniLibs/armeabi/libopencv_objdetect.so new file mode 100644 index 0000000..14fa486 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_objdetect.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_optflow.so b/app/src/main/jniLibs/armeabi/libopencv_optflow.so new file mode 100644 index 0000000..070791f Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_optflow.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_phase_unwrapping.so b/app/src/main/jniLibs/armeabi/libopencv_phase_unwrapping.so new file mode 100644 index 0000000..9dda02d Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_phase_unwrapping.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_photo.so b/app/src/main/jniLibs/armeabi/libopencv_photo.so new file mode 100644 index 0000000..8523b4e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_photo.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_plot.so b/app/src/main/jniLibs/armeabi/libopencv_plot.so new file mode 100644 index 0000000..be6d5c4 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_plot.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_saliency.so b/app/src/main/jniLibs/armeabi/libopencv_saliency.so new file mode 100644 index 0000000..b584651 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_saliency.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_shape.so b/app/src/main/jniLibs/armeabi/libopencv_shape.so new file mode 100644 index 0000000..cf85774 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_shape.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_stitching.so b/app/src/main/jniLibs/armeabi/libopencv_stitching.so new file mode 100644 index 0000000..ddc055b Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_stitching.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_structured_light.so b/app/src/main/jniLibs/armeabi/libopencv_structured_light.so new file mode 100644 index 0000000..60da47c Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_structured_light.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_superres.so b/app/src/main/jniLibs/armeabi/libopencv_superres.so new file mode 100644 index 0000000..effae92 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_superres.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_text.so b/app/src/main/jniLibs/armeabi/libopencv_text.so new file mode 100644 index 0000000..b294799 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_text.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_tracking.so b/app/src/main/jniLibs/armeabi/libopencv_tracking.so new file mode 100644 index 0000000..ebc6e55 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_tracking.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_video.so b/app/src/main/jniLibs/armeabi/libopencv_video.so new file mode 100644 index 0000000..e97c21e Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_video.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_videoio.so b/app/src/main/jniLibs/armeabi/libopencv_videoio.so new file mode 100644 index 0000000..6f52fb3 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_videoio.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_videostab.so b/app/src/main/jniLibs/armeabi/libopencv_videostab.so new file mode 100644 index 0000000..ec5d629 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_videostab.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_xfeatures2d.so b/app/src/main/jniLibs/armeabi/libopencv_xfeatures2d.so new file mode 100644 index 0000000..725d7e0 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_xfeatures2d.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_ximgproc.so b/app/src/main/jniLibs/armeabi/libopencv_ximgproc.so new file mode 100644 index 0000000..d020e16 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_ximgproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libopencv_xphoto.so b/app/src/main/jniLibs/armeabi/libopencv_xphoto.so new file mode 100644 index 0000000..cf854ad Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libopencv_xphoto.so differ diff --git a/app/src/main/jniLibs/armeabi/libpostproc.so b/app/src/main/jniLibs/armeabi/libpostproc.so new file mode 100644 index 0000000..c7e7808 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libpostproc.so differ diff --git a/app/src/main/jniLibs/armeabi/libswresample.so b/app/src/main/jniLibs/armeabi/libswresample.so new file mode 100644 index 0000000..859b049 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libswresample.so differ diff --git a/app/src/main/jniLibs/armeabi/libswscale.so b/app/src/main/jniLibs/armeabi/libswscale.so new file mode 100644 index 0000000..5dd557b Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libswscale.so differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..84330a7 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/add_icon.png b/app/src/main/res/drawable/add_icon.png new file mode 100644 index 0000000..8643052 Binary files /dev/null and b/app/src/main/res/drawable/add_icon.png differ diff --git a/app/src/main/res/drawable/background.jpg b/app/src/main/res/drawable/background.jpg new file mode 100644 index 0000000..f2e58b9 Binary files /dev/null and b/app/src/main/res/drawable/background.jpg differ diff --git a/app/src/main/res/drawable/btn_round_danger.xml b/app/src/main/res/drawable/btn_round_danger.xml new file mode 100644 index 0000000..395004c --- /dev/null +++ b/app/src/main/res/drawable/btn_round_danger.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/face_icon3.png b/app/src/main/res/drawable/face_icon3.png new file mode 100644 index 0000000..6cd5e2d Binary files /dev/null and b/app/src/main/res/drawable/face_icon3.png differ diff --git a/app/src/main/res/drawable/face_icon_logo.png b/app/src/main/res/drawable/face_icon_logo.png new file mode 100644 index 0000000..705170a Binary files /dev/null and b/app/src/main/res/drawable/face_icon_logo.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..cdfecfb --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/remove.png b/app/src/main/res/drawable/remove.png new file mode 100644 index 0000000..c562408 Binary files /dev/null and b/app/src/main/res/drawable/remove.png differ diff --git a/app/src/main/res/font/roboto_black.ttf b/app/src/main/res/font/roboto_black.ttf new file mode 100644 index 0000000..71f01ac Binary files /dev/null and b/app/src/main/res/font/roboto_black.ttf differ diff --git a/app/src/main/res/font/roboto_bold.ttf b/app/src/main/res/font/roboto_bold.ttf new file mode 100644 index 0000000..aaf374d Binary files /dev/null and b/app/src/main/res/font/roboto_bold.ttf differ diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf new file mode 100644 index 0000000..aa00de0 Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ diff --git a/app/src/main/res/font/roboto_thin.ttf b/app/src/main/res/font/roboto_thin.ttf new file mode 100644 index 0000000..d262d14 Binary files /dev/null and b/app/src/main/res/font/roboto_thin.ttf differ diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml new file mode 100644 index 0000000..b663616 --- /dev/null +++ b/app/src/main/res/layout/activity_camera.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_listview.xml b/app/src/main/res/layout/activity_listview.xml new file mode 100644 index 0000000..9f4c4d8 --- /dev/null +++ b/app/src/main/res/layout/activity_listview.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_lock_screen.xml b/app/src/main/res/layout/activity_lock_screen.xml new file mode 100644 index 0000000..c386830 --- /dev/null +++ b/app/src/main/res/layout/activity_lock_screen.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..6a56e6b --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_pin_code.xml b/app/src/main/res/layout/activity_pin_code.xml new file mode 100644 index 0000000..2f2240d --- /dev/null +++ b/app/src/main/res/layout/activity_pin_code.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + +