diff --git a/doric-android/devkit/build.gradle b/doric-android/devkit/build.gradle index 78479401..91639f06 100644 --- a/doric-android/devkit/build.gradle +++ b/doric-android/devkit/build.gradle @@ -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' } diff --git a/doric-android/devkit/src/main/AndroidManifest.xml b/doric-android/devkit/src/main/AndroidManifest.xml index aab69485..a6d467f8 100644 --- a/doric-android/devkit/src/main/AndroidManifest.xml +++ b/doric-android/devkit/src/main/AndroidManifest.xml @@ -7,5 +7,6 @@ + diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/DisplayUtil.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/DisplayUtil.java new file mode 100644 index 00000000..0032846b --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/DisplayUtil.java @@ -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); + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureActivity.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureActivity.java new file mode 100644 index 00000000..5b3a8c6a --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureActivity.java @@ -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 + *

+ * 默认的二维码扫描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(); + } + }; +} \ No newline at end of file diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureFragment.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureFragment.java new file mode 100644 index 00000000..96ee21ad --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CaptureFragment.java @@ -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 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); + } + + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CodeUtils.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CodeUtils.java new file mode 100644 index 00000000..1747f77a --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/activity/CodeUtils.java @@ -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 hints = new Hashtable(2); + // 可以解析的编码类型 + Vector decodeFormats = new Vector(); + if (decodeFormats == null || decodeFormats.isEmpty()) { + decodeFormats = new Vector(); + + // 这里设置可扫描的类型,我这里选择了都支持 + 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 hints = new Hashtable(); + 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); + } + } + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/AutoFocusCallback.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/AutoFocusCallback.java new file mode 100644 index 00000000..ba9f452b --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/AutoFocusCallback.java @@ -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"); + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/BitmapLuminanceSource.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/BitmapLuminanceSource.java new file mode 100644 index 00000000..edde3dfb --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/BitmapLuminanceSource.java @@ -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; + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraConfigurationManager.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraConfigurationManager.java new file mode 100644 index 00000000..276d1029 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraConfigurationManager.java @@ -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; + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraManager.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraManager.java new file mode 100644 index 00000000..6b1df354 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/CameraManager.java @@ -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; + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/FlashlightManager.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/FlashlightManager.java new file mode 100644 index 00000000..64f02206 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/FlashlightManager.java @@ -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. + *

+ * See + * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/ and + * + * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java. + * 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() { + } + + /** + * �����������ƿ��� + */ + //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); + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PlanarYUVLuminanceSource.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PlanarYUVLuminanceSource.java new file mode 100644 index 00000000..51075f13 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PlanarYUVLuminanceSource.java @@ -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. + *

+ * 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; + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PreviewCallback.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PreviewCallback.java new file mode 100644 index 00000000..f4bbec08 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/camera/PreviewCallback.java @@ -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"); + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/CaptureActivityHandler.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/CaptureActivityHandler.java new file mode 100644 index 00000000..b2515bf8 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/CaptureActivityHandler.java @@ -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 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(); + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeFormatManager.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeFormatManager.java new file mode 100644 index 00000000..2d7ad430 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeFormatManager.java @@ -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 PRODUCT_FORMATS; + public static final Vector ONE_D_FORMATS; + public static final Vector QR_CODE_FORMATS; + public static final Vector DATA_MATRIX_FORMATS; + + static { + PRODUCT_FORMATS = new Vector(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(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(1); + QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE); + DATA_MATRIX_FORMATS = new Vector(1); + DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX); + } + + private DecodeFormatManager() { + } + + static Vector parseDecodeFormats(Intent intent) { + List 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 parseDecodeFormats(Uri inputUri) { + List 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 parseDecodeFormats(Iterable scanFormats, + String decodeMode) { + if (scanFormats != null) { + Vector formats = new Vector(); + 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; + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeHandler.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeHandler.java new file mode 100644 index 00000000..71bc825d --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeHandler.java @@ -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 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(); + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeThread.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeThread.java new file mode 100644 index 00000000..376f0f34 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/DecodeThread.java @@ -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 hints; + private Handler handler; + private final CountDownLatch handlerInitLatch; + + DecodeThread(CaptureFragment fragment, + Vector decodeFormats, + String characterSet, + ResultPointCallback resultPointCallback) { + + this.fragment = fragment; + handlerInitLatch = new CountDownLatch(1); + + hints = new Hashtable(3); + + if (decodeFormats == null || decodeFormats.isEmpty()) { + decodeFormats = new Vector(); + 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(); + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/FinishListener.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/FinishListener.java new file mode 100644 index 00000000..69f1b5d5 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/FinishListener.java @@ -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(); + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/InactivityTimer.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/InactivityTimer.java new file mode 100644 index 00000000..0c2bf389 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/InactivityTimer.java @@ -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; + } + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/Intents.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/Intents.java new file mode 100644 index 00000000..e11bf214 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/decoding/Intents.java @@ -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. + *

