package org.telegram.ui; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ImageFormat; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Typeface; import android.graphics.drawable.ShapeDrawable; import android.hardware.Camera; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.util.SparseArray; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import androidx.core.graphics.ColorUtils; import com.google.android.gms.vision.Frame; import com.google.android.gms.vision.barcode.Barcode; import com.google.android.gms.vision.barcode.BarcodeDetector; import com.google.zxing.BinaryBitmap; import com.google.zxing.LuminanceSource; import com.google.zxing.PlanarYUVLuminanceSource; import com.google.zxing.RGBLuminanceSource; import com.google.zxing.Result; import com.google.zxing.common.GlobalHistogramBinarizer; import com.google.zxing.qrcode.QRCodeReader; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MrzRecognizer; import org.telegram.messenger.R; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.camera.CameraController; import org.telegram.messenger.camera.CameraSession; import org.telegram.messenger.camera.CameraView; import org.telegram.messenger.camera.Size; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.TextPaintWebpageUrlSpan; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanNoUnderline; import java.nio.ByteBuffer; import java.util.ArrayList; @TargetApi(18) public class CameraScanActivity extends BaseFragment implements Camera.PreviewCallback { private TextView titleTextView; private TextView descriptionText; private CameraView cameraView; private HandlerThread backgroundHandlerThread = new HandlerThread("ScanCamera"); private Handler handler; private TextView recognizedMrzView; private Paint paint = new Paint(); private Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Path path = new Path(); private ImageView galleryButton; private ImageView flashButton; private AnimatorSet flashAnimator; private CameraScanActivityDelegate delegate; private boolean recognized; private QRCodeReader qrReader; private BarcodeDetector visionQrReader; private boolean needGalleryButton; private int currentType; public static final int TYPE_MRZ = 0; public static final int TYPE_QR = 1; public static final int TYPE_QR_LOGIN = 2; public interface CameraScanActivityDelegate { default void didFindMrzInfo(MrzRecognizer.Result result) { } default void didFindQr(String text) { } } public static ActionBarLayout[] showAsSheet(BaseFragment parentFragment, boolean gallery, int type, CameraScanActivityDelegate delegate) { if (parentFragment == null || parentFragment.getParentActivity() == null) { return null; } ActionBarLayout[] actionBarLayout = new ActionBarLayout[]{new ActionBarLayout(parentFragment.getParentActivity())}; BottomSheet bottomSheet = new BottomSheet(parentFragment.getParentActivity(), false) { { actionBarLayout[0].init(new ArrayList<>()); CameraScanActivity fragment = new CameraScanActivity(type) { @Override public void finishFragment() { dismiss(); } @Override public void removeSelfFromStack() { dismiss(); } }; fragment.needGalleryButton = gallery; actionBarLayout[0].addFragmentToStack(fragment); actionBarLayout[0].showLastFragment(); actionBarLayout[0].setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); fragment.setDelegate(delegate); containerView = actionBarLayout[0]; setApplyBottomPadding(false); setApplyBottomPadding(false); setOnDismissListener(dialog -> fragment.onFragmentDestroy()); } @Override protected boolean canDismissWithSwipe() { return false; } @Override public void onBackPressed() { if (actionBarLayout[0] == null || actionBarLayout[0].fragmentsStack.size() <= 1) { super.onBackPressed(); } else { actionBarLayout[0].onBackPressed(); } } @Override public void dismiss() { super.dismiss(); actionBarLayout[0] = null; } }; bottomSheet.show(); return actionBarLayout; } public CameraScanActivity(int type) { super(); CameraController.getInstance().initCamera(() -> { // if (cameraView != null) { // cameraView.initCamera(); // } }); currentType = type; if (isQr()) { qrReader = new QRCodeReader(); visionQrReader = new BarcodeDetector.Builder(ApplicationLoader.applicationContext).setBarcodeFormats(Barcode.QR_CODE).build(); } } @Override public void onFragmentDestroy() { super.onFragmentDestroy(); destroy(false, null); if (getParentActivity() != null) { getParentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } if (visionQrReader != null) { visionQrReader.release(); } } @Override public boolean onFragmentCreate() { return super.onFragmentCreate(); } @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), false); actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); actionBar.setCastShadows(false); if (!AndroidUtilities.isTablet() && !isQr()) { actionBar.showActionModeTop(); } actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { finishFragment(); } } }); paint.setColor(0x7f000000); cornerPaint.setColor(0xffffffff); cornerPaint.setStyle(Paint.Style.STROKE); cornerPaint.setStrokeWidth(AndroidUtilities.dp(4)); cornerPaint.setStrokeJoin(Paint.Join.ROUND); ViewGroup viewGroup = new ViewGroup(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); actionBar.measure(widthMeasureSpec, heightMeasureSpec); if (currentType == TYPE_MRZ) { cameraView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (width * 0.704f), MeasureSpec.EXACTLY)); } else { cameraView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); recognizedMrzView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); if (galleryButton != null) { galleryButton.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); } flashButton.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); } titleTextView.measure(MeasureSpec.makeMeasureSpec(width - AndroidUtilities.dp(72), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); descriptionText.measure(MeasureSpec.makeMeasureSpec((int) (width * 0.9f), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED)); setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int width = r - l; int height = b - t; int y = 0; if (currentType == TYPE_MRZ) { cameraView.layout(0, y, cameraView.getMeasuredWidth(), y + cameraView.getMeasuredHeight()); y = (int) (height * 0.65f); titleTextView.layout(AndroidUtilities.dp(36), y, AndroidUtilities.dp(36) + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); recognizedMrzView.setTextSize(TypedValue.COMPLEX_UNIT_PX, cameraView.getMeasuredHeight() / 22); recognizedMrzView.setPadding(0, 0, 0, cameraView.getMeasuredHeight() / 15); } else { actionBar.layout(0, 0, actionBar.getMeasuredWidth(), actionBar.getMeasuredHeight()); cameraView.layout(0, 0, cameraView.getMeasuredWidth(), cameraView.getMeasuredHeight()); int size = (int) (Math.min(cameraView.getWidth(), cameraView.getHeight()) / 1.5f); if (currentType == TYPE_QR) { y = (cameraView.getMeasuredHeight() - size) / 2 - titleTextView.getMeasuredHeight() - AndroidUtilities.dp(30); } else { y = (cameraView.getMeasuredHeight() - size) / 2 - titleTextView.getMeasuredHeight() - AndroidUtilities.dp(64); } titleTextView.layout(AndroidUtilities.dp(36), y, AndroidUtilities.dp(36) + titleTextView.getMeasuredWidth(), y + titleTextView.getMeasuredHeight()); recognizedMrzView.layout(0, getMeasuredHeight() - recognizedMrzView.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); int x; if (needGalleryButton) { x = cameraView.getMeasuredWidth() / 2 + AndroidUtilities.dp(35); } else { x = cameraView.getMeasuredWidth() / 2 - flashButton.getMeasuredWidth() / 2; } y = (cameraView.getMeasuredHeight() - size) / 2 + size + AndroidUtilities.dp(30); flashButton.layout(x, y, x + flashButton.getMeasuredWidth(), y + flashButton.getMeasuredHeight()); if (galleryButton != null) { x = cameraView.getMeasuredWidth() / 2 - AndroidUtilities.dp(35) - galleryButton.getMeasuredWidth(); galleryButton.layout(x, y, x + galleryButton.getMeasuredWidth(), y + galleryButton.getMeasuredHeight()); } } y = (int) (height * 0.74f); int x = (int) (width * 0.05f); descriptionText.layout(x, y, x + descriptionText.getMeasuredWidth(), y + descriptionText.getMeasuredHeight()); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean result = super.drawChild(canvas, child, drawingTime); if (isQr() && child == cameraView) { int size = (int) (Math.min(child.getWidth(), child.getHeight()) / 1.5f); int x = (child.getWidth() - size) / 2; int y = (child.getHeight() - size) / 2; canvas.drawRect(0, 0, child.getMeasuredWidth(), y, paint); canvas.drawRect(0, y + size, child.getMeasuredWidth(), child.getMeasuredHeight(), paint); canvas.drawRect(0, y, x, y + size, paint); canvas.drawRect(x + size, y, child.getMeasuredWidth(), y + size, paint); path.reset(); path.moveTo(x, y + AndroidUtilities.dp(20)); path.lineTo(x, y); path.lineTo(x + AndroidUtilities.dp(20), y); canvas.drawPath(path, cornerPaint); path.reset(); path.moveTo(x + size, y + AndroidUtilities.dp(20)); path.lineTo(x + size, y); path.lineTo(x + size - AndroidUtilities.dp(20), y); canvas.drawPath(path, cornerPaint); path.reset(); path.moveTo(x, y + size - AndroidUtilities.dp(20)); path.lineTo(x, y + size); path.lineTo(x + AndroidUtilities.dp(20), y + size); canvas.drawPath(path, cornerPaint); path.reset(); path.moveTo(x + size, y + size - AndroidUtilities.dp(20)); path.lineTo(x + size, y + size); path.lineTo(x + size - AndroidUtilities.dp(20), y + size); canvas.drawPath(path, cornerPaint); } return result; } }; viewGroup.setOnTouchListener((v, event) -> true); fragmentView = viewGroup; cameraView = new CameraView(context, false); cameraView.setUseMaxPreview(true); cameraView.setOptimizeForBarcode(true); cameraView.setDelegate(new CameraView.CameraViewDelegate() { @Override public void onCameraCreated(Camera camera) { } @Override public void onCameraInit() { startRecognizing(); } }); viewGroup.addView(cameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); if (currentType == TYPE_MRZ) { actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } else { actionBar.setBackgroundDrawable(null); actionBar.setAddToContainer(false); actionBar.setItemsColor(0xffffffff, false); actionBar.setItemsBackgroundColor(0x22ffffff, false); viewGroup.setBackgroundColor(Theme.getColor(Theme.key_wallet_blackBackground)); viewGroup.addView(actionBar); } if (currentType == TYPE_QR_LOGIN) { actionBar.setTitle(LocaleController.getString("AuthAnotherClientScan", R.string.AuthAnotherClientScan)); } Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG); selectionPaint.setColor(ColorUtils.setAlphaComponent(Color.WHITE, 100)); titleTextView = new TextView(context) { LinkPath textPath; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (getText() instanceof Spanned) { Spanned spanned = (Spanned) getText(); URLSpanNoUnderline[] innerSpans = spanned.getSpans(0, spanned.length(), URLSpanNoUnderline.class); if (innerSpans != null && innerSpans.length > 0) { textPath = new LinkPath(true); textPath.setAllowReset(false); for (int a = 0; a < innerSpans.length; a++) { int start = spanned.getSpanStart(innerSpans[a]); int end = spanned.getSpanEnd(innerSpans[a]); textPath.setCurrentLayout(getLayout(), start, 0); int shift = getText() != null ? getPaint().baselineShift : 0; textPath.setBaselineShift(shift != 0 ? shift + AndroidUtilities.dp(shift > 0 ? 5 : -2) : 0); getLayout().getSelectionPath(start, end, textPath); } textPath.setAllowReset(true); } } } @Override protected void onDraw(Canvas canvas) { if (textPath != null) { canvas.drawPath(textPath, selectionPaint); } super.onDraw(canvas); } }; titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24); viewGroup.addView(titleTextView); descriptionText = new TextView(context); descriptionText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); descriptionText.setGravity(Gravity.CENTER_HORIZONTAL); descriptionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); viewGroup.addView(descriptionText); recognizedMrzView = new TextView(context); recognizedMrzView.setTextColor(0xffffffff); recognizedMrzView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); recognizedMrzView.setAlpha(0); if (currentType == TYPE_MRZ) { titleTextView.setText(LocaleController.getString("PassportScanPassport", R.string.PassportScanPassport)); descriptionText.setText(LocaleController.getString("PassportScanPassportInfo", R.string.PassportScanPassportInfo)); titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); recognizedMrzView.setTypeface(Typeface.MONOSPACE); cameraView.addView(recognizedMrzView); } else { if (needGalleryButton) { //titleTextView.setText(LocaleController.getString("WalletScanCode", R.string.WalletScanCode)); } else { if (currentType == TYPE_QR) { titleTextView.setText(LocaleController.getString("AuthAnotherClientScan", R.string.AuthAnotherClientScan)); } else { String text = LocaleController.getString("AuthAnotherClientInfo5", R.string.AuthAnotherClientInfo5); SpannableStringBuilder spanned = new SpannableStringBuilder(text); int index1 = text.indexOf('*'); int index2 = text.indexOf('*', index1 + 1); if (index1 != -1 && index2 != -1 && index1 != index2) { titleTextView.setMovementMethod(new AndroidUtilities.LinkMovementMethodMy()); spanned.replace(index2, index2 + 1, " "); spanned.replace(index1, index1 + 1, " "); index1 += 1; index2 += 1; spanned.setSpan(new URLSpanNoUnderline(LocaleController.getString("AuthAnotherClientDownloadClientUrl", R.string.AuthAnotherClientDownloadClientUrl)), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spanned.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } text = spanned.toString(); index1 = text.indexOf('*'); index2 = text.indexOf('*', index1 + 1); if (index1 != -1 && index2 != -1 && index1 != index2) { spanned.replace(index2, index2 + 1, " "); spanned.replace(index1, index1 + 1, " "); index1 += 1; index2 += 1; spanned.setSpan(new URLSpanNoUnderline(LocaleController.getString("AuthAnotherWebClientUrl", R.string.AuthAnotherWebClientUrl)), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spanned.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } titleTextView.setLinkTextColor(Color.WHITE); titleTextView.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); titleTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); titleTextView.setPadding(0, 0, 0, 0); titleTextView.setText(spanned); } } titleTextView.setTextColor(0xffffffff); recognizedMrzView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); recognizedMrzView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), AndroidUtilities.dp(10)); if (needGalleryButton) { //recognizedMrzView.setText(LocaleController.getString("WalletScanCodeNotFound", R.string.WalletScanCodeNotFound)); } else { recognizedMrzView.setText(LocaleController.getString("AuthAnotherClientNotFound", R.string.AuthAnotherClientNotFound)); } viewGroup.addView(recognizedMrzView); if (needGalleryButton) { galleryButton = new ImageView(context); galleryButton.setScaleType(ImageView.ScaleType.CENTER); galleryButton.setImageResource(R.drawable.qr_gallery); galleryButton.setBackgroundDrawable(Theme.createSelectorDrawableFromDrawables(Theme.createCircleDrawable(AndroidUtilities.dp(60), 0x22ffffff), Theme.createCircleDrawable(AndroidUtilities.dp(60), 0x44ffffff))); viewGroup.addView(galleryButton); galleryButton.setOnClickListener(currentImage -> { if (getParentActivity() == null) { return; } if (Build.VERSION.SDK_INT >= 23) { if (getParentActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { getParentActivity().requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 4); return; } } PhotoAlbumPickerActivity fragment = new PhotoAlbumPickerActivity(PhotoAlbumPickerActivity.SELECT_TYPE_QR, false, false, null); fragment.setMaxSelectedPhotos(1, false); fragment.setAllowSearchImages(false); fragment.setDelegate(new PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate() { @Override public void didSelectPhotos(ArrayList photos, boolean notify, int scheduleDate) { try { if (!photos.isEmpty()) { SendMessagesHelper.SendingMediaInfo info = photos.get(0); if (info.path != null) { Point screenSize = AndroidUtilities.getRealScreenSize(); Bitmap bitmap = ImageLoader.loadBitmap(info.path, null, screenSize.x, screenSize.y, true); String text = tryReadQr(null, null, 0, 0, 0, bitmap); if (text != null) { if (delegate != null) { delegate.didFindQr(text); } removeSelfFromStack(); } } } } catch (Throwable e) { FileLog.e(e); } } @Override public void startPhotoSelectActivity() { try { Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); photoPickerIntent.setType("image/*"); getParentActivity().startActivityForResult(photoPickerIntent, 11); } catch (Exception e) { FileLog.e(e); } } }); presentFragment(fragment); }); } flashButton = new ImageView(context); flashButton.setScaleType(ImageView.ScaleType.CENTER); flashButton.setImageResource(R.drawable.qr_flashlight); flashButton.setBackgroundDrawable(Theme.createCircleDrawable(AndroidUtilities.dp(60), 0x22ffffff)); viewGroup.addView(flashButton); flashButton.setOnClickListener(currentImage -> { CameraSession session = cameraView.getCameraSession(); if (session != null) { ShapeDrawable shapeDrawable = (ShapeDrawable) flashButton.getBackground(); if (flashAnimator != null) { flashAnimator.cancel(); flashAnimator = null; } flashAnimator = new AnimatorSet(); ObjectAnimator animator = ObjectAnimator.ofInt(shapeDrawable, AnimationProperties.SHAPE_DRAWABLE_ALPHA, flashButton.getTag() == null ? 0x44 : 0x22); animator.addUpdateListener(animation -> flashButton.invalidate()); flashAnimator.playTogether(animator); flashAnimator.setDuration(200); flashAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); flashAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { flashAnimator = null; } }); flashAnimator.start(); if (flashButton.getTag() == null) { flashButton.setTag(1); session.setTorchEnabled(true); } else { flashButton.setTag(null); session.setTorchEnabled(false); } } }); } if (getParentActivity() != null) { getParentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } fragmentView.setKeepScreenOn(true); return fragmentView; } @Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { if (requestCode == 11) { if (data == null || data.getData() == null) { return; } try { Point screenSize = AndroidUtilities.getRealScreenSize(); Bitmap bitmap = ImageLoader.loadBitmap(null, data.getData(), screenSize.x, screenSize.y, true); String text = tryReadQr(null, null, 0, 0, 0, bitmap); if (text != null) { if (delegate != null) { delegate.didFindQr(text); } finishFragment(); } } catch (Throwable e) { FileLog.e(e); } } } } public void setDelegate(CameraScanActivityDelegate cameraScanActivityDelegate) { delegate = cameraScanActivityDelegate; } public void destroy(boolean async, final Runnable beforeDestroyRunnable) { if (cameraView != null) { cameraView.destroy(async, beforeDestroyRunnable); cameraView = null; } backgroundHandlerThread.quitSafely(); } private void startRecognizing() { backgroundHandlerThread.start(); handler = new Handler(backgroundHandlerThread.getLooper()); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (cameraView != null && !recognized && cameraView.getCameraSession() != null) { cameraView.getCameraSession().setOneShotPreviewCallback(org.telegram.ui.CameraScanActivity.this); AndroidUtilities.runOnUIThread(this, 500); } } }); } private void onNoQrFound() { AndroidUtilities.runOnUIThread(() -> { if (recognizedMrzView.getTag() != null) { recognizedMrzView.setTag(null); recognizedMrzView.animate().setDuration(200).alpha(0.0f).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); } }); } @Override public void onPreviewFrame(final byte[] data, final Camera camera) { handler.post(() -> { try { Size size = cameraView.getPreviewSize(); if (currentType == TYPE_MRZ) { final MrzRecognizer.Result res = MrzRecognizer.recognize(data, size.getWidth(), size.getHeight(), cameraView.getCameraSession().getDisplayOrientation()); if (res != null && !TextUtils.isEmpty(res.firstName) && !TextUtils.isEmpty(res.lastName) && !TextUtils.isEmpty(res.number) && res.birthDay != 0 && (res.expiryDay != 0 || res.doesNotExpire) && res.gender != MrzRecognizer.Result.GENDER_UNKNOWN) { recognized = true; camera.stopPreview(); AndroidUtilities.runOnUIThread(() -> { recognizedMrzView.setText(res.rawMRZ); recognizedMrzView.animate().setDuration(200).alpha(1f).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); if (delegate != null) { delegate.didFindMrzInfo(res); } AndroidUtilities.runOnUIThread(this::finishFragment, 1200); }); } } else { int format = camera.getParameters().getPreviewFormat(); int side = (int) (Math.min(size.getWidth(), size.getHeight()) / 1.5f); int x = (size.getWidth() - side) / 2; int y = (size.getHeight() - side) / 2; String text = tryReadQr(data, size, x, y, side, null); if (text != null) { recognized = true; camera.stopPreview(); AndroidUtilities.runOnUIThread(() -> { if (delegate != null) { delegate.didFindQr(text); } finishFragment(); }); } } } catch (Throwable ignore) { onNoQrFound(); } }); } private String tryReadQr(byte[] data, Size size, int x, int y, int side, Bitmap bitmap) { try { String text; if (visionQrReader.isOperational()) { Frame frame; if (bitmap != null) { frame = new Frame.Builder().setBitmap(bitmap).build(); } else { frame = new Frame.Builder().setImageData(ByteBuffer.wrap(data), size.getWidth(), size.getHeight(), ImageFormat.NV21).build(); } SparseArray codes = visionQrReader.detect(frame); if (codes != null && codes.size() > 0) { text = codes.valueAt(0).rawValue; } else { text = null; } } else { LuminanceSource source; if (bitmap != null) { int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()]; bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray); } else { source = new PlanarYUVLuminanceSource(data, size.getWidth(), size.getHeight(), x, y, side, side, false); } Result result = qrReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source))); if (result == null) { onNoQrFound(); return null; } text = result.getText(); } if (TextUtils.isEmpty(text)) { onNoQrFound(); return null; } if (needGalleryButton) { if (!text.startsWith("ton://transfer/")) { //onNoWalletFound(bitmap != null); return null; } Uri uri = Uri.parse(text); String path = uri.getPath().replace("/", ""); } else { if (!text.startsWith("tg://login?token=")) { onNoQrFound(); return null; } } return text; } catch (Throwable ignore) { onNoQrFound(); } return null; } private boolean isQr() { return currentType == TYPE_QR || currentType == TYPE_QR_LOGIN; } @Override public ArrayList getThemeDescriptions() { ArrayList themeDescriptions = new ArrayList<>(); if (isQr()) { return themeDescriptions; } themeDescriptions.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); themeDescriptions.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarWhiteSelector)); themeDescriptions.add(new ThemeDescription(titleTextView, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); themeDescriptions.add(new ThemeDescription(descriptionText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6)); return themeDescriptions; } }