android:replace scan qrcode
This commit is contained in:
parent
3d1b336dac
commit
af8f37de3e
@ -36,7 +36,8 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
implementation 'cn.yipianfengye.android:zxing-library:2.2'
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
implementation 'com.google.zxing:android-core:3.3.0'
|
||||
|
||||
}
|
||||
|
||||
|
@ -7,5 +7,6 @@
|
||||
|
||||
<application>
|
||||
<activity android:name=".ui.DoricDevActivity" />
|
||||
<activity android:name=".qrcode.activity.CaptureActivity"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
@ -0,0 +1,43 @@
|
||||
package pub.doric.devkit.qrcode;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
|
||||
public class DisplayUtil
|
||||
{
|
||||
|
||||
public static int screenWidthPx; //屏幕宽 px
|
||||
public static int screenHeightPx; //屏幕高 px
|
||||
public static float density;//屏幕密度
|
||||
public static int densityDPI;//屏幕密度
|
||||
public static float screenWidthDip;// dp单位
|
||||
public static float screenHeightDip;// dp单位
|
||||
|
||||
public static void initDisplayOpinion(Context context) {
|
||||
DisplayMetrics dm =context.getResources().getDisplayMetrics();
|
||||
DisplayUtil.density = dm.density;
|
||||
DisplayUtil.densityDPI = dm.densityDpi;
|
||||
DisplayUtil.screenWidthPx = dm.widthPixels;
|
||||
DisplayUtil.screenHeightPx = dm.heightPixels;
|
||||
DisplayUtil.screenWidthDip = DisplayUtil.px2dip(context, dm.widthPixels);
|
||||
DisplayUtil.screenHeightDip = DisplayUtil.px2dip(context, dm.heightPixels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
|
||||
*/
|
||||
public static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
|
||||
*/
|
||||
public static int px2dip(Context context, float pxValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (pxValue / scale + 0.5f);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package pub.doric.devkit.qrcode.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import pub.doric.devkit.R;
|
||||
|
||||
|
||||
/**
|
||||
* Initial the camera
|
||||
* <p>
|
||||
* 默认的二维码扫描Activity
|
||||
*/
|
||||
public class CaptureActivity extends AppCompatActivity {
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.camera);
|
||||
CaptureFragment captureFragment = new CaptureFragment();
|
||||
captureFragment.setAnalyzeCallback(analyzeCallback);
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.fl_zxing_container, captureFragment).commit();
|
||||
captureFragment.setCameraInitCallBack(new CaptureFragment.CameraInitCallBack() {
|
||||
@Override
|
||||
public void callBack(Exception e) {
|
||||
if (e == null) {
|
||||
|
||||
} else {
|
||||
Log.e("TAG", "callBack: ", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码解析回调函数
|
||||
*/
|
||||
CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() {
|
||||
@Override
|
||||
public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
|
||||
Intent resultIntent = new Intent();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_SUCCESS);
|
||||
bundle.putString(CodeUtils.RESULT_STRING, result);
|
||||
resultIntent.putExtras(bundle);
|
||||
CaptureActivity.this.setResult(RESULT_OK, resultIntent);
|
||||
CaptureActivity.this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnalyzeFailed() {
|
||||
Intent resultIntent = new Intent();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_FAILED);
|
||||
bundle.putString(CodeUtils.RESULT_STRING, "");
|
||||
resultIntent.putExtras(bundle);
|
||||
CaptureActivity.this.setResult(RESULT_OK, resultIntent);
|
||||
CaptureActivity.this.finish();
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package pub.doric.devkit.qrcode.activity;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import pub.doric.devkit.R;
|
||||
import pub.doric.devkit.qrcode.camera.CameraManager;
|
||||
import pub.doric.devkit.qrcode.decoding.CaptureActivityHandler;
|
||||
import pub.doric.devkit.qrcode.decoding.InactivityTimer;
|
||||
import pub.doric.devkit.qrcode.view.ViewfinderView;
|
||||
|
||||
/**
|
||||
* 自定义实现的扫描Fragment
|
||||
*/
|
||||
public class CaptureFragment extends Fragment implements SurfaceHolder.Callback {
|
||||
|
||||
private CaptureActivityHandler handler;
|
||||
private ViewfinderView viewfinderView;
|
||||
private boolean hasSurface;
|
||||
private Vector<BarcodeFormat> decodeFormats;
|
||||
private String characterSet;
|
||||
private InactivityTimer inactivityTimer;
|
||||
private static final float BEEP_VOLUME = 0.10f;
|
||||
private boolean vibrate=false;
|
||||
private SurfaceView surfaceView;
|
||||
private SurfaceHolder surfaceHolder;
|
||||
private CodeUtils.AnalyzeCallback analyzeCallback;
|
||||
private Camera camera;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
CameraManager.init(getActivity().getApplication());
|
||||
|
||||
hasSurface = false;
|
||||
inactivityTimer = new InactivityTimer(this.getActivity());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
Bundle bundle = getArguments();
|
||||
View view = null;
|
||||
if (bundle != null) {
|
||||
int layoutId = bundle.getInt(CodeUtils.LAYOUT_ID);
|
||||
if (layoutId != -1) {
|
||||
view = inflater.inflate(layoutId, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (view == null) {
|
||||
view = inflater.inflate(R.layout.fragment_capture, null);
|
||||
}
|
||||
|
||||
viewfinderView = (ViewfinderView) view.findViewById(R.id.viewfinder_view);
|
||||
surfaceView = (SurfaceView) view.findViewById(R.id.preview_view);
|
||||
surfaceHolder = surfaceView.getHolder();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (hasSurface) {
|
||||
initCamera(surfaceHolder);
|
||||
} else {
|
||||
surfaceHolder.addCallback(this);
|
||||
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||
}
|
||||
decodeFormats = null;
|
||||
characterSet = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (handler != null) {
|
||||
handler.quitSynchronously();
|
||||
handler = null;
|
||||
}
|
||||
CameraManager.get().closeDriver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
inactivityTimer.shutdown();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler scan result
|
||||
*
|
||||
* @param result
|
||||
* @param barcode
|
||||
*/
|
||||
public void handleDecode(Result result, Bitmap barcode) {
|
||||
inactivityTimer.onActivity();
|
||||
playVibrate();
|
||||
|
||||
if (result == null || TextUtils.isEmpty(result.getText())) {
|
||||
if (analyzeCallback != null) {
|
||||
analyzeCallback.onAnalyzeFailed();
|
||||
}
|
||||
} else {
|
||||
if (analyzeCallback != null) {
|
||||
analyzeCallback.onAnalyzeSuccess(barcode, result.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initCamera(SurfaceHolder surfaceHolder) {
|
||||
try {
|
||||
CameraManager.get().openDriver(surfaceHolder);
|
||||
camera = CameraManager.get().getCamera();
|
||||
} catch (Exception e) {
|
||||
if (callBack != null) {
|
||||
callBack.callBack(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (callBack != null) {
|
||||
callBack.callBack(null);
|
||||
}
|
||||
if (handler == null) {
|
||||
handler = new CaptureActivityHandler(this, decodeFormats, characterSet, viewfinderView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
||||
int height) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (!hasSurface) {
|
||||
hasSurface = true;
|
||||
initCamera(holder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
hasSurface = false;
|
||||
if (camera != null) {
|
||||
if (camera != null && CameraManager.get().isPreviewing()) {
|
||||
if (!CameraManager.get().isUseOneShotPreviewCallback()) {
|
||||
camera.setPreviewCallback(null);
|
||||
}
|
||||
camera.stopPreview();
|
||||
CameraManager.get().getPreviewCallback().setHandler(null, 0);
|
||||
CameraManager.get().getAutoFocusCallback().setHandler(null, 0);
|
||||
CameraManager.get().setPreviewing(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public void drawViewfinder() {
|
||||
viewfinderView.drawViewfinder();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static final long VIBRATE_DURATION = 200L;
|
||||
|
||||
private void playVibrate() {
|
||||
if (vibrate) {
|
||||
Vibrator vibrator = (Vibrator) getActivity().getSystemService(getActivity().VIBRATOR_SERVICE);
|
||||
vibrator.vibrate(VIBRATE_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CodeUtils.AnalyzeCallback getAnalyzeCallback() {
|
||||
return analyzeCallback;
|
||||
}
|
||||
|
||||
public void setAnalyzeCallback(CodeUtils.AnalyzeCallback analyzeCallback) {
|
||||
this.analyzeCallback = analyzeCallback;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
CameraInitCallBack callBack;
|
||||
|
||||
/**
|
||||
* Set callback for Camera check whether Camera init success or not.
|
||||
*/
|
||||
public void setCameraInitCallBack(CameraInitCallBack callBack) {
|
||||
this.callBack = callBack;
|
||||
}
|
||||
|
||||
interface CameraInitCallBack {
|
||||
/**
|
||||
* Callback for Camera init result.
|
||||
* @param e If is's null,means success.otherwise Camera init failed with the Exception.
|
||||
*/
|
||||
void callBack(Exception e);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package pub.doric.devkit.qrcode.activity;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
import pub.doric.devkit.qrcode.camera.BitmapLuminanceSource;
|
||||
import pub.doric.devkit.qrcode.camera.CameraManager;
|
||||
import pub.doric.devkit.qrcode.decoding.DecodeFormatManager;
|
||||
|
||||
/**
|
||||
* 二维码扫描工具类
|
||||
*/
|
||||
public class CodeUtils {
|
||||
|
||||
public static final String RESULT_TYPE = "result_type";
|
||||
public static final String RESULT_STRING = "result_string";
|
||||
public static final int RESULT_SUCCESS = 1;
|
||||
public static final int RESULT_FAILED = 2;
|
||||
|
||||
public static final String LAYOUT_ID = "layout_id";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 解析二维码图片工具类
|
||||
* @param analyzeCallback
|
||||
*/
|
||||
public static void analyzeBitmap(String path, AnalyzeCallback analyzeCallback) {
|
||||
|
||||
/**
|
||||
* 首先判断图片的大小,若图片过大,则执行图片的裁剪操作,防止OOM
|
||||
*/
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true; // 先获取原大小
|
||||
Bitmap mBitmap = BitmapFactory.decodeFile(path, options);
|
||||
options.inJustDecodeBounds = false; // 获取新的大小
|
||||
|
||||
int sampleSize = (int) (options.outHeight / (float) 400);
|
||||
|
||||
if (sampleSize <= 0)
|
||||
sampleSize = 1;
|
||||
options.inSampleSize = sampleSize;
|
||||
mBitmap = BitmapFactory.decodeFile(path, options);
|
||||
|
||||
MultiFormatReader multiFormatReader = new MultiFormatReader();
|
||||
|
||||
// 解码的参数
|
||||
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(2);
|
||||
// 可以解析的编码类型
|
||||
Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>();
|
||||
if (decodeFormats == null || decodeFormats.isEmpty()) {
|
||||
decodeFormats = new Vector<BarcodeFormat>();
|
||||
|
||||
// 这里设置可扫描的类型,我这里选择了都支持
|
||||
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
|
||||
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
|
||||
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
|
||||
}
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||
// 设置继续的字符编码格式为UTF8
|
||||
// hints.put(DecodeHintType.CHARACTER_SET, "UTF8");
|
||||
// 设置解析配置参数
|
||||
multiFormatReader.setHints(hints);
|
||||
|
||||
// 开始对图像资源解码
|
||||
Result rawResult = null;
|
||||
try {
|
||||
rawResult = multiFormatReader.decodeWithState(new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(mBitmap))));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (rawResult != null) {
|
||||
if (analyzeCallback != null) {
|
||||
analyzeCallback.onAnalyzeSuccess(mBitmap, rawResult.getText());
|
||||
}
|
||||
} else {
|
||||
if (analyzeCallback != null) {
|
||||
analyzeCallback.onAnalyzeFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码图片
|
||||
* @param text
|
||||
* @param w
|
||||
* @param h
|
||||
* @param logo
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap createImage(String text,int w,int h,Bitmap logo) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Bitmap scaleLogo = getScaleLogo(logo,w,h);
|
||||
|
||||
int offsetX = w / 2;
|
||||
int offsetY = h / 2;
|
||||
|
||||
int scaleWidth = 0;
|
||||
int scaleHeight = 0;
|
||||
if (scaleLogo != null) {
|
||||
scaleWidth = scaleLogo.getWidth();
|
||||
scaleHeight = scaleLogo.getHeight();
|
||||
offsetX = (w - scaleWidth) / 2;
|
||||
offsetY = (h - scaleHeight) / 2;
|
||||
}
|
||||
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
//容错级别
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
|
||||
//设置空白边距的宽度
|
||||
hints.put(EncodeHintType.MARGIN, 0);
|
||||
BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, w, h, hints);
|
||||
int[] pixels = new int[w * h];
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
if(x >= offsetX && x < offsetX + scaleWidth && y>= offsetY && y < offsetY + scaleHeight){
|
||||
int pixel = scaleLogo.getPixel(x-offsetX,y-offsetY);
|
||||
if(pixel == 0){
|
||||
if(bitMatrix.get(x, y)){
|
||||
pixel = 0xff000000;
|
||||
}else{
|
||||
pixel = 0xffffffff;
|
||||
}
|
||||
}
|
||||
pixels[y * w + x] = pixel;
|
||||
}else{
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * w + x] = 0xff000000;
|
||||
} else {
|
||||
pixels[y * w + x] = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(w, h,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
|
||||
return bitmap;
|
||||
} catch (WriterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Bitmap getScaleLogo(Bitmap logo,int w,int h){
|
||||
if(logo == null)return null;
|
||||
Matrix matrix = new Matrix();
|
||||
float scaleFactor = Math.min(w * 1.0f / 5 / logo.getWidth(), h * 1.0f / 5 /logo.getHeight());
|
||||
matrix.postScale(scaleFactor,scaleFactor);
|
||||
Bitmap result = Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析二维码结果
|
||||
*/
|
||||
public interface AnalyzeCallback{
|
||||
|
||||
public void onAnalyzeSuccess(Bitmap mBitmap, String result);
|
||||
|
||||
public void onAnalyzeFailed();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为CaptureFragment设置layout参数
|
||||
* @param captureFragment
|
||||
* @param layoutId
|
||||
*/
|
||||
public static void setFragmentArgs(CaptureFragment captureFragment, int layoutId) {
|
||||
if (captureFragment == null || layoutId == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(LAYOUT_ID, layoutId);
|
||||
captureFragment.setArguments(bundle);
|
||||
}
|
||||
|
||||
public static void isLightEnable(boolean isEnable) {
|
||||
if (isEnable) {
|
||||
Camera camera = CameraManager.get().getCamera();
|
||||
if (camera != null) {
|
||||
Camera.Parameters parameter = camera.getParameters();
|
||||
parameter.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
|
||||
camera.setParameters(parameter);
|
||||
}
|
||||
} else {
|
||||
Camera camera = CameraManager.get().getCamera();
|
||||
if (camera != null) {
|
||||
Camera.Parameters parameter = camera.getParameters();
|
||||
parameter.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
|
||||
camera.setParameters(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
public final class AutoFocusCallback implements Camera.AutoFocusCallback {
|
||||
|
||||
private static final String TAG = AutoFocusCallback.class.getSimpleName();
|
||||
|
||||
private static final long AUTOFOCUS_INTERVAL_MS = 1500L;
|
||||
|
||||
private Handler autoFocusHandler;
|
||||
private int autoFocusMessage;
|
||||
|
||||
public void setHandler(Handler autoFocusHandler, int autoFocusMessage) {
|
||||
this.autoFocusHandler = autoFocusHandler;
|
||||
this.autoFocusMessage = autoFocusMessage;
|
||||
}
|
||||
|
||||
public void onAutoFocus(boolean success, Camera camera) {
|
||||
if (autoFocusHandler != null) {
|
||||
Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
|
||||
autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
|
||||
autoFocusHandler = null;
|
||||
} else {
|
||||
Log.d(TAG, "Got auto-focus callback, but no handler for it");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.google.zxing.LuminanceSource;
|
||||
|
||||
/**
|
||||
* Created by aaron on 16/7/27.
|
||||
* 自定义解析Bitmap LuminanceSource
|
||||
*/
|
||||
public class BitmapLuminanceSource extends LuminanceSource {
|
||||
|
||||
private byte bitmapPixels[];
|
||||
|
||||
public BitmapLuminanceSource(Bitmap bitmap) {
|
||||
super(bitmap.getWidth(), bitmap.getHeight());
|
||||
|
||||
// 首先,要取得该图片的像素数组内容
|
||||
int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||
this.bitmapPixels = new byte[bitmap.getWidth() * bitmap.getHeight()];
|
||||
bitmap.getPixels(data, 0, getWidth(), 0, 0, getWidth(), getHeight());
|
||||
|
||||
// 将int数组转换为byte数组,也就是取像素值中蓝色值部分作为辨析内容
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
this.bitmapPixels[i] = (byte) data[i];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMatrix() {
|
||||
// 返回我们生成好的像素数据
|
||||
return bitmapPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRow(int y, byte[] row) {
|
||||
// 这里要得到指定行的像素数据
|
||||
System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth());
|
||||
return row;
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class CameraConfigurationManager {
|
||||
|
||||
private static final String TAG = CameraConfigurationManager.class.getSimpleName();
|
||||
|
||||
private static final int TEN_DESIRED_ZOOM = 27;
|
||||
private static final int DESIRED_SHARPNESS = 30;
|
||||
|
||||
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
|
||||
|
||||
private final Context context;
|
||||
private Point screenResolution;
|
||||
private Point cameraResolution;
|
||||
private int previewFormat;
|
||||
private String previewFormatString;
|
||||
|
||||
CameraConfigurationManager(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads, one time, values from the camera that are needed by the app.
|
||||
*/
|
||||
void initFromCameraParameters(Camera camera) {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
previewFormat = parameters.getPreviewFormat();
|
||||
previewFormatString = parameters.get("preview-format");
|
||||
Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
|
||||
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = manager.getDefaultDisplay();
|
||||
screenResolution = new Point(display.getWidth(), display.getHeight());
|
||||
Log.d(TAG, "Screen resolution: " + screenResolution);
|
||||
|
||||
Point screenResolutionForCamera = new Point();
|
||||
screenResolutionForCamera.x = screenResolution.x;
|
||||
screenResolutionForCamera.y = screenResolution.y;
|
||||
// preview size is always something like 480*320, other 320*480
|
||||
if (screenResolution.x < screenResolution.y) {
|
||||
screenResolutionForCamera.x = screenResolution.y;
|
||||
screenResolutionForCamera.y = screenResolution.x;
|
||||
}
|
||||
Log.i("#########", "screenX:" + screenResolutionForCamera.x + " screenY:" + screenResolutionForCamera.y);
|
||||
cameraResolution = getCameraResolution(parameters, screenResolutionForCamera);
|
||||
|
||||
// cameraResolution = getCameraResolution(parameters, screenResolution);
|
||||
Log.d(TAG, "Camera resolution: " + screenResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera up to take preview images which are used for both preview and decoding.
|
||||
* We detect the preview format here so that buildLuminanceSource() can build an appropriate
|
||||
* LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
|
||||
* and the planar Y can be used for barcode scanning without a copy in some cases.
|
||||
*/
|
||||
void setDesiredCameraParameters(Camera camera) {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
Log.d(TAG, "Setting preview size: " + cameraResolution);
|
||||
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
|
||||
setFlash(parameters);
|
||||
setZoom(parameters);
|
||||
//setSharpness(parameters);
|
||||
//modify here
|
||||
camera.setDisplayOrientation(90);
|
||||
camera.setParameters(parameters);
|
||||
}
|
||||
|
||||
Point getCameraResolution() {
|
||||
return cameraResolution;
|
||||
}
|
||||
|
||||
Point getScreenResolution() {
|
||||
return screenResolution;
|
||||
}
|
||||
|
||||
int getPreviewFormat() {
|
||||
return previewFormat;
|
||||
}
|
||||
|
||||
String getPreviewFormatString() {
|
||||
return previewFormatString;
|
||||
}
|
||||
|
||||
private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) {
|
||||
|
||||
String previewSizeValueString = parameters.get("preview-size-values");
|
||||
// saw this on Xperia
|
||||
if (previewSizeValueString == null) {
|
||||
previewSizeValueString = parameters.get("preview-size-value");
|
||||
}
|
||||
|
||||
Point cameraResolution = null;
|
||||
|
||||
if (previewSizeValueString != null) {
|
||||
Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString);
|
||||
cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
|
||||
}
|
||||
|
||||
if (cameraResolution == null) {
|
||||
// Ensure that the camera resolution is a multiple of 8, as the screen may not be.
|
||||
cameraResolution = new Point(
|
||||
(screenResolution.x >> 3) << 3,
|
||||
(screenResolution.y >> 3) << 3);
|
||||
}
|
||||
|
||||
return cameraResolution;
|
||||
}
|
||||
|
||||
private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) {
|
||||
int bestX = 0;
|
||||
int bestY = 0;
|
||||
int diff = Integer.MAX_VALUE;
|
||||
for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
|
||||
|
||||
previewSize = previewSize.trim();
|
||||
int dimPosition = previewSize.indexOf('x');
|
||||
if (dimPosition < 0) {
|
||||
Log.w(TAG, "Bad preview-size: " + previewSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
int newX;
|
||||
int newY;
|
||||
try {
|
||||
newX = Integer.parseInt(previewSize.substring(0, dimPosition));
|
||||
newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w(TAG, "Bad preview-size: " + previewSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
|
||||
if (newDiff == 0) {
|
||||
bestX = newX;
|
||||
bestY = newY;
|
||||
break;
|
||||
} else if (newDiff < diff) {
|
||||
bestX = newX;
|
||||
bestY = newY;
|
||||
diff = newDiff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (bestX > 0 && bestY > 0) {
|
||||
return new Point(bestX, bestY);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) {
|
||||
int tenBestValue = 0;
|
||||
for (String stringValue : COMMA_PATTERN.split(stringValues)) {
|
||||
stringValue = stringValue.trim();
|
||||
double value;
|
||||
try {
|
||||
value = Double.parseDouble(stringValue);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return tenDesiredZoom;
|
||||
}
|
||||
int tenValue = (int) (10.0 * value);
|
||||
if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) {
|
||||
tenBestValue = tenValue;
|
||||
}
|
||||
}
|
||||
return tenBestValue;
|
||||
}
|
||||
|
||||
private void setFlash(Camera.Parameters parameters) {
|
||||
// FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
|
||||
// And this is a hack-hack to work around a different value on the Behold II
|
||||
// Restrict Behold II check to Cupcake, per Samsung's advice
|
||||
//if (Build.MODEL.contains("Behold II") &&
|
||||
// CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) {
|
||||
if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3 = Cupcake
|
||||
parameters.set("flash-value", 1);
|
||||
} else {
|
||||
parameters.set("flash-value", 2);
|
||||
}
|
||||
// This is the standard setting to turn the flash off that all devices should honor.
|
||||
parameters.set("flash-mode", "off");
|
||||
}
|
||||
|
||||
private void setZoom(Camera.Parameters parameters) {
|
||||
|
||||
String zoomSupportedString = parameters.get("zoom-supported");
|
||||
if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tenDesiredZoom = TEN_DESIRED_ZOOM;
|
||||
|
||||
String maxZoomString = parameters.get("max-zoom");
|
||||
if (maxZoomString != null) {
|
||||
try {
|
||||
int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString));
|
||||
if (tenDesiredZoom > tenMaxZoom) {
|
||||
tenDesiredZoom = tenMaxZoom;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w(TAG, "Bad max-zoom: " + maxZoomString);
|
||||
}
|
||||
}
|
||||
|
||||
String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max");
|
||||
if (takingPictureZoomMaxString != null) {
|
||||
try {
|
||||
int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);
|
||||
if (tenDesiredZoom > tenMaxZoom) {
|
||||
tenDesiredZoom = tenMaxZoom;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString);
|
||||
}
|
||||
}
|
||||
|
||||
String motZoomValuesString = parameters.get("mot-zoom-values");
|
||||
if (motZoomValuesString != null) {
|
||||
tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom);
|
||||
}
|
||||
|
||||
String motZoomStepString = parameters.get("mot-zoom-step");
|
||||
if (motZoomStepString != null) {
|
||||
try {
|
||||
double motZoomStep = Double.parseDouble(motZoomStepString.trim());
|
||||
int tenZoomStep = (int) (10.0 * motZoomStep);
|
||||
if (tenZoomStep > 1) {
|
||||
tenDesiredZoom -= tenDesiredZoom % tenZoomStep;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set zoom. This helps encourage the user to pull back.
|
||||
// Some devices like the Behold have a zoom parameter
|
||||
if (maxZoomString != null || motZoomValuesString != null) {
|
||||
parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
|
||||
}
|
||||
|
||||
// Most devices, like the Hero, appear to expose this zoom parameter.
|
||||
// It takes on values like "27" which appears to mean 2.7x zoom
|
||||
if (takingPictureZoomMaxString != null) {
|
||||
parameters.set("taking-picture-zoom", tenDesiredZoom);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getDesiredSharpness() {
|
||||
return DESIRED_SHARPNESS;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,342 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This object wraps the Camera service object and expects to be the only one talking to it. The
|
||||
* implementation encapsulates the steps needed to take preview-sized images, which are used for
|
||||
* both preview and decoding.
|
||||
*/
|
||||
public final class CameraManager {
|
||||
|
||||
private static final String TAG = CameraManager.class.getSimpleName();
|
||||
|
||||
public static int FRAME_WIDTH = -1;
|
||||
public static int FRAME_HEIGHT = -1;
|
||||
public static int FRAME_MARGINTOP = -1;
|
||||
|
||||
private static CameraManager cameraManager;
|
||||
|
||||
static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
|
||||
|
||||
static {
|
||||
int sdkInt;
|
||||
try {
|
||||
sdkInt = Integer.parseInt(Build.VERSION.SDK);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// Just to be safe
|
||||
sdkInt = 10000;
|
||||
}
|
||||
SDK_INT = sdkInt;
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final CameraConfigurationManager configManager;
|
||||
private Camera camera;
|
||||
private Rect framingRect;
|
||||
private Rect framingRectInPreview;
|
||||
private boolean initialized;
|
||||
private boolean previewing;
|
||||
private final boolean useOneShotPreviewCallback;
|
||||
/**
|
||||
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to
|
||||
* clear the handler so it will only receive one message.
|
||||
*/
|
||||
private final PreviewCallback previewCallback;
|
||||
/**
|
||||
* Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
|
||||
*/
|
||||
private final AutoFocusCallback autoFocusCallback;
|
||||
|
||||
/**
|
||||
* Initializes this static object with the Context of the calling Activity.
|
||||
*
|
||||
* @param context The Activity which wants to use the camera.
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
if (cameraManager == null) {
|
||||
cameraManager = new CameraManager(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CameraManager singleton instance.
|
||||
*
|
||||
* @return A reference to the CameraManager singleton.
|
||||
*/
|
||||
public static CameraManager get() {
|
||||
return cameraManager;
|
||||
}
|
||||
|
||||
private CameraManager(Context context) {
|
||||
|
||||
this.context = context;
|
||||
this.configManager = new CameraConfigurationManager(context);
|
||||
|
||||
// Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
|
||||
// Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
|
||||
// the more efficient one shot callback, as the older one can swamp the system and cause it
|
||||
// to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
|
||||
//useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;
|
||||
useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake
|
||||
|
||||
previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
|
||||
autoFocusCallback = new AutoFocusCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the camera driver and initializes the hardware parameters.
|
||||
*
|
||||
* @param holder The surface object which the camera will draw preview frames into.
|
||||
* @throws IOException Indicates the camera driver failed to open.
|
||||
*/
|
||||
public void openDriver(SurfaceHolder holder) throws IOException {
|
||||
if (camera == null) {
|
||||
camera = Camera.open();
|
||||
if (camera == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
camera.setPreviewDisplay(holder);
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
configManager.initFromCameraParameters(camera);
|
||||
}
|
||||
configManager.setDesiredCameraParameters(camera);
|
||||
|
||||
FlashlightManager.enableFlashlight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the camera driver if still in use.
|
||||
*/
|
||||
public void closeDriver() {
|
||||
if (camera != null) {
|
||||
FlashlightManager.disableFlashlight();
|
||||
camera.release();
|
||||
camera = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the camera hardware to begin drawing preview frames to the screen.
|
||||
*/
|
||||
public void startPreview() {
|
||||
if (camera != null && !previewing) {
|
||||
camera.startPreview();
|
||||
previewing = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the camera to stop drawing preview frames.
|
||||
*/
|
||||
public void stopPreview() {
|
||||
if (camera != null && previewing) {
|
||||
if (!useOneShotPreviewCallback) {
|
||||
camera.setPreviewCallback(null);
|
||||
}
|
||||
camera.stopPreview();
|
||||
previewCallback.setHandler(null, 0);
|
||||
autoFocusCallback.setHandler(null, 0);
|
||||
previewing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
|
||||
* in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
|
||||
* respectively.
|
||||
*
|
||||
* @param handler The handler to send the message to.
|
||||
* @param message The what field of the message to be sent.
|
||||
*/
|
||||
public void requestPreviewFrame(Handler handler, int message) {
|
||||
if (camera != null && previewing) {
|
||||
previewCallback.setHandler(handler, message);
|
||||
if (useOneShotPreviewCallback) {
|
||||
camera.setOneShotPreviewCallback(previewCallback);
|
||||
} else {
|
||||
camera.setPreviewCallback(previewCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the camera hardware to perform an autofocus.
|
||||
*
|
||||
* @param handler The Handler to notify when the autofocus completes.
|
||||
* @param message The message to deliver.
|
||||
*/
|
||||
public void requestAutoFocus(Handler handler, int message) {
|
||||
if (camera != null && previewing) {
|
||||
autoFocusCallback.setHandler(handler, message);
|
||||
//Log.d(TAG, "Requesting auto-focus callback");
|
||||
camera.autoFocus(autoFocusCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the framing rect which the UI should draw to show the user where to place the
|
||||
* barcode. This target helps with alignment as well as forces the user to hold the device
|
||||
* far enough away to ensure the image will be in focus.
|
||||
*
|
||||
* @return The rectangle to draw on screen in window coordinates.
|
||||
*/
|
||||
public Rect getFramingRect() {
|
||||
try {
|
||||
Point screenResolution = configManager.getScreenResolution();
|
||||
// if (framingRect == null) {
|
||||
if (camera == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int leftOffset = (screenResolution.x - FRAME_WIDTH) / 2;
|
||||
|
||||
int topOffset;
|
||||
if (FRAME_MARGINTOP != -1) {
|
||||
topOffset = FRAME_MARGINTOP;
|
||||
} else {
|
||||
topOffset = (screenResolution.y - FRAME_HEIGHT) / 2;
|
||||
}
|
||||
framingRect = new Rect(leftOffset, topOffset, leftOffset + FRAME_WIDTH, topOffset + FRAME_HEIGHT);
|
||||
// }
|
||||
return framingRect;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
|
||||
* not UI / screen.
|
||||
*/
|
||||
public Rect getFramingRectInPreview() {
|
||||
if (framingRectInPreview == null) {
|
||||
Rect rect = new Rect(getFramingRect());
|
||||
Point cameraResolution = configManager.getCameraResolution();
|
||||
Point screenResolution = configManager.getScreenResolution();
|
||||
//modify here
|
||||
// rect.left = rect.left * cameraResolution.x / screenResolution.x;
|
||||
// rect.right = rect.right * cameraResolution.x / screenResolution.x;
|
||||
// rect.top = rect.top * cameraResolution.y / screenResolution.y;
|
||||
// rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
|
||||
rect.left = rect.left * cameraResolution.y / screenResolution.x;
|
||||
rect.right = rect.right * cameraResolution.y / screenResolution.x;
|
||||
rect.top = rect.top * cameraResolution.x / screenResolution.y;
|
||||
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
|
||||
framingRectInPreview = rect;
|
||||
}
|
||||
return framingRectInPreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the result points from still resolution coordinates to screen coordinates.
|
||||
*
|
||||
* @param points The points returned by the Reader subclass through Result.getResultPoints().
|
||||
* @return An array of Points scaled to the size of the framing rect and offset appropriately
|
||||
* so they can be drawn in screen coordinates.
|
||||
*/
|
||||
/*
|
||||
public Point[] convertResultPoints(ResultPoint[] points) {
|
||||
Rect frame = getFramingRectInPreview();
|
||||
int count = points.length;
|
||||
Point[] output = new Point[count];
|
||||
for (int x = 0; x < count; x++) {
|
||||
output[x] = new Point();
|
||||
output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
|
||||
output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A factory method to build the appropriate LuminanceSource object based on the format
|
||||
* of the preview buffers, as described by Camera.Parameters.
|
||||
*
|
||||
* @param data A preview frame.
|
||||
* @param width The width of the image.
|
||||
* @param height The height of the image.
|
||||
* @return A PlanarYUVLuminanceSource instance.
|
||||
*/
|
||||
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
|
||||
Rect rect = getFramingRectInPreview();
|
||||
int previewFormat = configManager.getPreviewFormat();
|
||||
String previewFormatString = configManager.getPreviewFormatString();
|
||||
switch (previewFormat) {
|
||||
// This is the standard Android format which all devices are REQUIRED to support.
|
||||
// In theory, it's the only one we should ever care about.
|
||||
case PixelFormat.YCbCr_420_SP:
|
||||
// This format has never been seen in the wild, but is compatible as we only care
|
||||
// about the Y channel, so allow it.
|
||||
case PixelFormat.YCbCr_422_SP:
|
||||
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
|
||||
rect.width(), rect.height());
|
||||
default:
|
||||
// The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
|
||||
// Fortunately, it too has all the Y data up front, so we can read it.
|
||||
if ("yuv420p".equals(previewFormatString)) {
|
||||
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
|
||||
rect.width(), rect.height());
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported picture format: " +
|
||||
previewFormat + '/' + previewFormatString);
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public Camera getCamera() {
|
||||
return camera;
|
||||
}
|
||||
|
||||
public boolean isPreviewing() {
|
||||
return previewing;
|
||||
}
|
||||
|
||||
public boolean isUseOneShotPreviewCallback() {
|
||||
return useOneShotPreviewCallback;
|
||||
}
|
||||
|
||||
public PreviewCallback getPreviewCallback() {
|
||||
return previewCallback;
|
||||
}
|
||||
|
||||
public AutoFocusCallback getAutoFocusCallback() {
|
||||
return autoFocusCallback;
|
||||
}
|
||||
|
||||
public void setPreviewing(boolean previewing) {
|
||||
this.previewing = previewing;
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* This class is used to activate the weak light on some camera phones (not flash)
|
||||
* in order to illuminate surfaces for scanning. There is no official way to do this,
|
||||
* but, classes which allow access to this function still exist on some devices.
|
||||
* This therefore proceeds through a great deal of reflection.
|
||||
* <p>
|
||||
* See <a href="http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/">
|
||||
* http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/</a> and
|
||||
* <a href="http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java">
|
||||
* http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java</a>.
|
||||
* Thanks to Ryan Alford for pointing out the availability of this class.
|
||||
*/
|
||||
final class FlashlightManager {
|
||||
|
||||
private static final String TAG = FlashlightManager.class.getSimpleName();
|
||||
|
||||
private static final Object iHardwareService;
|
||||
private static final Method setFlashEnabledMethod;
|
||||
|
||||
static {
|
||||
iHardwareService = getHardwareService();
|
||||
setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);
|
||||
if (iHardwareService == null) {
|
||||
Log.v(TAG, "This device does supports control of a flashlight");
|
||||
} else {
|
||||
Log.v(TAG, "This device does not support control of a flashlight");
|
||||
}
|
||||
}
|
||||
|
||||
private FlashlightManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD><EFBFBD>
|
||||
*/
|
||||
//FIXME
|
||||
static void enableFlashlight() {
|
||||
setFlashlight(false);
|
||||
}
|
||||
|
||||
static void disableFlashlight() {
|
||||
setFlashlight(false);
|
||||
}
|
||||
|
||||
private static Object getHardwareService() {
|
||||
Class<?> serviceManagerClass = maybeForName("android.os.ServiceManager");
|
||||
if (serviceManagerClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class);
|
||||
if (getServiceMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object hardwareService = invoke(getServiceMethod, null, "hardware");
|
||||
if (hardwareService == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub");
|
||||
if (iHardwareServiceStubClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface", IBinder.class);
|
||||
if (asInterfaceMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return invoke(asInterfaceMethod, null, hardwareService);
|
||||
}
|
||||
|
||||
private static Method getSetFlashEnabledMethod(Object iHardwareService) {
|
||||
if (iHardwareService == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> proxyClass = iHardwareService.getClass();
|
||||
return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class);
|
||||
}
|
||||
|
||||
private static Class<?> maybeForName(String name) {
|
||||
try {
|
||||
return Class.forName(name);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
// OK
|
||||
return null;
|
||||
} catch (RuntimeException re) {
|
||||
Log.w(TAG, "Unexpected error while finding class " + name, re);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Method maybeGetMethod(Class<?> clazz, String name, Class<?>... argClasses) {
|
||||
try {
|
||||
return clazz.getMethod(name, argClasses);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// OK
|
||||
return null;
|
||||
} catch (RuntimeException re) {
|
||||
Log.w(TAG, "Unexpected error while finding method " + name, re);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object invoke(Method method, Object instance, Object... args) {
|
||||
try {
|
||||
return method.invoke(instance, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.w(TAG, "Unexpected error while invoking " + method, e);
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.w(TAG, "Unexpected error while invoking " + method, e.getCause());
|
||||
return null;
|
||||
} catch (RuntimeException re) {
|
||||
Log.w(TAG, "Unexpected error while invoking " + method, re);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFlashlight(boolean active) {
|
||||
if (iHardwareService != null) {
|
||||
invoke(setFlashEnabledMethod, iHardwareService, active);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.google.zxing.LuminanceSource;
|
||||
|
||||
/**
|
||||
* This object extends LuminanceSource around an array of YUV data returned from the camera driver,
|
||||
* with the option to crop to a rectangle within the full data. This can be used to exclude
|
||||
* superfluous pixels around the perimeter and speed up decoding.
|
||||
* <p>
|
||||
* It works for any pixel format where the Y channel is planar and appears first, including
|
||||
* YCbCr_420_SP and YCbCr_422_SP.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class PlanarYUVLuminanceSource extends LuminanceSource {
|
||||
private final byte[] yuvData;
|
||||
private final int dataWidth;
|
||||
private final int dataHeight;
|
||||
private final int left;
|
||||
private final int top;
|
||||
|
||||
public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top,
|
||||
int width, int height) {
|
||||
super(width, height);
|
||||
|
||||
if (left + width > dataWidth || top + height > dataHeight) {
|
||||
throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
|
||||
}
|
||||
|
||||
this.yuvData = yuvData;
|
||||
this.dataWidth = dataWidth;
|
||||
this.dataHeight = dataHeight;
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRow(int y, byte[] row) {
|
||||
if (y < 0 || y >= getHeight()) {
|
||||
throw new IllegalArgumentException("Requested row is outside the image: " + y);
|
||||
}
|
||||
int width = getWidth();
|
||||
if (row == null || row.length < width) {
|
||||
row = new byte[width];
|
||||
}
|
||||
int offset = (y + top) * dataWidth + left;
|
||||
System.arraycopy(yuvData, offset, row, 0, width);
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMatrix() {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// If the caller asks for the entire underlying image, save the copy and give them the
|
||||
// original data. The docs specifically warn that result.length must be ignored.
|
||||
if (width == dataWidth && height == dataHeight) {
|
||||
return yuvData;
|
||||
}
|
||||
|
||||
int area = width * height;
|
||||
byte[] matrix = new byte[area];
|
||||
int inputOffset = top * dataWidth + left;
|
||||
|
||||
// If the width matches the full width of the underlying data, perform a single copy.
|
||||
if (width == dataWidth) {
|
||||
System.arraycopy(yuvData, inputOffset, matrix, 0, area);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
// Otherwise copy one cropped row at a time.
|
||||
byte[] yuv = yuvData;
|
||||
for (int y = 0; y < height; y++) {
|
||||
int outputOffset = y * width;
|
||||
System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
|
||||
inputOffset += dataWidth;
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCropSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getDataWidth() {
|
||||
return dataWidth;
|
||||
}
|
||||
|
||||
public int getDataHeight() {
|
||||
return dataHeight;
|
||||
}
|
||||
|
||||
public Bitmap renderCroppedGreyscaleBitmap() {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
byte[] yuv = yuvData;
|
||||
int inputOffset = top * dataWidth + left;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
int outputOffset = y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
int grey = yuv[inputOffset + x] & 0xff;
|
||||
pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
|
||||
}
|
||||
inputOffset += dataWidth;
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.camera;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
public final class PreviewCallback implements Camera.PreviewCallback {
|
||||
|
||||
private static final String TAG = PreviewCallback.class.getSimpleName();
|
||||
|
||||
private final CameraConfigurationManager configManager;
|
||||
private final boolean useOneShotPreviewCallback;
|
||||
private Handler previewHandler;
|
||||
private int previewMessage;
|
||||
|
||||
PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) {
|
||||
this.configManager = configManager;
|
||||
this.useOneShotPreviewCallback = useOneShotPreviewCallback;
|
||||
}
|
||||
|
||||
public void setHandler(Handler previewHandler, int previewMessage) {
|
||||
this.previewHandler = previewHandler;
|
||||
this.previewMessage = previewMessage;
|
||||
}
|
||||
|
||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||
Point cameraResolution = configManager.getCameraResolution();
|
||||
if (!useOneShotPreviewCallback) {
|
||||
camera.setPreviewCallback(null);
|
||||
}
|
||||
if (previewHandler != null) {
|
||||
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
|
||||
cameraResolution.y, data);
|
||||
message.sendToTarget();
|
||||
previewHandler = null;
|
||||
} else {
|
||||
Log.d(TAG, "Got preview callback, but no handler for it");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import pub.doric.devkit.R;
|
||||
import pub.doric.devkit.qrcode.activity.CaptureFragment;
|
||||
import pub.doric.devkit.qrcode.camera.CameraManager;
|
||||
import pub.doric.devkit.qrcode.view.ViewfinderResultPointCallback;
|
||||
import pub.doric.devkit.qrcode.view.ViewfinderView;
|
||||
|
||||
/**
|
||||
* This class handles all the messaging which comprises the state machine for capture.
|
||||
*/
|
||||
public final class CaptureActivityHandler extends Handler {
|
||||
|
||||
private static final String TAG = CaptureActivityHandler.class.getSimpleName();
|
||||
|
||||
private final CaptureFragment fragment;
|
||||
private final DecodeThread decodeThread;
|
||||
private State state;
|
||||
|
||||
private enum State {
|
||||
PREVIEW,
|
||||
SUCCESS,
|
||||
DONE
|
||||
}
|
||||
|
||||
public CaptureActivityHandler(CaptureFragment fragment, Vector<BarcodeFormat> decodeFormats,
|
||||
String characterSet, ViewfinderView viewfinderView) {
|
||||
this.fragment = fragment;
|
||||
decodeThread = new DecodeThread(fragment, decodeFormats, characterSet,
|
||||
new ViewfinderResultPointCallback(viewfinderView));
|
||||
decodeThread.start();
|
||||
state = State.SUCCESS;
|
||||
// Start ourselves capturing previews and decoding.
|
||||
CameraManager.get().startPreview();
|
||||
restartPreviewAndDecode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == R.id.auto_focus) {
|
||||
//Log.d(TAG, "Got auto-focus message");
|
||||
// When one auto focus pass finishes, start another. This is the closest thing to
|
||||
// continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
|
||||
if (state == State.PREVIEW) {
|
||||
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
|
||||
}
|
||||
} else if (message.what == R.id.restart_preview) {
|
||||
Log.d(TAG, "Got restart preview message");
|
||||
restartPreviewAndDecode();
|
||||
} else if (message.what == R.id.decode_succeeded) {
|
||||
Log.d(TAG, "Got decode succeeded message");
|
||||
state = State.SUCCESS;
|
||||
Bundle bundle = message.getData();
|
||||
|
||||
/***********************************************************************/
|
||||
Bitmap barcode = bundle == null ? null :
|
||||
(Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
|
||||
|
||||
fragment.handleDecode((Result) message.obj, barcode);
|
||||
/***********************************************************************/
|
||||
} else if (message.what == R.id.decode_failed) {
|
||||
// We're decoding as fast as possible, so when one decode fails, start another.
|
||||
state = State.PREVIEW;
|
||||
CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
|
||||
} else if (message.what == R.id.return_scan_result) {
|
||||
Log.d(TAG, "Got return scan result message");
|
||||
fragment.getActivity().setResult(Activity.RESULT_OK, (Intent) message.obj);
|
||||
fragment.getActivity().finish();
|
||||
} else if (message.what == R.id.launch_product_query) {
|
||||
Log.d(TAG, "Got product query message");
|
||||
String url = (String) message.obj;
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
fragment.getActivity().startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void quitSynchronously() {
|
||||
state = State.DONE;
|
||||
CameraManager.get().stopPreview();
|
||||
Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
|
||||
quit.sendToTarget();
|
||||
try {
|
||||
decodeThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
// Be absolutely sure we don't send any queued up messages
|
||||
removeMessages(R.id.decode_succeeded);
|
||||
removeMessages(R.id.decode_failed);
|
||||
}
|
||||
|
||||
private void restartPreviewAndDecode() {
|
||||
if (state == State.SUCCESS) {
|
||||
state = State.PREVIEW;
|
||||
CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
|
||||
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
|
||||
fragment.drawViewfinder();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DecodeFormatManager {
|
||||
|
||||
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
|
||||
|
||||
public static final Vector<BarcodeFormat> PRODUCT_FORMATS;
|
||||
public static final Vector<BarcodeFormat> ONE_D_FORMATS;
|
||||
public static final Vector<BarcodeFormat> QR_CODE_FORMATS;
|
||||
public static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;
|
||||
|
||||
static {
|
||||
PRODUCT_FORMATS = new Vector<BarcodeFormat>(5);
|
||||
PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);
|
||||
PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);
|
||||
PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
|
||||
PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);
|
||||
// PRODUCT_FORMATS.add(BarcodeFormat.RSS14);
|
||||
ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 4);
|
||||
ONE_D_FORMATS.addAll(PRODUCT_FORMATS);
|
||||
ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
|
||||
ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
|
||||
ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
|
||||
ONE_D_FORMATS.add(BarcodeFormat.ITF);
|
||||
QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);
|
||||
QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
|
||||
DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1);
|
||||
DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);
|
||||
}
|
||||
|
||||
private DecodeFormatManager() {
|
||||
}
|
||||
|
||||
static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {
|
||||
List<String> scanFormats = null;
|
||||
String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);
|
||||
if (scanFormatsString != null) {
|
||||
scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
|
||||
}
|
||||
return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
|
||||
}
|
||||
|
||||
static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) {
|
||||
List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS);
|
||||
if (formats != null && formats.size() == 1 && formats.get(0) != null) {
|
||||
formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));
|
||||
}
|
||||
return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));
|
||||
}
|
||||
|
||||
private static Vector<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats,
|
||||
String decodeMode) {
|
||||
if (scanFormats != null) {
|
||||
Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
|
||||
try {
|
||||
for (String format : scanFormats) {
|
||||
formats.add(BarcodeFormat.valueOf(format));
|
||||
}
|
||||
return formats;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// ignore it then
|
||||
}
|
||||
}
|
||||
if (decodeMode != null) {
|
||||
if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {
|
||||
return PRODUCT_FORMATS;
|
||||
}
|
||||
if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {
|
||||
return QR_CODE_FORMATS;
|
||||
}
|
||||
if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) {
|
||||
return DATA_MATRIX_FORMATS;
|
||||
}
|
||||
if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {
|
||||
return ONE_D_FORMATS;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import pub.doric.devkit.R;
|
||||
import pub.doric.devkit.qrcode.activity.CaptureFragment;
|
||||
import pub.doric.devkit.qrcode.camera.CameraManager;
|
||||
import pub.doric.devkit.qrcode.camera.PlanarYUVLuminanceSource;
|
||||
|
||||
final class DecodeHandler extends Handler {
|
||||
|
||||
private static final String TAG = DecodeHandler.class.getSimpleName();
|
||||
|
||||
private final CaptureFragment fragment;
|
||||
private final MultiFormatReader multiFormatReader;
|
||||
|
||||
DecodeHandler(CaptureFragment fragment, Hashtable<DecodeHintType, Object> hints) {
|
||||
multiFormatReader = new MultiFormatReader();
|
||||
multiFormatReader.setHints(hints);
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == R.id.decode) {
|
||||
decode((byte[]) message.obj, message.arg1, message.arg2);
|
||||
} else if (message.what == R.id.quit) {
|
||||
Looper.myLooper().quit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
|
||||
* reuse the same reader objects from one decode to the next.
|
||||
*
|
||||
* @param data The YUV preview frame.
|
||||
* @param width The width of the preview frame.
|
||||
* @param height The height of the preview frame.
|
||||
*/
|
||||
private void decode(byte[] data, int width, int height) {
|
||||
long start = System.currentTimeMillis();
|
||||
Result rawResult = null;
|
||||
|
||||
//modify here
|
||||
byte[] rotatedData = new byte[data.length];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++)
|
||||
rotatedData[x * height + height - y - 1] = data[x + y * width];
|
||||
}
|
||||
int tmp = width; // Here we are swapping, that's the difference to #11
|
||||
width = height;
|
||||
height = tmp;
|
||||
|
||||
PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
try {
|
||||
rawResult = multiFormatReader.decodeWithState(bitmap);
|
||||
} catch (ReaderException re) {
|
||||
// continue
|
||||
} finally {
|
||||
multiFormatReader.reset();
|
||||
}
|
||||
|
||||
if (rawResult != null) {
|
||||
long end = System.currentTimeMillis();
|
||||
Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
|
||||
Message message = Message.obtain(fragment.getHandler(), R.id.decode_succeeded, rawResult);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
|
||||
message.setData(bundle);
|
||||
//Log.d(TAG, "Sending decode succeeded message...");
|
||||
message.sendToTarget();
|
||||
} else {
|
||||
Message message = Message.obtain(fragment.getHandler(), R.id.decode_failed);
|
||||
message.sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import pub.doric.devkit.qrcode.activity.CaptureFragment;
|
||||
|
||||
/**
|
||||
* This thread does all the heavy lifting of decoding the images.
|
||||
*/
|
||||
final class DecodeThread extends Thread {
|
||||
|
||||
public static final String BARCODE_BITMAP = "barcode_bitmap";
|
||||
private final CaptureFragment fragment;
|
||||
private final Hashtable<DecodeHintType, Object> hints;
|
||||
private Handler handler;
|
||||
private final CountDownLatch handlerInitLatch;
|
||||
|
||||
DecodeThread(CaptureFragment fragment,
|
||||
Vector<BarcodeFormat> decodeFormats,
|
||||
String characterSet,
|
||||
ResultPointCallback resultPointCallback) {
|
||||
|
||||
this.fragment = fragment;
|
||||
handlerInitLatch = new CountDownLatch(1);
|
||||
|
||||
hints = new Hashtable<DecodeHintType, Object>(3);
|
||||
|
||||
if (decodeFormats == null || decodeFormats.isEmpty()) {
|
||||
decodeFormats = new Vector<BarcodeFormat>();
|
||||
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
|
||||
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
|
||||
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
|
||||
}
|
||||
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||
|
||||
if (characterSet != null) {
|
||||
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
|
||||
}
|
||||
|
||||
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
|
||||
}
|
||||
|
||||
Handler getHandler() {
|
||||
try {
|
||||
handlerInitLatch.await();
|
||||
} catch (InterruptedException ie) {
|
||||
// continue?
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
handler = new DecodeHandler(fragment, hints);
|
||||
handlerInitLatch.countDown();
|
||||
Looper.loop();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
/**
|
||||
* Simple listener used to exit the app in a few cases.
|
||||
*/
|
||||
public final class FinishListener
|
||||
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable {
|
||||
|
||||
private final Activity activityToFinish;
|
||||
|
||||
public FinishListener(Activity activityToFinish) {
|
||||
this.activityToFinish = activityToFinish;
|
||||
}
|
||||
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
run();
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
run();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
activityToFinish.finish();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Finishes an activity after a period of inactivity.
|
||||
*/
|
||||
public final class InactivityTimer {
|
||||
|
||||
private static final int INACTIVITY_DELAY_SECONDS = 5 * 60;
|
||||
|
||||
private final ScheduledExecutorService inactivityTimer =
|
||||
Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory());
|
||||
private final Activity activity;
|
||||
private ScheduledFuture<?> inactivityFuture = null;
|
||||
|
||||
public InactivityTimer(Activity activity) {
|
||||
this.activity = activity;
|
||||
onActivity();
|
||||
}
|
||||
|
||||
public void onActivity() {
|
||||
cancel();
|
||||
inactivityFuture = inactivityTimer.schedule(new FinishListener(activity),
|
||||
INACTIVITY_DELAY_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
if (inactivityFuture != null) {
|
||||
inactivityFuture.cancel(true);
|
||||
inactivityFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
cancel();
|
||||
inactivityTimer.shutdown();
|
||||
}
|
||||
|
||||
private static final class DaemonThreadFactory implements ThreadFactory {
|
||||
public Thread newThread(Runnable runnable) {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.decoding;
|
||||
|
||||
/**
|
||||
* This class provides the constants to use when sending an Intent to Barcode Scanner.
|
||||
* These strings are effectively API and cannot be changed.
|
||||
*/
|
||||
public final class Intents {
|
||||
private Intents() {
|
||||
}
|
||||
|
||||
public static final class Scan {
|
||||
/**
|
||||
* Send this intent to open the Barcodes app in scanning mode, find a barcode, and return
|
||||
* the results.
|
||||
*/
|
||||
public static final String ACTION = "com.google.zxing.client.android.SCAN";
|
||||
|
||||
/**
|
||||
* By default, sending Scan.ACTION will decode all barcodes that we understand. However it
|
||||
* may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with
|
||||
* one of the values below ({@link #PRODUCT_MODE}, {@link #ONE_D_MODE}, {@link #QR_CODE_MODE}).
|
||||
* Optional.
|
||||
* <p>
|
||||
* Setting this is effectively shorthnad for setting explicit formats with {@link #SCAN_FORMATS}.
|
||||
* It is overridden by that setting.
|
||||
*/
|
||||
public static final String MODE = "SCAN_MODE";
|
||||
|
||||
/**
|
||||
* Comma-separated list of formats to scan for. The values must match the names of
|
||||
* {@link com.google.zxing.BarcodeFormat}s, such as {@link com.google.zxing.BarcodeFormat#EAN_13}.
|
||||
* Example: "EAN_13,EAN_8,QR_CODE"
|
||||
* <p>
|
||||
* This overrides {@link #MODE}.
|
||||
*/
|
||||
public static final String SCAN_FORMATS = "SCAN_FORMATS";
|
||||
|
||||
/**
|
||||
* @see com.google.zxing.DecodeHintType#CHARACTER_SET
|
||||
*/
|
||||
public static final String CHARACTER_SET = "CHARACTER_SET";
|
||||
|
||||
/**
|
||||
* Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get
|
||||
* prices, reviews, etc. for products.
|
||||
*/
|
||||
public static final String PRODUCT_MODE = "PRODUCT_MODE";
|
||||
|
||||
/**
|
||||
* Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128).
|
||||
*/
|
||||
public static final String ONE_D_MODE = "ONE_D_MODE";
|
||||
|
||||
/**
|
||||
* Decode only QR codes.
|
||||
*/
|
||||
public static final String QR_CODE_MODE = "QR_CODE_MODE";
|
||||
|
||||
/**
|
||||
* Decode only Data Matrix codes.
|
||||
*/
|
||||
public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE";
|
||||
|
||||
/**
|
||||
* If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which
|
||||
* requested the scan via startSubActivity(). The barcodes contents can be retrieved with
|
||||
* intent.getStringExtra(RESULT). If the user presses Back, the result code will be
|
||||
* RESULT_CANCELED.
|
||||
*/
|
||||
public static final String RESULT = "SCAN_RESULT";
|
||||
|
||||
/**
|
||||
* Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found.
|
||||
* See Contents.Format for possible values.
|
||||
*/
|
||||
public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT";
|
||||
|
||||
/**
|
||||
* Setting this to false will not save scanned codes in the history.
|
||||
*/
|
||||
public static final String SAVE_HISTORY = "SAVE_HISTORY";
|
||||
|
||||
private Scan() {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Encode {
|
||||
/**
|
||||
* Send this intent to encode a piece of data as a QR code and display it full screen, so
|
||||
* that another person can scan the barcode from your screen.
|
||||
*/
|
||||
public static final String ACTION = "com.google.zxing.client.android.ENCODE";
|
||||
|
||||
/**
|
||||
* The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a
|
||||
* Bundle, depending on the type and format specified. Non-QR Code formats should
|
||||
* just use a String here. For QR Code, see Contents for details.
|
||||
*/
|
||||
public static final String DATA = "ENCODE_DATA";
|
||||
|
||||
/**
|
||||
* The type of data being supplied if the format is QR Code. Use
|
||||
* Intent.putExtra(TYPE, type) with one of Contents.Type.
|
||||
*/
|
||||
public static final String TYPE = "ENCODE_TYPE";
|
||||
|
||||
/**
|
||||
* The barcode format to be displayed. If this isn't specified or is blank,
|
||||
* it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where
|
||||
* format is one of Contents.Format.
|
||||
*/
|
||||
public static final String FORMAT = "ENCODE_FORMAT";
|
||||
|
||||
private Encode() {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SearchBookContents {
|
||||
/**
|
||||
* Use Google Book Search to search the contents of the book provided.
|
||||
*/
|
||||
public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS";
|
||||
|
||||
/**
|
||||
* The book to search, identified by ISBN number.
|
||||
*/
|
||||
public static final String ISBN = "ISBN";
|
||||
|
||||
/**
|
||||
* An optional field which is the text to search for.
|
||||
*/
|
||||
public static final String QUERY = "QUERY";
|
||||
|
||||
private SearchBookContents() {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class WifiConnect {
|
||||
/**
|
||||
* Internal intent used to trigger connection to a wi-fi network.
|
||||
*/
|
||||
public static final String ACTION = "com.google.zxing.client.android.WIFI_CONNECT";
|
||||
|
||||
/**
|
||||
* The network to connect to, all the configuration provided here.
|
||||
*/
|
||||
public static final String SSID = "SSID";
|
||||
|
||||
/**
|
||||
* The network to connect to, all the configuration provided here.
|
||||
*/
|
||||
public static final String TYPE = "TYPE";
|
||||
|
||||
/**
|
||||
* The network to connect to, all the configuration provided here.
|
||||
*/
|
||||
public static final String PASSWORD = "PASSWORD";
|
||||
|
||||
private WifiConnect() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final class Share {
|
||||
/**
|
||||
* Give the user a choice of items to encode as a barcode, then render it as a QR Code and
|
||||
* display onscreen for a friend to scan with their phone.
|
||||
*/
|
||||
public static final String ACTION = "com.google.zxing.client.android.SHARE";
|
||||
|
||||
private Share() {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.view;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
|
||||
public final class ViewfinderResultPointCallback implements ResultPointCallback {
|
||||
|
||||
private final ViewfinderView viewfinderView;
|
||||
|
||||
public ViewfinderResultPointCallback(ViewfinderView viewfinderView) {
|
||||
this.viewfinderView = viewfinderView;
|
||||
}
|
||||
|
||||
public void foundPossibleResultPoint(ResultPoint point) {
|
||||
viewfinderView.addPossibleResultPoint(point);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package pub.doric.devkit.qrcode.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import pub.doric.devkit.R;
|
||||
import pub.doric.devkit.qrcode.DisplayUtil;
|
||||
import pub.doric.devkit.qrcode.camera.CameraManager;
|
||||
|
||||
|
||||
public final class ViewfinderView extends View {
|
||||
|
||||
private static final long ANIMATION_DELAY = 100L;
|
||||
private static final int OPAQUE = 0xFF;
|
||||
|
||||
private final Paint paint;
|
||||
private Bitmap resultBitmap;
|
||||
private final int maskColor;
|
||||
private final int resultColor;
|
||||
private final int resultPointColor;
|
||||
private Collection<ResultPoint> possibleResultPoints;
|
||||
private Collection<ResultPoint> lastPossibleResultPoints;
|
||||
|
||||
public ViewfinderView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ViewfinderView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, -1);
|
||||
|
||||
}
|
||||
|
||||
public ViewfinderView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
paint = new Paint();
|
||||
Resources resources = getResources();
|
||||
maskColor = resources.getColor(R.color.viewfinder_mask);
|
||||
resultColor = resources.getColor(R.color.result_view);
|
||||
resultPointColor = resources.getColor(R.color.possible_result_points);
|
||||
possibleResultPoints = new HashSet<>(5);
|
||||
|
||||
scanLight = BitmapFactory.decodeResource(resources,
|
||||
R.drawable.scan_light);
|
||||
|
||||
initInnerRect(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化内部框的大小
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
private void initInnerRect(Context context, AttributeSet attrs) {
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
|
||||
|
||||
// 扫描框距离顶部
|
||||
float innerMarginTop = ta.getDimension(R.styleable.ViewfinderView_inner_margintop, -1);
|
||||
if (innerMarginTop != -1) {
|
||||
CameraManager.FRAME_MARGINTOP = (int) innerMarginTop;
|
||||
}
|
||||
|
||||
// 扫描框的宽度
|
||||
CameraManager.FRAME_WIDTH = (int) ta.getDimension(R.styleable.ViewfinderView_inner_width, DisplayUtil.screenWidthPx / 2);
|
||||
|
||||
// 扫描框的高度
|
||||
CameraManager.FRAME_HEIGHT = (int) ta.getDimension(R.styleable.ViewfinderView_inner_height, DisplayUtil.screenWidthPx / 2);
|
||||
|
||||
// 扫描框边角颜色
|
||||
innercornercolor = ta.getColor(R.styleable.ViewfinderView_inner_corner_color, Color.parseColor("#45DDDD"));
|
||||
// 扫描框边角长度
|
||||
innercornerlength = (int) ta.getDimension(R.styleable.ViewfinderView_inner_corner_length, 65);
|
||||
// 扫描框边角宽度
|
||||
innercornerwidth = (int) ta.getDimension(R.styleable.ViewfinderView_inner_corner_width, 15);
|
||||
|
||||
// 扫描bitmap
|
||||
Drawable drawable = ta.getDrawable(R.styleable.ViewfinderView_inner_scan_bitmap);
|
||||
if (drawable != null) {
|
||||
}
|
||||
|
||||
// 扫描控件
|
||||
scanLight = BitmapFactory.decodeResource(getResources(), ta.getResourceId(R.styleable.ViewfinderView_inner_scan_bitmap, R.drawable.scan_light));
|
||||
// 扫描速度
|
||||
SCAN_VELOCITY = ta.getInt(R.styleable.ViewfinderView_inner_scan_speed, 5);
|
||||
|
||||
isCircle = ta.getBoolean(R.styleable.ViewfinderView_inner_scan_iscircle, true);
|
||||
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
Rect frame = CameraManager.get().getFramingRect();
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
int width = canvas.getWidth();
|
||||
int height = canvas.getHeight();
|
||||
|
||||
// Draw the exterior (i.e. outside the framing rect) darkened
|
||||
paint.setColor(resultBitmap != null ? resultColor : maskColor);
|
||||
canvas.drawRect(0, 0, width, frame.top, paint);
|
||||
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
|
||||
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
|
||||
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
|
||||
|
||||
if (resultBitmap != null) {
|
||||
// Draw the opaque result bitmap over the scanning rectangle
|
||||
paint.setAlpha(OPAQUE);
|
||||
canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
|
||||
} else {
|
||||
|
||||
drawFrameBounds(canvas, frame);
|
||||
|
||||
drawScanLight(canvas, frame);
|
||||
|
||||
Collection<ResultPoint> currentPossible = possibleResultPoints;
|
||||
Collection<ResultPoint> currentLast = lastPossibleResultPoints;
|
||||
if (currentPossible.isEmpty()) {
|
||||
lastPossibleResultPoints = null;
|
||||
} else {
|
||||
possibleResultPoints = new HashSet<ResultPoint>(5);
|
||||
lastPossibleResultPoints = currentPossible;
|
||||
paint.setAlpha(OPAQUE);
|
||||
paint.setColor(resultPointColor);
|
||||
|
||||
if (isCircle) {
|
||||
for (ResultPoint point : currentPossible) {
|
||||
canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentLast != null) {
|
||||
paint.setAlpha(OPAQUE / 2);
|
||||
paint.setColor(resultPointColor);
|
||||
|
||||
if (isCircle) {
|
||||
for (ResultPoint point : currentLast) {
|
||||
canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描线移动的y
|
||||
private int scanLineTop;
|
||||
// 扫描线移动速度
|
||||
private int SCAN_VELOCITY;
|
||||
// 扫描线
|
||||
private Bitmap scanLight;
|
||||
// 是否展示小圆点
|
||||
private boolean isCircle;
|
||||
|
||||
/**
|
||||
* 绘制移动扫描线
|
||||
*
|
||||
* @param canvas
|
||||
* @param frame
|
||||
*/
|
||||
private void drawScanLight(Canvas canvas, Rect frame) {
|
||||
|
||||
if (scanLineTop == 0) {
|
||||
scanLineTop = frame.top;
|
||||
}
|
||||
|
||||
if (scanLineTop >= frame.bottom - 30) {
|
||||
scanLineTop = frame.top;
|
||||
} else {
|
||||
scanLineTop += SCAN_VELOCITY;
|
||||
}
|
||||
Rect scanRect = new Rect(frame.left, scanLineTop, frame.right,
|
||||
scanLineTop + 30);
|
||||
canvas.drawBitmap(scanLight, null, scanRect, paint);
|
||||
}
|
||||
|
||||
|
||||
// 扫描框边角颜色
|
||||
private int innercornercolor;
|
||||
// 扫描框边角长度
|
||||
private int innercornerlength;
|
||||
// 扫描框边角宽度
|
||||
private int innercornerwidth;
|
||||
|
||||
/**
|
||||
* 绘制取景框边框
|
||||
*
|
||||
* @param canvas
|
||||
* @param frame
|
||||
*/
|
||||
private void drawFrameBounds(Canvas canvas, Rect frame) {
|
||||
|
||||
/*paint.setColor(Color.WHITE);
|
||||
paint.setStrokeWidth(2);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
canvas.drawRect(frame, paint);*/
|
||||
|
||||
paint.setColor(innercornercolor);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
|
||||
int corWidth = innercornerwidth;
|
||||
int corLength = innercornerlength;
|
||||
|
||||
// 左上角
|
||||
canvas.drawRect(frame.left, frame.top, frame.left + corWidth, frame.top
|
||||
+ corLength, paint);
|
||||
canvas.drawRect(frame.left, frame.top, frame.left
|
||||
+ corLength, frame.top + corWidth, paint);
|
||||
// 右上角
|
||||
canvas.drawRect(frame.right - corWidth, frame.top, frame.right,
|
||||
frame.top + corLength, paint);
|
||||
canvas.drawRect(frame.right - corLength, frame.top,
|
||||
frame.right, frame.top + corWidth, paint);
|
||||
// 左下角
|
||||
canvas.drawRect(frame.left, frame.bottom - corLength,
|
||||
frame.left + corWidth, frame.bottom, paint);
|
||||
canvas.drawRect(frame.left, frame.bottom - corWidth, frame.left
|
||||
+ corLength, frame.bottom, paint);
|
||||
// 右下角
|
||||
canvas.drawRect(frame.right - corWidth, frame.bottom - corLength,
|
||||
frame.right, frame.bottom, paint);
|
||||
canvas.drawRect(frame.right - corLength, frame.bottom - corWidth,
|
||||
frame.right, frame.bottom, paint);
|
||||
}
|
||||
|
||||
|
||||
public void drawViewfinder() {
|
||||
resultBitmap = null;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void addPossibleResultPoint(ResultPoint point) {
|
||||
possibleResultPoints.add(point);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
|
||||
*/
|
||||
public static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -18,9 +18,6 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.uuzuche.lib_zxing.activity.CaptureActivity;
|
||||
import com.uuzuche.lib_zxing.activity.CodeUtils;
|
||||
import com.uuzuche.lib_zxing.activity.ZXingLibrary;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@ -36,14 +33,18 @@ import pub.doric.devkit.event.ConnectExceptionEvent;
|
||||
import pub.doric.devkit.event.EOFExceptionEvent;
|
||||
import pub.doric.devkit.event.OpenEvent;
|
||||
import pub.doric.devkit.event.StartDebugEvent;
|
||||
import pub.doric.devkit.qrcode.DisplayUtil;
|
||||
import pub.doric.devkit.qrcode.activity.CaptureActivity;
|
||||
import pub.doric.devkit.qrcode.activity.CodeUtils;
|
||||
|
||||
public class DoricDevActivity extends AppCompatActivity {
|
||||
private int REQUEST_CODE=100;
|
||||
private int REQUEST_CODE = 100;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
EventBus.getDefault().register(this);
|
||||
ZXingLibrary.initDisplayOpinion(this);
|
||||
DisplayUtil.initDisplayOpinion(getApplicationContext());
|
||||
setContentView(R.layout.layout_debug_context);
|
||||
initDisconnect();
|
||||
if (DoricDev.getInstance().isInDevMode()) {
|
||||
@ -122,14 +123,14 @@ public class DoricDevActivity extends AppCompatActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void initDisconnect(){
|
||||
private void initDisconnect() {
|
||||
LinearLayout container = findViewById(R.id.container);
|
||||
Button button=new Button(this);
|
||||
Button button = new Button(this);
|
||||
button.setText("断开连接");
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(DoricDev.getInstance().isInDevMode()){
|
||||
if (DoricDev.getInstance().isInDevMode()) {
|
||||
DoricDev.getInstance().closeDevMode();
|
||||
}
|
||||
finish();
|
||||
@ -137,6 +138,7 @@ public class DoricDevActivity extends AppCompatActivity {
|
||||
});
|
||||
container.addView(button);
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
LinearLayout container = findViewById(R.id.container);
|
||||
LayoutInflater inflater = LayoutInflater.from(this);
|
||||
|
BIN
doric-android/devkit/src/main/res/drawable/scan_light.png
Normal file
BIN
doric-android/devkit/src/main/res/drawable/scan_light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
11
doric-android/devkit/src/main/res/layout/camera.xml
Normal file
11
doric-android/devkit/src/main/res/layout/camera.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_zxing_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/preview_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<pub.doric.devkit.qrcode.view.ViewfinderView
|
||||
android:id="@+id/viewfinder_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</FrameLayout>
|
16
doric-android/devkit/src/main/res/values/attrs.xml
Normal file
16
doric-android/devkit/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="ViewfinderView">
|
||||
<attr name="inner_width" format="dimension"/>
|
||||
<attr name="inner_height" format="dimension"/>
|
||||
<attr name="inner_margintop" format="dimension" />
|
||||
<attr name="inner_corner_color" format="color" />
|
||||
<attr name="inner_corner_length" format="dimension" />
|
||||
<attr name="inner_corner_width" format="dimension" />
|
||||
<attr name="inner_scan_bitmap" format="reference" />
|
||||
<attr name="inner_scan_speed" format="integer" />
|
||||
<attr name="inner_scan_iscircle" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
32
doric-android/devkit/src/main/res/values/colors.xml
Normal file
32
doric-android/devkit/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<resources>
|
||||
|
||||
<color name="cc">#59a9ff</color>
|
||||
|
||||
<color name="bg_color">#EDEDED</color>
|
||||
<color name="contents_text">#ff000000</color>
|
||||
<color name="encode_view">#ffffffff</color>
|
||||
<color name="help_button_view">#ffcccccc</color>
|
||||
<color name="help_view">#ff404040</color>
|
||||
<color name="possible_result_points">#c0ffff00</color>
|
||||
<color name="result_image_border">#ffffffff</color>
|
||||
<color name="result_minor_text">#ffc0c0c0</color>
|
||||
<color name="result_points">#c000ff00</color>
|
||||
<color name="result_text">#ffffffff</color>
|
||||
<color name="result_view">#b0000000</color>
|
||||
<color name="sbc_header_text">#ff808080</color>
|
||||
<color name="sbc_header_view">#ffffffff</color>
|
||||
<color name="sbc_list_item">#fffff0e0</color>
|
||||
<color name="sbc_layout_view">#ffffffff</color>
|
||||
<color name="sbc_page_number_text">#ff000000</color>
|
||||
<color name="sbc_snippet_text">#ff4b4b4b</color>
|
||||
<color name="share_text">#ff000000</color>
|
||||
<color name="share_view">#ffffffff</color>
|
||||
<color name="status_view">#50000000</color>
|
||||
<color name="status_text">#ffffffff</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
<color name="viewfinder_frame">#ff000000</color>
|
||||
<color name="viewfinder_laser">#ffff0000</color>
|
||||
<color name="viewfinder_mask">#60000000</color>
|
||||
<color name="header">#58567D</color>
|
||||
|
||||
</resources>
|
29
doric-android/devkit/src/main/res/values/ids.xml
Normal file
29
doric-android/devkit/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2008 ZXing authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Messages IDs -->
|
||||
<item type="id" name="auto_focus"/>
|
||||
<item type="id" name="decode"/>
|
||||
<item type="id" name="decode_failed"/>
|
||||
<item type="id" name="decode_succeeded"/>
|
||||
<item type="id" name="encode_failed"/>
|
||||
<item type="id" name="encode_succeeded"/>
|
||||
<item type="id" name="launch_product_query"/>
|
||||
<item type="id" name="quit"/>
|
||||
<item type="id" name="restart_preview"/>
|
||||
<item type="id" name="return_scan_result"/>
|
||||
</resources>
|
Reference in New Issue
Block a user