+ * 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" + *

+ * 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() { + } + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderResultPointCallback.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderResultPointCallback.java new file mode 100644 index 00000000..a7a7d688 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderResultPointCallback.java @@ -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); + } + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderView.java b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderView.java new file mode 100644 index 00000000..eb7e9dff --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/qrcode/view/ViewfinderView.java @@ -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 possibleResultPoints; + private Collection 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 currentPossible = possibleResultPoints; + Collection currentLast = lastPossibleResultPoints; + if (currentPossible.isEmpty()) { + lastPossibleResultPoints = null; + } else { + possibleResultPoints = new HashSet(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); + } + + +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java index aafe5446..b5900ecd 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java @@ -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); diff --git a/doric-android/devkit/src/main/res/drawable/scan_light.png b/doric-android/devkit/src/main/res/drawable/scan_light.png new file mode 100644 index 00000000..0ed22ab3 Binary files /dev/null and b/doric-android/devkit/src/main/res/drawable/scan_light.png differ diff --git a/doric-android/devkit/src/main/res/layout/camera.xml b/doric-android/devkit/src/main/res/layout/camera.xml new file mode 100644 index 00000000..6f4ac3fe --- /dev/null +++ b/doric-android/devkit/src/main/res/layout/camera.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/doric-android/devkit/src/main/res/layout/fragment_capture.xml b/doric-android/devkit/src/main/res/layout/fragment_capture.xml new file mode 100644 index 00000000..93ccdba7 --- /dev/null +++ b/doric-android/devkit/src/main/res/layout/fragment_capture.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/doric-android/devkit/src/main/res/values/attrs.xml b/doric-android/devkit/src/main/res/values/attrs.xml new file mode 100644 index 00000000..5821e1b1 --- /dev/null +++ b/doric-android/devkit/src/main/res/values/attrs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doric-android/devkit/src/main/res/values/colors.xml b/doric-android/devkit/src/main/res/values/colors.xml new file mode 100644 index 00000000..bc00646e --- /dev/null +++ b/doric-android/devkit/src/main/res/values/colors.xml @@ -0,0 +1,32 @@ + + + #59a9ff + + #EDEDED + #ff000000 + #ffffffff + #ffcccccc + #ff404040 + #c0ffff00 + #ffffffff + #ffc0c0c0 + #c000ff00 + #ffffffff + #b0000000 + #ff808080 + #ffffffff + #fffff0e0 + #ffffffff + #ff000000 + #ff4b4b4b + #ff000000 + #ffffffff + #50000000 + #ffffffff + #00000000 + #ff000000 + #ffff0000 + #60000000 + #58567D + + diff --git a/doric-android/devkit/src/main/res/values/ids.xml b/doric-android/devkit/src/main/res/values/ids.xml new file mode 100644 index 00000000..fb5c1c02 --- /dev/null +++ b/doric-android/devkit/src/main/res/values/ids.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + +