├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── ttvface.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── livedemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── com │ │ │ └── ttv │ │ │ │ └── livedemo │ │ │ │ ├── ActivationActivity.java │ │ │ │ ├── Base.java │ │ │ │ ├── CameraActivity.java │ │ │ │ ├── FaceRectTransformer.java │ │ │ │ ├── FaceRectView.java │ │ │ │ ├── FileChooser.java │ │ │ │ ├── LivenessDetectorProcesser.java │ │ │ │ └── PermissionsDelegate.java │ │ └── qrcode │ │ │ ├── QrCodeActivity.java │ │ │ ├── camera │ │ │ ├── AutoFocusCallback.java │ │ │ ├── CameraConfigurationManager.java │ │ │ ├── CameraManager.java │ │ │ └── PreviewCallback.java │ │ │ ├── decode │ │ │ ├── CaptureActivityHandler.java │ │ │ ├── DecodeHandler.java │ │ │ ├── DecodeImageCallback.java │ │ │ ├── DecodeImageThread.java │ │ │ ├── DecodeManager.java │ │ │ ├── DecodeThread.java │ │ │ ├── FinishListener.java │ │ │ └── InactivityTimer.java │ │ │ ├── utils │ │ │ ├── QrUtils.java │ │ │ └── ScreenUtils.java │ │ │ └── view │ │ │ └── QrCodeFinderView.java │ └── res │ │ ├── anim │ │ └── fade_in.xml │ │ ├── drawable │ │ ├── back.png │ │ ├── ic_baseline_credit_card_24.xml │ │ ├── ic_camera.xml │ │ ├── ic_done.png │ │ ├── ic_gallery.xml │ │ ├── ic_history.xml │ │ ├── ic_id_front.png │ │ ├── ic_launcher_background.xml │ │ ├── ic_outline_contact_mail_24.xml │ │ ├── ic_outline_credit_card_24.xml │ │ ├── ic_outline_photo_camera_24.xml │ │ ├── ic_refresh.xml │ │ ├── ic_start.xml │ │ ├── ic_stop.xml │ │ └── surlogo.png │ │ ├── font │ │ └── poppins_medium.xml │ │ ├── layout │ │ ├── activity_activation.xml │ │ ├── activity_camera.xml │ │ ├── activity_qr_code.xml │ │ └── layout_qr_code_scanner.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── font_certs.xml │ │ ├── ids.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── livedemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Slack 3 | · 4 | Website 5 | · 6 | Portfolio 7 | · 8 | Hugging Face 9 | · 10 | Free APIs 11 | · 12 | OpenKYC 13 | · 14 | Face Attendance 15 | · 16 | Contact 17 |
18 |

Face Liveness Detection (Anti Spoofing) SDK For Android

19 |

Robust, Realtime, On-Device Face Liveness Detection

20 | 21 | ### Documentation at https://docs.faceonlive.com 22 | 23 | ## :tada: Try It Yourself 24 | ### - Lite Version 25 | 26 | Get it on Google Play 27 | 28 | 29 | ### - Advanced Version (✨NEW) 30 | 31 | Get it on Google Play 32 | 33 | 34 | # 35 | Integrated into [Huggingface Spaces 🤗](https://huggingface.co/spaces) using [Gradio](https://github.com/gradio-app/gradio). Try out the Web Demo: [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/FaceOnLive/Face-Liveness-Detection-SDK) 36 | # 37 | https://user-images.githubusercontent.com/91896009/137519069-d4957c01-a6f1-4283-b5f0-844bc36b06e1.mp4 38 | 39 | 40 | ## :clap: Supporters 41 | [![Stargazers repo roster for @faceonlive/Face-Liveness-Detection-SDK-Android](https://reporoster.com/stars/faceonlive/Face-Liveness-Detection-SDK-Android)](https://github.com/faceonlive/Face-Liveness-Detection-SDK-Android/stargazers) 42 | [![Forkers repo roster for @faceonlive/Face-Liveness-Detection-SDK-Android](https://reporoster.com/forks/faceonlive/Face-Liveness-Detection-SDK-Android)](https://github.com/faceonlive/Face-Liveness-Detection-SDK-Android/network/members) 43 |

Animated footer bars

