lib补充文件提交
1
keeplibrary/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
39
keeplibrary/build.gradle
Normal file
@ -0,0 +1,39 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdk 19
|
||||
targetSdk 32
|
||||
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'androidx.annotation:annotation:1.6.0'
|
||||
implementation 'androidx.core:core:1.10.1'
|
||||
}
|
0
keeplibrary/consumer-rules.pro
Normal file
21
keeplibrary/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
26
keeplibrary/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.fanjun.keeplive">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS" />
|
||||
|
||||
<application>
|
||||
<receiver android:name="com.fanjun.keeplive.receiver.NotificationClickReceiver" />
|
||||
<activity
|
||||
android:name="com.fanjun.keeplive.activity.OnePixelActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@style/onePixelActivity" />
|
||||
|
||||
<service android:name="com.fanjun.keeplive.service.LocalService" />
|
||||
<service android:name="com.fanjun.keeplive.service.HideForegroundService" />
|
||||
<service
|
||||
android:name="com.fanjun.keeplive.service.JobHandlerService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name="com.fanjun.keeplive.service.RemoteService"
|
||||
android:process=":remote" />
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,6 @@
|
||||
package com.fanjun.keeplive.service;
|
||||
|
||||
interface GuardAidl {
|
||||
//相互唤醒服务
|
||||
void wakeUp(String title, String discription, int iconRes);
|
||||
}
|
103
keeplibrary/src/main/java/com/fanjun/keeplive/KeepLive.java
Normal file
@ -0,0 +1,103 @@
|
||||
package com.fanjun.keeplive;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.fanjun.keeplive.config.ForegroundNotification;
|
||||
import com.fanjun.keeplive.config.KeepLiveService;
|
||||
import com.fanjun.keeplive.service.JobHandlerService;
|
||||
import com.fanjun.keeplive.service.LocalService;
|
||||
import com.fanjun.keeplive.service.RemoteService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 保活工具
|
||||
*/
|
||||
public final class KeepLive {
|
||||
/**
|
||||
* 运行模式
|
||||
*/
|
||||
public static enum RunMode {
|
||||
/**
|
||||
* 省电模式
|
||||
* 省电一些,但保活效果会差一点
|
||||
*/
|
||||
ENERGY,
|
||||
/**
|
||||
* 流氓模式
|
||||
* 相对耗电,但可造就不死之身
|
||||
*/
|
||||
ROGUE
|
||||
}
|
||||
|
||||
public static ForegroundNotification foregroundNotification = null;
|
||||
public static KeepLiveService keepLiveService = null;
|
||||
public static RunMode runMode = null;
|
||||
public static boolean useSilenceMusice = true;
|
||||
|
||||
/**
|
||||
* 启动保活
|
||||
*
|
||||
* @param application your application
|
||||
* @param foregroundNotification 前台服务 必须要,安卓8.0后必须有前台通知才能正常启动Service
|
||||
* @param keepLiveService 保活业务
|
||||
*/
|
||||
public static void startWork(@NonNull Application application, @NonNull RunMode runMode, @NonNull ForegroundNotification foregroundNotification, @NonNull KeepLiveService keepLiveService) {
|
||||
if (isMain(application)) {
|
||||
KeepLive.foregroundNotification = foregroundNotification;
|
||||
KeepLive.keepLiveService = keepLiveService;
|
||||
KeepLive.runMode = runMode;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
//启动定时器,在定时器中启动本地服务和守护进程
|
||||
Intent intent = new Intent(application, JobHandlerService.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
application.startForegroundService(intent);
|
||||
} else {
|
||||
application.startService(intent);
|
||||
}
|
||||
} else {
|
||||
//启动本地服务
|
||||
Intent localIntent = new Intent(application, LocalService.class);
|
||||
//启动守护进程
|
||||
Intent guardIntent = new Intent(application, RemoteService.class);
|
||||
application.startService(localIntent);
|
||||
application.startService(guardIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用无声音乐
|
||||
* 如不设置,则默认启用
|
||||
* @param enable
|
||||
*/
|
||||
public static void useSilenceMusice(boolean enable){
|
||||
KeepLive.useSilenceMusice = enable;
|
||||
}
|
||||
|
||||
private static boolean isMain(Application application) {
|
||||
int pid = android.os.Process.myPid();
|
||||
String processName = "";
|
||||
ActivityManager mActivityManager = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = mActivityManager.getRunningAppProcesses();
|
||||
if (runningAppProcessInfos != null) {
|
||||
for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
|
||||
if (appProcess.pid == pid) {
|
||||
processName = appProcess.processName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String packageName = application.getPackageName();
|
||||
if (processName.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.fanjun.keeplive.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.view.Gravity;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
public final class OnePixelActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
//设定一像素的activity
|
||||
Window window = getWindow();
|
||||
window.setGravity(Gravity.START | Gravity.TOP);
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.x = 0;
|
||||
params.y = 0;
|
||||
params.height = 1;
|
||||
params.width = 1;
|
||||
window.setAttributes(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkScreenOn("onResume");
|
||||
}
|
||||
private void checkScreenOn(String methodName) {
|
||||
try{
|
||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
|
||||
boolean isScreenOn = pm.isScreenOn();
|
||||
if (isScreenOn) {
|
||||
finish();
|
||||
}
|
||||
}catch (Exception e){}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.fanjun.keeplive.config;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 默认前台服务样式
|
||||
*/
|
||||
public class ForegroundNotification implements Serializable {
|
||||
private String title;
|
||||
private String description;
|
||||
private int iconRes;
|
||||
private ForegroundNotificationClickListener foregroundNotificationClickListener;
|
||||
private ForegroundNotification(){
|
||||
|
||||
}
|
||||
public ForegroundNotification(String title, String description, int iconRes, ForegroundNotificationClickListener foregroundNotificationClickListener) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.iconRes = iconRes;
|
||||
this.foregroundNotificationClickListener = foregroundNotificationClickListener;
|
||||
}
|
||||
|
||||
public ForegroundNotification(String title, String description, int iconRes) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.iconRes = iconRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @return ForegroundNotification
|
||||
*/
|
||||
public static ForegroundNotification ini(){
|
||||
return new ForegroundNotification();
|
||||
}
|
||||
/**
|
||||
* 设置标题
|
||||
* @param title 标题
|
||||
* @return ForegroundNotification
|
||||
*/
|
||||
public ForegroundNotification title(@NonNull String title){
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 设置副标题
|
||||
* @param description 副标题
|
||||
* @return ForegroundNotification
|
||||
*/
|
||||
public ForegroundNotification description(@NonNull String description){
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 设置图标
|
||||
* @param iconRes 图标
|
||||
* @return ForegroundNotification
|
||||
*/
|
||||
public ForegroundNotification icon(@NonNull int iconRes){
|
||||
this.iconRes = iconRes;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 设置前台通知点击事件
|
||||
* @param foregroundNotificationClickListener 前台通知点击回调
|
||||
* @return ForegroundNotification
|
||||
*/
|
||||
public ForegroundNotification foregroundNotificationClickListener(@NonNull ForegroundNotificationClickListener foregroundNotificationClickListener){
|
||||
this.foregroundNotificationClickListener = foregroundNotificationClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title==null?"":title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description==null?"":description;
|
||||
}
|
||||
|
||||
public int getIconRes() {
|
||||
return iconRes;
|
||||
}
|
||||
|
||||
public ForegroundNotificationClickListener getForegroundNotificationClickListener() {
|
||||
return foregroundNotificationClickListener;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.fanjun.keeplive.config;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* 前台服务通知点击事件
|
||||
*/
|
||||
public interface ForegroundNotificationClickListener {
|
||||
void foregroundNotificationClick(Context context, Intent intent);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.fanjun.keeplive.config;
|
||||
|
||||
/**
|
||||
* 需要保活的服务
|
||||
*/
|
||||
public interface KeepLiveService {
|
||||
/**
|
||||
* 运行中
|
||||
* 由于服务可能会多次自动启动,该方法可能重复调用
|
||||
*/
|
||||
void onWorking();
|
||||
|
||||
/**
|
||||
* 服务终止
|
||||
* 由于服务可能会被多次终止,该方法可能重复调用,需同onWorking配套使用,如注册和注销
|
||||
*/
|
||||
void onStop();
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.fanjun.keeplive.config;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
|
||||
public class NotificationUtils extends ContextWrapper {
|
||||
private NotificationManager manager;
|
||||
private String id;
|
||||
private String name;
|
||||
private Context context;
|
||||
private NotificationChannel channel;
|
||||
|
||||
private NotificationUtils(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
id = context.getPackageName();
|
||||
name = context.getPackageName();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void createNotificationChannel() {
|
||||
if (channel == null) {
|
||||
channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.enableVibration(false);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setVibrationPattern(new long[]{0});
|
||||
channel.setSound(null, null);
|
||||
getManager().createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private NotificationManager getManager() {
|
||||
if (manager == null) {
|
||||
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public Notification.Builder getChannelNotification(String title, String content, int icon, Intent intent) {
|
||||
//PendingIntent.FLAG_UPDATE_CURRENT 这个类型才能传值
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, getPendingIntent());
|
||||
return new Notification.Builder(context, id)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(icon)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
}
|
||||
|
||||
private int getPendingIntent() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
}
|
||||
|
||||
public NotificationCompat.Builder getNotification_25(String title, String content, int icon, Intent intent) {
|
||||
//PendingIntent.FLAG_UPDATE_CURRENT 这个类型才能传值
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, getPendingIntent());
|
||||
return new NotificationCompat.Builder(context, id)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setSmallIcon(icon)
|
||||
.setAutoCancel(true)
|
||||
.setVibrate(new long[]{0})
|
||||
.setContentIntent(pendingIntent);
|
||||
}
|
||||
|
||||
public static void sendNotification(@NonNull Context context, @NonNull String title, @NonNull String content, @NonNull int icon, @NonNull Intent intent) {
|
||||
NotificationUtils notificationUtils = new NotificationUtils(context);
|
||||
Notification notification = null;
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
notificationUtils.createNotificationChannel();
|
||||
notification = notificationUtils.getChannelNotification(title, content, icon, intent).build();
|
||||
} else {
|
||||
notification = notificationUtils.getNotification_25(title, content, icon, intent).build();
|
||||
}
|
||||
notificationUtils.getManager().notify(new java.util.Random().nextInt(10000), notification);
|
||||
}
|
||||
|
||||
public static Notification createNotification(@NonNull Context context, @NonNull String title, @NonNull String content, @NonNull int icon, @NonNull Intent intent) {
|
||||
NotificationUtils notificationUtils = new NotificationUtils(context);
|
||||
Notification notification = null;
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
notificationUtils.createNotificationChannel();
|
||||
notification = notificationUtils.getChannelNotification(title, content, icon,intent).build();
|
||||
} else {
|
||||
notification = notificationUtils.getNotification_25(title, content, icon,intent).build();
|
||||
}
|
||||
return notification;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.fanjun.keeplive.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.fanjun.keeplive.KeepLive;
|
||||
|
||||
public final class NotificationClickReceiver extends BroadcastReceiver {
|
||||
public final static String CLICK_NOTIFICATION = "CLICK_NOTIFICATION";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(NotificationClickReceiver.CLICK_NOTIFICATION)) {
|
||||
if (KeepLive.foregroundNotification != null) {
|
||||
if (KeepLive.foregroundNotification.getForegroundNotificationClickListener() != null) {
|
||||
KeepLive.foregroundNotification.getForegroundNotificationClickListener().foregroundNotificationClick(context, intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.fanjun.keeplive.receiver;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.fanjun.keeplive.activity.OnePixelActivity;
|
||||
|
||||
public final class OnepxReceiver extends BroadcastReceiver {
|
||||
android.os.Handler mHander;
|
||||
boolean screenOn = true;
|
||||
|
||||
public OnepxReceiver() {
|
||||
mHander = new android.os.Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { //屏幕关闭的时候接受到广播
|
||||
screenOn = false;
|
||||
mHander.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(!screenOn){
|
||||
Intent intent2 = new Intent(context, OnePixelActivity.class);
|
||||
intent2.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent2, 0);
|
||||
try {
|
||||
pendingIntent.send();
|
||||
/*} catch (PendingIntent.CanceledException e) {*/
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
},1000);
|
||||
//通知屏幕已关闭,开始播放无声音乐
|
||||
context.sendBroadcast(new Intent("_ACTION_SCREEN_OFF"));
|
||||
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { //屏幕打开的时候发送广播 结束一像素
|
||||
screenOn = true;
|
||||
//通知屏幕已点亮,停止播放无声音乐
|
||||
context.sendBroadcast(new Intent("_ACTION_SCREEN_ON"));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.fanjun.keeplive.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.fanjun.keeplive.KeepLive;
|
||||
import com.fanjun.keeplive.config.NotificationUtils;
|
||||
import com.fanjun.keeplive.receiver.NotificationClickReceiver;
|
||||
|
||||
/**
|
||||
* 隐藏前台服务通知
|
||||
*/
|
||||
public class HideForegroundService extends Service {
|
||||
private android.os.Handler handler;
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
startForeground();
|
||||
if (handler == null){
|
||||
handler = new Handler();
|
||||
}
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}, 2000);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
|
||||
private void startForeground() {
|
||||
if (KeepLive.foregroundNotification != null) {
|
||||
Intent intent = new Intent(getApplicationContext(), NotificationClickReceiver.class);
|
||||
intent.setAction(NotificationClickReceiver.CLICK_NOTIFICATION);
|
||||
Notification notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification.getTitle(), KeepLive.foregroundNotification.getDescription(), KeepLive.foregroundNotification.getIconRes(), intent);
|
||||
startForeground(13691, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by Fernflower decompiler)
|
||||
//
|
||||
|
||||
package com.fanjun.keeplive.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.app.job.JobInfo.Builder;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build.VERSION;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.fanjun.keeplive.KeepLive;
|
||||
import com.fanjun.keeplive.config.NotificationUtils;
|
||||
import com.fanjun.keeplive.receiver.NotificationClickReceiver;
|
||||
import com.fanjun.keeplive.utils.ServiceUtils;
|
||||
|
||||
@RequiresApi(
|
||||
api = 21
|
||||
)
|
||||
public final class JobHandlerService extends JobService {
|
||||
private JobScheduler mJobScheduler;
|
||||
private int jobId = 100;
|
||||
|
||||
public JobHandlerService() {
|
||||
}
|
||||
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
this.startService(this);
|
||||
if (VERSION.SDK_INT >= 21) {
|
||||
this.mJobScheduler = (JobScheduler) this.getSystemService("jobscheduler");
|
||||
this.mJobScheduler.cancel(this.jobId);
|
||||
Builder builder = new Builder(this.jobId, new ComponentName(this.getPackageName(), JobHandlerService.class.getName()));
|
||||
if (VERSION.SDK_INT >= 24) {
|
||||
builder.setMinimumLatency(30000L);
|
||||
builder.setOverrideDeadline(30000L);
|
||||
builder.setMinimumLatency(30000L);
|
||||
builder.setBackoffCriteria(30000L, 0);
|
||||
} else {
|
||||
builder.setPeriodic(30000L);
|
||||
}
|
||||
|
||||
builder.setRequiredNetworkType(1);
|
||||
builder.setPersisted(true);
|
||||
this.mJobScheduler.schedule(builder.build());
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void startService(Context context) {
|
||||
Intent localIntent;
|
||||
if (VERSION.SDK_INT >= 26 && KeepLive.foregroundNotification != null) {
|
||||
localIntent = new Intent(this.getApplicationContext(), NotificationClickReceiver.class);
|
||||
localIntent.setAction("CLICK_NOTIFICATION");
|
||||
Notification notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification.getTitle(), KeepLive.foregroundNotification.getDescription(), KeepLive.foregroundNotification.getIconRes(), localIntent);
|
||||
this.startForeground(13691, notification);
|
||||
}
|
||||
|
||||
localIntent = new Intent(context, LocalService.class);
|
||||
Intent guardIntent = new Intent(context, RemoteService.class);
|
||||
this.startService(localIntent);
|
||||
this.startService(guardIntent);
|
||||
}
|
||||
|
||||
public boolean onStartJob(JobParameters jobParameters) {
|
||||
if (!ServiceUtils.isServiceRunning(this.getApplicationContext(), "com.fanjun.keeplive.service.LocalService") || !ServiceUtils.isRunningTaskExist(this.getApplicationContext(), this.getPackageName() + ":remote")) {
|
||||
this.startService(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onStopJob(JobParameters jobParameters) {
|
||||
if (!ServiceUtils.isServiceRunning(this.getApplicationContext(), "com.fanjun.keeplive.service.LocalService") || !ServiceUtils.isRunningTaskExist(this.getApplicationContext(), this.getPackageName() + ":remote")) {
|
||||
this.startService(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package com.fanjun.keeplive.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.fanjun.keeplive.KeepLive;
|
||||
import com.fanjun.keeplive.R;
|
||||
import com.fanjun.keeplive.config.NotificationUtils;
|
||||
import com.fanjun.keeplive.receiver.NotificationClickReceiver;
|
||||
import com.fanjun.keeplive.receiver.OnepxReceiver;
|
||||
import com.fanjun.keeplive.utils.ServiceUtils;
|
||||
|
||||
public final class LocalService extends Service {
|
||||
private OnepxReceiver mOnepxReceiver;
|
||||
private ScreenStateReceiver screenStateReceiver;
|
||||
private boolean isPause = true;//控制暂停
|
||||
private MediaPlayer mediaPlayer;
|
||||
private MyBilder mBilder;
|
||||
private android.os.Handler handler;
|
||||
private boolean mIsBoundRemoteService ;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (mBilder == null) {
|
||||
mBilder = new MyBilder();
|
||||
}
|
||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
|
||||
isPause = pm.isScreenOn();
|
||||
if (handler == null) {
|
||||
handler = new Handler();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (KeepLive.useSilenceMusice){
|
||||
//播放无声音乐
|
||||
if (mediaPlayer == null) {
|
||||
mediaPlayer = MediaPlayer.create(this, R.raw.novioce);
|
||||
if (mediaPlayer!= null){
|
||||
mediaPlayer.setVolume(0f, 0f);
|
||||
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mediaPlayer) {
|
||||
if (!isPause) {
|
||||
if (KeepLive.runMode == KeepLive.RunMode.ROGUE) {
|
||||
play();
|
||||
} else {
|
||||
if (handler != null) {
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
play();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
play();
|
||||
}
|
||||
}
|
||||
}
|
||||
//像素保活
|
||||
if (mOnepxReceiver == null) {
|
||||
mOnepxReceiver = new OnepxReceiver();
|
||||
}
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction("android.intent.action.SCREEN_OFF");
|
||||
intentFilter.addAction("android.intent.action.SCREEN_ON");
|
||||
registerReceiver(mOnepxReceiver, intentFilter);
|
||||
//屏幕点亮状态监听,用于单独控制音乐播放
|
||||
if (screenStateReceiver == null) {
|
||||
screenStateReceiver = new ScreenStateReceiver();
|
||||
}
|
||||
IntentFilter intentFilter2 = new IntentFilter();
|
||||
intentFilter2.addAction("_ACTION_SCREEN_OFF");
|
||||
intentFilter2.addAction("_ACTION_SCREEN_ON");
|
||||
registerReceiver(screenStateReceiver, intentFilter2);
|
||||
//启用前台服务,提升优先级
|
||||
if (KeepLive.foregroundNotification != null) {
|
||||
Intent intent2 = new Intent(getApplicationContext(), NotificationClickReceiver.class);
|
||||
intent2.setAction(NotificationClickReceiver.CLICK_NOTIFICATION);
|
||||
Notification notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification.getTitle(), KeepLive.foregroundNotification.getDescription(), KeepLive.foregroundNotification.getIconRes(), intent2);
|
||||
startForeground(13691, notification);
|
||||
}
|
||||
//绑定守护进程
|
||||
try {
|
||||
Intent intent3 = new Intent(this, RemoteService.class);
|
||||
mIsBoundRemoteService = this.bindService(intent3, connection, Context.BIND_ABOVE_CLIENT);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
//隐藏服务通知
|
||||
try {
|
||||
if(Build.VERSION.SDK_INT < 25){
|
||||
startService(new Intent(this, HideForegroundService.class));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (KeepLive.keepLiveService != null) {
|
||||
KeepLive.keepLiveService.onWorking();
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void play() {
|
||||
if (KeepLive.useSilenceMusice){
|
||||
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pause() {
|
||||
if (KeepLive.useSilenceMusice){
|
||||
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ScreenStateReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
if (intent.getAction().equals("_ACTION_SCREEN_OFF")) {
|
||||
isPause = false;
|
||||
play();
|
||||
} else if (intent.getAction().equals("_ACTION_SCREEN_ON")) {
|
||||
isPause = true;
|
||||
pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyBilder extends GuardAidl.Stub {
|
||||
|
||||
@Override
|
||||
public void wakeUp(String title, String discription, int iconRes) throws RemoteException {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private ServiceConnection connection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (ServiceUtils.isServiceRunning(getApplicationContext(), "com.fanjun.keeplive.service.LocalService")){
|
||||
Intent remoteService = new Intent(LocalService.this,
|
||||
RemoteService.class);
|
||||
LocalService.this.startService(remoteService);
|
||||
Intent intent = new Intent(LocalService.this, RemoteService.class);
|
||||
mIsBoundRemoteService = LocalService.this.bindService(intent, connection,
|
||||
Context.BIND_ABOVE_CLIENT);
|
||||
}
|
||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
|
||||
boolean isScreenOn = pm.isScreenOn();
|
||||
if (isScreenOn) {
|
||||
sendBroadcast(new Intent("_ACTION_SCREEN_ON"));
|
||||
} else {
|
||||
sendBroadcast(new Intent("_ACTION_SCREEN_OFF"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
try {
|
||||
if (mBilder != null && KeepLive.foregroundNotification != null) {
|
||||
GuardAidl guardAidl = GuardAidl.Stub.asInterface(service);
|
||||
guardAidl.wakeUp(KeepLive.foregroundNotification.getTitle(), KeepLive.foregroundNotification.getDescription(), KeepLive.foregroundNotification.getIconRes());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (connection != null){
|
||||
try {
|
||||
if (mIsBoundRemoteService){
|
||||
unbindService(connection);
|
||||
}
|
||||
}catch (Exception e){}
|
||||
}
|
||||
try {
|
||||
unregisterReceiver(mOnepxReceiver);
|
||||
unregisterReceiver(screenStateReceiver);
|
||||
}catch (Exception e){}
|
||||
if (KeepLive.keepLiveService != null) {
|
||||
KeepLive.keepLiveService.onStop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.fanjun.keeplive.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.fanjun.keeplive.config.NotificationUtils;
|
||||
import com.fanjun.keeplive.receiver.NotificationClickReceiver;
|
||||
import com.fanjun.keeplive.utils.ServiceUtils;
|
||||
|
||||
/**
|
||||
* 守护进程
|
||||
*/
|
||||
@SuppressWarnings(value={"unchecked", "deprecation"})
|
||||
public final class RemoteService extends Service {
|
||||
private MyBilder mBilder;
|
||||
private boolean mIsBoundLocalService ;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (mBilder == null){
|
||||
mBilder = new MyBilder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
try {
|
||||
mIsBoundLocalService = this.bindService(new Intent(RemoteService.this, LocalService.class),
|
||||
connection, Context.BIND_ABOVE_CLIENT);
|
||||
}catch (Exception e){
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (connection != null){
|
||||
try {
|
||||
if (mIsBoundLocalService){
|
||||
unbindService(connection);
|
||||
}
|
||||
}catch (Exception e){}
|
||||
}
|
||||
}
|
||||
private final class MyBilder extends GuardAidl.Stub {
|
||||
|
||||
@Override
|
||||
public void wakeUp(String title, String discription, int iconRes) throws RemoteException {
|
||||
if(Build.VERSION.SDK_INT < 25){
|
||||
Intent intent2 = new Intent(getApplicationContext(), NotificationClickReceiver.class);
|
||||
intent2.setAction(NotificationClickReceiver.CLICK_NOTIFICATION);
|
||||
Notification notification = NotificationUtils.createNotification(RemoteService.this, title, discription, iconRes, intent2);
|
||||
RemoteService.this.startForeground(13691, notification);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final ServiceConnection connection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (ServiceUtils.isRunningTaskExist(getApplicationContext(), getPackageName() + ":remote")){
|
||||
Intent localService = new Intent(RemoteService.this,
|
||||
LocalService.class);
|
||||
RemoteService.this.startService(localService);
|
||||
mIsBoundLocalService = RemoteService.this.bindService(new Intent(RemoteService.this,
|
||||
LocalService.class), connection, Context.BIND_ABOVE_CLIENT);
|
||||
}
|
||||
PowerManager pm = (PowerManager) RemoteService.this.getSystemService(Context.POWER_SERVICE);
|
||||
boolean isScreenOn = pm.isScreenOn();
|
||||
if (isScreenOn){
|
||||
sendBroadcast(new Intent("_ACTION_SCREEN_ON"));
|
||||
}else{
|
||||
sendBroadcast(new Intent("_ACTION_SCREEN_OFF"));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.fanjun.keeplive.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceUtils {
|
||||
public static boolean isServiceRunning(Context ctx, String className) {
|
||||
boolean isRunning = false;
|
||||
ActivityManager activityManager = (ActivityManager) ctx
|
||||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningServiceInfo> servicesList = activityManager
|
||||
.getRunningServices(Integer.MAX_VALUE);
|
||||
if (servicesList != null) {
|
||||
Iterator<ActivityManager.RunningServiceInfo> l = servicesList.iterator();
|
||||
while (l.hasNext()) {
|
||||
ActivityManager.RunningServiceInfo si = l.next();
|
||||
if (className.equals(si.service.getClassName())) {
|
||||
isRunning = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isRunning;
|
||||
}
|
||||
public static boolean isRunningTaskExist(Context context, String processName) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
List<ActivityManager.RunningAppProcessInfo> processList = am.getRunningAppProcesses();
|
||||
if (processList != null){
|
||||
for (ActivityManager.RunningAppProcessInfo info : processList) {
|
||||
if (info.processName.equals(processName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
BIN
keeplibrary/src/main/res/raw/novioce.wav
Normal file
2
keeplibrary/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<resources>
|
||||
</resources>
|
9
keeplibrary/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<!--1像素保活透明Activity-->
|
||||
<style name="onePixelActivity">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
</resources>
|
1
ocr_ui/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
37
ocr_ui/build.gradle
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
|
||||
// compileSdkVersion 27
|
||||
// buildToolsVersion "27.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android["minSdkVersion"]
|
||||
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
|
||||
versionCode rootProject.ext.android["versionCode"]
|
||||
versionName rootProject.ext.android["versionName"]
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
// implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
implementation files('libs/license.jar')
|
||||
}
|
BIN
ocr_ui/libs/license.jar
Normal file
18
ocr_ui/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/baidu/IDE/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
-dontwarn com.baidu.ocr.**
|
27
ocr_ui/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.baidu.ocr.ui">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-feature android:name="android.hardware.camera2.full"/>
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
|
||||
android:supportsRtl="true"
|
||||
android:vmSafeMode="true"
|
||||
>
|
||||
|
||||
<activity android:name="com.baidu.ocr.ui.camera.CameraActivity"
|
||||
android:configChanges="screenSize|orientation"
|
||||
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
ocr_ui/src/main/assets/models/integrity_model_secret.bin
Normal file
BIN
ocr_ui/src/main/assets/models/quality_model_secret.bin
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.idcardquality;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.baidu.idl.authority.AlgorithmOnMainThreadException;
|
||||
import com.baidu.idl.authority.IDLAuthorityException;
|
||||
import com.baidu.idl.license.License;
|
||||
import com.baidu.idl.util.UIThread;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class IDcardQualityProcess {
|
||||
final ReentrantReadWriteLock nativeModelLock = new ReentrantReadWriteLock();
|
||||
private static IDcardQualityProcess mInstance;
|
||||
private static String tokenString;
|
||||
private static int mAuthorityStatus;
|
||||
private static Throwable loadNativeException = null;
|
||||
private static volatile boolean hasReleased;
|
||||
|
||||
public IDcardQualityProcess() {
|
||||
}
|
||||
|
||||
public static synchronized IDcardQualityProcess getInstance() {
|
||||
if (null == mInstance) {
|
||||
mInstance = new IDcardQualityProcess();
|
||||
}
|
||||
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public native byte[] convertRGBImage(int[] colors, int width, int height);
|
||||
|
||||
public native int idcardQualityModelInit(AssetManager var1, String var2);
|
||||
|
||||
public native int idcardQualityCaptchaRelease();
|
||||
|
||||
public native int idcardQualityProcess(byte[] var1, int var2, int var3, boolean var4, int var5);
|
||||
|
||||
public static synchronized int init(String token) throws AlgorithmOnMainThreadException, IDLAuthorityException {
|
||||
if (UIThread.isUITread()) {
|
||||
throw new AlgorithmOnMainThreadException();
|
||||
} else {
|
||||
tokenString = token;
|
||||
|
||||
try {
|
||||
mAuthorityStatus = License.getInstance().init(tokenString);
|
||||
} catch (Exception var2) {
|
||||
var2.printStackTrace();
|
||||
}
|
||||
|
||||
return mAuthorityStatus;
|
||||
}
|
||||
}
|
||||
|
||||
public int idcardQualityInit(AssetManager assetManager, String modelPath) {
|
||||
if (mAuthorityStatus == 0) {
|
||||
hasReleased = false;
|
||||
nativeModelLock.writeLock().lock();
|
||||
int status = this.idcardQualityModelInit(assetManager, modelPath);
|
||||
nativeModelLock.writeLock().unlock();
|
||||
return status;
|
||||
} else {
|
||||
return mAuthorityStatus;
|
||||
}
|
||||
}
|
||||
|
||||
public int idcardQualityRelease() {
|
||||
if (mAuthorityStatus == 0) {
|
||||
hasReleased = true;
|
||||
nativeModelLock.writeLock().lock();
|
||||
this.idcardQualityCaptchaRelease();
|
||||
nativeModelLock.writeLock().unlock();
|
||||
return 0;
|
||||
} else {
|
||||
return mAuthorityStatus;
|
||||
}
|
||||
}
|
||||
|
||||
public int idcardQualityDetectionImg(Bitmap img, boolean isfont) {
|
||||
int status;
|
||||
if (mAuthorityStatus == 0) {
|
||||
if (hasReleased) {
|
||||
return -1;
|
||||
}
|
||||
int imgHeight = img.getHeight();
|
||||
int imgWidth = img.getWidth();
|
||||
byte[] imageData = this.getRGBImageData(img);
|
||||
nativeModelLock.readLock().lock();
|
||||
status = this.idcardQualityProcess(imageData, imgHeight, imgWidth, isfont, 3);
|
||||
nativeModelLock.readLock().unlock();
|
||||
return status;
|
||||
} else {
|
||||
return mAuthorityStatus;
|
||||
}
|
||||
}
|
||||
|
||||
public static Throwable getLoadSoException() {
|
||||
return loadNativeException;
|
||||
}
|
||||
|
||||
public byte[] getRGBImageData(Bitmap img) {
|
||||
int imgWidth = img.getWidth();
|
||||
int imgHeight = img.getHeight();
|
||||
int[] pixels = new int[imgWidth * imgHeight];
|
||||
img.getPixels(pixels, 0, imgWidth, 0, 0, imgWidth, imgHeight);
|
||||
byte[] imageData = convertRGBImage(pixels, imgWidth, imgHeight);
|
||||
return imageData;
|
||||
}
|
||||
|
||||
public void releaseModel() {
|
||||
this.idcardQualityRelease();
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("idl_license");
|
||||
System.loadLibrary("idcard_quality.1.1.1");
|
||||
} catch (Throwable e) {
|
||||
loadNativeException = e;
|
||||
}
|
||||
mInstance = null;
|
||||
mAuthorityStatus = 256;
|
||||
}
|
||||
}
|
573
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/Camera1Control.java
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.graphics.YuvImage;
|
||||
import android.hardware.Camera;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
/**
|
||||
* 5.0以下相机API的封装。
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Camera1Control implements ICameraControl {
|
||||
|
||||
private int displayOrientation = 0;
|
||||
private int cameraId = 0;
|
||||
private int flashMode;
|
||||
private AtomicBoolean takingPicture = new AtomicBoolean(false);
|
||||
private AtomicBoolean abortingScan = new AtomicBoolean(false);
|
||||
private Context context;
|
||||
private Camera camera;
|
||||
|
||||
private Camera.Parameters parameters;
|
||||
private PermissionCallback permissionCallback;
|
||||
private Rect previewFrame = new Rect();
|
||||
|
||||
private PreviewView previewView;
|
||||
private View displayView;
|
||||
private int rotation = 0;
|
||||
private OnDetectPictureCallback detectCallback;
|
||||
private int previewFrameCount = 0;
|
||||
private Camera.Size optSize;
|
||||
|
||||
/*
|
||||
* 非扫描模式
|
||||
*/
|
||||
private final int MODEL_NOSCAN = 0;
|
||||
|
||||
/*
|
||||
* 本地质量控制扫描模式
|
||||
*/
|
||||
private final int MODEL_SCAN = 1;
|
||||
|
||||
private int detectType = MODEL_NOSCAN;
|
||||
|
||||
public int getCameraRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public AtomicBoolean getAbortingScan() {
|
||||
return abortingScan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDetectCallback(OnDetectPictureCallback callback) {
|
||||
detectType = MODEL_SCAN;
|
||||
detectCallback = callback;
|
||||
}
|
||||
|
||||
private void onRequestDetect(byte[] data) {
|
||||
// 相机已经关闭
|
||||
if (camera == null || data == null || optSize == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
YuvImage img = new YuvImage(data, ImageFormat.NV21, optSize.width, optSize.height, null);
|
||||
ByteArrayOutputStream os = null;
|
||||
try {
|
||||
os = new ByteArrayOutputStream(data.length);
|
||||
img.compressToJpeg(new Rect(0, 0, optSize.width, optSize.height), 80, os);
|
||||
byte[] jpeg = os.toByteArray();
|
||||
int status = detectCallback.onDetect(jpeg, getCameraRotation());
|
||||
|
||||
if (status == 0) {
|
||||
clearPreviewCallback();
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
// 内存溢出则取消当次操作
|
||||
} finally {
|
||||
try {
|
||||
os.close();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) {
|
||||
this.displayOrientation = displayOrientation;
|
||||
switch (displayOrientation) {
|
||||
case CameraView.ORIENTATION_PORTRAIT:
|
||||
rotation = 90;
|
||||
break;
|
||||
case CameraView.ORIENTATION_HORIZONTAL:
|
||||
rotation = 0;
|
||||
break;
|
||||
case CameraView.ORIENTATION_INVERT:
|
||||
rotation = 180;
|
||||
break;
|
||||
default:
|
||||
rotation = 0;
|
||||
}
|
||||
previewView.requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void refreshPermission() {
|
||||
startPreview(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setFlashMode(@FlashMode int flashMode) {
|
||||
if (this.flashMode == flashMode) {
|
||||
return;
|
||||
}
|
||||
this.flashMode = flashMode;
|
||||
updateFlashMode(flashMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlashMode() {
|
||||
return flashMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
startPreview(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (camera != null) {
|
||||
camera.setPreviewCallback(null);
|
||||
stopPreview();
|
||||
// 避免同步代码,为了先设置null后release
|
||||
Camera tempC = camera;
|
||||
camera = null;
|
||||
tempC.release();
|
||||
camera = null;
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPreview() {
|
||||
if (camera != null) {
|
||||
camera.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
if (camera != null) {
|
||||
stopPreview();
|
||||
}
|
||||
setFlashMode(FLASH_MODE_OFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
takingPicture.set(false);
|
||||
if (camera == null) {
|
||||
openCamera();
|
||||
} else {
|
||||
previewView.textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
if (previewView.textureView.isAvailable()) {
|
||||
startPreview(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDisplayView() {
|
||||
return displayView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takePicture(final OnTakePictureCallback onTakePictureCallback) {
|
||||
if (takingPicture.get()) {
|
||||
return;
|
||||
}
|
||||
switch (displayOrientation) {
|
||||
case CameraView.ORIENTATION_PORTRAIT:
|
||||
parameters.setRotation(90);
|
||||
break;
|
||||
case CameraView.ORIENTATION_HORIZONTAL:
|
||||
parameters.setRotation(0);
|
||||
break;
|
||||
case CameraView.ORIENTATION_INVERT:
|
||||
parameters.setRotation(180);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Camera.Size picSize = getOptimalSize(camera.getParameters().getSupportedPictureSizes());
|
||||
parameters.setPictureSize(picSize.width, picSize.height);
|
||||
camera.setParameters(parameters);
|
||||
takingPicture.set(true);
|
||||
cancelAutoFocus();
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
camera.takePicture(null, null, new Camera.PictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(byte[] data, Camera camera) {
|
||||
startPreview(false);
|
||||
takingPicture.set(false);
|
||||
if (onTakePictureCallback != null) {
|
||||
onTakePictureCallback.onPictureTaken(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
startPreview(false);;
|
||||
takingPicture.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPermissionCallback(PermissionCallback callback) {
|
||||
this.permissionCallback = callback;
|
||||
}
|
||||
|
||||
public Camera1Control(Context context) {
|
||||
this.context = context;
|
||||
previewView = new PreviewView(context);
|
||||
openCamera();
|
||||
}
|
||||
|
||||
private void openCamera() {
|
||||
setupDisplayView();
|
||||
}
|
||||
|
||||
private void setupDisplayView() {
|
||||
final TextureView textureView = new TextureView(context);
|
||||
previewView.textureView = textureView;
|
||||
previewView.setTextureView(textureView);
|
||||
displayView = previewView;
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
}
|
||||
|
||||
private SurfaceTexture surfaceCache;
|
||||
|
||||
private byte[] buffer = null;
|
||||
|
||||
private void setPreviewCallbackImpl() {
|
||||
if (buffer == null) {
|
||||
buffer = new byte[displayView.getWidth()
|
||||
* displayView.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8];
|
||||
}
|
||||
if (camera != null && detectType == MODEL_SCAN) {
|
||||
camera.addCallbackBuffer(buffer);
|
||||
camera.setPreviewCallback(previewCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPreviewCallback() {
|
||||
if (camera != null && detectType == MODEL_SCAN) {
|
||||
camera.setPreviewCallback(null);
|
||||
stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
|
||||
@Override
|
||||
public void onPreviewFrame(final byte[] data, Camera camera) {
|
||||
// 扫描成功阻止打开新线程处理
|
||||
if (abortingScan.get()) {
|
||||
return;
|
||||
}
|
||||
// 节流
|
||||
if (previewFrameCount++ % 5 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在某些机型和某项项目中,某些帧的data的数据不符合nv21的格式,需要过滤,否则后续处理会导致crash
|
||||
if (data.length != parameters.getPreviewSize().width * parameters.getPreviewSize().height * 1.5) {
|
||||
return;
|
||||
}
|
||||
|
||||
camera.addCallbackBuffer(buffer);
|
||||
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Camera1Control.this.onRequestDetect(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private void initCamera() {
|
||||
try {
|
||||
if (camera == null) {
|
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
|
||||
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
|
||||
Camera.getCameraInfo(i, cameraInfo);
|
||||
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
||||
cameraId = i;
|
||||
}
|
||||
}
|
||||
try {
|
||||
camera = Camera.open(cameraId);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
startPreview(true);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
if (parameters == null) {
|
||||
parameters = camera.getParameters();
|
||||
parameters.setPreviewFormat(ImageFormat.NV21);
|
||||
}
|
||||
opPreviewSize(previewView.getWidth(), previewView.getHeight());
|
||||
camera.setPreviewTexture(surfaceCache);
|
||||
setPreviewCallbackImpl();
|
||||
startPreview(false);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
surfaceCache = surface;
|
||||
initCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||
opPreviewSize(previewView.getWidth(), previewView.getHeight());
|
||||
startPreview(false);
|
||||
setPreviewCallbackImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||
setPreviewCallbackImpl();
|
||||
}
|
||||
};
|
||||
|
||||
// 开启预览
|
||||
private void startPreview(boolean checkPermission) {
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
if (checkPermission && permissionCallback != null) {
|
||||
permissionCallback.onRequestPermission();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (camera == null) {
|
||||
initCamera();
|
||||
} else {
|
||||
camera.startPreview();
|
||||
startAutoFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelAutoFocus() {
|
||||
camera.cancelAutoFocus();
|
||||
CameraThreadPool.cancelAutoFocusTimer();
|
||||
}
|
||||
|
||||
private void startAutoFocus() {
|
||||
CameraThreadPool.createAutoFocusTimerTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (Camera1Control.this) {
|
||||
if (camera != null && !takingPicture.get()) {
|
||||
try {
|
||||
camera.autoFocus(new Camera.AutoFocusCallback() {
|
||||
@Override
|
||||
public void onAutoFocus(boolean success, Camera camera) {
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
// startPreview是异步实现,可能在某些机器上前几次调用会autofocus failß
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void opPreviewSize(int width, @SuppressWarnings("unused") int height) {
|
||||
|
||||
if (parameters != null && camera != null && width > 0) {
|
||||
optSize = getOptimalSize(camera.getParameters().getSupportedPreviewSizes());
|
||||
parameters.setPreviewSize(optSize.width, optSize.height);
|
||||
previewView.setRatio(1.0f * optSize.width / optSize.height);
|
||||
|
||||
camera.setDisplayOrientation(getSurfaceOrientation());
|
||||
stopPreview();
|
||||
try {
|
||||
camera.setParameters(parameters);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Camera.Size getOptimalSize(List<Camera.Size> sizes) {
|
||||
int width = previewView.textureView.getWidth();
|
||||
int height = previewView.textureView.getHeight();
|
||||
Camera.Size pictureSize = sizes.get(0);
|
||||
|
||||
List<Camera.Size> candidates = new ArrayList<>();
|
||||
|
||||
for (Camera.Size size : sizes) {
|
||||
if (size.width >= width && size.height >= height && size.width * height == size.height * width) {
|
||||
// 比例相同
|
||||
candidates.add(size);
|
||||
} else if (size.height >= width && size.width >= height && size.width * width == size.height * height) {
|
||||
// 反比例
|
||||
candidates.add(size);
|
||||
}
|
||||
}
|
||||
if (!candidates.isEmpty()) {
|
||||
return Collections.min(candidates, sizeComparator);
|
||||
}
|
||||
|
||||
for (Camera.Size size : sizes) {
|
||||
if (size.width > width && size.height > height) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
return pictureSize;
|
||||
}
|
||||
|
||||
private Comparator<Camera.Size> sizeComparator = new Comparator<Camera.Size>() {
|
||||
@Override
|
||||
public int compare(Camera.Size lhs, Camera.Size rhs) {
|
||||
return Long.signum((long) lhs.width * lhs.height - (long) rhs.width * rhs.height);
|
||||
}
|
||||
};
|
||||
|
||||
private void updateFlashMode(int flashMode) {
|
||||
switch (flashMode) {
|
||||
case FLASH_MODE_TORCH:
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
|
||||
break;
|
||||
case FLASH_MODE_OFF:
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
|
||||
break;
|
||||
case ICameraControl.FLASH_MODE_AUTO:
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
|
||||
break;
|
||||
default:
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
|
||||
break;
|
||||
}
|
||||
camera.setParameters(parameters);
|
||||
}
|
||||
|
||||
private int getSurfaceOrientation() {
|
||||
@CameraView.Orientation
|
||||
int orientation = displayOrientation;
|
||||
switch (orientation) {
|
||||
case CameraView.ORIENTATION_PORTRAIT:
|
||||
return 90;
|
||||
case CameraView.ORIENTATION_HORIZONTAL:
|
||||
return 0;
|
||||
case CameraView.ORIENTATION_INVERT:
|
||||
return 180;
|
||||
default:
|
||||
return 90;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 有些相机匹配不到完美的比例。比如。我们的layout是4:3的。预览只有16:9
|
||||
* 的,如果直接显示图片会拉伸,变形。缩放的话,又有黑边。所以我们采取的策略
|
||||
* 是,等比例放大。这样预览的有一部分会超出屏幕。拍照后再进行裁剪处理。
|
||||
*/
|
||||
private class PreviewView extends FrameLayout {
|
||||
|
||||
private TextureView textureView;
|
||||
|
||||
private float ratio = 0.75f;
|
||||
|
||||
void setTextureView(TextureView textureView) {
|
||||
this.textureView = textureView;
|
||||
removeAllViews();
|
||||
addView(textureView);
|
||||
}
|
||||
|
||||
void setRatio(float ratio) {
|
||||
this.ratio = ratio;
|
||||
requestLayout();
|
||||
relayout(getWidth(), getHeight());
|
||||
}
|
||||
|
||||
public PreviewView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
relayout(w, h);
|
||||
}
|
||||
|
||||
private void relayout(int w, int h) {
|
||||
int width = w;
|
||||
int height = h;
|
||||
if (w < h) {
|
||||
// 垂直模式,高度固定。
|
||||
height = (int) (width * ratio);
|
||||
} else {
|
||||
// 水平模式,宽度固定。
|
||||
width = (int) (height * ratio);
|
||||
}
|
||||
|
||||
int l = (getWidth() - width) / 2;
|
||||
int t = (getHeight() - height) / 2;
|
||||
|
||||
previewFrame.left = l;
|
||||
previewFrame.top = t;
|
||||
previewFrame.right = l + width;
|
||||
previewFrame.bottom = t + height;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
textureView.layout(previewFrame.left, previewFrame.top, previewFrame.right, previewFrame.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getPreviewFrame() {
|
||||
return previewFrame;
|
||||
}
|
||||
}
|
698
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/Camera2Control.java
Normal file
@ -0,0 +1,698 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraDevice;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import android.hardware.camera2.CaptureFailure;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.CaptureResult;
|
||||
import android.hardware.camera2.TotalCaptureResult;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
import android.util.Size;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class Camera2Control implements ICameraControl {
|
||||
@Override
|
||||
public void setDetectCallback(OnDetectPictureCallback callback) {
|
||||
// TODO 暂时只用camera
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtomicBoolean getAbortingScan() {
|
||||
// TODO 暂时只用camera
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion from screen rotation to JPEG orientation.
|
||||
*/
|
||||
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
||||
private static final int MAX_PREVIEW_SIZE = 2048;
|
||||
|
||||
static {
|
||||
ORIENTATIONS.append(Surface.ROTATION_0, 90);
|
||||
ORIENTATIONS.append(Surface.ROTATION_90, 0);
|
||||
ORIENTATIONS.append(Surface.ROTATION_180, 270);
|
||||
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
||||
}
|
||||
|
||||
private static final int STATE_PREVIEW = 0;
|
||||
private static final int STATE_WAITING_FOR_LOCK = 1;
|
||||
private static final int STATE_WAITING_FOR_CAPTURE = 2;
|
||||
private static final int STATE_CAPTURING = 3;
|
||||
private static final int STATE_PICTURE_TAKEN = 4;
|
||||
|
||||
private static final int MAX_PREVIEW_WIDTH = 1920;
|
||||
private static final int MAX_PREVIEW_HEIGHT = 1080;
|
||||
|
||||
private int flashMode;
|
||||
private int orientation = 0;
|
||||
private int state = STATE_PREVIEW;
|
||||
|
||||
private Context context;
|
||||
private OnTakePictureCallback onTakePictureCallback;
|
||||
private PermissionCallback permissionCallback;
|
||||
|
||||
private String cameraId;
|
||||
private TextureView textureView;
|
||||
private Size previewSize;
|
||||
private Rect previewFrame = new Rect();
|
||||
|
||||
private HandlerThread backgroundThread;
|
||||
private Handler backgroundHandler;
|
||||
private ImageReader imageReader;
|
||||
private CameraCaptureSession captureSession;
|
||||
private CameraDevice cameraDevice;
|
||||
|
||||
private CaptureRequest.Builder previewRequestBuilder;
|
||||
private CaptureRequest previewRequest;
|
||||
|
||||
private Semaphore cameraLock = new Semaphore(1);
|
||||
private int sensorOrientation;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
startBackgroundThread();
|
||||
if (textureView.isAvailable()) {
|
||||
openCamera(textureView.getWidth(), textureView.getHeight());
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
} else {
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
textureView.setSurfaceTextureListener(null);
|
||||
closeCamera();
|
||||
stopBackgroundThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
setFlashMode(FLASH_MODE_OFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
state = STATE_PREVIEW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDisplayView() {
|
||||
return textureView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getPreviewFrame() {
|
||||
return previewFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takePicture(OnTakePictureCallback callback) {
|
||||
this.onTakePictureCallback = callback;
|
||||
// 拍照第一步,对焦
|
||||
lockFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPermissionCallback(PermissionCallback callback) {
|
||||
this.permissionCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) {
|
||||
this.orientation = displayOrientation / 90;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshPermission() {
|
||||
openCamera(textureView.getWidth(), textureView.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFlashMode(@FlashMode int flashMode) {
|
||||
if (this.flashMode == flashMode) {
|
||||
return;
|
||||
}
|
||||
this.flashMode = flashMode;
|
||||
try {
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
updateFlashMode(flashMode, previewRequestBuilder);
|
||||
previewRequest = previewRequestBuilder.build();
|
||||
captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlashMode() {
|
||||
return flashMode;
|
||||
}
|
||||
|
||||
public Camera2Control(Context activity) {
|
||||
this.context = activity;
|
||||
textureView = new TextureView(activity);
|
||||
}
|
||||
|
||||
private final TextureView.SurfaceTextureListener surfaceTextureListener =
|
||||
new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
|
||||
openCamera(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
|
||||
configureTransform(width, height);
|
||||
previewFrame.left = 0;
|
||||
previewFrame.top = 0;
|
||||
previewFrame.right = width;
|
||||
previewFrame.bottom = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
|
||||
}
|
||||
};
|
||||
|
||||
private void openCamera(int width, int height) {
|
||||
// 6.0+的系统需要检查系统权限 。
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestCameraPermission();
|
||||
return;
|
||||
}
|
||||
setUpCameraOutputs(width, height);
|
||||
configureTransform(width, height);
|
||||
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
try {
|
||||
if (!cameraLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
|
||||
throw new RuntimeException("Time out waiting to lock camera opening.");
|
||||
}
|
||||
manager.openCamera(cameraId, deviceStateCallback, backgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() {
|
||||
@Override
|
||||
public void onOpened(@NonNull CameraDevice cameraDevice) {
|
||||
cameraLock.release();
|
||||
Camera2Control.this.cameraDevice = cameraDevice;
|
||||
createCameraPreviewSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
|
||||
cameraLock.release();
|
||||
cameraDevice.close();
|
||||
Camera2Control.this.cameraDevice = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull CameraDevice cameraDevice, int error) {
|
||||
cameraLock.release();
|
||||
cameraDevice.close();
|
||||
Camera2Control.this.cameraDevice = null;
|
||||
}
|
||||
};
|
||||
|
||||
private void createCameraPreviewSession() {
|
||||
try {
|
||||
SurfaceTexture texture = textureView.getSurfaceTexture();
|
||||
assert texture != null;
|
||||
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
|
||||
Surface surface = new Surface(texture);
|
||||
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
||||
previewRequestBuilder.addTarget(surface);
|
||||
updateFlashMode(this.flashMode, previewRequestBuilder);
|
||||
cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
|
||||
new CameraCaptureSession.StateCallback() {
|
||||
|
||||
@Override
|
||||
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
|
||||
// The camera is already closed
|
||||
if (null == cameraDevice) {
|
||||
return;
|
||||
}
|
||||
captureSession = cameraCaptureSession;
|
||||
try {
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
previewRequest = previewRequestBuilder.build();
|
||||
captureSession.setRepeatingRequest(previewRequest,
|
||||
captureCallback, backgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigureFailed(
|
||||
@NonNull CameraCaptureSession cameraCaptureSession) {
|
||||
// TODO
|
||||
}
|
||||
}, null
|
||||
);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final ImageReader.OnImageAvailableListener onImageAvailableListener =
|
||||
new ImageReader.OnImageAvailableListener() {
|
||||
@Override
|
||||
public void onImageAvailable(ImageReader reader) {
|
||||
if (onTakePictureCallback != null) {
|
||||
Image image = reader.acquireNextImage();
|
||||
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
|
||||
image.close();
|
||||
onTakePictureCallback.onPictureTaken(bytes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private CameraCaptureSession.CaptureCallback captureCallback =
|
||||
new CameraCaptureSession.CaptureCallback() {
|
||||
private void process(CaptureResult result) {
|
||||
switch (state) {
|
||||
case STATE_PREVIEW: {
|
||||
break;
|
||||
}
|
||||
case STATE_WAITING_FOR_LOCK: {
|
||||
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
|
||||
if (afState == null) {
|
||||
captureStillPicture();
|
||||
return;
|
||||
}
|
||||
switch (afState) {
|
||||
case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
|
||||
case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
|
||||
case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: {
|
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
||||
if (aeState == null
|
||||
|| aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
|
||||
captureStillPicture();
|
||||
} else {
|
||||
runPreCaptureSequence();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
captureStillPicture();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATE_WAITING_FOR_CAPTURE: {
|
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
||||
if (aeState == null
|
||||
|| aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE
|
||||
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
|
||||
state = STATE_CAPTURING;
|
||||
} else {
|
||||
if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
|
||||
captureStillPicture();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATE_CAPTURING: {
|
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
||||
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
|
||||
captureStillPicture();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull CaptureResult partialResult) {
|
||||
process(partialResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull TotalCaptureResult result) {
|
||||
process(result);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private Size getOptimalSize(Size[] choices, int textureViewWidth,
|
||||
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
|
||||
List<Size> bigEnough = new ArrayList<>();
|
||||
List<Size> notBigEnough = new ArrayList<>();
|
||||
int w = aspectRatio.getWidth();
|
||||
int h = aspectRatio.getHeight();
|
||||
for (Size option : choices) {
|
||||
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight
|
||||
&& option.getHeight() == option.getWidth() * h / w) {
|
||||
if (option.getWidth() >= textureViewWidth
|
||||
&& option.getHeight() >= textureViewHeight) {
|
||||
bigEnough.add(option);
|
||||
} else {
|
||||
notBigEnough.add(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the smallest of those big enough. If there is no one big enough, pick the
|
||||
// largest of those not big enough.
|
||||
if (bigEnough.size() > 0) {
|
||||
return Collections.min(bigEnough, sizeComparator);
|
||||
}
|
||||
|
||||
for (Size option : choices) {
|
||||
if (option.getWidth() >= maxWidth && option.getHeight() >= maxHeight) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
if (notBigEnough.size() > 0) {
|
||||
return Collections.max(notBigEnough, sizeComparator);
|
||||
}
|
||||
|
||||
return choices[0];
|
||||
}
|
||||
|
||||
private Comparator<Size> sizeComparator = new Comparator<Size>() {
|
||||
@Override
|
||||
public int compare(Size lhs, Size rhs) {
|
||||
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
private void requestCameraPermission() {
|
||||
if (permissionCallback != null) {
|
||||
permissionCallback.onRequestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
private void setUpCameraOutputs(int width, int height) {
|
||||
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
try {
|
||||
for (String cameraId : manager.getCameraIdList()) {
|
||||
CameraCharacteristics characteristics =
|
||||
manager.getCameraCharacteristics(cameraId);
|
||||
|
||||
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StreamConfigurationMap map = characteristics.get(
|
||||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
if (map == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// int preferredPictureSize = (int) (Math.max(textureView.getWidth(), textureView
|
||||
// .getHeight()) * 1.5);
|
||||
// preferredPictureSize = Math.min(preferredPictureSize, 2560);
|
||||
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Point screenSize = new Point();
|
||||
windowManager.getDefaultDisplay().getSize(screenSize);
|
||||
int maxImageSize = Math.max(MAX_PREVIEW_SIZE, screenSize.y * 3 / 2);
|
||||
|
||||
Size size = getOptimalSize(map.getOutputSizes(ImageFormat.JPEG), textureView.getWidth(),
|
||||
textureView.getHeight(), maxImageSize, maxImageSize, new Size(4, 3));
|
||||
|
||||
imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
|
||||
ImageFormat.JPEG, 1);
|
||||
imageReader.setOnImageAvailableListener(
|
||||
onImageAvailableListener, backgroundHandler);
|
||||
|
||||
int displayRotation = orientation;
|
||||
// noinspection ConstantConditions
|
||||
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||
boolean swappedDimensions = false;
|
||||
switch (displayRotation) {
|
||||
case Surface.ROTATION_0:
|
||||
case Surface.ROTATION_180:
|
||||
if (sensorOrientation == 90 || sensorOrientation == 270) {
|
||||
swappedDimensions = true;
|
||||
}
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
case Surface.ROTATION_270:
|
||||
if (sensorOrientation == 0 || sensorOrientation == 180) {
|
||||
swappedDimensions = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
int rotatedPreviewWidth = width;
|
||||
int rotatedPreviewHeight = height;
|
||||
int maxPreviewWidth = screenSize.x;
|
||||
int maxPreviewHeight = screenSize.y;
|
||||
|
||||
if (swappedDimensions) {
|
||||
rotatedPreviewWidth = height;
|
||||
rotatedPreviewHeight = width;
|
||||
maxPreviewWidth = screenSize.y;
|
||||
maxPreviewHeight = screenSize.x;
|
||||
}
|
||||
|
||||
maxPreviewWidth = Math.min(maxPreviewWidth, MAX_PREVIEW_WIDTH);
|
||||
maxPreviewHeight = Math.min(maxPreviewHeight, MAX_PREVIEW_HEIGHT);
|
||||
|
||||
previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
||||
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
|
||||
maxPreviewHeight, size);
|
||||
this.cameraId = cameraId;
|
||||
return;
|
||||
}
|
||||
} catch (CameraAccessException | NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void closeCamera() {
|
||||
try {
|
||||
cameraLock.acquire();
|
||||
if (null != captureSession) {
|
||||
captureSession.close();
|
||||
captureSession = null;
|
||||
}
|
||||
if (null != cameraDevice) {
|
||||
cameraDevice.close();
|
||||
cameraDevice = null;
|
||||
}
|
||||
if (null != imageReader) {
|
||||
imageReader.close();
|
||||
imageReader = null;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
|
||||
} finally {
|
||||
cameraLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackgroundThread() {
|
||||
backgroundThread = new HandlerThread("ocr_camera");
|
||||
backgroundThread.start();
|
||||
backgroundHandler = new Handler(backgroundThread.getLooper());
|
||||
}
|
||||
|
||||
private void stopBackgroundThread() {
|
||||
if (backgroundThread != null) {
|
||||
backgroundThread.quitSafely();
|
||||
backgroundThread = null;
|
||||
backgroundHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void configureTransform(int viewWidth, int viewHeight) {
|
||||
if (null == textureView || null == previewSize || null == context) {
|
||||
return;
|
||||
}
|
||||
int rotation = orientation;
|
||||
Matrix matrix = new Matrix();
|
||||
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
|
||||
RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
|
||||
float centerX = viewRect.centerX();
|
||||
float centerY = viewRect.centerY();
|
||||
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
|
||||
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
|
||||
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
|
||||
float scale = Math.max(
|
||||
(float) viewHeight / previewSize.getHeight(),
|
||||
(float) viewWidth / previewSize.getWidth());
|
||||
matrix.postScale(scale, scale, centerX, centerY);
|
||||
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
|
||||
} else if (Surface.ROTATION_180 == rotation) {
|
||||
matrix.postRotate(180, centerX, centerY);
|
||||
}
|
||||
textureView.setTransform(matrix);
|
||||
}
|
||||
|
||||
// 拍照前,先对焦
|
||||
private void lockFocus() {
|
||||
if (captureSession != null && state == STATE_PREVIEW) {
|
||||
try {
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
|
||||
CameraMetadata.CONTROL_AF_TRIGGER_START);
|
||||
state = STATE_WAITING_FOR_LOCK;
|
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback,
|
||||
backgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runPreCaptureSequence() {
|
||||
try {
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
|
||||
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
|
||||
state = STATE_WAITING_FOR_CAPTURE;
|
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback,
|
||||
backgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 拍照session
|
||||
private void captureStillPicture() {
|
||||
try {
|
||||
if (null == context || null == cameraDevice) {
|
||||
return;
|
||||
}
|
||||
final CaptureRequest.Builder captureBuilder =
|
||||
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
|
||||
captureBuilder.addTarget(imageReader.getSurface());
|
||||
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
|
||||
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(orientation));
|
||||
updateFlashMode(this.flashMode, captureBuilder);
|
||||
CameraCaptureSession.CaptureCallback captureCallback =
|
||||
new CameraCaptureSession.CaptureCallback() {
|
||||
@Override
|
||||
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull TotalCaptureResult result) {
|
||||
unlockFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureFailed(@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull CaptureFailure failure) {
|
||||
super.onCaptureFailed(session, request, failure);
|
||||
}
|
||||
};
|
||||
|
||||
// 停止预览
|
||||
captureSession.stopRepeating();
|
||||
captureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);
|
||||
state = STATE_PICTURE_TAKEN;
|
||||
// unlockFocus();
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private int getOrientation(int rotation) {
|
||||
return (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360;
|
||||
}
|
||||
|
||||
// 停止对焦
|
||||
private void unlockFocus() {
|
||||
try {
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
|
||||
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
|
||||
captureSession.capture(previewRequestBuilder.build(), captureCallback,
|
||||
backgroundHandler);
|
||||
state = STATE_PREVIEW;
|
||||
// 预览
|
||||
captureSession.setRepeatingRequest(previewRequest, captureCallback,
|
||||
backgroundHandler);
|
||||
textureView.setSurfaceTextureListener(surfaceTextureListener);
|
||||
} catch (CameraAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFlashMode(@FlashMode int flashMode, CaptureRequest.Builder builder) {
|
||||
switch (flashMode) {
|
||||
case FLASH_MODE_TORCH:
|
||||
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
|
||||
break;
|
||||
case FLASH_MODE_OFF:
|
||||
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
|
||||
break;
|
||||
case ICameraControl.FLASH_MODE_AUTO:
|
||||
default:
|
||||
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
515
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/CameraActivity.java
Normal file
@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.baidu.idcardquality.IDcardQualityProcess;
|
||||
import com.baidu.ocr.ui.R;
|
||||
import com.baidu.ocr.ui.crop.CropView;
|
||||
import com.baidu.ocr.ui.crop.FrameOverlayView;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
public class CameraActivity extends Activity {
|
||||
|
||||
public static final String KEY_OUTPUT_FILE_PATH = "outputFilePath";
|
||||
public static final String KEY_CONTENT_TYPE = "contentType";
|
||||
public static final String KEY_NATIVE_TOKEN = "nativeToken";
|
||||
public static final String KEY_NATIVE_ENABLE = "nativeEnable";
|
||||
public static final String KEY_NATIVE_MANUAL = "nativeEnableManual";
|
||||
|
||||
public static final String CONTENT_TYPE_GENERAL = "general";
|
||||
public static final String CONTENT_TYPE_ID_CARD_FRONT = "IDCardFront";
|
||||
public static final String CONTENT_TYPE_ID_CARD_BACK = "IDCardBack";
|
||||
public static final String CONTENT_TYPE_BANK_CARD = "bankCard";
|
||||
public static final String CONTENT_TYPE_PASSPORT = "passport";
|
||||
|
||||
private static final int REQUEST_CODE_PICK_IMAGE = 100;
|
||||
private static final int PERMISSIONS_REQUEST_CAMERA = 800;
|
||||
private static final int PERMISSIONS_EXTERNAL_STORAGE = 801;
|
||||
|
||||
private File outputFile;
|
||||
private String contentType;
|
||||
private Handler handler = new Handler();
|
||||
|
||||
private boolean isNativeEnable;
|
||||
private boolean isNativeManual;
|
||||
|
||||
private OCRCameraLayout takePictureContainer;
|
||||
private OCRCameraLayout cropContainer;
|
||||
private OCRCameraLayout confirmResultContainer;
|
||||
private ImageView lightButton;
|
||||
private CameraView cameraView;
|
||||
private ImageView displayImageView;
|
||||
private CropView cropView;
|
||||
private FrameOverlayView overlayView;
|
||||
private MaskView cropMaskView;
|
||||
private ImageView takePhotoBtn;
|
||||
private PermissionCallback permissionCallback = new PermissionCallback() {
|
||||
@Override
|
||||
public boolean onRequestPermission() {
|
||||
ActivityCompat.requestPermissions(CameraActivity.this,
|
||||
new String[] {Manifest.permission.CAMERA},
|
||||
PERMISSIONS_REQUEST_CAMERA);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.bd_ocr_activity_camera);
|
||||
|
||||
takePictureContainer = (OCRCameraLayout) findViewById(R.id.take_picture_container);
|
||||
confirmResultContainer = (OCRCameraLayout) findViewById(R.id.confirm_result_container);
|
||||
|
||||
cameraView = (CameraView) findViewById(R.id.camera_view);
|
||||
cameraView.getCameraControl().setPermissionCallback(permissionCallback);
|
||||
lightButton = (ImageView) findViewById(R.id.light_button);
|
||||
lightButton.setOnClickListener(lightButtonOnClickListener);
|
||||
takePhotoBtn = (ImageView) findViewById(R.id.take_photo_button);
|
||||
findViewById(R.id.album_button).setOnClickListener(albumButtonOnClickListener);
|
||||
takePhotoBtn.setOnClickListener(takeButtonOnClickListener);
|
||||
|
||||
// confirm result;
|
||||
displayImageView = (ImageView) findViewById(R.id.display_image_view);
|
||||
confirmResultContainer.findViewById(R.id.confirm_button).setOnClickListener(confirmButtonOnClickListener);
|
||||
confirmResultContainer.findViewById(R.id.cancel_button).setOnClickListener(confirmCancelButtonOnClickListener);
|
||||
findViewById(R.id.rotate_button).setOnClickListener(rotateButtonOnClickListener);
|
||||
|
||||
cropView = (CropView) findViewById(R.id.crop_view);
|
||||
cropContainer = (OCRCameraLayout) findViewById(R.id.crop_container);
|
||||
overlayView = (FrameOverlayView) findViewById(R.id.overlay_view);
|
||||
cropContainer.findViewById(R.id.confirm_button).setOnClickListener(cropConfirmButtonListener);
|
||||
cropMaskView = (MaskView) cropContainer.findViewById(R.id.crop_mask_view);
|
||||
cropContainer.findViewById(R.id.cancel_button).setOnClickListener(cropCancelButtonListener);
|
||||
|
||||
setOrientation(getResources().getConfiguration());
|
||||
initParams();
|
||||
|
||||
cameraView.setAutoPictureCallback(autoTakePictureCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
cameraView.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
cameraView.start();
|
||||
}
|
||||
|
||||
private void initParams() {
|
||||
String outputPath = getIntent().getStringExtra(KEY_OUTPUT_FILE_PATH);
|
||||
final String token = getIntent().getStringExtra(KEY_NATIVE_TOKEN);
|
||||
isNativeEnable = getIntent().getBooleanExtra(KEY_NATIVE_ENABLE, true);
|
||||
isNativeManual = getIntent().getBooleanExtra(KEY_NATIVE_MANUAL, false);
|
||||
|
||||
if (token == null && !isNativeManual) {
|
||||
isNativeEnable = false;
|
||||
}
|
||||
|
||||
if (outputPath != null) {
|
||||
outputFile = new File(outputPath);
|
||||
}
|
||||
|
||||
contentType = getIntent().getStringExtra(KEY_CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
contentType = CONTENT_TYPE_GENERAL;
|
||||
}
|
||||
int maskType;
|
||||
switch (contentType) {
|
||||
case CONTENT_TYPE_ID_CARD_FRONT:
|
||||
maskType = MaskView.MASK_TYPE_ID_CARD_FRONT;
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
if (isNativeEnable) {
|
||||
takePhotoBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TYPE_ID_CARD_BACK:
|
||||
maskType = MaskView.MASK_TYPE_ID_CARD_BACK;
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
if (isNativeEnable) {
|
||||
takePhotoBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TYPE_BANK_CARD:
|
||||
maskType = MaskView.MASK_TYPE_BANK_CARD;
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
case CONTENT_TYPE_PASSPORT:
|
||||
maskType = MaskView.MASK_TYPE_PASSPORT;
|
||||
overlayView.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
case CONTENT_TYPE_GENERAL:
|
||||
default:
|
||||
maskType = MaskView.MASK_TYPE_NONE;
|
||||
cropMaskView.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
}
|
||||
|
||||
// 身份证本地能力初始化
|
||||
if (maskType == MaskView.MASK_TYPE_ID_CARD_FRONT || maskType == MaskView.MASK_TYPE_ID_CARD_BACK) {
|
||||
if (isNativeEnable && !isNativeManual) {
|
||||
initNative(token);
|
||||
}
|
||||
}
|
||||
cameraView.setEnableScan(isNativeEnable);
|
||||
cameraView.setMaskType(maskType, this);
|
||||
cropMaskView.setMaskType(maskType);
|
||||
}
|
||||
|
||||
private void initNative(final String token) {
|
||||
CameraNativeHelper.init(CameraActivity.this, token,
|
||||
new CameraNativeHelper.CameraNativeInitCallback() {
|
||||
@Override
|
||||
public void onError(int errorCode, Throwable e) {
|
||||
cameraView.setInitNativeStatus(errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showTakePicture() {
|
||||
cameraView.getCameraControl().resume();
|
||||
updateFlashMode();
|
||||
takePictureContainer.setVisibility(View.VISIBLE);
|
||||
confirmResultContainer.setVisibility(View.INVISIBLE);
|
||||
cropContainer.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void showCrop() {
|
||||
cameraView.getCameraControl().pause();
|
||||
updateFlashMode();
|
||||
takePictureContainer.setVisibility(View.INVISIBLE);
|
||||
confirmResultContainer.setVisibility(View.INVISIBLE);
|
||||
cropContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void showResultConfirm() {
|
||||
cameraView.getCameraControl().pause();
|
||||
updateFlashMode();
|
||||
takePictureContainer.setVisibility(View.INVISIBLE);
|
||||
confirmResultContainer.setVisibility(View.VISIBLE);
|
||||
cropContainer.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
// take photo;
|
||||
private void updateFlashMode() {
|
||||
int flashMode = cameraView.getCameraControl().getFlashMode();
|
||||
if (flashMode == ICameraControl.FLASH_MODE_TORCH) {
|
||||
lightButton.setImageResource(R.drawable.bd_ocr_light_on);
|
||||
} else {
|
||||
lightButton.setImageResource(R.drawable.bd_ocr_light_off);
|
||||
}
|
||||
}
|
||||
|
||||
private View.OnClickListener albumButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
ActivityCompat.requestPermissions(CameraActivity.this,
|
||||
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSIONS_EXTERNAL_STORAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_PICK);
|
||||
intent.setType("image/*");
|
||||
startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener lightButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (cameraView.getCameraControl().getFlashMode() == ICameraControl.FLASH_MODE_OFF) {
|
||||
cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_TORCH);
|
||||
} else {
|
||||
cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_OFF);
|
||||
}
|
||||
updateFlashMode();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener takeButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cameraView.takePicture(outputFile, takePictureCallback);
|
||||
}
|
||||
};
|
||||
|
||||
private CameraView.OnTakePictureCallback autoTakePictureCallback = new CameraView.OnTakePictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(final Bitmap bitmap) {
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
|
||||
bitmap.recycle();
|
||||
fileOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private CameraView.OnTakePictureCallback takePictureCallback = new CameraView.OnTakePictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(final Bitmap bitmap) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
takePictureContainer.setVisibility(View.INVISIBLE);
|
||||
if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_NONE) {
|
||||
cropView.setFilePath(outputFile.getAbsolutePath());
|
||||
showCrop();
|
||||
} else if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_BANK_CARD) {
|
||||
cropView.setFilePath(outputFile.getAbsolutePath());
|
||||
cropMaskView.setVisibility(View.INVISIBLE);
|
||||
overlayView.setVisibility(View.VISIBLE);
|
||||
overlayView.setTypeWide();
|
||||
showCrop();
|
||||
} else {
|
||||
displayImageView.setImageBitmap(bitmap);
|
||||
showResultConfirm();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener cropCancelButtonListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// 释放 cropView中的bitmap;
|
||||
cropView.setFilePath(null);
|
||||
showTakePicture();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener cropConfirmButtonListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int maskType = cropMaskView.getMaskType();
|
||||
Rect rect;
|
||||
switch (maskType) {
|
||||
case MaskView.MASK_TYPE_BANK_CARD:
|
||||
case MaskView.MASK_TYPE_ID_CARD_BACK:
|
||||
case MaskView.MASK_TYPE_ID_CARD_FRONT:
|
||||
rect = cropMaskView.getFrameRect();
|
||||
break;
|
||||
case MaskView.MASK_TYPE_NONE:
|
||||
default:
|
||||
rect = overlayView.getFrameRect();
|
||||
break;
|
||||
}
|
||||
Bitmap cropped = cropView.crop(rect);
|
||||
displayImageView.setImageBitmap(cropped);
|
||||
cropAndConfirm();
|
||||
}
|
||||
};
|
||||
|
||||
private void cropAndConfirm() {
|
||||
cameraView.getCameraControl().pause();
|
||||
updateFlashMode();
|
||||
doConfirmResult();
|
||||
}
|
||||
|
||||
private void doConfirmResult() {
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
|
||||
Bitmap bitmap = ((BitmapDrawable) displayImageView.getDrawable()).getBitmap();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
|
||||
fileOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private View.OnClickListener confirmButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
doConfirmResult();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener confirmCancelButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
displayImageView.setImageBitmap(null);
|
||||
showTakePicture();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener rotateButtonOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cropView.rotate(90);
|
||||
}
|
||||
};
|
||||
|
||||
private String getRealPathFromURI(Uri contentURI) {
|
||||
String result;
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = getContentResolver().query(contentURI, null, null, null, null);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (cursor == null) {
|
||||
result = contentURI.getPath();
|
||||
} else {
|
||||
cursor.moveToFirst();
|
||||
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
|
||||
result = cursor.getString(idx);
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
setOrientation(newConfig);
|
||||
}
|
||||
|
||||
private void setOrientation(Configuration newConfig) {
|
||||
int rotation = getWindowManager().getDefaultDisplay().getRotation();
|
||||
int orientation;
|
||||
int cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT;
|
||||
switch (newConfig.orientation) {
|
||||
case Configuration.ORIENTATION_PORTRAIT:
|
||||
cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT;
|
||||
orientation = OCRCameraLayout.ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
case Configuration.ORIENTATION_LANDSCAPE:
|
||||
orientation = OCRCameraLayout.ORIENTATION_HORIZONTAL;
|
||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
|
||||
cameraViewOrientation = CameraView.ORIENTATION_HORIZONTAL;
|
||||
} else {
|
||||
cameraViewOrientation = CameraView.ORIENTATION_INVERT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
orientation = OCRCameraLayout.ORIENTATION_PORTRAIT;
|
||||
cameraView.setOrientation(CameraView.ORIENTATION_PORTRAIT);
|
||||
break;
|
||||
}
|
||||
takePictureContainer.setOrientation(orientation);
|
||||
cameraView.setOrientation(cameraViewOrientation);
|
||||
cropContainer.setOrientation(orientation);
|
||||
confirmResultContainer.setOrientation(orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uri = data.getData();
|
||||
cropView.setFilePath(getRealPathFromURI(uri));
|
||||
showCrop();
|
||||
} else {
|
||||
cameraView.getCameraControl().resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
switch (requestCode) {
|
||||
case PERMISSIONS_REQUEST_CAMERA: {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
cameraView.getCameraControl().refreshPermission();
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.camera_permission_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PERMISSIONS_EXTERNAL_STORAGE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 做一些收尾工作
|
||||
*
|
||||
*/
|
||||
private void doClear() {
|
||||
CameraThreadPool.cancelAutoFocusTimer();
|
||||
if (isNativeEnable && !isNativeManual) {
|
||||
IDcardQualityProcess.getInstance().releaseModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
this.doClear();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.baidu.idcardquality.IDcardQualityProcess;
|
||||
|
||||
/**
|
||||
* Created by ruanshimin on 2018/1/23.
|
||||
*/
|
||||
|
||||
public class CameraNativeHelper {
|
||||
|
||||
public interface CameraNativeInitCallback {
|
||||
/**
|
||||
* 加载本地库异常回调
|
||||
*
|
||||
* @param errorCode 错误代码
|
||||
* @param e 如果加载so异常则会有异常对象传入
|
||||
*/
|
||||
void onError(int errorCode, Throwable e);
|
||||
}
|
||||
|
||||
public static void init(final Context ctx, final String token, final CameraNativeInitCallback cb) {
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int status;
|
||||
// 加载本地so失败, 异常返回getloadSoException
|
||||
if (IDcardQualityProcess.getLoadSoException() != null) {
|
||||
status = CameraView.NATIVE_SOLOAD_FAIL;
|
||||
cb.onError(status, IDcardQualityProcess.getLoadSoException());
|
||||
return;
|
||||
}
|
||||
// 授权状态
|
||||
int authStatus = IDcardQualityProcess.init(token);
|
||||
if (authStatus != 0) {
|
||||
cb.onError(CameraView.NATIVE_AUTH_FAIL, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载模型状态
|
||||
int initModelStatus = IDcardQualityProcess.getInstance()
|
||||
.idcardQualityInit(ctx.getAssets(),
|
||||
"models");
|
||||
|
||||
if (initModelStatus != 0) {
|
||||
cb.onError(CameraView.NATIVE_INIT_FAIL, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void release() {
|
||||
IDcardQualityProcess.getInstance().releaseModel();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class CameraThreadPool {
|
||||
|
||||
static Timer timerFocus = null;
|
||||
|
||||
/*
|
||||
* 对焦频率
|
||||
*/
|
||||
static final long cameraScanInterval = 2000;
|
||||
|
||||
/*
|
||||
* 线程池大小
|
||||
*/
|
||||
private static int poolCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(poolCount);
|
||||
|
||||
/**
|
||||
* 给线程池添加任务
|
||||
* @param runnable 任务
|
||||
*/
|
||||
public static void execute(Runnable runnable) {
|
||||
fixedThreadPool.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个定时对焦的timer任务
|
||||
* @param runnable 对焦代码
|
||||
* @return Timer Timer对象,用来终止自动对焦
|
||||
*/
|
||||
public static Timer createAutoFocusTimerTask(final Runnable runnable) {
|
||||
if (timerFocus != null) {
|
||||
return timerFocus;
|
||||
}
|
||||
timerFocus = new Timer();
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
}
|
||||
};
|
||||
timerFocus.scheduleAtFixedRate(task, 0, cameraScanInterval);
|
||||
return timerFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 终止自动对焦任务,实际调用了cancel方法并且清空对象
|
||||
* 但是无法终止执行中的任务,需额外处理
|
||||
*
|
||||
*/
|
||||
public static void cancelAutoFocusTimer() {
|
||||
if (timerFocus != null) {
|
||||
timerFocus.cancel();
|
||||
timerFocus = null;
|
||||
}
|
||||
}
|
||||
}
|
651
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/CameraView.java
Normal file
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.baidu.idcardquality.IDcardQualityProcess;
|
||||
import com.baidu.ocr.ui.R;
|
||||
import com.baidu.ocr.ui.util.DimensionUtil;
|
||||
import com.baidu.ocr.ui.util.ImageUtil;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.media.ImageReader;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
/**
|
||||
* 负责,相机的管理。同时提供,裁剪遮罩功能。
|
||||
*/
|
||||
public class CameraView extends FrameLayout {
|
||||
|
||||
private int maskType;
|
||||
|
||||
/**
|
||||
* 照相回调
|
||||
*/
|
||||
interface OnTakePictureCallback {
|
||||
void onPictureTaken(Bitmap bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 垂直方向 {@link #setOrientation(int)}
|
||||
*/
|
||||
public static final int ORIENTATION_PORTRAIT = 0;
|
||||
/**
|
||||
* 水平方向 {@link #setOrientation(int)}
|
||||
*/
|
||||
public static final int ORIENTATION_HORIZONTAL = 90;
|
||||
/**
|
||||
* 水平翻转方向 {@link #setOrientation(int)}
|
||||
*/
|
||||
public static final int ORIENTATION_INVERT = 270;
|
||||
|
||||
/**
|
||||
* 本地模型授权,加载成功
|
||||
*/
|
||||
public static final int NATIVE_AUTH_INIT_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* 本地模型授权,缺少SO
|
||||
*/
|
||||
public static final int NATIVE_SOLOAD_FAIL = 10;
|
||||
|
||||
/**
|
||||
* 本地模型授权,授权失败,token异常
|
||||
*/
|
||||
public static final int NATIVE_AUTH_FAIL = 11;
|
||||
|
||||
/**
|
||||
* 本地模型授权,模型加载失败
|
||||
*/
|
||||
public static final int NATIVE_INIT_FAIL = 12;
|
||||
|
||||
|
||||
/**
|
||||
* 是否已经通过本地质量控制扫描
|
||||
*/
|
||||
private final int SCAN_SUCCESS = 0;
|
||||
|
||||
public void setInitNativeStatus(int initNativeStatus) {
|
||||
this.initNativeStatus = initNativeStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地检测初始化,模型加载标识
|
||||
*/
|
||||
private int initNativeStatus = NATIVE_AUTH_INIT_SUCCESS;
|
||||
|
||||
@IntDef({ORIENTATION_PORTRAIT, ORIENTATION_HORIZONTAL, ORIENTATION_INVERT})
|
||||
public @interface Orientation {
|
||||
|
||||
}
|
||||
|
||||
private CameraViewTakePictureCallback cameraViewTakePictureCallback = new CameraViewTakePictureCallback();
|
||||
|
||||
private ICameraControl cameraControl;
|
||||
|
||||
/**
|
||||
* 相机预览View
|
||||
*/
|
||||
private View displayView;
|
||||
/**
|
||||
* 身份证,银行卡,等裁剪用的遮罩
|
||||
*/
|
||||
private MaskView maskView;
|
||||
|
||||
/**
|
||||
* 用于显示提示证 "请对齐身份证正面" 之类的背景
|
||||
*/
|
||||
private ImageView hintView;
|
||||
|
||||
/**
|
||||
* 用于显示提示证 "请对齐身份证正面" 之类的文字
|
||||
*/
|
||||
private TextView hintViewText;
|
||||
|
||||
/**
|
||||
* 提示文案容器
|
||||
*/
|
||||
private LinearLayout hintViewTextWrapper;
|
||||
|
||||
/**
|
||||
* 是否是本地质量控制扫描
|
||||
*/
|
||||
private boolean isEnableScan;
|
||||
|
||||
public void setEnableScan(boolean enableScan) {
|
||||
isEnableScan = enableScan;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI线程的handler
|
||||
*/
|
||||
Handler uiHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public ICameraControl getCameraControl() {
|
||||
return cameraControl;
|
||||
}
|
||||
|
||||
public void setOrientation(@Orientation int orientation) {
|
||||
cameraControl.setDisplayOrientation(orientation);
|
||||
}
|
||||
public CameraView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CameraView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
cameraControl.start();
|
||||
setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cameraControl.stop();
|
||||
setKeepScreenOn(false);
|
||||
}
|
||||
|
||||
public void takePicture(final File file, final OnTakePictureCallback callback) {
|
||||
cameraViewTakePictureCallback.file = file;
|
||||
cameraViewTakePictureCallback.callback = callback;
|
||||
cameraControl.takePicture(cameraViewTakePictureCallback);
|
||||
}
|
||||
|
||||
private OnTakePictureCallback autoPictureCallback;
|
||||
|
||||
public void setAutoPictureCallback(OnTakePictureCallback callback) {
|
||||
autoPictureCallback = callback;
|
||||
}
|
||||
|
||||
public void setMaskType(@MaskView.MaskType int maskType, final Context ctx) {
|
||||
maskView.setMaskType(maskType);
|
||||
|
||||
maskView.setVisibility(VISIBLE);
|
||||
hintView.setVisibility(VISIBLE);
|
||||
|
||||
int hintResourceId = R.drawable.bd_ocr_hint_align_id_card;
|
||||
this.maskType = maskType;
|
||||
boolean isNeedSetImage = true;
|
||||
switch (maskType) {
|
||||
case MaskView.MASK_TYPE_ID_CARD_FRONT:
|
||||
hintResourceId = R.drawable.bd_ocr_round_corner;
|
||||
isNeedSetImage = false;
|
||||
break;
|
||||
case MaskView.MASK_TYPE_ID_CARD_BACK:
|
||||
isNeedSetImage = false;
|
||||
hintResourceId = R.drawable.bd_ocr_round_corner;
|
||||
break;
|
||||
case MaskView.MASK_TYPE_BANK_CARD:
|
||||
hintResourceId = R.drawable.bd_ocr_hint_align_bank_card;
|
||||
break;
|
||||
case MaskView.MASK_TYPE_PASSPORT:
|
||||
hintView.setVisibility(INVISIBLE);
|
||||
break;
|
||||
case MaskView.MASK_TYPE_NONE:
|
||||
default:
|
||||
maskView.setVisibility(INVISIBLE);
|
||||
hintView.setVisibility(INVISIBLE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isNeedSetImage) {
|
||||
hintView.setImageResource(hintResourceId);
|
||||
hintViewTextWrapper.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
if (maskType == MaskView.MASK_TYPE_ID_CARD_FRONT && isEnableScan) {
|
||||
cameraControl.setDetectCallback(new ICameraControl.OnDetectPictureCallback() {
|
||||
@Override
|
||||
public int onDetect(byte[] data, int rotation) {
|
||||
return detect(data, rotation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (maskType == MaskView.MASK_TYPE_ID_CARD_BACK && isEnableScan) {
|
||||
cameraControl.setDetectCallback(new ICameraControl.OnDetectPictureCallback() {
|
||||
@Override
|
||||
public int onDetect(byte[] data, int rotation) {
|
||||
return detect(data, rotation);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private int detect(byte[] data, final int rotation) {
|
||||
if (initNativeStatus != NATIVE_AUTH_INIT_SUCCESS) {
|
||||
showTipMessage(initNativeStatus);
|
||||
return 1;
|
||||
}
|
||||
// 扫描成功阻止多余的操作
|
||||
if (cameraControl.getAbortingScan().get()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Rect previewFrame = cameraControl.getPreviewFrame();
|
||||
|
||||
if (maskView.getWidth() == 0 || maskView.getHeight() == 0
|
||||
|| previewFrame.width() == 0 || previewFrame.height() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// BitmapRegionDecoder不会将整个图片加载到内存。
|
||||
BitmapRegionDecoder decoder = null;
|
||||
try {
|
||||
decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight();
|
||||
int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth();
|
||||
|
||||
Rect frameRect = maskView.getFrameRectExtend();
|
||||
|
||||
int left = width * frameRect.left / maskView.getWidth();
|
||||
int top = height * frameRect.top / maskView.getHeight();
|
||||
int right = width * frameRect.right / maskView.getWidth();
|
||||
int bottom = height * frameRect.bottom / maskView.getHeight();
|
||||
|
||||
// 高度大于图片
|
||||
if (previewFrame.top < 0) {
|
||||
// 宽度对齐。
|
||||
int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width();
|
||||
int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2)
|
||||
* getWidth() / previewFrame.width();
|
||||
int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth()
|
||||
/ previewFrame.width();
|
||||
|
||||
// 等比例投射到照片当中。
|
||||
top = topInFrame * height / previewFrame.height();
|
||||
bottom = bottomInFrame * height / previewFrame.height();
|
||||
} else {
|
||||
// 宽度大于图片
|
||||
if (previewFrame.left < 0) {
|
||||
// 高度对齐
|
||||
int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height();
|
||||
int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight()
|
||||
/ previewFrame.height();
|
||||
int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight()
|
||||
/ previewFrame.height();
|
||||
|
||||
// 等比例投射到照片当中。
|
||||
left = leftInFrame * width / previewFrame.width();
|
||||
right = rightInFrame * width / previewFrame.width();
|
||||
}
|
||||
}
|
||||
|
||||
Rect region = new Rect();
|
||||
region.left = left;
|
||||
region.top = top;
|
||||
region.right = right;
|
||||
region.bottom = bottom;
|
||||
|
||||
// 90度或者270度旋转
|
||||
if (rotation % 180 == 90) {
|
||||
int x = decoder.getWidth() / 2;
|
||||
int y = decoder.getHeight() / 2;
|
||||
|
||||
int rotatedWidth = region.height();
|
||||
int rotated = region.width();
|
||||
|
||||
// 计算,裁剪框旋转后的坐标
|
||||
region.left = x - rotatedWidth / 2;
|
||||
region.top = y - rotated / 2;
|
||||
region.right = x + rotatedWidth / 2;
|
||||
region.bottom = y + rotated / 2;
|
||||
region.sort();
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
// 最大图片大小。
|
||||
int maxPreviewImageSize = 2560;
|
||||
int size = Math.min(decoder.getWidth(), decoder.getHeight());
|
||||
size = Math.min(size, maxPreviewImageSize);
|
||||
|
||||
options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size);
|
||||
options.inScaled = true;
|
||||
options.inDensity = Math.max(options.outWidth, options.outHeight);
|
||||
options.inTargetDensity = size;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
Bitmap bitmap = decoder.decodeRegion(region, options);
|
||||
if (rotation != 0) {
|
||||
// 只能是裁剪完之后再旋转了。有没有别的更好的方案呢?
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(rotation);
|
||||
Bitmap rotatedBitmap = Bitmap.createBitmap(
|
||||
bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
|
||||
if (bitmap != rotatedBitmap) {
|
||||
// 有时候 createBitmap会复用对象
|
||||
bitmap.recycle();
|
||||
}
|
||||
bitmap = rotatedBitmap;
|
||||
}
|
||||
|
||||
final int status;
|
||||
|
||||
// 调用本地质量控制请求
|
||||
switch (maskType) {
|
||||
case MaskView.MASK_TYPE_ID_CARD_FRONT:
|
||||
status = IDcardQualityProcess.getInstance().idcardQualityDetectionImg(bitmap, true);
|
||||
break;
|
||||
case MaskView.MASK_TYPE_ID_CARD_BACK:
|
||||
status = IDcardQualityProcess.getInstance().idcardQualityDetectionImg(bitmap, false);
|
||||
break;
|
||||
default:
|
||||
status = 1;
|
||||
}
|
||||
|
||||
// 当有某个扫描处理线程调用成功后,阻止其他线程继续调用本地控制代码
|
||||
if (status == SCAN_SUCCESS) {
|
||||
// 扫描成功阻止多线程同时回调
|
||||
if (!cameraControl.getAbortingScan().compareAndSet(false, true)) {
|
||||
bitmap.recycle();
|
||||
return 0;
|
||||
}
|
||||
autoPictureCallback.onPictureTaken(bitmap);
|
||||
}
|
||||
|
||||
showTipMessage(status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private void showTipMessage(final int status) {
|
||||
// 提示tip文字变化
|
||||
uiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (status == 0) {
|
||||
hintViewText.setVisibility(View.INVISIBLE);
|
||||
} else if (!cameraControl.getAbortingScan().get()) {
|
||||
hintViewText.setVisibility(View.VISIBLE);
|
||||
hintViewText.setText(getScanMessage(status));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getScanMessage(int status) {
|
||||
String message;
|
||||
switch (status) {
|
||||
case 0:
|
||||
message = "";
|
||||
break;
|
||||
case 2:
|
||||
message = "身份证模糊,请重新尝试";
|
||||
break;
|
||||
case 3:
|
||||
message = "身份证反光,请重新尝试";
|
||||
break;
|
||||
case 4:
|
||||
message = "请将身份证前后反转再进行识别";
|
||||
break;
|
||||
case 5:
|
||||
message = "请拿稳镜头和身份证";
|
||||
break;
|
||||
case 6:
|
||||
message = "请将镜头靠近身份证";
|
||||
break;
|
||||
case 7:
|
||||
message = "请将身份证完整置于取景框内";
|
||||
break;
|
||||
case NATIVE_AUTH_FAIL:
|
||||
message = "本地质量控制授权失败";
|
||||
break;
|
||||
case NATIVE_INIT_FAIL:
|
||||
message = "本地模型加载失败";
|
||||
break;
|
||||
case NATIVE_SOLOAD_FAIL:
|
||||
message = "本地SO库加载失败";
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
message = "请将身份证置于取景框内";
|
||||
}
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// cameraControl = new Camera2Control(getContext());
|
||||
// } else {
|
||||
//
|
||||
// }
|
||||
cameraControl = new Camera1Control(getContext());
|
||||
|
||||
displayView = cameraControl.getDisplayView();
|
||||
addView(displayView);
|
||||
|
||||
maskView = new MaskView(getContext());
|
||||
addView(maskView);
|
||||
|
||||
hintView = new ImageView(getContext());
|
||||
addView(hintView);
|
||||
|
||||
hintViewTextWrapper = new LinearLayout(getContext());
|
||||
hintViewTextWrapper.setOrientation(LinearLayout.VERTICAL);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
DimensionUtil.dpToPx(25));
|
||||
|
||||
lp.gravity = Gravity.CENTER;
|
||||
hintViewText = new TextView(getContext());
|
||||
hintViewText.setBackgroundResource(R.drawable.bd_ocr_round_corner);
|
||||
hintViewText.setAlpha(0.5f);
|
||||
hintViewText.setPadding(DimensionUtil.dpToPx(10), 0, DimensionUtil.dpToPx(10), 0);
|
||||
hintViewTextWrapper.addView(hintViewText, lp);
|
||||
|
||||
|
||||
hintViewText.setGravity(Gravity.CENTER);
|
||||
hintViewText.setTextColor(Color.WHITE);
|
||||
hintViewText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
hintViewText.setText(getScanMessage(-1));
|
||||
|
||||
|
||||
addView(hintViewTextWrapper, lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
displayView.layout(left, 0, right, bottom - top);
|
||||
maskView.layout(left, 0, right, bottom - top);
|
||||
|
||||
int hintViewWidth = DimensionUtil.dpToPx(250);
|
||||
int hintViewHeight = DimensionUtil.dpToPx(25);
|
||||
|
||||
int hintViewLeft = (getWidth() - hintViewWidth) / 2;
|
||||
int hintViewTop = maskView.getFrameRect().bottom + DimensionUtil.dpToPx(16);
|
||||
|
||||
hintViewTextWrapper.layout(hintViewLeft, hintViewTop,
|
||||
hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight);
|
||||
|
||||
hintView.layout(hintViewLeft, hintViewTop,
|
||||
hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拍摄后的照片。需要进行裁剪。有些手机(比如三星)不会对照片数据进行旋转,而是将旋转角度写入EXIF信息当中,
|
||||
* 所以需要做旋转处理。
|
||||
*
|
||||
* @param outputFile 写入照片的文件。
|
||||
* @param data 原始照片数据。
|
||||
* @param rotation 照片exif中的旋转角度。
|
||||
*
|
||||
* @return 裁剪好的bitmap。
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private Bitmap crop(File outputFile, byte[] data, int rotation) {
|
||||
try {
|
||||
Rect previewFrame = cameraControl.getPreviewFrame();
|
||||
|
||||
if (maskView.getWidth() == 0 || maskView.getHeight() == 0
|
||||
|| previewFrame.width() == 0 || previewFrame.height() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// BitmapRegionDecoder不会将整个图片加载到内存。
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true);
|
||||
|
||||
|
||||
|
||||
int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight();
|
||||
int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth();
|
||||
|
||||
Rect frameRect = maskView.getFrameRect();
|
||||
|
||||
int left = width * frameRect.left / maskView.getWidth();
|
||||
int top = height * frameRect.top / maskView.getHeight();
|
||||
int right = width * frameRect.right / maskView.getWidth();
|
||||
int bottom = height * frameRect.bottom / maskView.getHeight();
|
||||
|
||||
// 高度大于图片
|
||||
if (previewFrame.top < 0) {
|
||||
// 宽度对齐。
|
||||
int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width();
|
||||
int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2)
|
||||
* getWidth() / previewFrame.width();
|
||||
int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth()
|
||||
/ previewFrame.width();
|
||||
|
||||
// 等比例投射到照片当中。
|
||||
top = topInFrame * height / previewFrame.height();
|
||||
bottom = bottomInFrame * height / previewFrame.height();
|
||||
} else {
|
||||
// 宽度大于图片
|
||||
if (previewFrame.left < 0) {
|
||||
// 高度对齐
|
||||
int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height();
|
||||
int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight()
|
||||
/ previewFrame.height();
|
||||
int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight()
|
||||
/ previewFrame.height();
|
||||
|
||||
// 等比例投射到照片当中。
|
||||
left = leftInFrame * width / previewFrame.width();
|
||||
right = rightInFrame * width / previewFrame.width();
|
||||
}
|
||||
}
|
||||
|
||||
Rect region = new Rect();
|
||||
region.left = left;
|
||||
region.top = top;
|
||||
region.right = right;
|
||||
region.bottom = bottom;
|
||||
|
||||
// 90度或者270度旋转
|
||||
if (rotation % 180 == 90) {
|
||||
int x = decoder.getWidth() / 2;
|
||||
int y = decoder.getHeight() / 2;
|
||||
|
||||
int rotatedWidth = region.height();
|
||||
int rotated = region.width();
|
||||
|
||||
// 计算,裁剪框旋转后的坐标
|
||||
region.left = x - rotatedWidth / 2;
|
||||
region.top = y - rotated / 2;
|
||||
region.right = x + rotatedWidth / 2;
|
||||
region.bottom = y + rotated / 2;
|
||||
region.sort();
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
// 最大图片大小。
|
||||
int maxPreviewImageSize = 2560;
|
||||
int size = Math.min(decoder.getWidth(), decoder.getHeight());
|
||||
size = Math.min(size, maxPreviewImageSize);
|
||||
|
||||
options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size);
|
||||
options.inScaled = true;
|
||||
options.inDensity = Math.max(options.outWidth, options.outHeight);
|
||||
options.inTargetDensity = size;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
Bitmap bitmap = decoder.decodeRegion(region, options);
|
||||
|
||||
if (rotation != 0) {
|
||||
// 只能是裁剪完之后再旋转了。有没有别的更好的方案呢?
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(rotation);
|
||||
Bitmap rotatedBitmap = Bitmap.createBitmap(
|
||||
bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
|
||||
if (bitmap != rotatedBitmap) {
|
||||
// 有时候 createBitmap会复用对象
|
||||
bitmap.recycle();
|
||||
}
|
||||
bitmap = rotatedBitmap;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!outputFile.exists()) {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
|
||||
fileOutputStream.flush();
|
||||
fileOutputStream.close();
|
||||
return bitmap;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
IDcardQualityProcess.getInstance().releaseModel();
|
||||
}
|
||||
|
||||
private class CameraViewTakePictureCallback implements ICameraControl.OnTakePictureCallback {
|
||||
|
||||
private File file;
|
||||
private OnTakePictureCallback callback;
|
||||
|
||||
@Override
|
||||
public void onPictureTaken(final byte[] data) {
|
||||
CameraThreadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int rotation = ImageUtil.getOrientation(data);
|
||||
Bitmap bitmap = crop(file, data, rotation);
|
||||
callback.onPictureTaken(bitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
122
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/ICameraControl.java
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Android 5.0 相机的API发生很大的变化。些类屏蔽掉了 api的变化。相机的操作和功能,抽象剥离出来。
|
||||
*/
|
||||
public interface ICameraControl {
|
||||
|
||||
/**
|
||||
* 闪光灯关 {@link #setFlashMode(int)}
|
||||
*/
|
||||
int FLASH_MODE_OFF = 0;
|
||||
/**
|
||||
* 闪光灯开 {@link #setFlashMode(int)}
|
||||
*/
|
||||
int FLASH_MODE_TORCH = 1;
|
||||
/**
|
||||
* 闪光灯自动 {@link #setFlashMode(int)}
|
||||
*/
|
||||
int FLASH_MODE_AUTO = 2;
|
||||
|
||||
@IntDef({FLASH_MODE_TORCH, FLASH_MODE_OFF, FLASH_MODE_AUTO})
|
||||
@interface FlashMode {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 照相回调。
|
||||
*/
|
||||
interface OnTakePictureCallback {
|
||||
void onPictureTaken(byte[] data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置本地质量控制回调,如果不设置则视为不扫描调用本地质量控制代码。
|
||||
*/
|
||||
void setDetectCallback(OnDetectPictureCallback callback);
|
||||
|
||||
/**
|
||||
* 预览回调
|
||||
*/
|
||||
interface OnDetectPictureCallback {
|
||||
int onDetect(byte[] data, int rotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开相机。
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* 关闭相机
|
||||
*/
|
||||
void stop();
|
||||
|
||||
void pause();
|
||||
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* 相机对应的预览视图。
|
||||
* @return 预览视图
|
||||
*/
|
||||
View getDisplayView();
|
||||
|
||||
/**
|
||||
* 看到的预览可能不是照片的全部。返回预览视图的全貌。
|
||||
* @return 预览视图frame;
|
||||
*/
|
||||
Rect getPreviewFrame();
|
||||
|
||||
/**
|
||||
* 拍照。结果在回调中获取。
|
||||
* @param callback 拍照结果回调
|
||||
*/
|
||||
void takePicture(OnTakePictureCallback callback);
|
||||
|
||||
/**
|
||||
* 设置权限回调,当手机没有拍照权限时,可在回调中获取。
|
||||
* @param callback 权限回调
|
||||
*/
|
||||
void setPermissionCallback(PermissionCallback callback);
|
||||
|
||||
/**
|
||||
* 设置水平方向
|
||||
* @param displayOrientation 参数值见 {@link com.baidu.ocr.ui.camera.CameraView.Orientation}
|
||||
*/
|
||||
void setDisplayOrientation(@CameraView.Orientation int displayOrientation);
|
||||
|
||||
/**
|
||||
* 获取到拍照权限时,调用些函数以继续。
|
||||
*/
|
||||
void refreshPermission();
|
||||
|
||||
/**
|
||||
* 获取已经扫描成功,处理中
|
||||
*/
|
||||
AtomicBoolean getAbortingScan();
|
||||
|
||||
/**
|
||||
* 设置闪光灯状态。
|
||||
* @param flashMode {@link #FLASH_MODE_TORCH,#FLASH_MODE_OFF,#FLASH_MODE_AUTO}
|
||||
*/
|
||||
void setFlashMode(@FlashMode int flashMode);
|
||||
|
||||
/**
|
||||
* 获取当前闪光灯状态
|
||||
* @return 当前闪光灯状态 参见 {@link #setFlashMode(int)}
|
||||
*/
|
||||
@FlashMode
|
||||
int getFlashMode();
|
||||
}
|
280
ocr_ui/src/main/java/com/baidu/ocr/ui/camera/MaskView.java
Normal file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.baidu.ocr.ui.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class MaskView extends View {
|
||||
|
||||
public static final int MASK_TYPE_NONE = 0;
|
||||
public static final int MASK_TYPE_ID_CARD_FRONT = 1;
|
||||
public static final int MASK_TYPE_ID_CARD_BACK = 2;
|
||||
public static final int MASK_TYPE_BANK_CARD = 11;
|
||||
public static final int MASK_TYPE_PASSPORT = 21;
|
||||
|
||||
@IntDef({MASK_TYPE_NONE, MASK_TYPE_ID_CARD_FRONT, MASK_TYPE_ID_CARD_BACK, MASK_TYPE_BANK_CARD,
|
||||
MASK_TYPE_PASSPORT})
|
||||
@interface MaskType {
|
||||
|
||||
}
|
||||
|
||||
public void setLineColor(int lineColor) {
|
||||
this.lineColor = lineColor;
|
||||
}
|
||||
|
||||
public void setMaskColor(int maskColor) {
|
||||
this.maskColor = maskColor;
|
||||
}
|
||||
|
||||
private int lineColor = Color.WHITE;
|
||||
|
||||
private int maskType = MASK_TYPE_ID_CARD_FRONT;
|
||||
|
||||
private int maskColor = Color.argb(100, 0, 0, 0);
|
||||
|
||||
private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint pen = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private Rect frame = new Rect();
|
||||
|
||||
private Rect framePassport = new Rect();
|
||||
|
||||
private Drawable locatorDrawable;
|
||||
|
||||
public Rect getFrameRect() {
|
||||
if (maskType == MASK_TYPE_NONE) {
|
||||
return new Rect(0, 0, getWidth(), getHeight());
|
||||
} else if (maskType == MASK_TYPE_PASSPORT) {
|
||||
return new Rect(framePassport);
|
||||
} else {
|
||||
return new Rect(frame);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Rect getFrameRectExtend() {
|
||||
Rect rc = new Rect(frame);
|
||||
int widthExtend = (int) ((frame.right - frame.left) * 0.02f);
|
||||
int heightExtend = (int) ((frame.bottom - frame.top) * 0.02f);
|
||||
rc.left -= widthExtend;
|
||||
rc.right += widthExtend;
|
||||
rc.top -= heightExtend;
|
||||
rc.bottom += heightExtend;
|
||||
return rc;
|
||||
}
|
||||
|
||||
public void setMaskType(@MaskType int maskType) {
|
||||
this.maskType = maskType;
|
||||
switch (maskType) {
|
||||
case MASK_TYPE_ID_CARD_FRONT:
|
||||
locatorDrawable = ResourcesCompat.getDrawable(getResources(),
|
||||
R.drawable.bd_ocr_id_card_locator_front, null);
|
||||
break;
|
||||
case MASK_TYPE_ID_CARD_BACK:
|
||||
locatorDrawable = ResourcesCompat.getDrawable(getResources(),
|
||||
R.drawable.bd_ocr_id_card_locator_back, null);
|
||||
break;
|
||||
case MASK_TYPE_PASSPORT:
|
||||
locatorDrawable = ResourcesCompat.getDrawable(getResources(),
|
||||
R.drawable.bd_ocr_passport_locator, null);
|
||||
break;
|
||||
case MASK_TYPE_BANK_CARD:
|
||||
break;
|
||||
case MASK_TYPE_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getMaskType() {
|
||||
return maskType;
|
||||
}
|
||||
|
||||
public void setOrientation(@CameraView.Orientation int orientation) {
|
||||
}
|
||||
|
||||
public MaskView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public MaskView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public MaskView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
locatorDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.bd_ocr_id_card_locator_front, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if (w > 0 && h > 0) {
|
||||
if (maskType != MASK_TYPE_PASSPORT) {
|
||||
float ratio = h > w ? 0.9f : 0.72f;
|
||||
|
||||
int width = (int) (w * ratio);
|
||||
int height = width * 400 / 620;
|
||||
|
||||
int left = (w - width) / 2;
|
||||
int top = (h - height) / 2;
|
||||
int right = width + left;
|
||||
int bottom = height + top;
|
||||
|
||||
frame.left = left;
|
||||
frame.top = top;
|
||||
frame.right = right;
|
||||
frame.bottom = bottom;
|
||||
} else {
|
||||
float ratio = 0.9f;
|
||||
|
||||
int width = (int) (w * ratio);
|
||||
int height = width * 330 / 470;
|
||||
|
||||
int left = (w - width) / 2;
|
||||
int top = (h - height) / 2;
|
||||
int right = width + left;
|
||||
int bottom = height + top;
|
||||
|
||||
framePassport.left = left;
|
||||
framePassport.top = top;
|
||||
framePassport.right = right;
|
||||
framePassport.bottom = bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
Rect frame = this.frame;
|
||||
if (maskType == MASK_TYPE_PASSPORT) {
|
||||
frame = framePassport;
|
||||
}
|
||||
|
||||
int width = frame.width();
|
||||
int height = frame.height();
|
||||
|
||||
int left = frame.left;
|
||||
int top = frame.top;
|
||||
int right = frame.right;
|
||||
int bottom = frame.bottom;
|
||||
|
||||
canvas.drawColor(maskColor);
|
||||
fillRectRound(left, top, right, bottom, 30, 30, false);
|
||||
canvas.drawPath(path, pen);
|
||||
canvas.drawPath(path, eraser);
|
||||
|
||||
if (maskType == MASK_TYPE_ID_CARD_FRONT) {
|
||||
locatorDrawable.setBounds(
|
||||
(int) (left + 601f / 1006 * width),
|
||||
(int) (top + (110f / 632) * height),
|
||||
(int) (left + (963f / 1006) * width),
|
||||
(int) (top + (476f / 632) * height));
|
||||
} else if (maskType == MASK_TYPE_ID_CARD_BACK) {
|
||||
locatorDrawable.setBounds(
|
||||
(int) (left + 51f / 1006 * width),
|
||||
(int) (top + (48f / 632) * height),
|
||||
(int) (left + (250f / 1006) * width),
|
||||
(int) (top + (262f / 632) * height));
|
||||
} else if (maskType == MASK_TYPE_PASSPORT) {
|
||||
locatorDrawable.setBounds(
|
||||
(int) (left + 30f / 1006 * width),
|
||||
(int) (top + (20f / 632) * height),
|
||||
(int) (left + (303f / 1006) * width),
|
||||
(int) (top + (416f / 632) * height));
|
||||
}
|
||||
if (locatorDrawable != null) {
|
||||
locatorDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
private Path path = new Path();
|
||||
|
||||
private Path fillRectRound(float left, float top, float right, float bottom, float rx, float ry, boolean
|
||||
conformToOriginalPost) {
|
||||
|
||||
path.reset();
|
||||
if (rx < 0) {
|
||||
rx = 0;
|
||||
}
|
||||
if (ry < 0) {
|
||||
ry = 0;
|
||||
}
|
||||
float width = right - left;
|
||||
float height = bottom - top;
|
||||
if (rx > width / 2) {
|
||||
rx = width / 2;
|
||||
}
|
||||
if (ry > height / 2) {
|
||||
ry = height / 2;
|
||||
}
|
||||
float widthMinusCorners = (width - (2 * rx));
|
||||
float heightMinusCorners = (height - (2 * ry));
|
||||
|
||||
path.moveTo(right, top + ry);
|
||||
path.rQuadTo(0, -ry, -rx, -ry);
|
||||
path.rLineTo(-widthMinusCorners, 0);
|
||||
path.rQuadTo(-rx, 0, -rx, ry);
|
||||
path.rLineTo(0, heightMinusCorners);
|
||||
|
||||
if (conformToOriginalPost) {
|
||||
path.rLineTo(0, ry);
|
||||
path.rLineTo(width, 0);
|
||||
path.rLineTo(0, -ry);
|
||||
} else {
|
||||
path.rQuadTo(0, ry, rx, ry);
|
||||
path.rLineTo(widthMinusCorners, 0);
|
||||
path.rQuadTo(rx, 0, rx, -ry);
|
||||
}
|
||||
|
||||
path.rLineTo(0, -heightMinusCorners);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
|
||||
{
|
||||
// 硬件加速不支持,图层混合。
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
pen.setColor(Color.WHITE);
|
||||
pen.setStyle(Paint.Style.STROKE);
|
||||
pen.setStrokeWidth(6);
|
||||
|
||||
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
}
|
||||
|
||||
private void capture(File file) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
import com.baidu.ocr.ui.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class OCRCameraLayout extends FrameLayout {
|
||||
|
||||
public static int ORIENTATION_PORTRAIT = 0;
|
||||
public static int ORIENTATION_HORIZONTAL = 1;
|
||||
|
||||
private int orientation = ORIENTATION_PORTRAIT;
|
||||
private View contentView;
|
||||
private View centerView;
|
||||
private View leftDownView;
|
||||
private View rightUpView;
|
||||
|
||||
private int contentViewId;
|
||||
private int centerViewId;
|
||||
private int leftDownViewId;
|
||||
private int rightUpViewId;
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
if (this.orientation == orientation) {
|
||||
return;
|
||||
}
|
||||
this.orientation = orientation;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public OCRCameraLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public OCRCameraLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
parseAttrs(attrs);
|
||||
}
|
||||
|
||||
public OCRCameraLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
parseAttrs(attrs);
|
||||
}
|
||||
|
||||
{
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
private void parseAttrs(AttributeSet attrs) {
|
||||
TypedArray a = getContext().getTheme().obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.OCRCameraLayout,
|
||||
0, 0);
|
||||
try {
|
||||
contentViewId = a.getResourceId(R.styleable.OCRCameraLayout_contentView, -1);
|
||||
centerViewId = a.getResourceId(R.styleable.OCRCameraLayout_centerView, -1);
|
||||
leftDownViewId = a.getResourceId(R.styleable.OCRCameraLayout_leftDownView, -1);
|
||||
rightUpViewId = a.getResourceId(R.styleable.OCRCameraLayout_rightUpView, -1);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
contentView = findViewById(contentViewId);
|
||||
if (centerViewId != -1) {
|
||||
centerView = findViewById(centerViewId);
|
||||
}
|
||||
leftDownView = findViewById(leftDownViewId);
|
||||
rightUpView = findViewById(rightUpViewId);
|
||||
}
|
||||
|
||||
private Rect backgroundRect = new Rect();
|
||||
private Paint paint = new Paint();
|
||||
|
||||
{
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(Color.argb(83, 0, 0, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int left;
|
||||
int top;
|
||||
|
||||
ViewGroup.MarginLayoutParams leftDownViewLayoutParams = (MarginLayoutParams) leftDownView.getLayoutParams();
|
||||
ViewGroup.MarginLayoutParams rightUpViewLayoutParams = (MarginLayoutParams) rightUpView.getLayoutParams();
|
||||
if (r < b) {
|
||||
int contentHeight = width * 4 / 3;
|
||||
int heightLeft = height - contentHeight;
|
||||
contentView.layout(l, t, r, contentHeight);
|
||||
|
||||
backgroundRect.left = 0;
|
||||
backgroundRect.top = contentHeight;
|
||||
backgroundRect.right = width;
|
||||
backgroundRect.bottom = height;
|
||||
|
||||
// layout centerView;
|
||||
if (centerView != null) {
|
||||
left = (width - centerView.getMeasuredWidth()) / 2;
|
||||
top = contentHeight + (heightLeft - centerView.getMeasuredHeight()) / 2;
|
||||
centerView
|
||||
.layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight());
|
||||
}
|
||||
// layout leftDownView
|
||||
|
||||
left = leftDownViewLayoutParams.leftMargin;
|
||||
top = contentHeight + (heightLeft - leftDownView.getMeasuredHeight()) / 2;
|
||||
leftDownView
|
||||
.layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight());
|
||||
// layout rightUpView
|
||||
left = width - rightUpView.getMeasuredWidth() - rightUpViewLayoutParams.rightMargin;
|
||||
top = contentHeight + (heightLeft - rightUpView.getMeasuredHeight()) / 2;
|
||||
rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight());
|
||||
} else {
|
||||
int contentWidth = height * 4 / 3;
|
||||
int widthLeft = width - contentWidth;
|
||||
contentView.layout(l, t, contentWidth, height);
|
||||
|
||||
backgroundRect.left = contentWidth;
|
||||
backgroundRect.top = 0;
|
||||
backgroundRect.right = width;
|
||||
backgroundRect.bottom = height;
|
||||
|
||||
// layout centerView
|
||||
if (centerView != null) {
|
||||
left = contentWidth + (widthLeft - centerView.getMeasuredWidth()) / 2;
|
||||
top = (height - centerView.getMeasuredHeight()) / 2;
|
||||
centerView
|
||||
.layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight());
|
||||
}
|
||||
// layout leftDownView
|
||||
left = contentWidth + (widthLeft - leftDownView.getMeasuredWidth()) / 2;
|
||||
top = height - leftDownView.getMeasuredHeight() - leftDownViewLayoutParams.bottomMargin;
|
||||
leftDownView
|
||||
.layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight());
|
||||
// layout rightUpView
|
||||
left = contentWidth + (widthLeft - rightUpView.getMeasuredWidth()) / 2;
|
||||
|
||||
top = rightUpViewLayoutParams.topMargin;
|
||||
rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawRect(backgroundRect, paint);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class OCRFrameLayout extends ViewGroup {
|
||||
|
||||
public OCRFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public OCRFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
parseAttrs(attrs);
|
||||
}
|
||||
|
||||
public OCRFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
parseAttrs(attrs);
|
||||
}
|
||||
|
||||
private void parseAttrs(AttributeSet attrs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View view = getChildAt(i);
|
||||
view.layout(l, t, r, b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int width;
|
||||
int height;
|
||||
int childCount = getChildCount();
|
||||
|
||||
width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View view = getChildAt(i);
|
||||
measureChild(view, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec
|
||||
(height, MeasureSpec.EXACTLY));
|
||||
}
|
||||
setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.camera;
|
||||
|
||||
public interface PermissionCallback {
|
||||
boolean onRequestPermission();
|
||||
}
|
325
ocr_ui/src/main/java/com/baidu/ocr/ui/crop/CropView.java
Normal file
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.crop;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.baidu.ocr.ui.util.ImageUtil;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.media.ExifInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class CropView extends View {
|
||||
|
||||
public CropView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CropView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CropView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setFilePath(String path) {
|
||||
|
||||
if (this.bitmap != null && !this.bitmap.isRecycled()) {
|
||||
this.bitmap.recycle();
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
Bitmap original = BitmapFactory.decodeFile(path, options);
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(path);
|
||||
int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
Matrix matrix = new Matrix();
|
||||
int rotationInDegrees = ImageUtil.exifToDegrees(rotation);
|
||||
if (rotation != 0f) {
|
||||
matrix.preRotate(rotationInDegrees);
|
||||
}
|
||||
|
||||
// 图片太大会导致内存泄露,所以在显示前对图片进行裁剪。
|
||||
int maxPreviewImageSize = 2560;
|
||||
|
||||
int min = Math.min(options.outWidth, options.outHeight);
|
||||
min = Math.min(min, maxPreviewImageSize);
|
||||
|
||||
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
Point screenSize = new Point();
|
||||
windowManager.getDefaultDisplay().getSize(screenSize);
|
||||
min = Math.min(min, screenSize.x * 2 / 3);
|
||||
|
||||
options.inSampleSize = ImageUtil.calculateInSampleSize(options, min, min);
|
||||
options.inScaled = true;
|
||||
options.inDensity = options.outWidth;
|
||||
options.inTargetDensity = min * options.inSampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
|
||||
options.inJustDecodeBounds = false;
|
||||
this.bitmap = BitmapFactory.decodeFile(path, options);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.bitmap = original;
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
setBitmap(this.bitmap);
|
||||
}
|
||||
|
||||
private void setBitmap(Bitmap bitmap) {
|
||||
this.bitmap = bitmap;
|
||||
matrix.reset();
|
||||
centerImage(getWidth(), getHeight());
|
||||
rotation = 0;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
centerImage(w, h);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public Bitmap crop(Rect frame) {
|
||||
float scale = getScale();
|
||||
|
||||
float[] src = new float[] {frame.left, frame.top};
|
||||
float[] desc = new float[] {0, 0};
|
||||
|
||||
Matrix invertedMatrix = new Matrix();
|
||||
this.matrix.invert(invertedMatrix);
|
||||
invertedMatrix.mapPoints(desc, src);
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
int width = (int) (frame.width() / scale);
|
||||
int height = (int) (frame.height() / scale);
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
Bitmap originalBitmap = this.bitmap;
|
||||
matrix.postTranslate(-desc[0], -desc[1]);
|
||||
canvas.drawBitmap(originalBitmap, matrix, null);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public void setMinimumScale(float setMinimumScale) {
|
||||
this.setMinimumScale = setMinimumScale;
|
||||
}
|
||||
|
||||
public void setMaximumScale(float maximumScale) {
|
||||
this.maximumScale = maximumScale;
|
||||
}
|
||||
|
||||
private float setMinimumScale = 0.2f;
|
||||
private float maximumScale = 4.0f;
|
||||
|
||||
private float[] matrixArray = new float[9];
|
||||
private Matrix matrix = new Matrix();
|
||||
private Bitmap bitmap;
|
||||
|
||||
private GestureDetector gestureDetector;
|
||||
|
||||
private ScaleGestureDetector scaleGestureDetector;
|
||||
private ScaleGestureDetector.OnScaleGestureListener onScaleGestureListener =
|
||||
new ScaleGestureDetector.OnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
scale(detector);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
float scale = detector.getScaleFactor();
|
||||
matrix.postScale(scale, scale);
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
private void init() {
|
||||
scaleGestureDetector = new ScaleGestureDetector(getContext(), onScaleGestureListener);
|
||||
gestureDetector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
translate(distanceX, distanceY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int rotation = 0;
|
||||
|
||||
public void rotate(int degrees) {
|
||||
if (this.bitmap == null) {
|
||||
return;
|
||||
}
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
int dx = this.bitmap.getWidth() / 2;
|
||||
int dy = this.bitmap.getHeight() / 2;
|
||||
|
||||
matrix.postTranslate(-dx, -dy);
|
||||
matrix.postRotate(degrees);
|
||||
matrix.postTranslate(dy, dx);
|
||||
Bitmap scaledBitmap = this.bitmap;
|
||||
Bitmap rotatedBitmap = Bitmap.createBitmap(scaledBitmap.getHeight(), scaledBitmap.getWidth(),
|
||||
Bitmap.Config.RGB_565);
|
||||
Canvas canvas = new Canvas(rotatedBitmap);
|
||||
canvas.drawBitmap(this.bitmap, matrix, null);
|
||||
this.bitmap.recycle();
|
||||
this.bitmap = rotatedBitmap;
|
||||
centerImage(getWidth(), getHeight());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void translate(float distanceX, float distanceY) {
|
||||
matrix.getValues(matrixArray);
|
||||
float left = matrixArray[Matrix.MTRANS_X];
|
||||
float top = matrixArray[Matrix.MTRANS_Y];
|
||||
|
||||
Rect bound = getRestrictedBound();
|
||||
if (bound != null) {
|
||||
float scale = getScale();
|
||||
float right = left + (int) (bitmap.getWidth() / scale);
|
||||
float bottom = top + (int) (bitmap.getHeight() / scale);
|
||||
|
||||
if (left - distanceX > bound.left) {
|
||||
distanceX = left - bound.left;
|
||||
}
|
||||
if (top - distanceY > bound.top) {
|
||||
distanceY = top - bound.top;
|
||||
}
|
||||
|
||||
if (distanceX > 0) {
|
||||
if (right - distanceX < bound.right) {
|
||||
distanceX = right - bound.right;
|
||||
}
|
||||
}
|
||||
if (distanceY > 0) {
|
||||
if (bottom - distanceY < bound.bottom) {
|
||||
distanceY = bottom - bound.bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
matrix.postTranslate(-distanceX, -distanceY);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void scale(ScaleGestureDetector detector) {
|
||||
float scale = detector.getScaleFactor();
|
||||
float currentScale = getScale();
|
||||
if (currentScale * scale < setMinimumScale) {
|
||||
scale = setMinimumScale / currentScale;
|
||||
}
|
||||
if (currentScale * scale > maximumScale) {
|
||||
scale = maximumScale / currentScale;
|
||||
}
|
||||
matrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void centerImage(int width, int height) {
|
||||
if (width <= 0 || height <= 0 || bitmap == null) {
|
||||
return;
|
||||
}
|
||||
float widthRatio = 1.0f * height / this.bitmap.getHeight();
|
||||
float heightRatio = 1.0f * width / this.bitmap.getWidth();
|
||||
|
||||
float ratio = Math.min(widthRatio, heightRatio);
|
||||
|
||||
float dx = (width - this.bitmap.getWidth()) / 2;
|
||||
float dy = (height - this.bitmap.getHeight()) / 2;
|
||||
matrix.setTranslate(0, 0);
|
||||
matrix.setScale(ratio, ratio, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
|
||||
matrix.postTranslate(dx, dy);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private float getScale() {
|
||||
matrix.getValues(matrixArray);
|
||||
float scale = matrixArray[Matrix.MSCALE_X];
|
||||
if (Math.abs(scale) <= 0.1) {
|
||||
scale = matrixArray[Matrix.MSKEW_X];
|
||||
}
|
||||
return Math.abs(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (bitmap != null) {
|
||||
canvas.drawBitmap(bitmap, matrix, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean result = scaleGestureDetector.onTouchEvent(event);
|
||||
result = gestureDetector.onTouchEvent(event) || result;
|
||||
return result || super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private Rect restrictBound;
|
||||
|
||||
private Rect getRestrictedBound() {
|
||||
return restrictBound;
|
||||
}
|
||||
|
||||
public void setRestrictBound(Rect rect) {
|
||||
this.restrictBound = rect;
|
||||
}
|
||||
}
|
290
ocr_ui/src/main/java/com/baidu/ocr/ui/crop/FrameOverlayView.java
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.crop;
|
||||
|
||||
|
||||
import com.baidu.ocr.ui.util.DimensionUtil;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
public class FrameOverlayView extends View {
|
||||
|
||||
interface OnFrameChangeListener {
|
||||
void onFrameChange(RectF newFrame);
|
||||
}
|
||||
|
||||
public Rect getFrameRect() {
|
||||
Rect rect = new Rect();
|
||||
rect.left = (int) frameRect.left;
|
||||
rect.top = (int) frameRect.top;
|
||||
rect.right = (int) frameRect.right;
|
||||
rect.bottom = (int) frameRect.bottom;
|
||||
return rect;
|
||||
}
|
||||
|
||||
public FrameOverlayView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public FrameOverlayView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public FrameOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
translate(distanceX, distanceY);
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static final int CORNER_LEFT_TOP = 1;
|
||||
private static final int CORNER_RIGHT_TOP = 2;
|
||||
private static final int CORNER_RIGHT_BOTTOM = 3;
|
||||
private static final int CORNER_LEFT_BOTTOM = 4;
|
||||
|
||||
private int currentCorner = -1;
|
||||
int margin = 20;
|
||||
int cornerLength = 100;
|
||||
int cornerLineWidth = 6;
|
||||
|
||||
private int maskColor = Color.argb(180, 0, 0, 0);
|
||||
|
||||
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private GestureDetector gestureDetector;
|
||||
private RectF touchRect = new RectF();
|
||||
private RectF frameRect = new RectF();
|
||||
|
||||
private OnFrameChangeListener onFrameChangeListener;
|
||||
|
||||
{
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(6);
|
||||
|
||||
eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
}
|
||||
|
||||
public void setOnFrameChangeListener(OnFrameChangeListener onFrameChangeListener) {
|
||||
this.onFrameChangeListener = onFrameChangeListener;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
gestureDetector = new GestureDetector(getContext(), onGestureListener);
|
||||
cornerLength = DimensionUtil.dpToPx(18);
|
||||
cornerLineWidth = DimensionUtil.dpToPx(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
resetFrameRect(w, h);
|
||||
}
|
||||
|
||||
private void resetFrameRect(int w, int h) {
|
||||
if (shapeType == 1) {
|
||||
frameRect.left = (int) (w * 0.05);
|
||||
frameRect.top = (int) (h * 0.25);
|
||||
} else {
|
||||
frameRect.left = (int) (w * 0.2);
|
||||
frameRect.top = (int) (h * 0.2);
|
||||
}
|
||||
frameRect.right = w - frameRect.left;
|
||||
frameRect.bottom = h - frameRect.top;
|
||||
}
|
||||
|
||||
private int shapeType = 0;
|
||||
|
||||
public void setTypeWide() {
|
||||
shapeType = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void translate(float x, float y) {
|
||||
if (x > 0) {
|
||||
// moving left;
|
||||
if (frameRect.left - x < margin) {
|
||||
x = frameRect.left - margin;
|
||||
}
|
||||
} else {
|
||||
if (frameRect.right - x > getWidth() - margin) {
|
||||
x = frameRect.right - getWidth() + margin;
|
||||
}
|
||||
}
|
||||
|
||||
if (y > 0) {
|
||||
if (frameRect.top - y < margin) {
|
||||
y = frameRect.top - margin;
|
||||
}
|
||||
} else {
|
||||
if (frameRect.bottom - y > getHeight() - margin) {
|
||||
y = frameRect.bottom - getHeight() + margin;
|
||||
}
|
||||
}
|
||||
frameRect.offset(-x, -y);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
canvas.drawColor(maskColor);
|
||||
|
||||
paint.setStrokeWidth(DimensionUtil.dpToPx(1));
|
||||
canvas.drawRect(frameRect, paint);
|
||||
canvas.drawRect(frameRect, eraser);
|
||||
drawCorners(canvas);
|
||||
}
|
||||
|
||||
private void drawCorners(Canvas canvas) {
|
||||
paint.setStrokeWidth(cornerLineWidth);
|
||||
// left top
|
||||
drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.top, cornerLength, 0);
|
||||
drawLine(canvas, frameRect.left, frameRect.top, 0, cornerLength);
|
||||
|
||||
// right top
|
||||
drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.top, -cornerLength, 0);
|
||||
drawLine(canvas, frameRect.right, frameRect.top, 0, cornerLength);
|
||||
|
||||
// right bottom
|
||||
drawLine(canvas, frameRect.right, frameRect.bottom, 0, -cornerLength);
|
||||
drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.bottom, -cornerLength, 0);
|
||||
|
||||
// left bottom
|
||||
drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.bottom, cornerLength, 0);
|
||||
drawLine(canvas, frameRect.left, frameRect.bottom, 0, -cornerLength);
|
||||
}
|
||||
|
||||
private void drawLine(Canvas canvas, float x, float y, int dx, int dy) {
|
||||
canvas.drawLine(x, y, x + dx, y + dy, paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean result = handleDown(event);
|
||||
float ex = 60;
|
||||
RectF rectExtend = new RectF(frameRect.left - ex, frameRect.top - ex,
|
||||
frameRect.right + ex, frameRect.bottom + ex);
|
||||
if (!result) {
|
||||
if (rectExtend.contains(event.getX(), event.getY())) {
|
||||
gestureDetector.onTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean handleDown(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
currentCorner = -1;
|
||||
break;
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
float radius = cornerLength;
|
||||
touchRect.set(event.getX() - radius, event.getY() - radius, event.getX() + radius,
|
||||
event.getY() + radius);
|
||||
if (touchRect.contains(frameRect.left, frameRect.top)) {
|
||||
currentCorner = CORNER_LEFT_TOP;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (touchRect.contains(frameRect.right, frameRect.top)) {
|
||||
currentCorner = CORNER_RIGHT_TOP;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (touchRect.contains(frameRect.right, frameRect.bottom)) {
|
||||
currentCorner = CORNER_RIGHT_BOTTOM;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (touchRect.contains(frameRect.left, frameRect.bottom)) {
|
||||
currentCorner = CORNER_LEFT_BOTTOM;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
return handleScale(event);
|
||||
default:
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleScale(MotionEvent event) {
|
||||
switch (currentCorner) {
|
||||
case CORNER_LEFT_TOP:
|
||||
scaleTo(event.getX(), event.getY(), frameRect.right, frameRect.bottom);
|
||||
return true;
|
||||
case CORNER_RIGHT_TOP:
|
||||
scaleTo(frameRect.left, event.getY(), event.getX(), frameRect.bottom);
|
||||
return true;
|
||||
case CORNER_RIGHT_BOTTOM:
|
||||
scaleTo(frameRect.left, frameRect.top, event.getX(), event.getY());
|
||||
return true;
|
||||
case CORNER_LEFT_BOTTOM:
|
||||
scaleTo(event.getX(), frameRect.top, frameRect.right, event.getY());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void scaleTo(float left, float top, float right, float bottom) {
|
||||
if (bottom - top < getMinimumFrameHeight()) {
|
||||
top = frameRect.top;
|
||||
bottom = frameRect.bottom;
|
||||
}
|
||||
if (right - left < getMinimumFrameWidth()) {
|
||||
left = frameRect.left;
|
||||
right = frameRect.right;
|
||||
}
|
||||
left = Math.max(margin, left);
|
||||
top = Math.max(margin, top);
|
||||
right = Math.min(getWidth() - margin, right);
|
||||
bottom = Math.min(getHeight() - margin, bottom);
|
||||
|
||||
frameRect.set(left, top, right, bottom);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void notifyFrameChange() {
|
||||
if (onFrameChangeListener != null) {
|
||||
onFrameChangeListener.onFrameChange(frameRect);
|
||||
}
|
||||
}
|
||||
|
||||
private float getMinimumFrameWidth() {
|
||||
return 2.4f * cornerLength;
|
||||
}
|
||||
|
||||
private float getMinimumFrameHeight() {
|
||||
return 2.4f * cornerLength;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.util;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
public class DimensionUtil {
|
||||
|
||||
public static int dpToPx(int dp) {
|
||||
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
}
|
159
ocr_ui/src/main/java/com/baidu/ocr/ui/util/ImageUtil.java
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
*/
|
||||
package com.baidu.ocr.ui.util;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.ExifInterface;
|
||||
import android.util.Log;
|
||||
|
||||
public class ImageUtil {
|
||||
private static final String TAG = "CameraExif";
|
||||
|
||||
public static int exifToDegrees(int exifOrientation) {
|
||||
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
|
||||
return 90;
|
||||
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
|
||||
return 180;
|
||||
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
return 270;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
|
||||
public static int getOrientation(byte[] jpeg) {
|
||||
if (jpeg == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
int length = 0;
|
||||
|
||||
// ISO/IEC 10918-1:1993(E)
|
||||
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
|
||||
int marker = jpeg[offset] & 0xFF;
|
||||
|
||||
// Check if the marker is a padding.
|
||||
if (marker == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
offset++;
|
||||
|
||||
// Check if the marker is SOI or TEM.
|
||||
if (marker == 0xD8 || marker == 0x01) {
|
||||
continue;
|
||||
}
|
||||
// Check if the marker is EOI or SOS.
|
||||
if (marker == 0xD9 || marker == 0xDA) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the length and check if it is reasonable.
|
||||
length = pack(jpeg, offset, 2, false);
|
||||
if (length < 2 || offset + length > jpeg.length) {
|
||||
Log.e(TAG, "Invalid length");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Break if the marker is EXIF in APP1.
|
||||
if (marker == 0xE1 && length >= 8
|
||||
&& pack(jpeg, offset + 2, 4, false) == 0x45786966
|
||||
&& pack(jpeg, offset + 6, 2, false) == 0) {
|
||||
offset += 8;
|
||||
length -= 8;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip other markers.
|
||||
offset += length;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
// JEITA CP-3451 Exif Version 2.2
|
||||
if (length > 8) {
|
||||
// Identify the byte order.
|
||||
int tag = pack(jpeg, offset, 4, false);
|
||||
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
|
||||
Log.e(TAG, "Invalid byte order");
|
||||
return 0;
|
||||
}
|
||||
boolean littleEndian = (tag == 0x49492A00);
|
||||
|
||||
// Get the offset and check if it is reasonable.
|
||||
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
|
||||
if (count < 10 || count > length) {
|
||||
Log.e(TAG, "Invalid offset");
|
||||
return 0;
|
||||
}
|
||||
offset += count;
|
||||
length -= count;
|
||||
|
||||
// Get the count and go through all the elements.
|
||||
count = pack(jpeg, offset - 2, 2, littleEndian);
|
||||
while (count-- > 0 && length >= 12) {
|
||||
// Get the tag and check if it is orientation.
|
||||
tag = pack(jpeg, offset, 2, littleEndian);
|
||||
if (tag == 0x0112) {
|
||||
// We do not really care about type and count, do we?
|
||||
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
|
||||
switch (orientation) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 3:
|
||||
return 180;
|
||||
case 6:
|
||||
return 90;
|
||||
case 8:
|
||||
return 270;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
offset += 12;
|
||||
length -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Orientation not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int pack(byte[] bytes, int offset, int length,
|
||||
boolean littleEndian) {
|
||||
int step = 1;
|
||||
if (littleEndian) {
|
||||
offset += length - 1;
|
||||
step = -1;
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
while (length-- > 0) {
|
||||
value = (value << 8) | (bytes[offset] & 0xFF);
|
||||
offset += step;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int calculateInSampleSize(
|
||||
BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||
// Raw height and width of image
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while ((halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
}
|
BIN
ocr_ui/src/main/jniLibs/arm64-v8a/libidcard_quality.1.1.1.so
Normal file
BIN
ocr_ui/src/main/jniLibs/arm64-v8a/libidl_license.so
Normal file
BIN
ocr_ui/src/main/jniLibs/armeabi-v7a/libidcard_quality.1.1.1.so
Normal file
BIN
ocr_ui/src/main/jniLibs/armeabi-v7a/libidl_license.so
Normal file
BIN
ocr_ui/src/main/jniLibs/armeabi/libidcard_quality.1.1.1.so
Normal file
BIN
ocr_ui/src/main/jniLibs/armeabi/libidl_license.so
Normal file
BIN
ocr_ui/src/main/jniLibs/x86/libidcard_quality.1.1.1.so
Normal file
BIN
ocr_ui/src/main/jniLibs/x86/libidl_license.so
Normal file
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_cancel.png
Normal file
After Width: | Height: | Size: 519 B |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_close.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_confirm.png
Normal file
After Width: | Height: | Size: 911 B |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_gallery.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_hint_align_id_card.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_light_off.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_light_on.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_passport_locator.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_reset.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_rotate.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#000000" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
After Width: | Height: | Size: 9.9 KiB |
BIN
ocr_ui/src/main/res/drawable-xhdpi/bd_ocr_take_photo_normal.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 按压时 -->
|
||||
<item android:drawable="@drawable/bd_ocr_take_photo_highlight" android:state_pressed="true" />
|
||||
<!-- 被选中时 -->
|
||||
<item android:drawable="@drawable/bd_ocr_take_photo_normal" android:state_selected="true" />
|
||||
<!-- 被激活时 -->
|
||||
<item android:drawable="@drawable/bd_ocr_take_photo_normal" android:state_activated="true" />
|
||||
<!-- 默认时 -->
|
||||
<item android:drawable="@drawable/bd_ocr_take_photo_normal" />
|
||||
</selector>
|
30
ocr_ui/src/main/res/layout/bd_ocr_activity_camera.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/take_picture_container"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
layout="@layout/bd_ocr_take_picture"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/crop_container"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"
|
||||
layout="@layout/bd_ocr_crop"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/confirm_result_container"
|
||||
layout="@layout/bd_ocr_confirm_result"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</RelativeLayout>
|
43
ocr_ui/src/main/res/layout/bd_ocr_confirm_result.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<com.baidu.ocr.ui.camera.OCRCameraLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:contentView="@+id/display_image_view"
|
||||
app:leftDownView="@+id/cancel_button"
|
||||
app:rightUpView="@+id/confirm_button">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/display_image_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:srcCompat="@drawable/bd_ocr_id_card_locator_front"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/bd_ocr_cancel"
|
||||
android:layout_marginLeft="@dimen/camera_left_margin"
|
||||
android:layout_marginStart="@dimen/camera_left_margin"
|
||||
android:layout_marginBottom="@dimen/camera_bottom_margin"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/confirm_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/bd_ocr_confirm"
|
||||
android:layout_marginRight="@dimen/camera_right_margin"
|
||||
android:layout_marginEnd="@dimen/camera_right_margin"
|
||||
android:layout_marginTop="@dimen/camera_top_margin"
|
||||
/>
|
||||
|
||||
</com.baidu.ocr.ui.camera.OCRCameraLayout>
|
64
ocr_ui/src/main/res/layout/bd_ocr_crop.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<com.baidu.ocr.ui.camera.OCRCameraLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:contentView="@+id/crop_view_container"
|
||||
app:leftDownView="@+id/cancel_button"
|
||||
app:centerView="@+id/rotate_button"
|
||||
app:rightUpView="@+id/confirm_button">
|
||||
|
||||
<com.baidu.ocr.ui.camera.OCRFrameLayout
|
||||
android:id="@+id/crop_view_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.baidu.ocr.ui.crop.CropView
|
||||
android:id="@+id/crop_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<com.baidu.ocr.ui.crop.FrameOverlayView
|
||||
android:id="@+id/overlay_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<com.baidu.ocr.ui.camera.MaskView
|
||||
android:id="@+id/crop_mask_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</com.baidu.ocr.ui.camera.OCRFrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/rotate_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="58dp"
|
||||
android:padding="18dp"
|
||||
android:src="@drawable/bd_ocr_rotate"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/confirm_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/bd_ocr_confirm"
|
||||
android:layout_marginRight="@dimen/camera_right_margin"
|
||||
android:layout_marginEnd="@dimen/camera_right_margin"
|
||||
android:layout_marginTop="@dimen/camera_top_margin"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/bd_ocr_cancel"
|
||||
android:layout_marginLeft="@dimen/camera_left_margin"
|
||||
android:layout_marginStart="@dimen/camera_left_margin"
|
||||
android:layout_marginBottom="@dimen/camera_bottom_margin"/>
|
||||
|
||||
</com.baidu.ocr.ui.camera.OCRCameraLayout>
|
51
ocr_ui/src/main/res/layout/bd_ocr_take_picture.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<com.baidu.ocr.ui.camera.OCRCameraLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:centerView="@+id/take_photo_button"
|
||||
app:contentView="@+id/camera_view"
|
||||
app:leftDownView="@+id/album_button"
|
||||
app:rightUpView="@+id/light_button">
|
||||
|
||||
<com.baidu.ocr.ui.camera.CameraView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"></com.baidu.ocr.ui.camera.CameraView>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/album_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="@dimen/camera_left_margin"
|
||||
android:layout_marginLeft="@dimen/camera_left_margin"
|
||||
android:layout_marginBottom="@dimen/camera_bottom_margin"
|
||||
android:padding="18dp"
|
||||
android:src="@drawable/bd_ocr_gallery"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/take_photo_button"
|
||||
android:layout_width="58dp"
|
||||
android:layout_height="58dp"
|
||||
android:background="@drawable/bd_ocr_take_photo_selector"
|
||||
android:clickable="true" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/light_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="@dimen/camera_top_margin"
|
||||
android:layout_marginEnd="@dimen/camera_right_margin"
|
||||
android:layout_marginRight="@dimen/camera_right_margin"
|
||||
android:clickable="true"
|
||||
android:padding="15dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:src="@drawable/bd_ocr_light_off" />
|
||||
|
||||
</com.baidu.ocr.ui.camera.OCRCameraLayout>
|
BIN
ocr_ui/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
ocr_ui/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
ocr_ui/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
ocr_ui/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
ocr_ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
10
ocr_ui/src/main/res/values/bd_ocr_dimensions.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<resources>
|
||||
<dimen name="camera_left_margin">18dp</dimen>
|
||||
<dimen name="camera_right_margin">18dp</dimen>
|
||||
<dimen name="camera_bottom_margin">16dp</dimen>
|
||||
<dimen name="camera_top_margin">16dp</dimen>
|
||||
</resources>
|
12
ocr_ui/src/main/res/values/bd_ocr_widgets.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<resources>
|
||||
<declare-styleable name="OCRCameraLayout">
|
||||
<attr name="contentView" format="reference"/>
|
||||
<attr name="centerView" format="reference"/>
|
||||
<attr name="leftDownView" format="reference"/>
|
||||
<attr name="rightUpView" format="reference"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
9
ocr_ui/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
7
ocr_ui/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">OCR_UI</string>
|
||||
<string name="camera_permission_required">本功能需要相机权限!</string>
|
||||
</resources>
|
14
ocr_ui/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<!--
|
||||
~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|