/* * Copyright (c) 2015 [1076559197@qq.com | tchen0707@gmail.com] * * 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 com.github.obsessive.simplifyreader.ui.activity.qrcode; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RadioGroup; import android.widget.RelativeLayout; import com.afollestad.materialdialogs.MaterialDialog; import com.github.obsessive.library.base.BaseSwipeBackCompatActivity; import com.github.obsessive.library.eventbus.EventCenter; import com.github.obsessive.library.netstatus.NetUtils; import com.github.obsessive.library.utils.CommonUtils; import com.github.obsessive.simplifyreader.R; import com.github.obsessive.simplifyreader.ui.activity.picker.CommonImagePickerDetailActivity; import com.github.obsessive.simplifyreader.ui.activity.picker.CommonImagePickerListActivity; import com.github.obsessive.simplifyreader.ui.activity.qrcode.camera.CameraManager; import com.github.obsessive.simplifyreader.ui.activity.qrcode.decode.DecodeThread; import com.github.obsessive.simplifyreader.ui.activity.qrcode.decode.DecodeUtils; import com.github.obsessive.simplifyreader.ui.activity.qrcode.utils.BeepManager; import com.github.obsessive.simplifyreader.ui.activity.qrcode.utils.InactivityTimer; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.ObjectAnimator; import com.nineoldandroids.animation.PropertyValuesHolder; import com.nineoldandroids.animation.ValueAnimator; import com.nineoldandroids.view.ViewHelper; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import java.io.IOException; import butterknife.InjectView; /** * This activity opens the camera and does the actual scanning on a background thread. It draws a * viewfinder to help the user place the barcode correctly, shows feedback as the image processing * is happening, and then overlays the results when a scan is successful. * * @author dswitkin@google.com (Daniel Switkin) * @author Sean Owen */ public class CaptureActivity extends BaseSwipeBackCompatActivity implements SurfaceHolder.Callback { public static final int IMAGE_PICKER_REQUEST_CODE = 100; @InjectView(R.id.capture_preview) SurfaceView capturePreview; @InjectView(R.id.capture_error_mask) ImageView captureErrorMask; @InjectView(R.id.capture_scan_mask) ImageView captureScanMask; @InjectView(R.id.capture_crop_view) FrameLayout captureCropView; @InjectView(R.id.capture_picture_btn) Button capturePictureBtn; @InjectView(R.id.capture_light_btn) Button captureLightBtn; @InjectView(R.id.capture_mode_group) RadioGroup captureModeGroup; @InjectView(R.id.capture_container) RelativeLayout captureContainer; private CameraManager cameraManager; private CaptureActivityHandler handler; private boolean hasSurface; private boolean isLightOn; private InactivityTimer mInactivityTimer; private BeepManager mBeepManager; private int mQrcodeCropWidth = 0; private int mQrcodeCropHeight = 0; private int mBarcodeCropWidth = 0; private int mBarcodeCropHeight = 0; private ObjectAnimator mScanMaskObjectAnimator = null; private Rect cropRect; private int dataMode = DecodeUtils.DECODE_DATA_MODE_QRCODE; @Override protected int getContentViewLayoutID() { return R.layout.activity_capture; } @Override protected void onEventComming(EventCenter eventCenter) { } @Override protected View getLoadingTargetView() { return null; } @Override protected void initViewsAndEvents() { hasSurface = false; mInactivityTimer = new InactivityTimer(this); mBeepManager = new BeepManager(this); initCropViewAnimator(); capturePictureBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readyGoForResult(CommonImagePickerListActivity.class, IMAGE_PICKER_REQUEST_CODE); } }); captureLightBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isLightOn) { cameraManager.setTorch(false); captureLightBtn.setSelected(false); } else { cameraManager.setTorch(true); captureLightBtn.setSelected(true); } isLightOn = !isLightOn; } }); captureModeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.capture_mode_barcode) { PropertyValuesHolder qr2barWidthVH = PropertyValuesHolder.ofFloat("width", 1.0f, (float) mBarcodeCropWidth / mQrcodeCropWidth); PropertyValuesHolder qr2barHeightVH = PropertyValuesHolder.ofFloat("height", 1.0f, (float) mBarcodeCropHeight / mQrcodeCropHeight); ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(qr2barWidthVH, qr2barHeightVH); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float fractionW = (Float) animation.getAnimatedValue("width"); Float fractionH = (Float) animation.getAnimatedValue("height"); RelativeLayout.LayoutParams parentLayoutParams = (RelativeLayout.LayoutParams) captureCropView.getLayoutParams(); parentLayoutParams.width = (int) (mQrcodeCropWidth * fractionW); parentLayoutParams.height = (int) (mQrcodeCropHeight * fractionH); captureCropView.setLayoutParams(parentLayoutParams); } }); valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { initCrop(); setDataMode(DecodeUtils.DECODE_DATA_MODE_BARCODE); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.start(); } else if (checkedId == R.id.capture_mode_qrcode) { PropertyValuesHolder bar2qrWidthVH = PropertyValuesHolder.ofFloat("width", 1.0f, (float) mQrcodeCropWidth / mBarcodeCropWidth); PropertyValuesHolder bar2qrHeightVH = PropertyValuesHolder.ofFloat("height", 1.0f, (float) mQrcodeCropHeight / mBarcodeCropHeight); ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(bar2qrWidthVH, bar2qrHeightVH); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float fractionW = (Float) animation.getAnimatedValue("width"); Float fractionH = (Float) animation.getAnimatedValue("height"); RelativeLayout.LayoutParams parentLayoutParams = (RelativeLayout.LayoutParams) captureCropView.getLayoutParams(); parentLayoutParams.width = (int) (mBarcodeCropWidth * fractionW); parentLayoutParams.height = (int) (mBarcodeCropHeight * fractionH); captureCropView.setLayoutParams(parentLayoutParams); } }); valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { initCrop(); setDataMode(DecodeUtils.DECODE_DATA_MODE_QRCODE); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.start(); } } }); } private void initCropViewAnimator() { mQrcodeCropWidth = getResources().getDimensionPixelSize(R.dimen.qrcode_crop_width); mQrcodeCropHeight = getResources().getDimensionPixelSize(R.dimen.qrcode_crop_height); mBarcodeCropWidth = getResources().getDimensionPixelSize(R.dimen.barcode_crop_width); mBarcodeCropHeight = getResources().getDimensionPixelSize(R.dimen.barcode_crop_height); } @Override protected void onNetworkConnected(NetUtils.NetType type) { } @Override protected void onNetworkDisConnected() { } @Override protected boolean isApplyStatusBarTranslucency() { return true; } @Override protected boolean isBindEventBusHere() { return false; } @Override protected boolean toggleOverridePendingTransition() { return true; } @Override protected TransitionMode getOverridePendingTransitionMode() { return TransitionMode.RIGHT; } public Handler getHandler() { return handler; } public CameraManager getCameraManager() { return cameraManager; } public void initCrop() { int cameraWidth = cameraManager.getCameraResolution().y; int cameraHeight = cameraManager.getCameraResolution().x; int[] location = new int[2]; captureCropView.getLocationInWindow(location); int cropLeft = location[0]; int cropTop = location[1]; int cropWidth = captureCropView.getWidth(); int cropHeight = captureCropView.getHeight(); int containerWidth = captureContainer.getWidth(); int containerHeight = captureContainer.getHeight(); int x = cropLeft * cameraWidth / containerWidth; int y = cropTop * cameraHeight / containerHeight; int width = cropWidth * cameraWidth / containerWidth; int height = cropHeight * cameraHeight / containerHeight; setCropRect(new Rect(x, y, width + x, height + y)); } @Override protected void onResume() { super.onResume(); // CameraManager must be initialized here, not in onCreate(). This is necessary because we don't // want to open the camera driver and measure the screen size if we're going to show the help on // first launch. That led to bugs where the scanning rectangle was the wrong size and partially // off screen. cameraManager = new CameraManager(getApplication()); handler = null; if (hasSurface) { // The activity was paused but not stopped, so the surface still exists. Therefore // surfaceCreated() won't be called, so init the camera here. initCamera(capturePreview.getHolder()); } else { // Install the callback and wait for surfaceCreated() to init the camera. capturePreview.getHolder().addCallback(this); } mInactivityTimer.onResume(); } @Override protected void onPause() { if (handler != null) { handler.quitSynchronously(); handler = null; } mBeepManager.close(); mInactivityTimer.onPause(); cameraManager.closeDriver(); if (!hasSurface) { capturePreview.getHolder().removeCallback(this); } if (null != mScanMaskObjectAnimator && mScanMaskObjectAnimator.isStarted()) { mScanMaskObjectAnimator.cancel(); } super.onPause(); } @Override protected void onDestroy() { mInactivityTimer.shutdown(); super.onDestroy(); } @Override protected void getBundleExtras(Bundle extras) { } @Override public void surfaceCreated(SurfaceHolder holder) { if (holder == null) { Log.e(TAG_LOG, "*** WARNING *** surfaceCreated() gave us a null surface!"); } if (!hasSurface) { hasSurface = true; // initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { initCamera(holder); } /** * A valid barcode has been found, so give an indication of success and show the results. */ public void handleDecode(String result, Bundle bundle) { mInactivityTimer.onActivity(); mBeepManager.playBeepSoundAndVibrate(); if (!CommonUtils.isEmpty(result) && CommonUtils.isUrl(result)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(result)); startActivity(intent); } else { bundle.putString(ResultActivity.BUNDLE_KEY_SCAN_RESULT, result); readyGo(ResultActivity.class, bundle); } } private void onCameraPreviewSuccess() { initCrop(); captureErrorMask.setVisibility(View.GONE); ViewHelper.setPivotX(captureScanMask, 0.0f); ViewHelper.setPivotY(captureScanMask, 0.0f); mScanMaskObjectAnimator = ObjectAnimator.ofFloat(captureScanMask, "scaleY", 0.0f, 1.0f); mScanMaskObjectAnimator.setDuration(2000); mScanMaskObjectAnimator.setInterpolator(new DecelerateInterpolator()); mScanMaskObjectAnimator.setRepeatCount(-1); mScanMaskObjectAnimator.setRepeatMode(ObjectAnimator.RESTART); mScanMaskObjectAnimator.start(); } private void initCamera(SurfaceHolder surfaceHolder) { if (surfaceHolder == null) { throw new IllegalStateException("No SurfaceHolder provided"); } if (cameraManager.isOpen()) { Log.w(TAG_LOG, "initCamera() while already open -- late SurfaceView callback?"); return; } try { cameraManager.openDriver(surfaceHolder); // Creating the handler starts the preview, which can also throw a RuntimeException. if (handler == null) { handler = new CaptureActivityHandler(this, cameraManager); } onCameraPreviewSuccess(); } catch (IOException ioe) { Log.w(TAG_LOG, ioe); displayFrameworkBugMessageAndExit(); } catch (RuntimeException e) { // Barcode Scanner has seen crashes in the wild of this variety: // java.?lang.?RuntimeException: Fail to connect to camera service Log.w(TAG_LOG, "Unexpected error initializing camera", e); displayFrameworkBugMessageAndExit(); } } private void displayFrameworkBugMessageAndExit() { captureErrorMask.setVisibility(View.VISIBLE); final MaterialDialog.Builder builder = new MaterialDialog.Builder(this); builder.cancelable(true); builder.title(R.string.app_name); builder.content(R.string.tips_open_camera_error); builder.positiveText(R.string.btn_ok); builder.callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { super.onPositive(dialog); finish(); } }); builder.show(); } public Rect getCropRect() { return cropRect; } public void setCropRect(Rect cropRect) { this.cropRect = cropRect; } public int getDataMode() { return dataMode; } public void setDataMode(int dataMode) { this.dataMode = dataMode; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) { return; } if (requestCode == IMAGE_PICKER_REQUEST_CODE) { String imagePath = data.getStringExtra(CommonImagePickerDetailActivity .KEY_BUNDLE_RESULT_IMAGE_PATH); if (!CommonUtils.isEmpty(imagePath)) { ImageLoader.getInstance().loadImage("file://" + imagePath, new ImageLoadingListener() { @Override public void onLoadingStarted(String imageUri, View view) { } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { } @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { String resultZxing = new DecodeUtils(DecodeUtils.DECODE_DATA_MODE_ALL) .decodeWithZxing(loadedImage); String resultZbar = new DecodeUtils(DecodeUtils.DECODE_DATA_MODE_ALL) .decodeWithZbar(loadedImage); if (!CommonUtils.isEmpty(resultZbar)) { Bundle extras = new Bundle(); extras.putInt(DecodeThread.DECODE_MODE, DecodeUtils.DECODE_MODE_ZBAR); handleDecode(resultZbar, extras); } else if (!CommonUtils.isEmpty(resultZxing)) { Bundle extras = new Bundle(); extras.putInt(DecodeThread.DECODE_MODE, DecodeUtils.DECODE_MODE_ZXING); handleDecode(resultZxing, extras); } else { showToast(getResources().getString(R.string.tips_decode_null)); } } @Override public void onLoadingCancelled(String imageUri, View view) { } }); } } } }