44 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | dataBinding { 10 | enabled = true 11 | } 12 | 13 | defaultConfig { 14 | applicationId "com.ttv.livedemo" 15 | minSdkVersion 21 16 | targetSdkVersion 30 17 | versionCode 14 18 | versionName "1.4" 19 | multiDexEnabled true 20 | 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | 23 | // ndk { 24 | // abiFilters 'armeabi-v7a' 25 | // } 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | } 39 | 40 | repositories { 41 | maven { url 'https://jitpack.io' } 42 | maven { 43 | url 'https://alphacephei.com/maven/' 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation 'androidx.appcompat:appcompat:1.3.1' 50 | implementation 'com.google.android.material:material:1.4.0' 51 | implementation 'androidx.constraintlayout:constraintlayout:2.1.0' 52 | 53 | implementation files('libs/ttvface.aar') 54 | implementation 'com.google.zxing:core:3.4.0' 55 | implementation 'com.github.d-max:spots-dialog:1.1@aar' 56 | 57 | // implementation 'io.fotoapparat:facedetector:1.0.0' 58 | implementation 'io.fotoapparat.fotoapparat:library:1.0.4' 59 | 60 | testImplementation 'junit:junit:4.+' 61 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 63 | } -------------------------------------------------------------------------------- /app/libs/ttvface.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaceOnLive/Face-Liveness-Detection-SDK-Android/3c771cdd8bff26afb4f2019bfcdf873db43fe91b/app/libs/ttvface.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/livedemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.example.ttvlivedemo", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/ActivationActivity.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.Manifest; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.graphics.Bitmap; 9 | import android.graphics.Camera; 10 | import android.graphics.Color; 11 | import android.net.Uri; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.os.Environment; 15 | import android.provider.Settings; 16 | import android.util.Log; 17 | import android.view.View; 18 | import android.widget.Button; 19 | import android.widget.EditText; 20 | import android.widget.Toast; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.appcompat.app.AppCompatActivity; 24 | import androidx.core.app.ActivityCompat; 25 | import androidx.core.content.ContextCompat; 26 | 27 | import com.google.zxing.BarcodeFormat; 28 | import com.google.zxing.WriterException; 29 | import com.google.zxing.common.BitMatrix; 30 | import com.google.zxing.qrcode.QRCodeWriter; 31 | import com.ttv.face.FaceEngine; 32 | 33 | import java.io.File; 34 | 35 | import qrcode.QrCodeActivity; 36 | 37 | import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 38 | import static android.Manifest.permission.READ_PHONE_STATE; 39 | import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 40 | import static android.os.Build.VERSION.SDK_INT; 41 | 42 | public class ActivationActivity extends AppCompatActivity implements View.OnClickListener { 43 | 44 | private static final String TAG = "ActivationActivity"; 45 | 46 | final static int REQUEST_CODE = 3333; 47 | 48 | private EditText mEditHWID; 49 | private String mHWID; 50 | private File mLastFile; 51 | private Context mContext; 52 | // private FaceSDK mFaceSDK = null; 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | setContentView(R.layout.activity_activation); 58 | 59 | mContext = this; 60 | ((Button) findViewById(R.id.btnHWID)).setOnClickListener(this); 61 | ((Button) findViewById(R.id.btnLicense)).setOnClickListener(this); 62 | ((Button) findViewById(R.id.btnScan)).setOnClickListener(this); 63 | ((Button) findViewById(R.id.btnSendEmail)).setOnClickListener(this); 64 | ((Button) findViewById(R.id.btnSetActivate)).setOnClickListener(this); 65 | 66 | 67 | // mFaceSDK = new FaceEngine(this); 68 | // mHWID = mFaceSDK.getCurrentHWID(); 69 | mEditHWID = (EditText) findViewById(R.id.editHWID); 70 | 71 | mEditHWID.setText(mHWID); 72 | 73 | updateQRCode(); 74 | } 75 | 76 | @Override 77 | public void onResume() { 78 | super.onResume(); 79 | 80 | if (permission()) { 81 | // openNewScreen(); 82 | } else { 83 | RequestPermission_Dialog(); 84 | } 85 | } 86 | 87 | public boolean permission() { 88 | if (SDK_INT >= Build.VERSION_CODES.R) { 89 | return Environment.isExternalStorageManager(); 90 | } else { 91 | int write = ContextCompat.checkSelfPermission(getApplicationContext(), WRITE_EXTERNAL_STORAGE); 92 | int read = ContextCompat.checkSelfPermission(getApplicationContext(), READ_EXTERNAL_STORAGE); 93 | int phoneState = ContextCompat.checkSelfPermission(getApplicationContext(), READ_PHONE_STATE); 94 | return write == PackageManager.PERMISSION_GRANTED && read == PackageManager.PERMISSION_GRANTED && phoneState == PackageManager.PERMISSION_GRANTED; 95 | } 96 | } 97 | 98 | private void openNewScreen() { 99 | 100 | } 101 | 102 | public void RequestPermission_Dialog() { 103 | if (SDK_INT >= Build.VERSION_CODES.R) { 104 | try { 105 | Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); 106 | intent.addCategory("android.intent.category.DEFAULT"); 107 | intent.setData(Uri.parse(String.format("package:%s", new Object[]{getApplicationContext().getPackageName()}))); 108 | startActivityForResult(intent, 2000); 109 | } catch (Exception e) { 110 | Intent obj = new Intent(); 111 | obj.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); 112 | startActivityForResult(obj, 2000); 113 | } 114 | } else { 115 | ActivityCompat.requestPermissions(ActivationActivity.this, new String[]{WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE, READ_PHONE_STATE}, REQUEST_CODE); 116 | } 117 | } 118 | 119 | @Override 120 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 121 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 122 | switch (requestCode) { 123 | case REQUEST_CODE: 124 | if (grantResults.length > 0) { 125 | boolean storage = grantResults[0] == PackageManager.PERMISSION_GRANTED; 126 | boolean read = grantResults[1] == PackageManager.PERMISSION_GRANTED; 127 | boolean phoneState = grantResults[2] == PackageManager.PERMISSION_GRANTED; 128 | if (storage && read && phoneState) { 129 | openNewScreen(); 130 | } else { 131 | //permission denied 132 | } 133 | } 134 | break; 135 | case 1: 136 | if (grantResults.length > 0) { 137 | boolean storage = grantResults[0] == PackageManager.PERMISSION_GRANTED; 138 | if (storage) { 139 | Intent intent = new Intent(this, QrCodeActivity.class); 140 | startActivityForResult(intent, 0); 141 | } else { 142 | //permission denied 143 | } 144 | } 145 | break; 146 | } 147 | } 148 | 149 | private int askForPermission(String permission, Integer requestCode) { 150 | 151 | if (ContextCompat.checkSelfPermission(ActivationActivity.this, permission) != PackageManager.PERMISSION_GRANTED) { 152 | 153 | // Should we show an explanation? 154 | if (ActivityCompat.shouldShowRequestPermissionRationale(ActivationActivity.this, permission)) { 155 | 156 | //This is called if user has denied the permission before 157 | //In this case I am just asking the permission again 158 | ActivityCompat.requestPermissions(ActivationActivity.this, new String[]{permission}, requestCode); 159 | } else { 160 | 161 | ActivityCompat.requestPermissions(ActivationActivity.this, new String[]{permission}, requestCode); 162 | } 163 | return 0; 164 | } else { 165 | return 1; 166 | } 167 | } 168 | 169 | @Override 170 | public void onClick(View v) { 171 | 172 | switch (v.getId()) { 173 | case R.id.btnHWID: { 174 | FileChooser fileChooser = new FileChooser(ActivationActivity.this, "Select folder", FileChooser.DialogType.SELECT_DIRECTORY, mLastFile); 175 | FileChooser.FileSelectionCallback callback = new FileChooser.FileSelectionCallback() { 176 | 177 | @Override 178 | public void onSelect(File file) { 179 | //Do something with the selected file 180 | Log.e(TAG, "file path: " + file.getPath()); 181 | mLastFile = file; 182 | 183 | try { 184 | Base.saveStringToFile(mContext, file.getPath() + "/hwid.txt", mHWID); 185 | Toast.makeText(getBaseContext(), "File saved successfully!", 186 | Toast.LENGTH_SHORT).show(); 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | } 190 | 191 | } 192 | }; 193 | fileChooser.show(callback); 194 | 195 | break; 196 | } 197 | case R.id.btnLicense: { 198 | FileChooser fileChooser = new FileChooser(ActivationActivity.this, "Select license file", FileChooser.DialogType.SELECT_FILE, mLastFile); 199 | FileChooser.FileSelectionCallback callback = new FileChooser.FileSelectionCallback() { 200 | 201 | @Override 202 | public void onSelect(File file) { 203 | //Do something with the selected file 204 | Log.e(TAG, "file path: " + file.getPath()); 205 | mLastFile = file; 206 | 207 | try { 208 | String licenseStr = Base.getStringFromFile(file.getPath()); 209 | Log.e(TAG, "licenseStr: " + licenseStr); 210 | 211 | // Log.e(TAG, "setActivation: " + activated); 212 | // 213 | // if (activated != ErrorInfo.MOK) { 214 | // AlertDialog.Builder alertBuilder = new AlertDialog.Builder(mContext); 215 | // alertBuilder.setTitle("Warning").setMessage("Activation Failed!").setPositiveButton(android.R.string.ok, null).show(); 216 | // } else { 217 | // Base.saveStringToFile(mContext, Base.getAppDir(mContext) + "/license.txt", licenseStr); 218 | // Intent intent = new Intent(mContext, CameraActivity.class); 219 | // startActivity(intent); 220 | // finish(); 221 | // } 222 | } catch (Exception e) { 223 | e.printStackTrace(); 224 | } 225 | } 226 | }; 227 | fileChooser.show(callback); 228 | 229 | break; 230 | } 231 | case R.id.btnScan: { 232 | if(askForPermission(Manifest.permission.CAMERA, 1) == 1) { 233 | Intent intent = new Intent(this, QrCodeActivity.class); 234 | startActivityForResult(intent, 0); 235 | } 236 | break; 237 | } 238 | case R.id.btnSendEmail: { 239 | Intent intent = new Intent(Intent.ACTION_SEND); 240 | 241 | String strExportTitle = "Get License"; 242 | 243 | intent.putExtra(Intent.EXTRA_SUBJECT, strExportTitle); 244 | intent.setType("text/plain"); 245 | intent.putExtra(android.content.Intent.EXTRA_TEXT, mHWID); 246 | intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"turing311@outlook.com"}); 247 | startActivity(Intent.createChooser(intent, "send to email")); 248 | break; 249 | } 250 | case R.id.btnSetActivate: { 251 | try { 252 | EditText editLicense = (EditText) findViewById(R.id.editLicense); 253 | String licenseStr = editLicense.getText().toString(); 254 | Log.e(TAG, "licenseStr: " + licenseStr); 255 | 256 | // int activated = mFaceSDK.setActivation(licenseStr); 257 | // Log.e(TAG, "setActivation: " + activated); 258 | // 259 | // if (activated != ErrorInfo.MOK) { 260 | // AlertDialog.Builder alertBuilder = new AlertDialog.Builder(mContext); 261 | // alertBuilder.setTitle("Warning").setMessage("Activation Failed!").setPositiveButton(android.R.string.ok, null).show(); 262 | // } else { 263 | // Base.saveStringToFile(mContext, Base.getAppDir(mContext) + "/license.txt", licenseStr); 264 | // Intent intent = new Intent(mContext, CameraActivity.class); 265 | // startActivity(intent); 266 | // finish(); 267 | // } 268 | } catch (Exception e) { 269 | e.printStackTrace(); 270 | } 271 | break; 272 | } 273 | } 274 | } 275 | 276 | public void updateQRCode() { 277 | if(mHWID == null) 278 | return; 279 | 280 | com.google.zxing.Writer writer = new QRCodeWriter(); 281 | try { 282 | BitMatrix bm = writer.encode(mHWID, BarcodeFormat.QR_CODE, 800, 800); 283 | Bitmap qrcodeBmp = Bitmap.createBitmap(800, 800, Bitmap.Config.ARGB_8888); 284 | for (int i = 0; i < 800; i++) { 285 | for (int j = 0; j < 800; j++) { 286 | qrcodeBmp.setPixel(i, j, bm.get(i, j) ? Color.BLACK : Color.WHITE); 287 | } 288 | } 289 | 290 | // ((ImageView) findViewById(R.id.hwid_qrcode_view)).setImageBitmap(qrcodeBmp); 291 | } catch (WriterException e) { 292 | e.printStackTrace(); 293 | } 294 | } 295 | 296 | @Override 297 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 298 | Log.e(TAG, "requestCode = " + requestCode + " resultCode = " + resultCode); 299 | switch (requestCode) { 300 | case 0: { 301 | if (resultCode == RESULT_OK) { 302 | try { 303 | // String licenseStr = data.getExtras().getString("Result"); 304 | // int activated = mFaceSDK.setActivation(licenseStr); 305 | // Log.e(TAG, "setActivation: " + activated); 306 | // 307 | // if (activated != ErrorInfo.MOK) { 308 | // AlertDialog.Builder alertBuilder = new AlertDialog.Builder(mContext); 309 | // alertBuilder.setTitle("Warning").setMessage("Activation Failed!").setPositiveButton(android.R.string.ok, null).show(); 310 | // } else { 311 | // Base.saveStringToFile(mContext, Base.getAppDir(mContext) + "/license.txt", licenseStr); 312 | // Intent intent = new Intent(mContext, CameraActivity.class); 313 | // startActivity(intent); 314 | // finish(); 315 | // } 316 | } catch (Exception e) { 317 | e.printStackTrace(); 318 | } 319 | } 320 | break; 321 | } 322 | } 323 | } 324 | 325 | } 326 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/Base.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Matrix; 8 | import android.media.MediaScannerConnection; 9 | import android.net.Uri; 10 | import android.util.Log; 11 | 12 | import java.io.BufferedOutputStream; 13 | import java.io.BufferedReader; 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.io.OutputStreamWriter; 21 | 22 | /** 23 | * Created by demid on 4/22/2017. 24 | */ 25 | 26 | public class Base { 27 | 28 | public static String mLastPath = ""; 29 | public static final int SDK_MIN_IMAGE_WIDTH = 1080; 30 | public static final int SDK_MAX_IMAGE_HEIGHT = 1920; 31 | 32 | public static String getAppDir(Context context) { 33 | PackageManager m = context.getPackageManager(); 34 | String s = context.getPackageName(); 35 | try { 36 | PackageInfo p = m.getPackageInfo(s, 0); 37 | return p.applicationInfo.dataDir; 38 | } catch (PackageManager.NameNotFoundException e) { 39 | Log.w("yourtag", "Error Package name not found ", e); 40 | } 41 | 42 | return null; 43 | } 44 | 45 | public static void copyRes(Context context, String path, int resID, String dicFile) { 46 | try { 47 | InputStream inputStream = context.getResources().openRawResource(resID); 48 | byte[] dst = new byte[inputStream.available()]; 49 | inputStream.read(dst); 50 | inputStream.close(); 51 | 52 | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path + dicFile)); 53 | bos.write(dst); 54 | bos.flush(); 55 | bos.close(); 56 | 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | public static String convertStreamToString(InputStream is) throws Exception { 63 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 64 | StringBuilder sb = new StringBuilder(); 65 | String line = null; 66 | while ((line = reader.readLine()) != null) { 67 | sb.append(line).append("\n"); 68 | } 69 | reader.close(); 70 | return sb.toString(); 71 | } 72 | 73 | public static String getStringFromFile(String filePath) throws Exception { 74 | try { 75 | File fl = new File(filePath); 76 | FileInputStream fin = new FileInputStream(fl); 77 | String ret = convertStreamToString(fin); 78 | //Make sure you close all streams. 79 | fin.close(); 80 | return ret; 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | } 84 | return ""; 85 | } 86 | 87 | public static void saveStringToFile(Context context, String filePath, String str) throws Exception { 88 | try { 89 | File fl = new File(filePath); 90 | FileOutputStream fileout = new FileOutputStream(fl); 91 | OutputStreamWriter outputWriter=new OutputStreamWriter(fileout); 92 | outputWriter.write(str); 93 | outputWriter.close(); 94 | 95 | new SingleMediaScanner(context, filePath); 96 | } catch (Exception e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | 101 | public static Bitmap rotateBitmap(Bitmap bitmap, int rotationDegrees, boolean flipX, boolean flipY) { 102 | 103 | Matrix matrix = new Matrix(); 104 | matrix.postRotate(rotationDegrees); 105 | matrix.postScale(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f); 106 | Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 107 | return rotatedBitmap; 108 | } 109 | 110 | public static void saveDataToFile(Context context, String filePath, byte[] data) throws Exception { 111 | File fl = new File(filePath); 112 | FileOutputStream fileout = new FileOutputStream(fl); 113 | BufferedOutputStream outputWriter=new BufferedOutputStream(fileout); 114 | outputWriter.write(data); 115 | outputWriter.close(); 116 | 117 | new SingleMediaScanner(context, filePath); 118 | } 119 | 120 | public static byte[] readBytesFromFile(String filePath) { 121 | 122 | FileInputStream fileInputStream = null; 123 | byte[] bytesArray = null; 124 | 125 | try { 126 | 127 | File file = new File(filePath); 128 | bytesArray = new byte[(int) file.length()]; 129 | 130 | //read file into bytes[] 131 | fileInputStream = new FileInputStream(file); 132 | fileInputStream.read(bytesArray); 133 | 134 | } catch (IOException e) { 135 | e.printStackTrace(); 136 | } finally { 137 | if (fileInputStream != null) { 138 | try { 139 | fileInputStream.close(); 140 | } catch (IOException e) { 141 | e.printStackTrace(); 142 | } 143 | } 144 | 145 | } 146 | 147 | return bytesArray; 148 | 149 | } 150 | 151 | public static 152 | int max(int a, int b) { 153 | return (a < b) ? b : a; 154 | } 155 | 156 | public static int min(int a, int b) { 157 | return (a < b) ? a : b; 158 | } 159 | 160 | public static Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) { 161 | int width = bm.getWidth(); 162 | int height = bm.getHeight(); 163 | float scaleWidth = ((float) newWidth) / width; 164 | float scaleHeight = ((float) newHeight) / height; 165 | // CREATE A MATRIX FOR THE MANIPULATION 166 | Matrix matrix = new Matrix(); 167 | // RESIZE THE BIT MAP 168 | matrix.postScale(scaleWidth, scaleHeight); 169 | 170 | // "RECREATE" THE NEW BITMAP 171 | Bitmap resizedBitmap = Bitmap.createBitmap( 172 | bm, 0, 0, width, height, matrix, false); 173 | bm.recycle(); 174 | return resizedBitmap; 175 | } 176 | 177 | public static Bitmap prepareInputImage(Bitmap bm) { 178 | int max_size = max(bm.getWidth(), bm.getHeight()); 179 | int min_size = min(bm.getWidth(), bm.getHeight()); 180 | 181 | if (max_size <= SDK_MAX_IMAGE_HEIGHT && min_size <= SDK_MIN_IMAGE_WIDTH) 182 | return bm; 183 | 184 | float max_rate = 1.0f * SDK_MAX_IMAGE_HEIGHT / max_size; 185 | float min_rate = 1.0f * SDK_MIN_IMAGE_WIDTH / min_size; 186 | float rate = max_rate < min_rate ? max_rate : min_rate; 187 | 188 | int new_width = (int)(rate * bm.getWidth()); 189 | int new_height = (int)(rate * bm.getHeight()); 190 | 191 | return getResizedBitmap(bm, new_width, new_height); 192 | } 193 | 194 | private static class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient 195 | { 196 | private MediaScannerConnection mMs; 197 | private String mPath; 198 | SingleMediaScanner(Context context, String f) 199 | { 200 | mPath = f; 201 | mMs = new MediaScannerConnection(context, this); 202 | mMs.connect(); 203 | } 204 | @Override 205 | public void onMediaScannerConnected() 206 | { 207 | mMs.scanFile(mPath, null); 208 | } 209 | 210 | @Override 211 | public void onScanCompleted(String path, Uri uri) 212 | { 213 | mMs.disconnect(); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/CameraActivity.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | 4 | import android.app.AlertDialog; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Color; 10 | import android.graphics.Rect; 11 | import android.net.Uri; 12 | import android.os.AsyncTask; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.os.Environment; 16 | import android.provider.Settings; 17 | import android.util.Log; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.ImageView; 21 | import android.widget.TextView; 22 | import android.widget.Toast; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.appcompat.app.AppCompatActivity; 26 | import androidx.core.app.ActivityCompat; 27 | import androidx.core.content.ContextCompat; 28 | 29 | import com.ttv.face.FaceEngine; 30 | import com.ttv.face.FaceResult; 31 | 32 | import org.json.JSONArray; 33 | import org.json.JSONObject; 34 | 35 | import java.io.File; 36 | import java.io.IOException; 37 | import java.util.ArrayList; 38 | import java.util.Collection; 39 | import java.util.List; 40 | 41 | import dmax.dialog.SpotsDialog; 42 | import io.fotoapparat.Fotoapparat; 43 | import io.fotoapparat.FotoapparatSwitcher; 44 | import io.fotoapparat.parameter.LensPosition; 45 | import io.fotoapparat.parameter.Size; 46 | import io.fotoapparat.parameter.selector.SelectorFunction; 47 | import io.fotoapparat.parameter.selector.SizeSelectors; 48 | import io.fotoapparat.result.PendingResult; 49 | import io.fotoapparat.view.CameraView; 50 | 51 | import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 52 | import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 53 | import static android.os.Build.VERSION.SDK_INT; 54 | import static io.fotoapparat.parameter.selector.LensPositionSelectors.lensPosition; 55 | 56 | public class CameraActivity extends AppCompatActivity { 57 | 58 | private final PermissionsDelegate permissionsDelegate = new PermissionsDelegate(this); 59 | private boolean hasPermission; 60 | private CameraView cameraView; 61 | private FaceRectView rectanglesView; 62 | 63 | private FotoapparatSwitcher fotoapparatSwitcher; 64 | private Fotoapparat frontFotoapparat; 65 | private Fotoapparat backFotoapparat; 66 | private FaceRectTransformer faceRectTransformer; 67 | 68 | ImageView back; 69 | TextView resultView,partial; 70 | boolean mSwitchCamera = false; 71 | 72 | @Override 73 | protected void onCreate(Bundle savedInstanceState) { 74 | super.onCreate(savedInstanceState); 75 | setContentView(R.layout.activity_camera); 76 | 77 | resultView=findViewById(R.id.resultView); 78 | partial=findViewById(R.id.partial); 79 | cameraView = (CameraView) findViewById(R.id.camera_view); 80 | rectanglesView = (FaceRectView) findViewById(R.id.rectanglesView); 81 | hasPermission = permissionsDelegate.hasPermissions(); 82 | 83 | FaceEngine.createInstance(this); 84 | if (hasPermission) { 85 | FaceEngine.getInstance().init(); 86 | cameraView.setVisibility(View.VISIBLE); 87 | } else { 88 | permissionsDelegate.requestPermissions(); 89 | } 90 | 91 | frontFotoapparat = createFotoapparat(LensPosition.FRONT); 92 | backFotoapparat = createFotoapparat(LensPosition.BACK); 93 | fotoapparatSwitcher = FotoapparatSwitcher.withDefault(frontFotoapparat); 94 | 95 | View switchCameraButton = findViewById(R.id.switchCamera); 96 | switchCameraButton.setVisibility( 97 | canSwitchCameras() 98 | ? View.VISIBLE 99 | : View.GONE 100 | ); 101 | switchCameraButton.setOnClickListener(new View.OnClickListener() { 102 | @Override 103 | public void onClick(View v) { 104 | switchCamera(); 105 | } 106 | }); 107 | 108 | resultView.setText(R.string.liveness_detection); 109 | rectanglesView.setMode(0); 110 | } 111 | 112 | @Override 113 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 114 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 115 | } 116 | 117 | private boolean canSwitchCameras() { 118 | return frontFotoapparat.isAvailable() == backFotoapparat.isAvailable(); 119 | } 120 | 121 | private Fotoapparat createFotoapparat(LensPosition position) { 122 | return Fotoapparat 123 | .with(this) 124 | .into(cameraView) 125 | .lensPosition(lensPosition(position)) 126 | .frameProcessor( 127 | LivenessDetectorProcesser.with(this) 128 | .listener(new LivenessDetectorProcesser.OnFacesDetectedListener() { 129 | @Override 130 | public void onFacesDetected(List faces, Size frameSize) { 131 | 132 | LensPosition lensPosition; 133 | if (fotoapparatSwitcher.getCurrentFotoapparat() == frontFotoapparat) { 134 | lensPosition = LensPosition.FRONT; 135 | } else { 136 | lensPosition = LensPosition.BACK; 137 | } 138 | 139 | if(faceRectTransformer == null || mSwitchCamera == true) 140 | { 141 | mSwitchCamera =false; 142 | int displayOrientation = 0; 143 | ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(cameraView, 144 | cameraView, rectanglesView, 145 | new Size(frameSize.width, frameSize.height), displayOrientation, 1.0f); 146 | 147 | faceRectTransformer = new FaceRectTransformer( 148 | frameSize.height, frameSize.width, 149 | cameraView.getLayoutParams().width, cameraView.getLayoutParams().height, 150 | displayOrientation, lensPosition, false, 151 | false, 152 | false); 153 | } 154 | 155 | List drawInfoList = new ArrayList<>(); 156 | for(int i = 0; i < faces.size(); i ++) { 157 | Rect rect = faceRectTransformer.adjustRect(new Rect(faces.get(i).left, faces.get(i).top, faces.get(i).right, faces.get(i).bottom)); 158 | 159 | FaceRectView.DrawInfo drawInfo; 160 | if(faces.get(i).livenessScore > 0.5) 161 | drawInfo = new FaceRectView.DrawInfo(rect, 0, 0, 1, Color.GREEN, "", faces.get(i).livenessScore, -1); 162 | else if(faces.get(i).livenessScore < 0) 163 | drawInfo = new FaceRectView.DrawInfo(rect, 0, 0, -1, Color.YELLOW, "", faces.get(i).livenessScore, -1); 164 | else 165 | drawInfo = new FaceRectView.DrawInfo(rect, 0, 0, 0, Color.RED, "", faces.get(i).livenessScore, -1); 166 | drawInfo.setMaskInfo(faces.get(i).mask); 167 | drawInfoList.add(drawInfo); 168 | } 169 | 170 | rectanglesView.clearFaceInfo(); 171 | rectanglesView.addFaceInfo(drawInfoList); 172 | } 173 | }) 174 | .build() 175 | ) 176 | .previewSize(new SelectorFunction() { 177 | @Override 178 | public Size select(Collection collection) { 179 | return new Size(1280, 720); 180 | } 181 | }) 182 | .build(); 183 | } 184 | 185 | private void switchCamera() { 186 | if (fotoapparatSwitcher.getCurrentFotoapparat() == frontFotoapparat) { 187 | fotoapparatSwitcher.switchTo(backFotoapparat); 188 | } else { 189 | fotoapparatSwitcher.switchTo(frontFotoapparat); 190 | } 191 | 192 | mSwitchCamera = true; 193 | } 194 | 195 | @Override 196 | protected void onStart() { 197 | super.onStart(); 198 | if (hasPermission) { 199 | fotoapparatSwitcher.start(); 200 | } 201 | } 202 | 203 | @Override 204 | protected void onResume() { 205 | super.onResume(); 206 | if(permissionsDelegate.hasPermissions() && hasPermission == false) { 207 | hasPermission = true; 208 | 209 | FaceEngine.getInstance().init(); 210 | fotoapparatSwitcher.start(); 211 | cameraView.setVisibility(View.VISIBLE); 212 | } else { 213 | permissionsDelegate.requestPermissions(); 214 | } 215 | } 216 | 217 | @Override 218 | protected void onStop() { 219 | super.onStop(); 220 | if (hasPermission) { 221 | try { 222 | fotoapparatSwitcher.stop(); 223 | } catch (Exception e) { 224 | e.printStackTrace(); 225 | } 226 | } 227 | } 228 | 229 | @Override 230 | public void onDestroy() { 231 | super.onDestroy(); 232 | } 233 | 234 | private ViewGroup.LayoutParams adjustPreviewViewSize(View rgbPreview, View previewView, FaceRectView faceRectView, Size previewSize, int displayOrientation, float scale) { 235 | ViewGroup.LayoutParams layoutParams = previewView.getLayoutParams(); 236 | int measuredWidth = previewView.getMeasuredWidth(); 237 | int measuredHeight = previewView.getMeasuredHeight(); 238 | 239 | layoutParams.width = measuredWidth; 240 | layoutParams.height = measuredHeight; 241 | previewView.setLayoutParams(layoutParams); 242 | faceRectView.setLayoutParams(layoutParams); 243 | return layoutParams; 244 | } 245 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/FaceRectTransformer.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.graphics.Rect; 4 | 5 | import io.fotoapparat.parameter.LensPosition; 6 | 7 | public class FaceRectTransformer { 8 | private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation; 9 | private boolean isMirror; 10 | private boolean mirrorHorizontal = false, mirrorVertical = false; 11 | private LensPosition cameraId; 12 | 13 | public FaceRectTransformer(int previewWidth, int previewHeight, int canvasWidth, 14 | int canvasHeight, int cameraDisplayOrientation, LensPosition cameraId, 15 | boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) { 16 | this.previewWidth = previewWidth; 17 | this.previewHeight = previewHeight; 18 | this.canvasWidth = canvasWidth; 19 | this.canvasHeight = canvasHeight; 20 | this.cameraDisplayOrientation = cameraDisplayOrientation; 21 | this.cameraId = cameraId; 22 | this.isMirror = isMirror; 23 | this.mirrorHorizontal = mirrorHorizontal; 24 | this.mirrorVertical = mirrorVertical; 25 | } 26 | 27 | public Rect adjustRect(Rect ftRect) { 28 | int previewWidth = this.previewWidth; 29 | int previewHeight = this.previewHeight; 30 | int canvasWidth = this.canvasWidth; 31 | int canvasHeight = this.canvasHeight; 32 | int cameraDisplayOrientation = this.cameraDisplayOrientation; 33 | LensPosition cameraId = this.cameraId; 34 | boolean isMirror = this.isMirror; 35 | boolean mirrorHorizontal = this.mirrorHorizontal; 36 | boolean mirrorVertical = this.mirrorVertical; 37 | 38 | if (ftRect == null) { 39 | return null; 40 | } 41 | 42 | Rect rect = new Rect(ftRect); 43 | float horizontalRatio; 44 | float verticalRatio; 45 | if (cameraDisplayOrientation % 180 == 0) { 46 | horizontalRatio = (float) canvasWidth / (float) previewWidth; 47 | verticalRatio = (float) canvasHeight / (float) previewHeight; 48 | } else { 49 | horizontalRatio = (float) canvasHeight / (float) previewWidth; 50 | verticalRatio = (float) canvasWidth / (float) previewHeight; 51 | } 52 | rect.left *= horizontalRatio; 53 | rect.right *= horizontalRatio; 54 | rect.top *= verticalRatio; 55 | rect.bottom *= verticalRatio; 56 | 57 | Rect newRect = new Rect(); 58 | switch (cameraDisplayOrientation) { 59 | case 0: 60 | if (cameraId != LensPosition.FRONT) { 61 | newRect.left = canvasWidth - rect.right; 62 | newRect.right = canvasWidth - rect.left; 63 | } else { 64 | newRect.left = rect.left; 65 | newRect.right = rect.right; 66 | } 67 | newRect.top = rect.top; 68 | newRect.bottom = rect.bottom; 69 | break; 70 | case 90: 71 | newRect.right = canvasWidth - rect.top; 72 | newRect.left = canvasWidth - rect.bottom; 73 | if (cameraId == LensPosition.FRONT) { 74 | newRect.top = canvasHeight - rect.right; 75 | newRect.bottom = canvasHeight - rect.left; 76 | } else { 77 | newRect.top = rect.left; 78 | newRect.bottom = rect.right; 79 | } 80 | break; 81 | case 180: 82 | newRect.top = canvasHeight - rect.bottom; 83 | newRect.bottom = canvasHeight - rect.top; 84 | if (cameraId == LensPosition.FRONT) { 85 | newRect.left = rect.left; 86 | newRect.right = rect.right; 87 | } else { 88 | newRect.left = canvasWidth - rect.right; 89 | newRect.right = canvasWidth - rect.left; 90 | } 91 | break; 92 | case 270: 93 | newRect.left = rect.top; 94 | newRect.right = rect.bottom; 95 | if (cameraId == LensPosition.FRONT) { 96 | newRect.top = rect.left; 97 | newRect.bottom = rect.right; 98 | } else { 99 | newRect.top = canvasHeight - rect.right; 100 | newRect.bottom = canvasHeight - rect.left; 101 | } 102 | break; 103 | default: 104 | break; 105 | } 106 | 107 | /** 108 | * isMirror mirrorHorizontal finalIsMirrorHorizontal 109 | * true true false 110 | * false false false 111 | * true false true 112 | * false true true 113 | * 114 | * XOR 115 | */ 116 | if (isMirror ^ mirrorHorizontal) { 117 | int left = newRect.left; 118 | int right = newRect.right; 119 | newRect.left = canvasWidth - right; 120 | newRect.right = canvasWidth - left; 121 | } 122 | if (mirrorVertical) { 123 | int top = newRect.top; 124 | int bottom = newRect.bottom; 125 | newRect.top = canvasHeight - bottom; 126 | newRect.bottom = canvasHeight - top; 127 | } 128 | return newRect; 129 | } 130 | 131 | public void setPreviewWidth(int previewWidth) { 132 | this.previewWidth = previewWidth; 133 | } 134 | 135 | public void setPreviewHeight(int previewHeight) { 136 | this.previewHeight = previewHeight; 137 | } 138 | 139 | public void setCanvasWidth(int canvasWidth) { 140 | this.canvasWidth = canvasWidth; 141 | } 142 | 143 | public void setCanvasHeight(int canvasHeight) { 144 | this.canvasHeight = canvasHeight; 145 | } 146 | 147 | public void setCameraDisplayOrientation(int cameraDisplayOrientation) { 148 | this.cameraDisplayOrientation = cameraDisplayOrientation; 149 | } 150 | 151 | public void setCameraId(LensPosition cameraId) { 152 | this.cameraId = cameraId; 153 | } 154 | 155 | public void setMirror(boolean mirror) { 156 | isMirror = mirror; 157 | } 158 | 159 | public int getPreviewWidth() { 160 | return previewWidth; 161 | } 162 | 163 | public int getPreviewHeight() { 164 | return previewHeight; 165 | } 166 | 167 | public int getCanvasWidth() { 168 | return canvasWidth; 169 | } 170 | 171 | public int getCanvasHeight() { 172 | return canvasHeight; 173 | } 174 | 175 | public int getCameraDisplayOrientation() { 176 | return cameraDisplayOrientation; 177 | } 178 | 179 | public LensPosition getCameraId() { 180 | return cameraId; 181 | } 182 | 183 | public boolean isMirror() { 184 | return isMirror; 185 | } 186 | 187 | public boolean isMirrorHorizontal() { 188 | return mirrorHorizontal; 189 | } 190 | 191 | public void setMirrorHorizontal(boolean mirrorHorizontal) { 192 | this.mirrorHorizontal = mirrorHorizontal; 193 | } 194 | 195 | public boolean isMirrorVertical() { 196 | return mirrorVertical; 197 | } 198 | 199 | public void setMirrorVertical(boolean mirrorVertical) { 200 | this.mirrorVertical = mirrorVertical; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/FaceRectView.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.graphics.Rect; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | 14 | import androidx.annotation.Nullable; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.CopyOnWriteArrayList; 18 | 19 | 20 | public class FaceRectView extends View { 21 | private CopyOnWriteArrayList drawInfoList = new CopyOnWriteArrayList<>(); 22 | 23 | private Paint paint; 24 | private static final int DEFAULT_FACE_RECT_THICKNESS = 6; 25 | 26 | private Paint scrimPaint; 27 | private Paint eraserPaint; 28 | private Paint boxPaint; 29 | private int mShader = 0; 30 | private int mMode = 0; 31 | 32 | Context mContext; 33 | 34 | public FaceRectView(Context context) { 35 | this(context, null); 36 | 37 | mContext = context; 38 | init(); 39 | } 40 | 41 | public FaceRectView(Context context, @Nullable AttributeSet attrs) { 42 | super(context, attrs); 43 | mContext = context; 44 | paint = new Paint(); 45 | 46 | init(); 47 | } 48 | 49 | public void setMode(int mode) { 50 | mMode = mode; 51 | } 52 | 53 | public void init() { 54 | setLayerType(View.LAYER_TYPE_SOFTWARE, null); 55 | 56 | scrimPaint = new Paint(); 57 | // Sets up a gradient background color at vertical. 58 | 59 | eraserPaint = new Paint(); 60 | eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 61 | 62 | boxPaint = new Paint(); 63 | boxPaint.setStyle(Paint.Style.STROKE); 64 | boxPaint.setStrokeWidth(1); 65 | boxPaint.setColor(Color.WHITE); 66 | 67 | } 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | super.onDraw(canvas); 72 | 73 | if (drawInfoList != null && drawInfoList.size() > 0) { 74 | for (int i = 0; i < drawInfoList.size(); i++) { 75 | drawFaceRect(canvas, drawInfoList.get(i), DEFAULT_FACE_RECT_THICKNESS, paint); 76 | } 77 | } 78 | } 79 | 80 | public void clearFaceInfo() { 81 | drawInfoList.clear(); 82 | postInvalidate(); 83 | } 84 | 85 | public void addFaceInfo(DrawInfo faceInfo) { 86 | drawInfoList.add(faceInfo); 87 | postInvalidate(); 88 | } 89 | 90 | public void addFaceInfo(List faceInfoList) { 91 | drawInfoList.addAll(faceInfoList); 92 | postInvalidate(); 93 | } 94 | 95 | public void drawRealtimeFaceInfo(List drawInfoList) { 96 | clearFaceInfo(); 97 | if (drawInfoList == null || drawInfoList.size() == 0) { 98 | return; 99 | } 100 | addFaceInfo(drawInfoList); 101 | } 102 | 103 | public static class DrawInfo { 104 | private Rect rect; 105 | private int sex; 106 | private int age; 107 | private int maskInfo; 108 | private int liveness; 109 | private int color; 110 | private int isWithinBoundary; 111 | private String name = null; 112 | private boolean drawRectInfo; 113 | private Rect foreheadRect; 114 | private boolean rgbRect; 115 | public float livenessScore; 116 | public DrawInfo(Rect rect, int sex, int age, int liveness, int color, String name, float livenessScore, int mask) { 117 | this.rect = rect; 118 | this.sex = sex; 119 | this.age = age; 120 | this.liveness = liveness; 121 | this.color = color; 122 | this.name = name; 123 | this.livenessScore = livenessScore; 124 | this.maskInfo = mask; 125 | } 126 | 127 | public DrawInfo(DrawInfo drawInfo) { 128 | if (drawInfo == null) { 129 | return; 130 | } 131 | this.rect = drawInfo.rect; 132 | this.sex = drawInfo.sex; 133 | this.age = drawInfo.age; 134 | this.liveness = drawInfo.liveness; 135 | this.color = drawInfo.color; 136 | this.name = drawInfo.name; 137 | } 138 | 139 | public String getName() { 140 | return name; 141 | } 142 | 143 | public void setName(String name) { 144 | this.name = name; 145 | } 146 | 147 | public Rect getRect() { 148 | return rect; 149 | } 150 | 151 | public void setRect(Rect rect) { 152 | this.rect = rect; 153 | } 154 | 155 | public int getSex() { 156 | return sex; 157 | } 158 | 159 | public void setSex(int sex) { 160 | this.sex = sex; 161 | } 162 | 163 | public int getAge() { 164 | return age; 165 | } 166 | 167 | public void setAge(int age) { 168 | this.age = age; 169 | } 170 | 171 | public int getLiveness() { 172 | return liveness; 173 | } 174 | 175 | public void setLiveness(int liveness) { 176 | this.liveness = liveness; 177 | } 178 | 179 | public int getColor() { 180 | return color; 181 | } 182 | 183 | public void setColor(int color) { 184 | this.color = color; 185 | } 186 | 187 | public int getMaskInfo() {return maskInfo;} 188 | 189 | public void setMaskInfo(int mask) {maskInfo = mask;} 190 | 191 | public boolean isDrawRectInfo() { 192 | return drawRectInfo; 193 | } 194 | 195 | public void setDrawRectInfo(boolean drawRectInfo) { 196 | this.drawRectInfo = drawRectInfo; 197 | } 198 | 199 | public Rect getForeheadRect() { 200 | return foreheadRect; 201 | } 202 | 203 | public void setForeheadRect(Rect foreheadRect) { 204 | this.foreheadRect = foreheadRect; 205 | } 206 | 207 | public int getIsWithinBoundary() { 208 | return isWithinBoundary; 209 | } 210 | 211 | public void setIsWithinBoundary(int isWithinBoundary) { 212 | this.isWithinBoundary = isWithinBoundary; 213 | } 214 | } 215 | 216 | private static void drawFaceRect(Canvas canvas, DrawInfo drawInfo, int faceRectThickness, Paint paint) { 217 | if (canvas == null || drawInfo == null) { 218 | return; 219 | } 220 | paint.setStyle(Paint.Style.STROKE); 221 | paint.setStrokeWidth(faceRectThickness); 222 | paint.setColor(drawInfo.getColor()); 223 | paint.setAntiAlias(true); 224 | 225 | Path mPath = new Path(); 226 | 227 | Rect rect = drawInfo.getRect(); 228 | mPath.moveTo(rect.left, rect.top + rect.height() / 4); 229 | mPath.lineTo(rect.left, rect.top); 230 | mPath.lineTo(rect.left + rect.width() / 4, rect.top); 231 | 232 | mPath.moveTo(rect.right - rect.width() / 4, rect.top); 233 | mPath.lineTo(rect.right, rect.top); 234 | mPath.lineTo(rect.right, rect.top + rect.height() / 4); 235 | 236 | mPath.moveTo(rect.right, rect.bottom - rect.height() / 4); 237 | mPath.lineTo(rect.right, rect.bottom); 238 | mPath.lineTo(rect.right - rect.width() / 4, rect.bottom); 239 | 240 | mPath.moveTo(rect.left + rect.width() / 4, rect.bottom); 241 | mPath.lineTo(rect.left, rect.bottom); 242 | mPath.lineTo(rect.left, rect.bottom - rect.height() / 4); 243 | canvas.drawPath(mPath, paint); 244 | 245 | paint.setStrokeWidth(1); 246 | 247 | if (drawInfo.getName() != null) { 248 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 249 | paint.setTextSize(rect.width() / 12); 250 | canvas.drawText(drawInfo.getName(), rect.left + 180, rect.top - 10, paint); 251 | } 252 | 253 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 254 | paint.setTextSize(rect.width() / 12); 255 | String str = (drawInfo.getLiveness() == 1 ? "REAL" : "FAKE"); 256 | if(drawInfo.getLiveness() < 0) 257 | str = "UNK"; 258 | 259 | canvas.drawText(str, rect.left, rect.top - 10, paint); 260 | 261 | paint.setStyle(Paint.Style.FILL_AND_STROKE); 262 | int textSize = rect.width() / 8; 263 | paint.setStrokeWidth(1); 264 | paint.setTextSize(50); 265 | int defX = rect.left; 266 | int defY = rect.bottom + rect.width() / 8; 267 | 268 | // if(drawInfo.getMaskInfo() >= 0) { 269 | // String strInfo1 = "Mask: " + (drawInfo.getMaskInfo() == 1 ? "Yes" : "No"); 270 | // canvas.drawText(strInfo1, defX, defY, paint); 271 | // } 272 | } 273 | 274 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/FileChooser.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.app.AlertDialog.Builder; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.DialogInterface.OnCancelListener; 8 | import android.content.DialogInterface.OnClickListener; 9 | import android.graphics.Bitmap; 10 | import android.graphics.BitmapFactory; 11 | import android.graphics.drawable.BitmapDrawable; 12 | import android.graphics.drawable.Drawable; 13 | import android.os.Environment; 14 | import android.util.Base64; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.ArrayAdapter; 18 | import android.widget.EditText; 19 | import android.widget.ListAdapter; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import java.io.File; 24 | import java.io.FilenameFilter; 25 | import java.io.IOException; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.Comparator; 29 | 30 | /** 31 | * 32 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 33 | */ 34 | public class FileChooser implements OnClickListener { 35 | public enum DialogType { 36 | SELECT_FILE, SELECT_DIRECTORY, SAVE_AS 37 | } 38 | 39 | /** 40 | * 41 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 42 | */ 43 | public interface FileSelectionCallback { 44 | public void onSelect(File file); 45 | } 46 | 47 | private FileSelectionCallback callback; 48 | 49 | //private static final String TAG = "FileChooserDialog"; 50 | 51 | private static final String PARENT_FILE_NAME = ".."; 52 | 53 | private File currentFile; 54 | private ArrayList fileList; 55 | 56 | /** 57 | * 58 | */ 59 | private FilenameFilter filenameFilter = new FilenameFilter() { 60 | 61 | @Override 62 | public boolean accept(File dir, String filename) { 63 | File file = new File(dir, filename); 64 | return !file.isHidden(); 65 | } 66 | }; 67 | 68 | private Context context; 69 | private String title; 70 | 71 | private Dialog currentDialog; 72 | private DialogType dialogType = DialogType.SELECT_FILE; 73 | 74 | private Drawable folderDrawable; 75 | private Drawable fileDrawable; 76 | private Drawable upFolderDrawable; 77 | 78 | /** 79 | * @param context 80 | * @param title 81 | * @param dialogType 82 | * @param startingFile 83 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 84 | */ 85 | public FileChooser(Context context, String title, DialogType dialogType, 86 | File startingFile) { 87 | this.dialogType = dialogType; 88 | this.title = title; 89 | this.context = context; 90 | if (startingFile == null || !startingFile.exists()) { 91 | startingFile = Environment.getExternalStorageDirectory(); 92 | } 93 | if (!startingFile.exists()) { 94 | startingFile = new File("/"); 95 | } 96 | if (startingFile.exists()) { 97 | this.currentFile = startingFile; 98 | } 99 | 100 | ImageLib imgLib = new ImageLib(); 101 | folderDrawable = imgLib.get(ImageLib.FOLDER); 102 | fileDrawable = imgLib.get(ImageLib.FILE_2); 103 | upFolderDrawable = imgLib.get(ImageLib.UP_FOLDER_1); 104 | 105 | } 106 | 107 | /** 108 | * @param filenameFilter 109 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 110 | */ 111 | public void setFilelistFilter(FilenameFilter filenameFilter) { 112 | this.filenameFilter = filenameFilter; 113 | } 114 | 115 | /** 116 | * @param commaSeparatedExtensions 117 | * @param hiddenAllowed 118 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 119 | */ 120 | public void setFilelistFilter(final String commaSeparatedExtensions, 121 | final boolean hiddenAllowed) { 122 | this.filenameFilter = new FilenameFilter() { 123 | 124 | @Override 125 | public boolean accept(File dir, String filename) { 126 | File file = new File(dir, filename); 127 | if (file.isHidden()) { 128 | return hiddenAllowed; 129 | } 130 | if (file.isDirectory()) { 131 | return true; 132 | } 133 | String fileExtension = getFileExtension(filename); 134 | 135 | String[] allowedExtensions = commaSeparatedExtensions 136 | .split(","); 137 | for (String allowedExtension : allowedExtensions) { 138 | if (fileExtension.equals(allowedExtension)) { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } 144 | }; 145 | } 146 | 147 | protected String getFileExtension(String filename) { 148 | int lastIndexOfDot = filename.lastIndexOf("."); 149 | if (lastIndexOfDot + 1 < filename.length()) { 150 | return filename.substring(lastIndexOfDot + 1); 151 | } 152 | return null; 153 | } 154 | 155 | /** 156 | * @param callback 157 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 158 | */ 159 | public void show(FileSelectionCallback callback) { 160 | this.callback = callback; 161 | loadFilelist(); 162 | } 163 | 164 | /** 165 | * 166 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 167 | */ 168 | private void loadFilelist() { 169 | if (currentDialog != null) { 170 | currentDialog.dismiss(); 171 | } 172 | 173 | Builder builder = new Builder(context); 174 | builder.setTitle(title); 175 | 176 | addAdapter(context, builder); 177 | 178 | if (dialogType == DialogType.SELECT_DIRECTORY 179 | || dialogType == DialogType.SAVE_AS) { 180 | addPositiveButton(context, builder); 181 | } 182 | currentDialog = builder.show(); 183 | } 184 | 185 | /** 186 | * @param context 187 | * @param builder 188 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 189 | */ 190 | private void addAdapter(Context context, Builder builder) { 191 | fileList = new ArrayList(); 192 | 193 | if (currentFile.getParentFile() != null) { 194 | fileList.add(PARENT_FILE_NAME); 195 | } 196 | 197 | File[] childFiles = currentFile.listFiles(filenameFilter); 198 | if (childFiles != null) { 199 | Arrays.sort(childFiles, new Comparator() { 200 | 201 | @Override 202 | public int compare(File lhs, File rhs) { 203 | if (lhs.isDirectory() && rhs.isFile()) { 204 | return -1; 205 | } else if (rhs.isDirectory() && lhs.isFile()) { 206 | return 1; 207 | } 208 | return lhs.getName().compareToIgnoreCase(rhs.getName()); 209 | } 210 | }); 211 | for (File childFile : childFiles) { 212 | fileList.add(childFile.getName()); 213 | } 214 | } 215 | 216 | ListAdapter adapter; 217 | adapter = new ArrayAdapter(context, 218 | android.R.layout.simple_list_item_1, fileList) { 219 | @Override 220 | public View getView(int position, View convertView, ViewGroup parent) { 221 | TextView view = (TextView) super.getView(position, convertView, 222 | parent); 223 | if (fileList.get(position).equals(PARENT_FILE_NAME)) { 224 | view.setCompoundDrawablesWithIntrinsicBounds( 225 | upFolderDrawable, null, null, null); 226 | } else { 227 | File file = new File(currentFile, fileList.get(position)); 228 | if (file.isDirectory()) { 229 | view.setCompoundDrawablesWithIntrinsicBounds( 230 | folderDrawable, null, null, null); 231 | } else { 232 | view.setCompoundDrawablesWithIntrinsicBounds( 233 | fileDrawable, null, null, null); 234 | } 235 | } 236 | 237 | return view; 238 | } 239 | }; 240 | builder.setAdapter(adapter, this); 241 | } 242 | 243 | /** 244 | * @param context 245 | * @param builder 246 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 247 | */ 248 | private void addPositiveButton(final Context context, Builder builder) { 249 | String positiveButtonText = "Ok"; 250 | 251 | OnClickListener positiveButtonClickListener = new OnClickListener() { 252 | 253 | @Override 254 | public void onClick(DialogInterface dialog, int which) { 255 | 256 | if (dialogType == DialogType.SAVE_AS) { 257 | loadFilenameDialog(""); 258 | } else { 259 | completeSelection(""); 260 | } 261 | 262 | } 263 | }; 264 | builder.setPositiveButton(positiveButtonText, 265 | positiveButtonClickListener); 266 | } 267 | 268 | /** 269 | * @param filename 270 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 271 | */ 272 | protected void completeSelection(String filename) { 273 | currentDialog.dismiss(); 274 | if (callback != null) { 275 | callback.onSelect(new File(currentFile, filename)); 276 | callback = null; 277 | } 278 | } 279 | 280 | /** 281 | * @param filename 282 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 283 | */ 284 | protected void loadFilenameDialog(String filename) { 285 | if (currentDialog != null) { 286 | currentDialog.dismiss(); 287 | } 288 | 289 | Builder builder = new Builder(context); 290 | builder.setTitle("Please type a filename"); 291 | 292 | final EditText et = new EditText(context); 293 | et.setHint("filename"); 294 | et.setText(filename); 295 | builder.setView(et); 296 | 297 | builder.setPositiveButton("Go", new OnClickListener() { 298 | 299 | @Override 300 | public void onClick(DialogInterface dialog, int which) { 301 | String filename = et.getText().toString(); 302 | if (filename == null || filename.length() <= 0) { 303 | Toast.makeText(context, "Invalid name", Toast.LENGTH_SHORT) 304 | .show(); 305 | } else { 306 | if (new File(currentFile, filename).exists()) { 307 | loadDeleteExistingFileDialog(filename); 308 | } else { 309 | completeSelection(filename); 310 | } 311 | } 312 | } 313 | }); 314 | 315 | builder.setOnCancelListener(new OnCancelListener() { 316 | 317 | @Override 318 | public void onCancel(DialogInterface arg0) { 319 | loadFilelist(); 320 | } 321 | }); 322 | currentDialog = builder.show(); 323 | } 324 | 325 | /** 326 | * @param newFileName 327 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 328 | */ 329 | protected void loadDeleteExistingFileDialog(final String newFileName) { 330 | Builder builder = new Builder(context); 331 | builder.setTitle("File already exist. Delete existing ?"); 332 | builder.setPositiveButton("Yes", new OnClickListener() { 333 | 334 | @Override 335 | public void onClick(DialogInterface arg0, int arg1) { 336 | File selectedFile = new File(currentFile, newFileName); 337 | selectedFile.delete(); 338 | try { 339 | selectedFile.createNewFile(); 340 | completeSelection(newFileName); 341 | } catch (IOException e) { 342 | e.printStackTrace(); 343 | loadFilelist(); 344 | } 345 | } 346 | }); 347 | 348 | builder.setNegativeButton("No", new OnClickListener() { 349 | 350 | @Override 351 | public void onClick(DialogInterface dialog, int which) { 352 | loadFilelist(); 353 | } 354 | }); 355 | builder.setOnCancelListener(new OnCancelListener() { 356 | 357 | @Override 358 | public void onCancel(DialogInterface arg0) { 359 | loadFilelist(); 360 | } 361 | }); 362 | builder.show(); 363 | 364 | } 365 | 366 | /* 367 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com )(non-Javadoc) 368 | * 369 | * @see 370 | * android.content.DialogInterface.OnClickListener#onClick(android.content 371 | * .DialogInterface, int) 372 | */ 373 | @Override 374 | public void onClick(DialogInterface dialog, int which) { 375 | if (fileList.get(which).equals(PARENT_FILE_NAME)) { 376 | currentFile = currentFile.getParentFile(); 377 | loadFilelist(); 378 | } else { 379 | File selectedFile = new File(currentFile, fileList.get(which)); 380 | if (selectedFile.isDirectory()) { 381 | currentFile = selectedFile; 382 | loadFilelist(); 383 | } else { 384 | if (dialogType == DialogType.SAVE_AS) { 385 | loadFilenameDialog(selectedFile.getName()); 386 | } else { 387 | completeSelection(selectedFile.getName()); 388 | } 389 | } 390 | } 391 | } 392 | 393 | /** 394 | * 395 | * @author MD IMRAN HASAN HIRA ( imranhasanhira@gmail.com ) 396 | */ 397 | class ImageLib { 398 | public static final String FOLDER = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAANbY1E9YMgAAAAlwSFlzAAAOwwAADsMBx2+oZAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTAw9HKhAAAPpUlEQVR4Xu2aSWxW1xXHXSmRUCtlkUWnXZVIkVqpVZVI7Sa7tou2q2yatGqEwjyFDJAwBEiYh5rBGJvRDoPNYIwBY2MGm8FgY4yZTOKkSaMq3SVSpWwqpACv53fP/d93/RV1XVJ/0tG58z3//zn3vvfd+6qqxn5jDIwxMMbAGANjDIwxMMbAGAOjGejuO/qtuq6bi7eevvX37WdufynZ1T38ZUP38Bc7zt7+wtJBGnqGv2jsuROkwYSyxnNl2so+2Hb61h8eKY5XHRn445au28XW08NFvcm2M8PFjrN3kq4/dTvkkZ3dHxQ783R3Vm51tLFx7q9uG3jukSGh+vi1ujoDiUDC9jN3iu0GZIvlSUMIwCkTOTsrgG+zdpR5mzvFxhNDbz4yBPzl+FBvbdetYt3xoaL+FBHgQEvxfCDHABIligjyioy8z8aO62sfCQKWt/Q9u7Hj5oMtRgAeZykoGkgT/gBWdFBWe/JWarPuGKSV/ahHjNR/rG7t++7/NAk1HTd+a4Z+vuHEjWJT581iU4eJ6RrSJkZMQZ005aqjbUjHPhs7sjEoM7Gyf27quDGyqeP6SE3njRGLCtcnro9sMEGvbx8K6erjrte3e9m6Y9eSrDk6OLLqyNWRVW1XR1a0DowsPzyQ9JKD/SNLW66MvHuof2RB86WRxQf6RhYf7O+ft6/3v2/E8/d0f2/N0Wv/woOrj14rVhwZLNYcHSpWtQ2aXAuy2mRFq+dXtF4NbcivNI14+WCx7PBAyFOf65CO7Zdbf/VT34e1XXr4qo3n8l7LQPHuoYFi8cErxTv7+4t3DvQXC5r7irf2Xgp63r7LxdtRyJOe33Q51M1v6vt6av3Jpx8agV1XB59YvP/Cy0zChJokDGoDzLUJ5uzpLd7Y3Vu8/v7F4jWTVxsuBHmt0YX07KhTOiufFeul87a0p1ya9Mxd54tZJugZO88V03ecK6aZTN7WU0ysP1tM2tpdTNx6NqQn1J0txm85U4yvPR3KJ5tMsXb0oT9zYfvshp5Xm8+cfyKRUN3aM27vhZGmlv5PHrRe+bRou/q3om3g0+KoaeTYoAvp44OfWbqU49c+S23UvlIz3hEbD2H8w/2fBG3zBTnU99fi4GUT02ZHsf/yx8X+Sx8Xzb0mpptM77v4UdFksvfCR8Uea7P7/Gh5//yHYdNtPPehP5JNGnpclHdtm7Np268ebO682bR478lxVdtO3557+MonyUjAH4vgEwmBlJIY6kVM0ORjGeDoF4gEfCSVckhgLkhAjyLASDhgAngR0NT7UQDPUwUt8HuMANLvRyIgAGkwAhy4awALOHpXJER67dHBuVX29taEcXhCRspjeSRQ9rCoqPQ4Y1SOk8ADPHoe8Hg9SIwAvE46eD96HuB7owDagRtgA4smGkjL44DfZWDZpOV98pL04mZlG04MNVXZa2wt4PEIhmKACEhEZGFMGcS4jl6O+TzU8bzICF6P46OZT4LXXT5OEeCh794HJHp39Hi5BKLXY6gr/AGaAyedgyYqaEPZ+vZrtVXbzwwv0XpUeMrwoLP1mwOUV8O6zsB5XydTYR5CPXoejwMe0HiZcA9phb5pAFMXPG/A0Qp5CAGsBI+TJgIU2vkrO2mWULkXsA9Ymb3Y2WN1SVX9qZsz5Q0MzUN0FLi4eeXrWCSEfsGzvq5FZL7RaQ4IkNdz0Ap7PC/vE94Ke4+Acq0T9nhS4MMaj54FsN5G0RBTvp06GeTXtF2dWVXTef1FrUUBEIhRkRHDWOEs77IzV25skMhY7u241uM6DxtcDHE0wHPQjIfXfe07aO36bHp4nDJA4d2wtjPvK+Q9CtzTigjeTrfa67vIWNl65cWq6uODv9EmhGdEhnZoDA6PKhNPO7g8UkpPa32PfrwBFE/l4S4SAJcTEIAT9vFxp81OIU9e652wJ5y1w+sPWgj7+N9F0cCrO/9bvI2TsvRQ32+q1rb1P9dsYRceQfExlK9NrdnkybiG86jJwzvt6iIzjhnWdVzn+2w+7fKEuHZ6hbxCPd/hPdRL0WYnD6c/YtmfNv1TFfD8Dx1li5ovPFe17GDvU2wyevbmz2FIwTshQrJnNEQ5Yf7Ykme1tj3voV6GfPlyw3zytNZ4esbHRx3gA+hswwO0NjsRoB1eXoeQErCHu/8jzaLC0vyRm7en+6mq6gNtT8Ksh6HWo7+BJQBxt9buTLl2cb2xhfUc1zTtwttbfJTJ28prV1eYa40DOG10ydu+1rWTl4cwHsaAFWB0+Ica/6aPPsgp/77Trtb+na7ae/DJqr0tWx+zTeS+jMbY3bbJ5MZrSYgkRYny7k0DbYABN6o8kqEwr/R4ucH5yw0eBmzydHx70+GKCCjXuXtWf811gpWfZEFE+AufkWP/bO/XNax7LPwnsHeBr3LD9OaFh1SusAWc6kVa/rxO6biJ5ePqWZ4/x8vneQxvHlHhJKl8dOk0SgcwOmTRSZWD9TOH/OyCcv7JihwOeEhDhP3t/ir9IbIBP8/DUOsx//NBWflC8mEgRi8pYbPKHlf5HxaltXvLwzlwvaSkx1p8m8NY37hc8nAP3ozAdViDp3Uwo0MbdF0g5taoQ5vq9qHPEwFbT90aTgbFdaj1KHAA0Hs4ewYvJl5XvqCIBPXNPe1vaYS2P8ODZK+tkKDjNT26tJbzsMaLeFsnTjqxAiBEbLaTKUWB0pDCYQ59vexWYYctw4mALV03+2SUHjcynskxvDJstTvr0aQdm3FIpxeU+IamnVgvMOGwNL6kANg3Ml/LemZrRw9ejEdsgAhHcHg0CqBUHoBanpMpNKdS1IVTqnhixYmWnRr1lWcCx4e6/nOXdS/xooHIc5X/sCrfwfUmlntXj6rgWb2gmE6bU9y9tYZDKEePSgeQFQJIyvnnB1Adx1GmcqXRNZ1OBOPY/4CuREBNx/XDhJteFB52ulv5B0OPJoVr2Kjk0XiEjlfVL4RxXLfazJTHuxgmzzoRDgJj3fjyvLG6/XoAjbfx5vp2k3BO6WeQGy0tMigLbTOhz5q2wcOJADt0fJ+NIg8/bTjl46R8jur5ChCBUAgrfNMzOXsEeRj7GtRJsrwokApf5XUgC5BwUGtAzF47Yb4eD2cdIKCpl0AKachK5TZGIMpk5ZGrjdkSuFbLxOkxEtdYfhSuNAbTTgYKaN6WesJNIDW21mp+aqwQjSfGKZwV1u5ZnUTnAJ0EJERAFA5V6YMAnnr0OtNr7bBXEbOqdaA2EWBHzGsESAbJMwDJQxFjBEQbT9hwspAlz0RhLOtfbZPnIUxaANEK4dx7CnO0PD4a0FAYVyRIc5qdlwFa7UI6krLs8JU1iQA7X18cDIkhRohox9Rak9fSWor3BCEkY3vdG+Qe9juEPHzdo/JMAGhtKoGQ51YqaDNc9YCwo/tQRhrPyru05xif8lAX21b2IW93BosTActa+uaG0IiGwDgDwRyN0yYTN5zqGG5pHcZwC6EXNyQ04ORBrUWMFRgBw3AZnIyNZYDlLiJoE7TuJ7yt32Hk5aQpd/F+akOefov2X5qTCHjvUN+MnOG0brKQgRSBYmC1V0gJjMItadZfNFReK41z4FyicOHB+uWyRAAB7hcyfsGiyxnV6zKFPGnaq09+URP6xQsazfFO86UZiYBFB/omrA03QD6hDKZMHlFYSTvj3Bw522XIOeulF0oPjDI8AlppY+i2KNw4Ua7bJMtDSOXt01Iji7ZLDl2xem8j4WJH7WkHsbqJok4ELGy+/EoiYEFT70vOpk/uLJZXXckjBkxMAzD1CVdn9PUytQnXZAKaX5NFozEoAMxAkEa4mcqvxHQ1JlDK0592CGnK6ZvXQwL9qA9pazN/X+9LiYCFTRdeEDsYDKtiMb/3k3HSAM1ZlReCh2I4Mxb3ePRh8iWWlkG6hpPRlC+yuz76C6ja4m0MJy/JSVIfytSHMuZGNB71jPX2nvMvJALm7D7/ezpxiYiB7zJZnGhpi7NbesCNyC8qRxmU+jnTjMN470XDmDzk4yUnaYRLTgxl/iShzknJyymjT2hv2m12YLo0ZT5h0T1n6mPt32js+V0iYHbDuV/TWGyhNWCY2AZeaJekmizoaByDyqsyQPWMg/EL9/eNGpuyfC61wwHc+C40oU0uEER+7p6LoS9j+th+Q7zogM81b98lJyHOoTE0h+aetbPnV4kAyzyvQcqOPqAEw0hTD+DKOvVDy1j6aOJwlR2NxfiUtzJuoFVmV9gZONIO0ts4mWqrMm6vQzq18bkoF0GyS2NM3db9fCLg1Z1nfgn7mijcpZugK8UNLI3GGO7gg1Hxrl7gKJNxGg8tkAsiGNrk7ah/yzyJpj3jhX6RKMor7VL/h9occQQbok0ztp/6RSJgxvauZ/VRAQMpHQyIQlmoM4Es0jJIdeTz/qQBwncFaq++lX34/kBE+LcIMR/nHUVStFFz6aMIJ83t0zx5P+Gi3fRtXc8mAqbWd/6MSSuFry5o7OW9Ka0yBnwriLchj6afPqp4Y/fFVEa52tCOjyyUD2PF+fK0xkarP2NDEHnIDf1i39cayzErbREBlE/a0vHTRMCU+s6fvGlfT1Dxpg04J3qAwV18IhFUWc6XFwB1g5yMvA/jza0YA0DMlZMe5o7A9EVKymfjBjttTs2hOWU/dpQ2ODn5eMwzua7zx4mACbUdz/DZCyAcjH8Gg6Yxk1UK9SpTW/XVpzRupI8X2sa08szHpytebuMxV5SZO3vCZzcak3rGyu2gTHaEunx8S6tOeDQ2kffypvZnEgHja9qftm9zHlAhIvQdEAbmAEm/nhGFEeonnchMpGpcAxrnCMaJHNP0LcHKAWWb5IwEUg4rSchtCXaazI7fMOkbofCdUOPFB+M3tT+VCJi8ufWH9jHSPWt0n4asI2sUNBIGojzqRFIkAq+IqBQZ0avlcnEPEa6uHaSWljyr+jJfej0niHrZpcgNTos2Kzpkt3BYVN2fufPcvYkbW35Q/hnasGbc9B09d42t+/kXXOosNt1LpTcVkgKRr3+t7XxfSBta3LBGb6ajN2FtcIoS5hYppGftOmcO8SUi+0hTJ0cBelaDfyGmL9JmN5y/P2179905q5aNyz+Xe/zl9cf6JtWdujthc+fXEzZ33JtY23nvlRrTlp5kaWTyFpOop5A2mVrXeW96/cl705A6aS+bYTK9vjPoadZuWl2HpV0rP93S00OdyjvuTd3iMqVW6U6z44TN3REkzG3abTMxjc25yHY05SFfa9hqu+7+ufooR+KP5wR82zI/N/mTyXITzsu+KbLZsNSYbDJZFjGC9Ts5AVwS8vHg901+ZMLXlN80YdMDGxjB6hejY78xBsYYGGNgjIH/Ywb+DbS8aASxw9FIAAAAAElFTkSuQmCC"; 399 | public static final String FILE_1 = "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"; 400 | public static final String FILE_2 = "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"; 401 | public static final String UP_FOLDER_1 = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAALcklEQVR4Xu1aTYwcxRl9X0/PzK69YJv1en9wjPkxDsZgxzbSEBQFRYqSXJNDfoREjiFSgnJCcEAkkeCQHJJwCMopcAgcEilSJBBSUBKJZEgOIKJEAgw2xsuud9f2ev9md/qnXqZLpf6qe+yNvcysjaBWpa7p7ump9+p9r76qXiGJT3IJ8EkunxLwKQGfEhDCK088P4k+l0atHjQBIGqbewG8hh6WWr0o6Me+PnGtKEDBHz18A7LqiGigp4W2XoMhoOBPnoatWbtaEyWhl/BJkLxmCGh0gDrwdJ2kJeHY54aVhB4yQF47CmiEVWlmQE+cBgxUoYbACUdCWIWS0JMAIHg1CVDwaN5zJAOvw0KhbYrr5IkPiHuO7OwNCaStpGX46hCwa3w4tOBDdEZ+Zweg65eTqBCAH6sA3vsAyO6thOwNCZLVq6SA2elzxzIgx47stCNfFqMfBtok3judKWHEklCpBPdvDLvzGOKqmWAjqJjOyI90RpWgb0xgETj1Ommv2e9k34WkfxGR+zbAgJogN18BjSDIwO/qjLzxJe7AC+iTAYEyJO4Iq5qjh0cgkr4qIp+/UhM05FWZBhsSpM2jHfAnJwlCICTAsj0T7rRTAEGKEuRQnJw0yJ4FSf9+JZ7gGHZ/m0dAA5I0jxwetR2nQ0iIxiPE61KJGFBVoaRYEo4czkhILtsYmcuf4CaFQAPogD80hlNu5HUeVpT0O0RRRejBtQWg5KScmjTIng1cHglewOFyGQg/Cngibh7OwE8ZtXgpO7yGOVmCbOwFUIoQ9Dm0zz50aBRvvjnTBML1F1CER7z0VQENMm7efXAMk9MsTjtUKRY6xYLj6w3ih4W7LvDd3P7GXQdHQcbrKoFObejzNNgwJmreeecYPpwhaBwyitqPSl8/kUVPpB+3AkLJUULFgULntwyy3zSMLkkCjffQ3puggr/jwDjOzBqA7AbjEHa3BSD1HESBlo2LVFAeeWfmDA7cMQ5j2h4J3QowQF9MsJGm7eb+/WOYPWsUYCF6yzL37/HBs0hGtzp8cvS5Bpg5a7B//zjSEgnl6Ra9CwEFv2/fOObO02HTTlKTD50BFIzvCwpYgAI+Uq8rWapmJcz2Yd9t40jStQIJOe8GPQ2BRpKsNW++ZRznLxh1b7V3TWIgIEVH3qg23FGr6tSTK2GKglaSIWqSAM4vGNzS6VOSKAk+Y6YHIeDArzZv2juOxSWjNOfx27UAUTSl8yQgTiVCcQDFv927p6hiQlRhnlkuLAF79k4gTlYtCWrCvQmBRhyvNnd/ZgJLK1R5qdmoCLRqk0qWJ9HuqZBUDygbp3+Pnzp411ZaBlkfs75Ww8GG+sCGFaDgxycm0FpjtzkZP6/XtlZ6Uqer5Qy4nCz5kteAKajLdHMOAqtrxPiNE4yiDgnVwQaJDa0FFHznQbvGxhHF1BEr5S+g5OdNeZmr19QbFLwTqca7ATVFVmXpOXG/A1UMSjNFFFFGx8dMFLeatdrgZS+ghKT/XqARRa3myNgETLeLKAIpt6U75kRv6v6eWFCXVfR7+l3wEtmuIAiYzJ2ZDmu1Lfc+8e3dr13RWiADbx327JzBFZaBwa1BfWAw7xdJoMyVhsxF41RKuJcX55Gm6RX1hTQBADosclkK6EX56e+nOTA45ARBkJIjEgIoqJkQtwiSnAxxF90gU7A4P8c0TW4FsIQrLE8+ePvZfq8Gu6UKOpBOpsaCAiFF1xJtU5lRIgz9ELnQATOPHpQ+EqD+JaXUR8PVgXXgjSPKUWbbkFLoiGgfr3ECNKu7xDW6WKBRyihqDsxZLDzu40GA4ry0oTn8SohI/plGv1CeYIIgqH5sCPAcDIR4MOhtGImfURfvoYBij/lcKkGlDgAfHw+gOgCkSEpue+JaBrZNzbLUI10jkOC6j8d/iFDNjeXcv+QMphzr+rmwpRoEFQC8CRss3/vZ61p//sZmeIBDIaW9fgFYDBF3EAWQS18jIqhUsTTf+uH3f/Gf9ztJ0ZT2uXBcee7xL1x8zpcAIoIDxw4OAbj36b+lr/7gi5XVSyZCj/9uCj/5zgQ2Un78whRrA1uh460ARYrAhXAmqFLXIvmUGYhgZvoMwrSNj1IqYYiwVsPiYuuZXz1810OXVECacMMkEIQhncmx4OXGYlSiDTyvEClMoQIWVBMEARKpA7B+UBpgsTcOXX/9+nFeCbB8YQE0ZmrdENg1tq3D+MJGSehOfkT1wKJX5G6vRHiUUc2yWq9h4fwF9zxxxwClgbN+calSq1dBwxjA7LomeGZqASQQR9xoJgySniIUMEobpaQoYaB3nSCYn6pWa2qmpKsmq6qoNP0/CqjAGBMBOLeuAp767o3YaNEVoIpdhGAe9J7RCUpeASgd3v0GqNaqoDGgk7xzCC+YkIHDeiUMKzBJEqkC+jALGJPmyY8U1gbUV126k+TkTFCDXz3ALaJIohJWAIgFKbZF6ws+hSZN1ldAGCBNYgPgrb7lAaRDVnot1h0B3dvgEC+E8oYaXVgLYei21kikxtha+JxclARVDVlpR/Fs3wgQkZgogtejAvIZIv1Kf1T1epqFQQ3MQKYKPKsmqylBEkkS56T6tVqtIokTCDDZ50RIztOYMQmC3Asg5ZWQdBGj53QdYI+ABZmQGBisY/4cIbmNGIj9IO6cIEaMSli35/3ZtVoLEa2tgcDrfUuFH/71cSH4Jk0C0IGAkyzU9Vl6EaJjTg0J+GEkSKIIg1u3uM1XA0PCVl8FJNI0tQox7lyS2DbqHfLaK60lkH/qmwJ++dA+Pvbs8T8ixFfErV79XF+60xwlo7QGJrQYG+tEVYIOkBrWWu3yukLNVYiovYZqbSD3m3q94siIUgCv9FMBQZLwryaNmI0CaZD7m7qfgkdp6jcKnHrexi5Je9x+wzaIKFcQnzFnhmkMY1QdW4YG0VpqAcBsO4rnf/PIsf6tBtuRLJCM0jSyRgX/XUI5UaJHRdEbNYmyhpc680uxY3hHNp87ElQ6ItBCA5PGeda4bccQVhaXYxB/6PdymACWDPHPrANxHCNJEithsvy2WDzY4ngiWJJAEscuxg3aq1GWD3QAXWcBa2VueEEggGRyX0WarmFo2yBWs5BJ11YAPtPHbXE1wnqdX64EwcupqUNEbKcrlYp7YRFAV8CltJkE3dxu0tR74Wqy85aE7cPbUKkITrz1nlIuko+2iG69BlLBvoP7MXXqDNL28j/a7eg+lX/PTVCNsEPCn7dswZIx8XVkYKVrJWvBS773Txf45MUMkGCe9Jg8zV2YX8TuvROduN6K1vKKsugKqXPuyMQo2msR0nZrAeRTm/BiRMujv33na4byYrttNQr7J5JnZBAUCzU9poK/6CvunaPDqNVDvP3vt4r4RaeSen0Atx3ch1PHJ8Go9TbAA0//6JDp+56gLqhuf+mxZ99ti7BuDLWPpoxcNFeiollvQM6fnceeW3ZjZHwX5qZnLMHKEy3RN+3bi3MzF8B4dQHgg+0oNpukAC2dnOBLJF5praLnZevQFoztHsXx/76NlaVlzSIB7LntZoRhFTOnp9fExC+2o+gbGvubRICScOKFJDXfbLd7T/COnduxY3g7zs7MYWlh0Z0bxsDgIKZOTqcmXn0X4NGO9Fc2+b2AX5IHwkrlq2nIbXah5nZ5rRf4IaCm4M8KuizSc/n1+bMX3MywA7vGR2FItJZXMf3+dGKS1VmA93vgN18BqoJ3RsjgVJJgsFobQlAJC2+MWfggbl6H/re5+D4oMKnNLxDH7YyAnFQYgzRuL9Kkb4D8Vgf8GQC4qgSIyI0A6sMTt372gUeff1KCgbtBJbzwL606C5Syxa578mNYDQEE9np7dfH1yeP/eunl5x55BcApkieuNgFlIvagd6UCIIXd7NwaJPEajUkdCHxA8kNcQfkfxBBhNOifL3cAAAAASUVORK5CYII="; 402 | 403 | BitmapDrawable get(String base64Enc) { 404 | byte[] decodedString = Base64.decode(base64Enc, Base64.DEFAULT); 405 | Bitmap bitmap = BitmapFactory.decodeByteArray(decodedString, 0, 406 | decodedString.length); 407 | return new BitmapDrawable(context.getResources(), bitmap); 408 | } 409 | } 410 | 411 | } 412 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/LivenessDetectorProcesser.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | 8 | import com.ttv.face.FaceEngine; 9 | import com.ttv.face.FaceResult; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import io.fotoapparat.parameter.Size; 15 | import io.fotoapparat.preview.Frame; 16 | import io.fotoapparat.preview.FrameProcessor; 17 | 18 | /** 19 | * {@link FrameProcessor} which detects faces on camera frames. 20 | *

21 | * Use {@link #with(Context)} to create a new instance. 22 | */ 23 | public class LivenessDetectorProcesser implements FrameProcessor { 24 | 25 | private static Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper()); 26 | 27 | private final OnFacesDetectedListener listener; 28 | private FaceEngine faceEngine; 29 | 30 | private LivenessDetectorProcesser(Builder builder) { 31 | faceEngine = FaceEngine.getInstance(); 32 | listener = builder.listener; 33 | } 34 | 35 | public static Builder with(Context context) { 36 | return new Builder(context); 37 | } 38 | 39 | @Override 40 | public void processFrame(Frame frame) { 41 | Bitmap bitmap = faceEngine.yuvToBitmap(frame.image, frame.size.width, frame.size.height, frame.size.width, frame.size.height, frame.rotation, true); 42 | List faceInfoList = faceEngine.detectFace(bitmap); 43 | MAIN_THREAD_HANDLER.post(new Runnable() { 44 | @Override 45 | public void run() { 46 | listener.onFacesDetected(faceInfoList, frame.size); 47 | } 48 | }); 49 | } 50 | 51 | /** 52 | * Notified when faces are detected. 53 | */ 54 | public interface OnFacesDetectedListener { 55 | 56 | /** 57 | * Null-object for {@link OnFacesDetectedListener}. 58 | */ 59 | OnFacesDetectedListener NULL = new OnFacesDetectedListener() { 60 | @Override 61 | public void onFacesDetected(List faces, Size frameSize) { 62 | // Do nothing 63 | } 64 | }; 65 | 66 | /** 67 | * Called when faces are detected. Always called on the main thread. 68 | * 69 | * @param faces detected faces. If no faces were detected - an empty list. 70 | */ 71 | void onFacesDetected(List faces, Size frameSize); 72 | 73 | } 74 | 75 | /** 76 | * Builder for {@link LivenessDetectorProcesser}. 77 | */ 78 | public static class Builder { 79 | 80 | private final Context context; 81 | private OnFacesDetectedListener listener = OnFacesDetectedListener.NULL; 82 | 83 | private Builder(Context context) { 84 | this.context = context; 85 | } 86 | 87 | /** 88 | * @param listener which will be notified when faces are detected. 89 | */ 90 | public Builder listener(OnFacesDetectedListener listener) { 91 | this.listener = listener != null 92 | ? listener 93 | : OnFacesDetectedListener.NULL; 94 | 95 | return this; 96 | } 97 | 98 | public LivenessDetectorProcesser build() { 99 | return new LivenessDetectorProcesser(this); 100 | } 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/ttv/livedemo/PermissionsDelegate.java: -------------------------------------------------------------------------------- 1 | package com.ttv.livedemo; 2 | 3 | import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 4 | import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; 5 | import static android.os.Build.VERSION.SDK_INT; 6 | 7 | import android.Manifest; 8 | import android.app.Activity; 9 | import android.content.Intent; 10 | import android.content.pm.PackageManager; 11 | import android.net.Uri; 12 | import android.os.Build; 13 | import android.os.Environment; 14 | import android.provider.Settings; 15 | import android.util.Log; 16 | 17 | import androidx.core.app.ActivityCompat; 18 | import androidx.core.content.ContextCompat; 19 | 20 | public class PermissionsDelegate { 21 | 22 | private static final int REQUEST_CODE = 10; 23 | private final Activity activity; 24 | 25 | public PermissionsDelegate(Activity activity) { 26 | this.activity = activity; 27 | } 28 | 29 | public boolean hasCameraPersmission() { 30 | return (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED); 31 | } 32 | 33 | public boolean hasPermissions() { 34 | int write = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE); 35 | int read = ContextCompat.checkSelfPermission(activity, READ_EXTERNAL_STORAGE); 36 | int camera = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA); 37 | return (write == PackageManager.PERMISSION_GRANTED && 38 | read == PackageManager.PERMISSION_GRANTED && 39 | camera == PackageManager.PERMISSION_GRANTED); 40 | } 41 | 42 | public void requestPermissions() { 43 | if(ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || 44 | ContextCompat.checkSelfPermission(activity, READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 45 | ActivityCompat.requestPermissions(activity, new String[]{WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE}, REQUEST_CODE); 46 | return; 47 | } 48 | 49 | if(ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 50 | ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE); 51 | return; 52 | } 53 | } 54 | 55 | public boolean resultGranted(int requestCode, 56 | String[] permissions, 57 | int[] grantResults) { 58 | 59 | if (requestCode != REQUEST_CODE) { 60 | return false; 61 | } 62 | 63 | if(hasPermissions()) 64 | return true; 65 | 66 | requestPermissions(); 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/QrCodeActivity.java: -------------------------------------------------------------------------------- 1 | package qrcode; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.database.Cursor; 9 | import android.graphics.SurfaceTexture; 10 | import android.media.AudioManager; 11 | import android.media.MediaPlayer; 12 | import android.net.Uri; 13 | import android.os.Bundle; 14 | import android.os.Handler; 15 | import android.os.Message; 16 | import android.text.TextUtils; 17 | import android.view.TextureView; 18 | import android.view.View; 19 | import android.view.View.OnClickListener; 20 | import android.view.Window; 21 | import android.widget.Toast; 22 | 23 | import com.ttv.livedemo.R; 24 | import com.google.zxing.Result; 25 | 26 | import java.io.IOException; 27 | import java.lang.ref.WeakReference; 28 | import java.util.concurrent.Executor; 29 | import java.util.concurrent.Executors; 30 | 31 | import qrcode.camera.CameraManager; 32 | import qrcode.decode.CaptureActivityHandler; 33 | import qrcode.decode.DecodeImageCallback; 34 | import qrcode.decode.DecodeImageThread; 35 | import qrcode.decode.DecodeManager; 36 | import qrcode.decode.InactivityTimer; 37 | import qrcode.view.QrCodeFinderView; 38 | 39 | /** 40 | * Created by xingli on 12/26/15. 41 | *

42 | * 二维码扫描类。 43 | */ 44 | public class QrCodeActivity extends Activity implements TextureView.SurfaceTextureListener, OnClickListener { 45 | 46 | private static final int REQUEST_SYSTEM_PICTURE = 0; 47 | private static final int REQUEST_PICTURE = 1; 48 | public static final int MSG_DECODE_SUCCEED = 1; 49 | public static final int MSG_DECODE_FAIL = 2; 50 | private CaptureActivityHandler mCaptureActivityHandler; 51 | private boolean mHasSurface; 52 | private boolean mPermissionOk; 53 | private InactivityTimer mInactivityTimer; 54 | private QrCodeFinderView mQrCodeFinderView; 55 | private TextureView mSurfaceView; 56 | private final DecodeManager mDecodeManager = new DecodeManager(); 57 | /** 58 | * 声音和振动相关参数 59 | */ 60 | private static final float BEEP_VOLUME = 0.10f; 61 | private static final long VIBRATE_DURATION = 200L; 62 | private MediaPlayer mMediaPlayer; 63 | private boolean mPlayBeep; 64 | private boolean mVibrate; 65 | private Executor mQrCodeExecutor; 66 | private Handler mHandler; 67 | 68 | private static Intent createIntent(Context context) { 69 | Intent i = new Intent(context, QrCodeActivity.class); 70 | return i; 71 | } 72 | 73 | public static void launch(Context context) { 74 | Intent i = createIntent(context); 75 | context.startActivity(i); 76 | } 77 | 78 | @Override 79 | public void onCreate(Bundle savedInstanceState) { 80 | requestWindowFeature(Window.FEATURE_NO_TITLE); 81 | super.onCreate(savedInstanceState); 82 | setContentView(R.layout.activity_qr_code); 83 | initView(); 84 | initData(); 85 | } 86 | 87 | private void checkPermission() { 88 | boolean hasHardware = checkCameraHardWare(this); 89 | if (hasHardware) { 90 | if (!hasCameraPermission()) { 91 | findViewById(R.id.qr_code_view_background).setVisibility(View.VISIBLE); 92 | mQrCodeFinderView.setVisibility(View.GONE); 93 | mPermissionOk = false; 94 | } else { 95 | mPermissionOk = true; 96 | } 97 | } else { 98 | mPermissionOk = false; 99 | finish(); 100 | } 101 | } 102 | 103 | private void initView() { 104 | mQrCodeFinderView = (QrCodeFinderView) findViewById(R.id.qr_code_view_finder); 105 | mSurfaceView = (TextureView) findViewById(R.id.qr_code_preview_view); 106 | mSurfaceView.setSurfaceTextureListener(this); 107 | mSurfaceView.setOnClickListener(this); 108 | 109 | mHasSurface = false; 110 | } 111 | 112 | private void initData() { 113 | CameraManager.init(this); 114 | mInactivityTimer = new InactivityTimer(QrCodeActivity.this); 115 | mQrCodeExecutor = Executors.newSingleThreadExecutor(); 116 | mHandler = new WeakHandler(this); 117 | } 118 | 119 | private boolean hasCameraPermission() { 120 | PackageManager pm = getPackageManager(); 121 | return PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.CAMERA", getPackageName()); 122 | } 123 | 124 | @Override 125 | protected void onResume() { 126 | super.onResume(); 127 | checkPermission(); 128 | if (!mPermissionOk) { 129 | mDecodeManager.showPermissionDeniedDialog(this); 130 | return; 131 | } 132 | SurfaceTexture surfaceHolder = mSurfaceView.getSurfaceTexture(); 133 | if (mHasSurface) { 134 | initCamera(surfaceHolder); 135 | } 136 | 137 | mPlayBeep = true; 138 | AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE); 139 | if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { 140 | mPlayBeep = false; 141 | } 142 | initBeepSound(); 143 | mVibrate = true; 144 | } 145 | 146 | @Override 147 | protected void onPause() { 148 | super.onPause(); 149 | if (mCaptureActivityHandler != null) { 150 | mCaptureActivityHandler.quitSynchronously(); 151 | mCaptureActivityHandler = null; 152 | } 153 | CameraManager.get().closeDriver(); 154 | } 155 | 156 | @Override 157 | protected void onDestroy() { 158 | if (null != mInactivityTimer) { 159 | mInactivityTimer.shutdown(); 160 | } 161 | super.onDestroy(); 162 | } 163 | 164 | @Override 165 | public void onClick(View v) { 166 | finish(); 167 | } 168 | 169 | /** 170 | * Handler scan result 171 | * 172 | * @param result 173 | */ 174 | public void handleDecode(Result result) { 175 | mInactivityTimer.onActivity(); 176 | playBeepSoundAndVibrate(); 177 | if (null == result) { 178 | mDecodeManager.showCouldNotReadQrCodeFromScanner(this, new DecodeManager.OnRefreshCameraListener() { 179 | @Override 180 | public void refresh() { 181 | 182 | restartPreview(); 183 | } 184 | }); 185 | } else { 186 | String resultString = result.getText(); 187 | handleResult(resultString); 188 | } 189 | } 190 | 191 | private void initCamera(SurfaceTexture surfaceHolder) { 192 | try { 193 | CameraManager.get().openDriver(surfaceHolder); 194 | } catch (IOException e) { 195 | // 基本不会出现相机不存在的情况 196 | Toast.makeText(this, getString(R.string.qr_code_camera_not_found), Toast.LENGTH_SHORT).show(); 197 | finish(); 198 | return; 199 | } catch (RuntimeException re) { 200 | re.printStackTrace(); 201 | mDecodeManager.showPermissionDeniedDialog(this); 202 | return; 203 | } 204 | mQrCodeFinderView.setVisibility(View.VISIBLE); 205 | mSurfaceView.setVisibility(View.VISIBLE); 206 | findViewById(R.id.qr_code_view_background).setVisibility(View.GONE); 207 | if (mCaptureActivityHandler == null) { 208 | mCaptureActivityHandler = new CaptureActivityHandler(this); 209 | } 210 | } 211 | 212 | private void restartPreview() { 213 | if (null != mCaptureActivityHandler) { 214 | mCaptureActivityHandler.restartPreviewAndDecode(); 215 | } 216 | } 217 | 218 | /* 检测相机是否存在 */ 219 | private boolean checkCameraHardWare(Context context) { 220 | PackageManager packageManager = context.getPackageManager(); 221 | return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA); 222 | } 223 | 224 | @Override 225 | public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) { 226 | if (!mHasSurface) { 227 | mHasSurface = true; 228 | initCamera(arg0); 229 | } 230 | } 231 | 232 | @Override 233 | public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) { 234 | mHasSurface = false; 235 | return true; 236 | } 237 | 238 | @Override 239 | public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1, int arg2) { 240 | } 241 | 242 | @Override 243 | public void onSurfaceTextureUpdated(SurfaceTexture arg0) { 244 | // Matrix transform = new Matrix(); 245 | // transform.setScale(-1, 1, mSurfaceView.getWidth() / 2, 0); 246 | // mSurfaceView.setTransform(transform); 247 | } 248 | 249 | public Handler getCaptureActivityHandler() { 250 | return mCaptureActivityHandler; 251 | } 252 | 253 | private void initBeepSound() { 254 | if (mPlayBeep && mMediaPlayer == null) { 255 | // The volume on STREAM_SYSTEM is not adjustable, and users found it too loud, 256 | // so we now play on the music stream. 257 | // setVolumeControlStream(AudioManager.STREAM_MUSIC); 258 | // mMediaPlayer = new MediaPlayer(); 259 | // mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 260 | // mMediaPlayer.setOnCompletionListener(mBeepListener); 261 | // 262 | // AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.beep); 263 | // try { 264 | // mMediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); 265 | // file.close(); 266 | // mMediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); 267 | // mMediaPlayer.prepare(); 268 | // } catch (IOException e) { 269 | // mMediaPlayer = null; 270 | // } 271 | } 272 | } 273 | 274 | private void playBeepSoundAndVibrate() { 275 | if (mPlayBeep && mMediaPlayer != null) { 276 | mMediaPlayer.start(); 277 | } 278 | // if (mVibrate) { 279 | // Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); 280 | // vibrator.vibrate(VIBRATE_DURATION); 281 | // } 282 | } 283 | 284 | /** 285 | * When the beep has finished playing, rewind to queue up another one. 286 | */ 287 | private final MediaPlayer.OnCompletionListener mBeepListener = new MediaPlayer.OnCompletionListener() { 288 | public void onCompletion(MediaPlayer mediaPlayer) { 289 | mediaPlayer.seekTo(0); 290 | } 291 | }; 292 | 293 | private void handleResult(String resultString) { 294 | if (TextUtils.isEmpty(resultString)) { 295 | mDecodeManager.showCouldNotReadQrCodeFromScanner(this, new DecodeManager.OnRefreshCameraListener() { 296 | @Override 297 | public void refresh() { 298 | restartPreview(); 299 | } 300 | }); 301 | } else { 302 | // mDecodeManager.showResultDialog(this, resultString, new DialogInterface.OnClickListener() { 303 | // @Override 304 | // public void onClick(DialogInterface dialog, int which) { 305 | // dialog.dismiss(); 306 | // restartPreview(); 307 | // } 308 | // }); 309 | Intent intent = new Intent(); 310 | intent.putExtra("Result", resultString); 311 | setResult(RESULT_OK, intent); 312 | finish(); 313 | } 314 | } 315 | 316 | @Override 317 | protected void onActivityResult(int requestCode, int resultCode, final Intent data) { 318 | if (resultCode != RESULT_OK) { 319 | return; 320 | } 321 | switch (requestCode) { 322 | case REQUEST_PICTURE: 323 | finish(); 324 | break; 325 | case REQUEST_SYSTEM_PICTURE: 326 | Uri uri = data.getData(); 327 | Cursor cursor = getContentResolver().query(uri, null, null, null, null); 328 | if (null != cursor) { 329 | cursor.moveToFirst(); 330 | String imgPath = cursor.getString(1); // 图片文件路径 331 | cursor.close(); 332 | if (null != mQrCodeExecutor && !TextUtils.isEmpty(imgPath)) { 333 | mQrCodeExecutor.execute(new DecodeImageThread(imgPath, mDecodeImageCallback)); 334 | } 335 | } 336 | break; 337 | } 338 | } 339 | 340 | private DecodeImageCallback mDecodeImageCallback = new DecodeImageCallback() { 341 | @Override 342 | public void decodeSucceed(Result result) { 343 | mHandler.obtainMessage(MSG_DECODE_SUCCEED, result).sendToTarget(); 344 | } 345 | 346 | @Override 347 | public void decodeFail(int type, String reason) { 348 | mHandler.sendEmptyMessage(MSG_DECODE_FAIL); 349 | } 350 | }; 351 | 352 | private static class WeakHandler extends Handler { 353 | private WeakReference mWeakQrCodeActivity; 354 | private DecodeManager mDecodeManager = new DecodeManager(); 355 | 356 | public WeakHandler(QrCodeActivity imagePickerActivity) { 357 | super(); 358 | this.mWeakQrCodeActivity = new WeakReference<>(imagePickerActivity); 359 | } 360 | 361 | @Override 362 | public void handleMessage(Message msg) { 363 | QrCodeActivity qrCodeActivity = mWeakQrCodeActivity.get(); 364 | switch (msg.what) { 365 | case MSG_DECODE_SUCCEED: 366 | Result result = (Result) msg.obj; 367 | if (null == result) { 368 | mDecodeManager.showCouldNotReadQrCodeFromPicture(qrCodeActivity); 369 | } else { 370 | String resultString = result.getText(); 371 | handleResult(resultString); 372 | } 373 | break; 374 | case MSG_DECODE_FAIL: 375 | mDecodeManager.showCouldNotReadQrCodeFromPicture(qrCodeActivity); 376 | break; 377 | } 378 | super.handleMessage(msg); 379 | } 380 | 381 | private void handleResult(String resultString) { 382 | QrCodeActivity imagePickerActivity = mWeakQrCodeActivity.get(); 383 | mDecodeManager.showResultDialog(imagePickerActivity, resultString, new DialogInterface.OnClickListener() { 384 | @Override 385 | public void onClick(DialogInterface dialog, int which) { 386 | dialog.dismiss(); 387 | } 388 | }); 389 | } 390 | 391 | } 392 | } -------------------------------------------------------------------------------- /app/src/main/java/qrcode/camera/AutoFocusCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.camera; 15 | 16 | import android.hardware.Camera; 17 | import android.os.Handler; 18 | import android.os.Message; 19 | import android.util.Log; 20 | 21 | final class AutoFocusCallback implements Camera.AutoFocusCallback { 22 | private static final String TAG = AutoFocusCallback.class.getName(); 23 | private static final long AUTO_FOCUS_INTERVAL_MS = 1500L; 24 | 25 | private Handler mAutoFocusHandler; 26 | private int mAutoFocusMessage; 27 | 28 | void setHandler(Handler autoFocusHandler, int autoFocusMessage) { 29 | this.mAutoFocusHandler = autoFocusHandler; 30 | this.mAutoFocusMessage = autoFocusMessage; 31 | } 32 | 33 | public void onAutoFocus(boolean success, Camera camera) { 34 | if (mAutoFocusHandler != null) { 35 | Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success); 36 | mAutoFocusHandler.sendMessageDelayed(message, AUTO_FOCUS_INTERVAL_MS); 37 | mAutoFocusHandler = null; 38 | } else { 39 | Log.v(TAG, "Got auto-focus callback, but no handler for it"); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/camera/CameraConfigurationManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.camera; 15 | 16 | import android.content.Context; 17 | import android.graphics.Point; 18 | import android.hardware.Camera; 19 | import android.util.Log; 20 | 21 | import java.util.Collections; 22 | import java.util.Comparator; 23 | import java.util.List; 24 | import java.util.regex.Pattern; 25 | 26 | import qrcode.utils.ScreenUtils; 27 | 28 | final class CameraConfigurationManager { 29 | private static final String TAG = CameraConfigurationManager.class.getName(); 30 | private static final int TEN_DESIRED_ZOOM = 10; 31 | private static final int DESIRED_SHARPNESS = 30; 32 | 33 | private static final Pattern COMMA_PATTERN = Pattern.compile(","); 34 | 35 | private Camera.Size mCameraResolution; 36 | private Camera.Size mPictureResolution; 37 | private Context mContext; 38 | 39 | CameraConfigurationManager(Context context) { 40 | this.mContext = context; 41 | } 42 | 43 | /** 44 | * Reads, one time, values from the camera that are needed by the app. 45 | */ 46 | void initFromCameraParameters(Camera camera) { 47 | Camera.Parameters parameters = camera.getParameters(); 48 | mCameraResolution = findCloselySize(ScreenUtils.getScreenWidth(mContext), ScreenUtils.getScreenHeight(mContext), 49 | parameters.getSupportedPreviewSizes()); 50 | Log.e(TAG, "Setting preview size: " + mCameraResolution.width + "-" + mCameraResolution.height); 51 | mPictureResolution = findCloselySize(ScreenUtils.getScreenWidth(mContext), 52 | ScreenUtils.getScreenHeight(mContext), parameters.getSupportedPictureSizes()); 53 | Log.e(TAG, "Setting picture size: " + mPictureResolution.width + "-" + mPictureResolution.height); 54 | } 55 | 56 | /** 57 | * Sets the camera up to take preview images which are used for both preview and decoding. We detect the preview 58 | * format here so that buildLuminanceSource() can build an appropriate LuminanceSource subclass. In the future we 59 | * may want to force YUV420SP as it's the smallest, and the planar Y can be used for barcode scanning without a copy 60 | * in some cases. 61 | */ 62 | void setDesiredCameraParameters(Camera camera) { 63 | 64 | Camera.Parameters parameters = camera.getParameters(); 65 | parameters.setPreviewSize(mCameraResolution.width, mCameraResolution.height); 66 | parameters.setPictureSize(mPictureResolution.width, mPictureResolution.height); 67 | setZoom(parameters); 68 | camera.setDisplayOrientation(90); 69 | camera.setParameters(parameters); 70 | } 71 | 72 | Camera.Size getCameraResolution() { 73 | return mCameraResolution; 74 | } 75 | 76 | private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) { 77 | 78 | String previewSizeValueString = parameters.get("preview-size-values"); 79 | // saw this on Xperia 80 | if (previewSizeValueString == null) { 81 | previewSizeValueString = parameters.get("preview-size-value"); 82 | } 83 | 84 | Point cameraResolution = null; 85 | 86 | if (previewSizeValueString != null) { 87 | Log.e(TAG, "preview-size-values parameter: " + previewSizeValueString); 88 | cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); 89 | } 90 | 91 | if (cameraResolution == null) { 92 | // Ensure that the camera resolution is a multiple of 8, as the screen may not be. 93 | cameraResolution = new Point((screenResolution.x >> 3) << 3, (screenResolution.y >> 3) << 3); 94 | } 95 | 96 | return cameraResolution; 97 | } 98 | 99 | private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) { 100 | int bestX = 0; 101 | int bestY = 0; 102 | int diff = Integer.MAX_VALUE; 103 | for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { 104 | 105 | previewSize = previewSize.trim(); 106 | int dimPosition = previewSize.indexOf('x'); 107 | if (dimPosition < 0) { 108 | Log.e(TAG, "Bad preview-size: " + previewSize); 109 | continue; 110 | } 111 | 112 | int newX; 113 | int newY; 114 | try { 115 | newY = Integer.parseInt(previewSize.substring(0, dimPosition)); 116 | newX = Integer.parseInt(previewSize.substring(dimPosition + 1)); 117 | } catch (NumberFormatException nfe) { 118 | Log.e(TAG, "Bad preview-size: " + previewSize); 119 | continue; 120 | } 121 | 122 | int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); 123 | if (newDiff == 0) { 124 | bestX = newX; 125 | bestY = newY; 126 | break; 127 | } else if (newDiff < diff) { 128 | bestX = newX; 129 | bestY = newY; 130 | diff = newDiff; 131 | } 132 | 133 | } 134 | 135 | if (bestX > 0 && bestY > 0) { 136 | return new Point(bestX, bestY); 137 | } 138 | return null; 139 | } 140 | 141 | private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) { 142 | int tenBestValue = 0; 143 | for (String stringValue : COMMA_PATTERN.split(stringValues)) { 144 | stringValue = stringValue.trim(); 145 | double value; 146 | try { 147 | value = Double.parseDouble(stringValue); 148 | } catch (NumberFormatException nfe) { 149 | return tenDesiredZoom; 150 | } 151 | int tenValue = (int) (10.0 * value); 152 | if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) { 153 | tenBestValue = tenValue; 154 | } 155 | } 156 | return tenBestValue; 157 | } 158 | 159 | private void setZoom(Camera.Parameters parameters) { 160 | 161 | String zoomSupportedString = parameters.get("zoom-supported"); 162 | if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) { 163 | return; 164 | } 165 | 166 | int tenDesiredZoom = TEN_DESIRED_ZOOM; 167 | 168 | String maxZoomString = parameters.get("max-zoom"); 169 | if (maxZoomString != null) { 170 | try { 171 | int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString)); 172 | if (tenDesiredZoom > tenMaxZoom) { 173 | tenDesiredZoom = tenMaxZoom; 174 | } 175 | } catch (NumberFormatException nfe) { 176 | Log.e(TAG, "Bad max-zoom: " + maxZoomString); 177 | } 178 | } 179 | 180 | String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max"); 181 | if (takingPictureZoomMaxString != null) { 182 | try { 183 | int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString); 184 | if (tenDesiredZoom > tenMaxZoom) { 185 | tenDesiredZoom = tenMaxZoom; 186 | } 187 | } catch (NumberFormatException nfe) { 188 | Log.e(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString); 189 | } 190 | } 191 | 192 | String motZoomValuesString = parameters.get("mot-zoom-values"); 193 | if (motZoomValuesString != null) { 194 | tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom); 195 | } 196 | 197 | String motZoomStepString = parameters.get("mot-zoom-step"); 198 | if (motZoomStepString != null) { 199 | try { 200 | double motZoomStep = Double.parseDouble(motZoomStepString.trim()); 201 | int tenZoomStep = (int) (10.0 * motZoomStep); 202 | if (tenZoomStep > 1) { 203 | tenDesiredZoom -= tenDesiredZoom % tenZoomStep; 204 | } 205 | } catch (NumberFormatException nfe) { 206 | // continue 207 | } 208 | } 209 | 210 | // Set zoom. This helps encourage the user to pull back. 211 | // Some devices like the Behold have a zoom parameter 212 | // if (maxZoomString != null || motZoomValuesString != null) { 213 | // parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0)); 214 | // } 215 | if (parameters.isZoomSupported()) { 216 | Log.e(TAG, "max-zoom:" + parameters.getMaxZoom()); 217 | parameters.setZoom(parameters.getMaxZoom() / 10); 218 | } else { 219 | Log.e(TAG, "Unsupported zoom."); 220 | } 221 | 222 | // Most devices, like the Hero, appear to expose this zoom parameter. 223 | // It takes on values like "27" which appears to mean 2.7x zoom 224 | // if (takingPictureZoomMaxString != null) { 225 | // parameters.set("taking-picture-zoom", tenDesiredZoom); 226 | // } 227 | } 228 | 229 | public static int getDesiredSharpness() { 230 | return DESIRED_SHARPNESS; 231 | } 232 | 233 | /** 234 | * 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择) 235 | * 236 | * @param surfaceWidth 需要被进行对比的原宽 237 | * @param surfaceHeight 需要被进行对比的原高 238 | * @param preSizeList 需要对比的预览尺寸列表 239 | * @return 得到与原宽高比例最接近的尺寸 240 | */ 241 | protected Camera.Size findCloselySize(int surfaceWidth, int surfaceHeight, List preSizeList) { 242 | 243 | // // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高 244 | // int ReqTmpWidth = surfaceHeight; 245 | // int ReqTmpHeight = surfaceWidth; 246 | // 247 | // // 先查找preview中是否存在与SurfaceView相同宽高的尺寸 248 | // for (Size size : preSizeList) { 249 | // if ((size.width == ReqTmpWidth) && (size.height == ReqTmpHeight)) { 250 | // return size; 251 | // } 252 | // } 253 | // 254 | // // 得到与传入的宽高比最接近的size 255 | // float reqRatio = ((float) ReqTmpWidth) / ReqTmpHeight; 256 | // float curRatio, deltaRatio; 257 | // float deltaRatioMin = Float.MAX_VALUE; 258 | // Size retSize = null; 259 | // for (Size size : preSizeList) { 260 | // curRatio = ((float) size.width) / size.height; 261 | // deltaRatio = Math.abs(reqRatio - curRatio); 262 | // if (deltaRatio < deltaRatioMin) { 263 | // deltaRatioMin = deltaRatio; 264 | // retSize = size; 265 | // } 266 | // } 267 | Collections.sort(preSizeList, new SizeComparator(surfaceWidth, surfaceHeight)); 268 | return preSizeList.get(0); 269 | } 270 | 271 | /** 272 | * 预览尺寸与给定的宽高尺寸比较器。首先比较宽高的比例,在宽高比相同的情况下,根据宽和高的最小差进行比较。 273 | */ 274 | private static class SizeComparator implements Comparator { 275 | 276 | private final int width; 277 | private final int height; 278 | private final float ratio; 279 | 280 | SizeComparator(int width, int height) { 281 | if (width < height) { 282 | this.width = height; 283 | this.height = width; 284 | } else { 285 | this.width = width; 286 | this.height = height; 287 | } 288 | this.ratio = (float) this.height / this.width; 289 | } 290 | 291 | @Override 292 | public int compare(Camera.Size size1, Camera.Size size2) { 293 | int width1 = size1.width; 294 | int height1 = size1.height; 295 | int width2 = size2.width; 296 | int height2 = size2.height; 297 | 298 | float ratio1 = Math.abs((float) height1 / width1 - ratio); 299 | float ratio2 = Math.abs((float) height2 / width2 - ratio); 300 | int result = Float.compare(ratio1, ratio2); 301 | if (result != 0) { 302 | return result; 303 | } else { 304 | int minGap1 = Math.abs(width - width1) + Math.abs(height - height1); 305 | int minGap2 = Math.abs(width - width2) + Math.abs(height - height2); 306 | return minGap1 - minGap2; 307 | } 308 | } 309 | } 310 | } -------------------------------------------------------------------------------- /app/src/main/java/qrcode/camera/CameraManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.camera; 15 | 16 | import android.content.Context; 17 | import android.graphics.SurfaceTexture; 18 | import android.hardware.Camera; 19 | import android.os.Handler; 20 | 21 | import java.io.IOException; 22 | import java.util.List; 23 | 24 | /** 25 | * This object wraps the Camera service object and expects to be the only one talking to it. The implementation 26 | * encapsulates the steps needed to take preview-sized images, which are used for both preview and decoding. 27 | * 28 | */ 29 | public final class CameraManager { 30 | 31 | private static CameraManager sCameraManager; 32 | 33 | private final CameraConfigurationManager mConfigManager; 34 | private Camera mCamera; 35 | private boolean mInitialized; 36 | private boolean mPreviewing; 37 | /** 38 | * Preview frames are delivered here, which we pass on to the registered handler. Make sure to clear the handler so 39 | * it will only receive one message. 40 | */ 41 | private final PreviewCallback mPreviewCallback; 42 | /** Auto-focus callbacks arrive here, and are dispatched to the Handler which requested them. */ 43 | private final AutoFocusCallback mAutoFocusCallback; 44 | 45 | /** 46 | * Initializes this static object with the Context of the calling Activity. 47 | */ 48 | public static void init(Context context) { 49 | if (sCameraManager == null) { 50 | sCameraManager = new CameraManager(context); 51 | } 52 | } 53 | 54 | /** 55 | * Gets the CameraManager singleton instance. 56 | * 57 | * @return A reference to the CameraManager singleton. 58 | */ 59 | public static CameraManager get() { 60 | return sCameraManager; 61 | } 62 | 63 | private CameraManager(Context context) { 64 | this.mConfigManager = new CameraConfigurationManager(context); 65 | mPreviewCallback = new PreviewCallback(mConfigManager); 66 | mAutoFocusCallback = new AutoFocusCallback(); 67 | } 68 | 69 | /** 70 | * Opens the mCamera driver and initializes the hardware parameters. 71 | * 72 | * @param holder The surface object which the mCamera will draw preview frames into. 73 | * @throws IOException Indicates the mCamera driver failed to open. 74 | */ 75 | public void openDriver(SurfaceTexture holder) throws IOException { 76 | if (mCamera == null) { 77 | mCamera = Camera.open(); 78 | if (mCamera == null) { 79 | throw new IOException(); 80 | } 81 | mCamera.setPreviewTexture(holder); 82 | 83 | if (!mInitialized) { 84 | mInitialized = true; 85 | mConfigManager.initFromCameraParameters(mCamera); 86 | } 87 | mConfigManager.setDesiredCameraParameters(mCamera); 88 | } 89 | } 90 | 91 | /** 92 | * 打开或关闭闪光灯 93 | * 94 | * @param open 控制是否打开 95 | * @return 打开或关闭失败,则返回false。 96 | */ 97 | public boolean setFlashLight(boolean open) { 98 | if (mCamera == null) { 99 | return false; 100 | } 101 | Camera.Parameters parameters = mCamera.getParameters(); 102 | if (parameters == null) { 103 | return false; 104 | } 105 | List flashModes = parameters.getSupportedFlashModes(); 106 | // Check if camera flash exists 107 | if (null == flashModes || 0 == flashModes.size()) { 108 | // Use the screen as a flashlight (next best thing) 109 | return false; 110 | } 111 | String flashMode = parameters.getFlashMode(); 112 | if (open) { 113 | if (Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode)) { 114 | return true; 115 | } 116 | // Turn on the flash 117 | if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) { 118 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 119 | mCamera.setParameters(parameters); 120 | return true; 121 | } else { 122 | return false; 123 | } 124 | } else { 125 | if (Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)) { 126 | return true; 127 | } 128 | // Turn on the flash 129 | if (flashModes.contains(Camera.Parameters.FLASH_MODE_OFF)) { 130 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 131 | mCamera.setParameters(parameters); 132 | return true; 133 | } else 134 | return false; 135 | } 136 | } 137 | 138 | /** 139 | * Closes the camera driver if still in use. 140 | */ 141 | public void closeDriver() { 142 | if (mCamera != null) { 143 | mCamera.release(); 144 | mInitialized = false; 145 | mPreviewing = false; 146 | mCamera = null; 147 | } 148 | } 149 | 150 | /** 151 | * Asks the mCamera hardware to begin drawing preview frames to the screen. 152 | */ 153 | public void startPreview() { 154 | if (mCamera != null && !mPreviewing) { 155 | mCamera.startPreview(); 156 | mPreviewing = true; 157 | } 158 | } 159 | 160 | /** 161 | * Tells the mCamera to stop drawing preview frames. 162 | */ 163 | public void stopPreview() { 164 | if (mCamera != null && mPreviewing) { 165 | mCamera.stopPreview(); 166 | mPreviewCallback.setHandler(null, 0); 167 | mAutoFocusCallback.setHandler(null, 0); 168 | mPreviewing = false; 169 | } 170 | } 171 | 172 | /** 173 | * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] in the 174 | * message.obj field, with width and height encoded as message.arg1 and message.arg2, respectively. 175 | * 176 | * @param handler The handler to send the message to. 177 | * @param message The what field of the message to be sent. 178 | */ 179 | public void requestPreviewFrame(Handler handler, int message) { 180 | if (mCamera != null && mPreviewing) { 181 | mPreviewCallback.setHandler(handler, message); 182 | mCamera.setOneShotPreviewCallback(mPreviewCallback); 183 | } 184 | } 185 | 186 | /** 187 | * Asks the mCamera hardware to perform an autofocus. 188 | * 189 | * @param handler The Handler to notify when the autofocus completes. 190 | * @param message The message to deliver. 191 | */ 192 | public void requestAutoFocus(Handler handler, int message) { 193 | if (mCamera != null && mPreviewing) { 194 | mAutoFocusCallback.setHandler(handler, message); 195 | // Log.d(TAG, "Requesting auto-focus callback"); 196 | mCamera.autoFocus(mAutoFocusCallback); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/camera/PreviewCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.camera; 15 | 16 | import android.hardware.Camera; 17 | import android.os.Handler; 18 | import android.os.Message; 19 | import android.util.Log; 20 | 21 | final class PreviewCallback implements Camera.PreviewCallback { 22 | private static final String TAG = PreviewCallback.class.getName(); 23 | private final CameraConfigurationManager mConfigManager; 24 | private Handler mPreviewHandler; 25 | private int mPreviewMessage; 26 | 27 | PreviewCallback(CameraConfigurationManager configManager) { 28 | this.mConfigManager = configManager; 29 | } 30 | 31 | void setHandler(Handler previewHandler, int previewMessage) { 32 | this.mPreviewHandler = previewHandler; 33 | this.mPreviewMessage = previewMessage; 34 | } 35 | 36 | @Override 37 | public void onPreviewFrame(byte[] data, Camera camera) { 38 | Camera.Size cameraResolution = mConfigManager.getCameraResolution(); 39 | if (mPreviewHandler != null) { 40 | Message message = 41 | mPreviewHandler.obtainMessage(mPreviewMessage, cameraResolution.width, cameraResolution.height, data); 42 | message.sendToTarget(); 43 | mPreviewHandler = null; 44 | } else { 45 | Log.v(TAG, "no handler callback."); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/CaptureActivityHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.decode; 15 | 16 | import android.os.Handler; 17 | import android.os.Message; 18 | import android.util.Log; 19 | 20 | import com.ttv.livedemo.R; 21 | import com.google.zxing.Result; 22 | 23 | import qrcode.QrCodeActivity; 24 | import qrcode.camera.CameraManager; 25 | 26 | /** 27 | * This class handles all the messaging which comprises the state machine for capture. 28 | */ 29 | public final class CaptureActivityHandler extends Handler { 30 | private static final String TAG = CaptureActivityHandler.class.getName(); 31 | 32 | private final QrCodeActivity mActivity; 33 | private final DecodeThread mDecodeThread; 34 | private State mState; 35 | 36 | private enum State { 37 | PREVIEW, SUCCESS, DONE 38 | } 39 | 40 | public CaptureActivityHandler(QrCodeActivity activity) { 41 | this.mActivity = activity; 42 | mDecodeThread = new DecodeThread(activity); 43 | mDecodeThread.start(); 44 | mState = State.SUCCESS; 45 | // Start ourselves capturing previews and decoding. 46 | restartPreviewAndDecode(); 47 | } 48 | 49 | @Override 50 | public void handleMessage(Message message) { 51 | switch (message.what) { 52 | case R.id.auto_focus: 53 | // Log.d(TAG, "Got auto-focus message"); 54 | // When one auto focus pass finishes, start another. This is the closest thing to 55 | // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do. 56 | if (mState == State.PREVIEW) { 57 | CameraManager.get().requestAutoFocus(this, R.id.auto_focus); 58 | } 59 | break; 60 | case R.id.decode_succeeded: 61 | Log.e(TAG, "Got decode succeeded message"); 62 | mState = State.SUCCESS; 63 | mActivity.handleDecode((Result) message.obj); 64 | break; 65 | case R.id.decode_failed: 66 | // We're decoding as fast as possible, so when one decode fails, start another. 67 | mState = State.PREVIEW; 68 | CameraManager.get().requestPreviewFrame(mDecodeThread.getHandler(), R.id.decode); 69 | break; 70 | } 71 | } 72 | 73 | public void quitSynchronously() { 74 | mState = State.DONE; 75 | CameraManager.get().stopPreview(); 76 | Message quit = Message.obtain(mDecodeThread.getHandler(), R.id.quit); 77 | quit.sendToTarget(); 78 | try { 79 | mDecodeThread.join(); 80 | } catch (InterruptedException e) { 81 | // continue 82 | } 83 | 84 | // Be absolutely sure we don't send any queued up messages 85 | removeMessages(R.id.decode_succeeded); 86 | removeMessages(R.id.decode_failed); 87 | } 88 | 89 | public void restartPreviewAndDecode() { 90 | if (mState != State.PREVIEW) { 91 | CameraManager.get().startPreview(); 92 | mState = State.PREVIEW; 93 | CameraManager.get().requestPreviewFrame(mDecodeThread.getHandler(), R.id.decode); 94 | CameraManager.get().requestAutoFocus(this, R.id.auto_focus); 95 | } 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/DecodeHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.decode; 15 | 16 | import android.os.Handler; 17 | import android.os.Looper; 18 | import android.os.Message; 19 | 20 | import com.ttv.livedemo.R; 21 | import com.google.zxing.BarcodeFormat; 22 | import com.google.zxing.BinaryBitmap; 23 | import com.google.zxing.DecodeHintType; 24 | import com.google.zxing.PlanarYUVLuminanceSource; 25 | import com.google.zxing.ReaderException; 26 | import com.google.zxing.Result; 27 | import com.google.zxing.common.HybridBinarizer; 28 | import com.google.zxing.qrcode.QRCodeReader; 29 | 30 | import java.util.Arrays; 31 | import java.util.Hashtable; 32 | import java.util.Map; 33 | 34 | import qrcode.QrCodeActivity; 35 | 36 | final class DecodeHandler extends Handler { 37 | 38 | private final QrCodeActivity mActivity; 39 | private final QRCodeReader mQrCodeReader; 40 | private final Map mHints; 41 | private byte[] mRotatedData; 42 | 43 | DecodeHandler(QrCodeActivity activity) { 44 | this.mActivity = activity; 45 | mQrCodeReader = new QRCodeReader(); 46 | mHints = new Hashtable<>(); 47 | mHints.put(DecodeHintType.CHARACTER_SET, "utf-8"); 48 | mHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); 49 | mHints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); 50 | } 51 | 52 | @Override 53 | public void handleMessage(Message message) { 54 | switch (message.what) { 55 | case R.id.decode: 56 | decode((byte[]) message.obj, message.arg1, message.arg2); 57 | break; 58 | case R.id.quit: 59 | Looper looper = Looper.myLooper(); 60 | if (null != looper) { 61 | looper.quit(); 62 | } 63 | break; 64 | } 65 | } 66 | 67 | /** 68 | * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, reuse the same reader 69 | * objects from one decode to the next. 70 | * 71 | * @param data The YUV preview frame. 72 | * @param width The width of the preview frame. 73 | * @param height The height of the preview frame. 74 | */ 75 | private void decode(byte[] data, int width, int height) { 76 | if (null == mRotatedData) { 77 | mRotatedData = new byte[width * height]; 78 | } else { 79 | if (mRotatedData.length < width * height) { 80 | mRotatedData = new byte[width * height]; 81 | } 82 | } 83 | Arrays.fill(mRotatedData, (byte) 0); 84 | for (int y = 0; y < height; y++) { 85 | for (int x = 0; x < width; x++) { 86 | if (x + y * width >= data.length) { 87 | break; 88 | } 89 | mRotatedData[x * height + height - y - 1] = data[x + y * width]; 90 | } 91 | } 92 | int tmp = width; // Here we are swapping, that's the difference to #11 93 | width = height; 94 | height = tmp; 95 | 96 | Result rawResult = null; 97 | try { 98 | PlanarYUVLuminanceSource source = 99 | new PlanarYUVLuminanceSource(mRotatedData, width, height, 0, 0, width, height, false); 100 | BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source)); 101 | rawResult = mQrCodeReader.decode(bitmap1, mHints); 102 | } catch (ReaderException e) { 103 | } finally { 104 | mQrCodeReader.reset(); 105 | } 106 | 107 | if (rawResult != null) { 108 | Message message = Message.obtain(mActivity.getCaptureActivityHandler(), R.id.decode_succeeded, rawResult); 109 | message.sendToTarget(); 110 | } else { 111 | Message message = Message.obtain(mActivity.getCaptureActivityHandler(), R.id.decode_failed); 112 | message.sendToTarget(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/DecodeImageCallback.java: -------------------------------------------------------------------------------- 1 | package qrcode.decode; 2 | 3 | import com.google.zxing.Result; 4 | 5 | /** 6 | * Created by xingli on 1/4/16. 7 | * 8 | * 图片解析二维码回调方法 9 | */ 10 | public interface DecodeImageCallback { 11 | 12 | void decodeSucceed(Result result); 13 | 14 | void decodeFail(int type, String reason); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/DecodeImageThread.java: -------------------------------------------------------------------------------- 1 | package qrcode.decode; 2 | 3 | import android.graphics.Bitmap; 4 | import android.text.TextUtils; 5 | 6 | import com.google.zxing.Result; 7 | 8 | import qrcode.utils.QrUtils; 9 | 10 | /** 11 | * Created by xingli on 1/4/16. 12 | * 13 | * 解析图像二维码线程 14 | */ 15 | public class DecodeImageThread implements Runnable { 16 | private static final int MAX_PICTURE_PIXEL = 256; 17 | private byte[] mData; 18 | private int mWidth; 19 | private int mHeight; 20 | private String mImgPath; 21 | private DecodeImageCallback mCallback; 22 | 23 | public DecodeImageThread(String imgPath, DecodeImageCallback callback) { 24 | this.mImgPath = imgPath; 25 | this.mCallback = callback; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | if (null == mData) { 31 | if (!TextUtils.isEmpty(mImgPath)) { 32 | Bitmap bitmap = QrUtils.decodeSampledBitmapFromFile(mImgPath, MAX_PICTURE_PIXEL, MAX_PICTURE_PIXEL); 33 | this.mData = QrUtils.getYUV420sp(bitmap.getWidth(), bitmap.getHeight(), bitmap); 34 | this.mWidth = bitmap.getWidth(); 35 | this.mHeight = bitmap.getHeight(); 36 | } 37 | } 38 | 39 | if (mData == null || mData.length == 0 || mWidth == 0 || mHeight == 0) { 40 | if (null != mCallback) { 41 | mCallback.decodeFail(0, "No image data"); 42 | } 43 | return; 44 | } 45 | 46 | final Result result = QrUtils.decodeImage(mData, mWidth, mHeight); 47 | 48 | if (null != mCallback) { 49 | if (null != result) { 50 | mCallback.decodeSucceed(result); 51 | } else { 52 | mCallback.decodeFail(0, "Decode image failed."); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/DecodeManager.java: -------------------------------------------------------------------------------- 1 | package qrcode.decode; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | 8 | import com.ttv.livedemo.R; 9 | 10 | 11 | /** 12 | * Created by xingli on 1/8/16. 13 | *

14 | * 二维码解析管理。 15 | */ 16 | public class DecodeManager { 17 | 18 | public void showPermissionDeniedDialog(Context context) { 19 | // 权限在安装时被关闭了,如小米手机 20 | new AlertDialog.Builder(context).setTitle(R.string.qr_code_notification) 21 | .setMessage(R.string.qr_code_camera_not_open) 22 | .setPositiveButton(R.string.qr_code_positive_button_know, new DialogInterface.OnClickListener() { 23 | @Override 24 | public void onClick(DialogInterface dialog, int which) { 25 | dialog.dismiss(); 26 | } 27 | }).show(); 28 | } 29 | 30 | public void showResultDialog(Activity activity, String resultString, DialogInterface.OnClickListener listener) { 31 | new AlertDialog.Builder(activity).setTitle(R.string.qr_code_notification).setMessage(resultString) 32 | .setPositiveButton(R.string.qr_code_positive_button_confirm, listener).show(); 33 | } 34 | 35 | public void showCouldNotReadQrCodeFromScanner(Context context, final OnRefreshCameraListener listener) { 36 | new AlertDialog.Builder(context).setTitle(R.string.qr_code_notification) 37 | .setMessage(R.string.qr_code_could_not_read_qr_code_from_scanner) 38 | .setPositiveButton(R.string.qc_code_close, new DialogInterface.OnClickListener() { 39 | @Override 40 | public void onClick(DialogInterface dialog, int which) { 41 | dialog.dismiss(); 42 | if (listener != null) { 43 | listener.refresh(); 44 | } 45 | } 46 | }).show(); 47 | } 48 | 49 | public void showCouldNotReadQrCodeFromPicture(Context context) { 50 | new AlertDialog.Builder(context).setTitle(R.string.qr_code_notification) 51 | .setMessage(R.string.qr_code_could_not_read_qr_code_from_picture) 52 | .setPositiveButton(R.string.qc_code_close, new DialogInterface.OnClickListener() { 53 | @Override 54 | public void onClick(DialogInterface dialog, int which) { 55 | dialog.dismiss(); 56 | } 57 | }).show(); 58 | } 59 | 60 | public interface OnRefreshCameraListener { 61 | void refresh(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/DecodeThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.decode; 15 | 16 | import android.os.Handler; 17 | import android.os.Looper; 18 | 19 | import java.util.concurrent.CountDownLatch; 20 | 21 | import qrcode.QrCodeActivity; 22 | 23 | 24 | /** 25 | * This thread does all the heavy lifting of decoding the images. 26 | */ 27 | final class DecodeThread extends Thread { 28 | 29 | private final QrCodeActivity mActivity; 30 | private Handler mHandler; 31 | private final CountDownLatch mHandlerInitLatch; 32 | 33 | DecodeThread(QrCodeActivity activity) { 34 | this.mActivity = activity; 35 | mHandlerInitLatch = new CountDownLatch(1); 36 | } 37 | 38 | Handler getHandler() { 39 | try { 40 | mHandlerInitLatch.await(); 41 | } catch (InterruptedException ie) { 42 | // continue? 43 | } 44 | return mHandler; 45 | } 46 | 47 | @Override 48 | public void run() { 49 | Looper.prepare(); 50 | mHandler = new DecodeHandler(mActivity); 51 | mHandlerInitLatch.countDown(); 52 | Looper.loop(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/FinishListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.decode; 15 | 16 | import android.app.Activity; 17 | import android.content.DialogInterface; 18 | 19 | /** 20 | * Simple listener used to exit the app in a few cases. 21 | * 22 | */ 23 | public final class FinishListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, 24 | Runnable { 25 | 26 | private final Activity mActivityToFinish; 27 | 28 | public FinishListener(Activity activityToFinish) { 29 | this.mActivityToFinish = activityToFinish; 30 | } 31 | 32 | public void onCancel(DialogInterface dialogInterface) { 33 | run(); 34 | } 35 | 36 | public void onClick(DialogInterface dialogInterface, int i) { 37 | run(); 38 | } 39 | 40 | public void run() { 41 | mActivityToFinish.finish(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/decode/InactivityTimer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.decode; 15 | 16 | import android.app.Activity; 17 | 18 | import java.util.concurrent.Executors; 19 | import java.util.concurrent.ScheduledExecutorService; 20 | import java.util.concurrent.ScheduledFuture; 21 | import java.util.concurrent.ThreadFactory; 22 | import java.util.concurrent.TimeUnit; 23 | import androidx.annotation.NonNull; 24 | /** 25 | * Finishes an activity after a period of inactivity. 26 | */ 27 | public final class InactivityTimer { 28 | 29 | private static final int INACTIVITY_DELAY_SECONDS = 5 * 60; 30 | 31 | private final ScheduledExecutorService inactivityTimer = Executors 32 | .newSingleThreadScheduledExecutor(new DaemonThreadFactory()); 33 | private final Activity activity; 34 | private ScheduledFuture inactivityFuture = null; 35 | 36 | public InactivityTimer(Activity activity) { 37 | this.activity = activity; 38 | onActivity(); 39 | } 40 | 41 | public void onActivity() { 42 | cancel(); 43 | inactivityFuture = 44 | inactivityTimer.schedule(new FinishListener(activity), INACTIVITY_DELAY_SECONDS, TimeUnit.SECONDS); 45 | } 46 | 47 | private void cancel() { 48 | if (inactivityFuture != null) { 49 | inactivityFuture.cancel(true); 50 | inactivityFuture = null; 51 | } 52 | } 53 | 54 | public void shutdown() { 55 | cancel(); 56 | inactivityTimer.shutdown(); 57 | } 58 | 59 | private static final class DaemonThreadFactory implements ThreadFactory { 60 | public Thread newThread(@NonNull Runnable runnable) { 61 | Thread thread = new Thread(runnable); 62 | thread.setDaemon(true); 63 | return thread; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/utils/QrUtils.java: -------------------------------------------------------------------------------- 1 | package qrcode.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | 6 | import com.google.zxing.BarcodeFormat; 7 | import com.google.zxing.BinaryBitmap; 8 | import com.google.zxing.DecodeHintType; 9 | import com.google.zxing.PlanarYUVLuminanceSource; 10 | import com.google.zxing.ReaderException; 11 | import com.google.zxing.Result; 12 | import com.google.zxing.common.GlobalHistogramBinarizer; 13 | import com.google.zxing.qrcode.QRCodeReader; 14 | 15 | import java.util.Arrays; 16 | import java.util.Hashtable; 17 | 18 | /** 19 | * Created by xingli on 12/25/15. 20 | * 21 | * 二维码相关功能类 22 | */ 23 | public class QrUtils { 24 | private static byte[] yuvs; 25 | 26 | /** 27 | * YUV420sp 28 | * 29 | * @param inputWidth 30 | * @param inputHeight 31 | * @param scaled 32 | * @return 33 | */ 34 | public static byte[] getYUV420sp(int inputWidth, int inputHeight, Bitmap scaled) { 35 | int[] argb = new int[inputWidth * inputHeight]; 36 | 37 | scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight); 38 | 39 | /** 40 | * 需要转换成偶数的像素点,否则编码YUV420的时候有可能导致分配的空间大小不够而溢出。 41 | */ 42 | int requiredWidth = inputWidth % 2 == 0 ? inputWidth : inputWidth + 1; 43 | int requiredHeight = inputHeight % 2 == 0 ? inputHeight : inputHeight + 1; 44 | 45 | int byteLength = requiredWidth * requiredHeight * 3 / 2; 46 | if (yuvs == null || yuvs.length < byteLength) { 47 | yuvs = new byte[byteLength]; 48 | } else { 49 | Arrays.fill(yuvs, (byte) 0); 50 | } 51 | 52 | encodeYUV420SP(yuvs, argb, inputWidth, inputHeight); 53 | 54 | scaled.recycle(); 55 | 56 | return yuvs; 57 | } 58 | 59 | /** 60 | * RGB转YUV420sp 61 | * 62 | * @param yuv420sp inputWidth * inputHeight * 3 / 2 63 | * @param argb inputWidth * inputHeight 64 | * @param width 65 | * @param height 66 | */ 67 | private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) { 68 | // 帧图片的像素大小 69 | final int frameSize = width * height; 70 | // ---YUV数据--- 71 | int Y, U, V; 72 | // Y的index从0开始 73 | int yIndex = 0; 74 | // UV的index从frameSize开始 75 | int uvIndex = frameSize; 76 | 77 | // ---颜色数据--- 78 | // int a, R, G, B; 79 | int R, G, B; 80 | // 81 | int argbIndex = 0; 82 | // 83 | 84 | // ---循环所有像素点,RGB转YUV--- 85 | for (int j = 0; j < height; j++) { 86 | for (int i = 0; i < width; i++) { 87 | 88 | // a is not used obviously 89 | // a = (argb[argbIndex] & 0xff000000) >> 24; 90 | R = (argb[argbIndex] & 0xff0000) >> 16; 91 | G = (argb[argbIndex] & 0xff00) >> 8; 92 | B = (argb[argbIndex] & 0xff); 93 | // 94 | argbIndex++; 95 | 96 | // well known RGB to YUV algorithm 97 | Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; 98 | U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; 99 | V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; 100 | 101 | // 102 | Y = Math.max(0, Math.min(Y, 255)); 103 | U = Math.max(0, Math.min(U, 255)); 104 | V = Math.max(0, Math.min(V, 255)); 105 | 106 | // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 107 | // meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other 108 | // pixel AND every other scanline. 109 | // ---Y--- 110 | yuv420sp[yIndex++] = (byte) Y; 111 | // ---UV--- 112 | if ((j % 2 == 0) && (i % 2 == 0)) { 113 | // 114 | yuv420sp[uvIndex++] = (byte) V; 115 | // 116 | yuv420sp[uvIndex++] = (byte) U; 117 | } 118 | } 119 | } 120 | } 121 | 122 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 123 | // Raw height and width of image 124 | final int height = options.outHeight; 125 | final int width = options.outWidth; 126 | int inSampleSize = 1; 127 | 128 | if (height > reqHeight || width > reqWidth) { 129 | 130 | final int halfHeight = height / 2; 131 | final int halfWidth = width / 2; 132 | 133 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 134 | // height and width larger than the requested height and width. 135 | while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { 136 | inSampleSize *= 2; 137 | } 138 | } 139 | 140 | return inSampleSize; 141 | } 142 | 143 | public static Bitmap decodeSampledBitmapFromFile(String imgPath, int reqWidth, int reqHeight) { 144 | 145 | // First decode with inJustDecodeBounds=true to check dimensions 146 | final BitmapFactory.Options options = new BitmapFactory.Options(); 147 | options.inJustDecodeBounds = true; 148 | BitmapFactory.decodeFile(imgPath, options); 149 | 150 | // Calculate inSampleSize 151 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 152 | 153 | // Decode bitmap with inSampleSize set 154 | options.inJustDecodeBounds = false; 155 | return BitmapFactory.decodeFile(imgPath, options); 156 | } 157 | 158 | 159 | /** 160 | * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, reuse the same reader 161 | * objects from one decode to the next. 162 | */ 163 | public static Result decodeImage(byte[] data, int width, int height) { 164 | // 处理 165 | Result result = null; 166 | try { 167 | Hashtable hints = new Hashtable(); 168 | hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); 169 | hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); 170 | hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); 171 | PlanarYUVLuminanceSource source = 172 | new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false); 173 | /** 174 | * HybridBinarizer算法使用了更高级的算法,但使用GlobalHistogramBinarizer识别效率确实比HybridBinarizer要高一些。 175 | * 176 | * GlobalHistogram算法:(http://kuangjianwei.blog.163.com/blog/static/190088953201361015055110/) 177 | * 178 | * 二值化的关键就是定义出黑白的界限,我们的图像已经转化为了灰度图像,每个点都是由一个灰度值来表示,就需要定义出一个灰度值,大于这个值就为白(0),低于这个值就为黑(1)。 179 | * 在GlobalHistogramBinarizer中,是从图像中均匀取5行(覆盖整个图像高度),每行取中间五分之四作为样本;以灰度值为X轴,每个灰度值的像素个数为Y轴建立一个直方图, 180 | * 从直方图中取点数最多的一个灰度值,然后再去给其他的灰度值进行分数计算,按照点数乘以与最多点数灰度值的距离的平方来进行打分,选分数最高的一个灰度值。接下来在这两个灰度值中间选取一个区分界限, 181 | * 取的原则是尽量靠近中间并且要点数越少越好。界限有了以后就容易了,与整幅图像的每个点进行比较,如果灰度值比界限小的就是黑,在新的矩阵中将该点置1,其余的就是白,为0。 182 | */ 183 | BinaryBitmap bitmap1 = new BinaryBitmap(new GlobalHistogramBinarizer(source)); 184 | // BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source)); 185 | QRCodeReader reader2 = new QRCodeReader(); 186 | result = reader2.decode(bitmap1, hints); 187 | } catch (ReaderException e) { 188 | } 189 | return result; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/utils/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package qrcode.utils; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | 6 | /** 7 | * ScreenUtils 8 | *

    9 | * Convert between dp and sp 10 | *
11 | * 12 | * @author Trinea 2014-2-14 13 | */ 14 | public class ScreenUtils { 15 | 16 | private ScreenUtils() { 17 | throw new AssertionError(); 18 | } 19 | 20 | /** 21 | * 获取屏幕宽度 22 | * 23 | * @return 24 | */ 25 | public static int getScreenWidth(Context context) { 26 | 27 | DisplayMetrics dm = context.getResources().getDisplayMetrics(); 28 | return dm.widthPixels; 29 | } 30 | 31 | /** 32 | * 获取屏幕高度 33 | * 34 | * @return 35 | */ 36 | public static int getScreenHeight(Context context) { 37 | DisplayMetrics dm = context.getResources().getDisplayMetrics(); 38 | int screenHeight = dm.heightPixels; 39 | return screenHeight; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/qrcode/view/QrCodeFinderView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 ZXing authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package qrcode.view; 15 | 16 | import android.content.Context; 17 | import android.content.res.Resources; 18 | import android.graphics.Canvas; 19 | import android.graphics.Paint; 20 | import android.graphics.Rect; 21 | import android.util.AttributeSet; 22 | import android.view.LayoutInflater; 23 | import android.widget.FrameLayout; 24 | import android.widget.RelativeLayout; 25 | 26 | 27 | import com.ttv.livedemo.R; 28 | 29 | import qrcode.utils.ScreenUtils; 30 | 31 | /** 32 | * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial transparency outside 33 | * it, as well as the laser scanner animation and result points. 34 | */ 35 | public final class QrCodeFinderView extends RelativeLayout { 36 | 37 | private static final int[] SCANNER_ALPHA = { 0, 64, 128, 192, 255, 192, 128, 64 }; 38 | private static final long ANIMATION_DELAY = 100L; 39 | private static final int OPAQUE = 0xFF; 40 | 41 | private Context mContext; 42 | private Paint mPaint; 43 | private int mScannerAlpha; 44 | private int mMaskColor; 45 | private int mFrameColor; 46 | private int mLaserColor; 47 | private int mTextColor; 48 | private Rect mFrameRect; 49 | private int mFocusThick; 50 | private int mAngleThick; 51 | private int mAngleLength; 52 | 53 | public QrCodeFinderView(Context context) { 54 | this(context, null); 55 | } 56 | 57 | public QrCodeFinderView(Context context, AttributeSet attrs) { 58 | this(context, attrs, 0); 59 | } 60 | 61 | public QrCodeFinderView(Context context, AttributeSet attrs, int defStyleAttr) { 62 | super(context, attrs, defStyleAttr); 63 | mContext = context; 64 | mPaint = new Paint(); 65 | 66 | Resources resources = getResources(); 67 | mMaskColor = resources.getColor(R.color.qr_code_finder_mask); 68 | mFrameColor = resources.getColor(R.color.qr_code_finder_frame); 69 | mLaserColor = resources.getColor(R.color.qr_code_finder_laser); 70 | mTextColor = resources.getColor(R.color.qr_code_white); 71 | 72 | mFocusThick = 1; 73 | mAngleThick = 8; 74 | mAngleLength = 40; 75 | mScannerAlpha = 0; 76 | init(context); 77 | } 78 | 79 | private void init(Context context) { 80 | if (isInEditMode()) { 81 | return; 82 | } 83 | // 需要调用下面的方法才会执行onDraw方法 84 | setWillNotDraw(false); 85 | LayoutInflater inflater = LayoutInflater.from(context); 86 | RelativeLayout relativeLayout = (RelativeLayout) inflater.inflate(R.layout.layout_qr_code_scanner, this); 87 | FrameLayout frameLayout = (FrameLayout) relativeLayout.findViewById(R.id.qr_code_fl_scanner); 88 | mFrameRect = new Rect(); 89 | LayoutParams layoutParams = (LayoutParams) frameLayout.getLayoutParams(); 90 | mFrameRect.left = (ScreenUtils.getScreenWidth(context) - layoutParams.width) / 2; 91 | mFrameRect.top = layoutParams.topMargin; 92 | mFrameRect.right = mFrameRect.left + layoutParams.width; 93 | mFrameRect.bottom = mFrameRect.top + layoutParams.height; 94 | } 95 | 96 | @Override 97 | public void onDraw(Canvas canvas) { 98 | if (isInEditMode()) { 99 | return; 100 | } 101 | Rect frame = mFrameRect; 102 | if (frame == null) { 103 | return; 104 | } 105 | int width = canvas.getWidth(); 106 | int height = canvas.getHeight(); 107 | 108 | // 绘制焦点框外边的暗色背景 109 | mPaint.setColor(mMaskColor); 110 | canvas.drawRect(0, 0, width, frame.top, mPaint); 111 | canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, mPaint); 112 | canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, mPaint); 113 | canvas.drawRect(0, frame.bottom + 1, width, height, mPaint); 114 | 115 | drawFocusRect(canvas, frame); 116 | drawAngle(canvas, frame); 117 | drawText(canvas, frame); 118 | drawLaser(canvas, frame); 119 | 120 | // Request another update at the animation interval, but only repaint the laser line, 121 | // not the entire viewfinder mask. 122 | postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom); 123 | } 124 | 125 | /** 126 | * 画聚焦框,白色的 127 | * 128 | * @param canvas 129 | * @param rect 130 | */ 131 | private void drawFocusRect(Canvas canvas, Rect rect) { 132 | // 绘制焦点框(黑色) 133 | mPaint.setColor(mFrameColor); 134 | // 上 135 | canvas.drawRect(rect.left + mAngleLength, rect.top, rect.right - mAngleLength, rect.top + mFocusThick, mPaint); 136 | // 左 137 | canvas.drawRect(rect.left, rect.top + mAngleLength, rect.left + mFocusThick, rect.bottom - mAngleLength, 138 | mPaint); 139 | // 右 140 | canvas.drawRect(rect.right - mFocusThick, rect.top + mAngleLength, rect.right, rect.bottom - mAngleLength, 141 | mPaint); 142 | // 下 143 | canvas.drawRect(rect.left + mAngleLength, rect.bottom - mFocusThick, rect.right - mAngleLength, rect.bottom, 144 | mPaint); 145 | } 146 | 147 | /** 148 | * 画粉色的四个角 149 | * 150 | * @param canvas 151 | * @param rect 152 | */ 153 | private void drawAngle(Canvas canvas, Rect rect) { 154 | mPaint.setColor(mLaserColor); 155 | mPaint.setAlpha(OPAQUE); 156 | mPaint.setStyle(Paint.Style.FILL); 157 | mPaint.setStrokeWidth(mAngleThick); 158 | int left = rect.left; 159 | int top = rect.top; 160 | int right = rect.right; 161 | int bottom = rect.bottom; 162 | // 左上角 163 | canvas.drawRect(left, top, left + mAngleLength, top + mAngleThick, mPaint); 164 | canvas.drawRect(left, top, left + mAngleThick, top + mAngleLength, mPaint); 165 | // 右上角 166 | canvas.drawRect(right - mAngleLength, top, right, top + mAngleThick, mPaint); 167 | canvas.drawRect(right - mAngleThick, top, right, top + mAngleLength, mPaint); 168 | // 左下角 169 | canvas.drawRect(left, bottom - mAngleLength, left + mAngleThick, bottom, mPaint); 170 | canvas.drawRect(left, bottom - mAngleThick, left + mAngleLength, bottom, mPaint); 171 | // 右下角 172 | canvas.drawRect(right - mAngleLength, bottom - mAngleThick, right, bottom, mPaint); 173 | canvas.drawRect(right - mAngleThick, bottom - mAngleLength, right, bottom, mPaint); 174 | } 175 | 176 | private void drawText(Canvas canvas, Rect rect) { 177 | int margin = 40; 178 | mPaint.setColor(mTextColor); 179 | mPaint.setTextSize(getResources().getDimension(R.dimen.text_size_13sp)); 180 | String text = getResources().getString(R.string.qr_code_auto_scan_notification); 181 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); 182 | float fontTotalHeight = fontMetrics.bottom - fontMetrics.top; 183 | float offY = fontTotalHeight / 2 - fontMetrics.bottom; 184 | float newY = rect.bottom + margin + offY; 185 | float left = (ScreenUtils.getScreenWidth(mContext) - mPaint.getTextSize() * text.length()) / 2; 186 | canvas.drawText(text, left, newY, mPaint); 187 | } 188 | 189 | private void drawLaser(Canvas canvas, Rect rect) { 190 | // 绘制焦点框内固定的一条扫描线(红色) 191 | mPaint.setColor(mLaserColor); 192 | mPaint.setAlpha(SCANNER_ALPHA[mScannerAlpha]); 193 | mScannerAlpha = (mScannerAlpha + 1) % SCANNER_ALPHA.length; 194 | int middle = rect.height() / 2 + rect.top; 195 | canvas.drawRect(rect.left + 2, middle - 1, rect.right - 1, middle + 2, mPaint); 196 | 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaceOnLive/Face-Liveness-Detection-SDK-Android/3c771cdd8bff26afb4f2019bfcdf873db43fe91b/app/src/main/res/drawable/back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_credit_card_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaceOnLive/Face-Liveness-Detection-SDK-Android/3c771cdd8bff26afb4f2019bfcdf873db43fe91b/app/src/main/res/drawable/ic_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gallery.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_history.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_id_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaceOnLive/Face-Liveness-Detection-SDK-Android/3c771cdd8bff26afb4f2019bfcdf873db43fe91b/app/src/main/res/drawable/ic_id_front.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_contact_mail_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_credit_card_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_photo_camera_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_start.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/surlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaceOnLive/Face-Liveness-Detection-SDK-Android/3c771cdd8bff26afb4f2019bfcdf873db43fe91b/app/src/main/res/drawable/surlogo.png -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_medium.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_activation.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | 30 | 31 |