All Products
Search
Document Center

Use the scan feature in custom UI

Last Updated: Nov 26, 2021

This topic describes how to customize a UI and add the scan capability in the custom UI to a project. This process involves the following four steps:

  1. Create a dependency project

  2. Create and customize a UI in the dependency project

  3. Use the scan feature in the dependency project

  4. Call the scan feature in your custom UI in the main project

Procedure

Create a dependency project

  1. Choose File > New > New Module. 1

  2. Select Android Library and click Next. 2

  3. Enter custom in Module name and click Finish. 3

Create and customize a UI in the dependency project

  1. Create a widget package in the com.example.custom package of the custom module. In the widget package, add the APSurfaceTexture class that inherits from the SurfaceTexture class to capture image streams.

    public class APSurfaceTexture extends SurfaceTexture {
    
     private static final String TAG = "APSurfaceTexture";
    
     public SurfaceTexture mSurface;
    
     public APSurfaceTexture() {
         super(0);
     }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void attachToGLContext(int texName) {
         mSurface.attachToGLContext(texName);
     }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void detachFromGLContext() {
         try {
             mSurface.detachFromGLContext();
         } catch (Exception ex) {
             try {
                 Method nativeMethod = SurfaceTexture.class.getDeclaredMethod("nativeDetachFromGLContext");
                 nativeMethod.setAccessible(true);
                 int retCode = (Integer) nativeMethod.invoke(mSurface);
                 LoggerFactory.getTraceLogger().debug(TAG, "nativeDetachFromGLContext invoke retCode:" + retCode);
             } catch (Exception e) {
                 LoggerFactory.getTraceLogger().error(TAG, "nativeDetachFromGLContext invoke exception:" + e.getMessage());
             }
             LoggerFactory.getTraceLogger().error(TAG, "mSurface.detachFromGLContext() exception:" + ex.getMessage());
         }
     }
    
     @Override
     public boolean equals(Object o) {
         return mSurface.equals(o);
     }
    
     @Override
     public long getTimestamp() {
         return mSurface.getTimestamp();
     }
    
     @Override
     public void getTransformMatrix(float[] mtx) {
         mSurface.getTransformMatrix(mtx);
     }
    
     @Override
     public void release() {
         super.release();
         mSurface.release();
     }
    
     @Override
     public int hashCode() {
         return mSurface.hashCode();
     }
    
     @TargetApi(Build.VERSION_CODES.KITKAT)
     @Override
     public void releaseTexImage() {
         mSurface.releaseTexImage();
     }
    
     @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
     @Override
     public void setDefaultBufferSize(int width, int height) {
         mSurface.setDefaultBufferSize(width, height);
     }
    
     @Override
     public void setOnFrameAvailableListener(OnFrameAvailableListener listener) {
         mSurface.setOnFrameAvailableListener(listener);
     }
    
     @Override
     public String toString() {
         return mSurface.toString();
     }
    
     @Override
     public void updateTexImage() {
         mSurface.updateTexImage();
     }
    }
  2. In the widget package of the custom module, add the APTextureView class that inherits from the TextureView class for the display of image streams.

    public class APTextureView extends TextureView {
    
     private static final String TAG = "APTextureView";
    
     private Field mSurfaceField;
    
     public APTextureView(Context context) {
         super(context);
     }
    
     public APTextureView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
    
     public APTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
    
     @Override
     protected void onDetachedFromWindow() {
         try {
             super.onDetachedFromWindow();
         } catch (Exception ex) {
             LoggerFactory.getTraceLogger().error(TAG, "onDetachedFromWindow exception:" + ex.getMessage());
         }
     }
    
     @Override
     public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
         super.setSurfaceTexture(surfaceTexture);
         afterSetSurfaceTexture();
     }
    
     private void afterSetSurfaceTexture() {
         LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture Build.VERSION.SDK_INT:" + Build.VERSION.SDK_INT);
         if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 20) {
             return;
         }
    
         try {
             if (mSurfaceField == null) {
                 mSurfaceField = TextureView.class.getDeclaredField("mSurface");
                 mSurfaceField.setAccessible(true);
             }
    
             SurfaceTexture innerSurface = (SurfaceTexture) mSurfaceField.get(this);
             if (innerSurface != null) {
                 if (!(innerSurface instanceof APSurfaceTexture)) {
                     APSurfaceTexture wrapSurface = new APSurfaceTexture();
                     wrapSurface.mSurface = innerSurface;
                     mSurfaceField.set(this, wrapSurface);
                     LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture wrap mSurface");
                 }
             }
         } catch (Exception ex) {
             LoggerFactory.getTraceLogger().error(TAG, "afterSetSurfaceTexture exception:" + ex.getMessage());
         }
     }
    }
  3. In the com.example.custom package, create a Utils class for the conversion of images.

    public class Utils {
    
     private static String TAG = "Utils";
    
     public static void toast(Context context, String msg) {
         Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
     }
    
     public static Bitmap changeBitmapColor(Bitmap bitmap, int color) {
         int bitmap_w = bitmap.getWidth();
         int bitmap_h = bitmap.getHeight();
         int[] arrayColor = new int[bitmap_w * bitmap_h];
    
         int count = 0;
         for (int i = 0; i < bitmap_h; i++) {
             for (int j = 0; j < bitmap_w; j++) {
    
                 int originColor = bitmap.getPixel(j, i);
                 // Non-transparent area
                 if (originColor != 0) {
                     originColor = color;
                 }
    
                 arrayColor[count] = originColor;
                 count++;
             }
         }
         return Bitmap.createBitmap(arrayColor, bitmap_w, bitmap_h, Bitmap.Config.ARGB_8888);
     }
    
     public static Bitmap uri2Bitmap(Context context, Uri uri) {
         Bitmap bitmap = null;
         InputStream in;
         try {
             in = context.getContentResolver().openInputStream(uri);
             if (in != null) {
                 bitmap = BitmapFactory.decodeStream(in);
                 in.close();
             }
         } catch (Exception e) {
             LoggerFactory.getTraceLogger().error(TAG, "uri2Bitmap: Exception " + e.getMessage());
         }
         return bitmap;
     }
    }
  4. In the custom module, create an attrs.xml file under the res > values directory, and add the following code to the file.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
     <declare-styleable name="scan">
         <attr name="shadowColor" format="color" />
     </declare-styleable>
    </resources>
  5. Create a drawable folder under the res directory of the custom module, and copy the resource files to the drawable folder, as shown in the figure below.12

  6. In the widget package of the custom module, add the FinderView class that inherits from the View class. Add the following code to implement the drawing of the scan window, corners, and border shadows.

    public class FinderView extends View {
    
     private static final int DEFAULT_SHADOW_COLOR = 0x96000000;
    
     private int scanWindowLeft, scanWindowTop, scanWindowRight, scanWindowBottom;
     private Bitmap leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner;
     private Paint paint;
     private int shadowColor;
    
     public FinderView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         init(context, attrs);
     }
    
     public FinderView(Context context, AttributeSet attrs) {
         super(context, attrs);
         init(context, attrs);
     }
    
     private void init(Context context, AttributeSet attrs) {
         applyConfig(context, attrs);
         setVisibility(INVISIBLE);
         initCornerBitmap(context);
    
         paint = new Paint();
         paint.setAntiAlias(true);
     }
    
     private void applyConfig(Context context, AttributeSet attrs) {
         if (attrs != null) {
             TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.scan);
             shadowColor = typedArray.getColor(R.styleable.scan_shadowColor, DEFAULT_SHADOW_COLOR);
             typedArray.recycle();
         }
     }
     //Initialize the corner style of the scan window.
     private void initCornerBitmap(Context context) {
         Resources res = context.getResources();
         leftTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_top);
         rightTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_top);
         leftBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_bottom);
         rightBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_bottom);
     }
    
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
         drawShadow(canvas);
         drawCorner(canvas);
     }
     //Draw the corner styles of the scan window.
     private void drawCorner(Canvas canvas) {
         paint.setAlpha(255);
         canvas.drawBitmap(leftTopCorner, scanWindowLeft, scanWindowTop, paint);
         canvas.drawBitmap(rightTopCorner, scanWindowRight - rightTopCorner.getWidth(), scanWindowTop, paint);
         canvas.drawBitmap(leftBottomCorner, scanWindowLeft, scanWindowBottom - leftBottomCorner.getHeight(), paint);
         canvas.drawBitmap(rightBottomCorner, scanWindowRight - rightBottomCorner.getWidth(), scanWindowBottom - rightBottomCorner.getHeight(), paint);
     }
     //Draw the border shadows of the scan window.
     private void drawShadow(Canvas canvas) {
         paint.setColor(shadowColor);
         canvas.drawRect(0, 0, getWidth(), scanWindowTop, paint);
         canvas.drawRect(0, scanWindowTop, scanWindowLeft, scanWindowBottom, paint);
         canvas.drawRect(scanWindowRight, scanWindowTop, getWidth(), scanWindowBottom, paint);
         canvas.drawRect(0, scanWindowBottom, getWidth(), getHeight(), paint);
     }
    
     /**
      * Determine the location of the scan window according to the position of RayView.
      */
     public void setScanWindowLocation(int left, int top, int right, int bottom) {
         scanWindowLeft = left;
         scanWindowTop = top;
         scanWindowRight = right;
         scanWindowBottom = bottom;
         invalidate();
         setVisibility(VISIBLE);
     }
    
     public void setShadowColor(int shadowColor) {
         this.shadowColor = shadowColor;
     }
     //Set the corner colors of the scan window.
     public void setCornerColor(int angleColor) {
         leftTopCorner = Utils.changeBitmapColor(leftTopCorner, angleColor);
         rightTopCorner = Utils.changeBitmapColor(rightTopCorner, angleColor);
         leftBottomCorner = Utils.changeBitmapColor(leftBottomCorner, angleColor);
         rightBottomCorner = Utils.changeBitmapColor(rightBottomCorner, angleColor);
     }
    }
  7. In the widget package, add the RayView class that inherits from the ImageView class. Add the following code to implement the drawing of the scan lines.

    public class RayView extends ImageView {
    
     private FinderView mFinderView;
     private ScaleAnimation scanAnimation;
     private int[] location = new int[2];
    
     public RayView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
    
     public RayView(Context context) {
         super(context);
     }
    
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
    
         //Set the position of the scan window in FinderView.
         getLocationOnScreen(location);
         if (mFinderView != null) {
             mFinderView.setScanWindowLocation(location[0], location[1], location[0] + getWidth(), location[1] + getHeight());
         }
     }
    
     public void startScanAnimation() {
         setVisibility(VISIBLE);
         if (scanAnimation == null) {
             scanAnimation = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f);
             scanAnimation.setDuration(3000L);
             scanAnimation.setFillAfter(true);
             scanAnimation.setRepeatCount(Animation.INFINITE);
             scanAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
         }
         startAnimation(scanAnimation);
     }
    
     public void stopScanAnimation() {
         setVisibility(INVISIBLE);
         if (scanAnimation != null) {
             this.clearAnimation();
             scanAnimation = null;
         }
     }
    
     public void setFinderView(FinderView FinderView) {
         mFinderView = FinderView;
     }
    }
  8. Create a res > layout > File folder, then create a view_scan.xml file under the folder, and add the following code to draw the layout of the scan page.

     <?xml version="1.0" encoding="utf-8"?>
     <merge xmlns:android="http://schemas.android.com/apk/res/android">
    
         <com.example.custom.widget.FinderView
             android:id="@+id/finder_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
    
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="20dp"
             android:gravity="center_vertical"
             android:orientation="horizontal">
    
             <ImageView
                 android:id="@+id/back"
                 android:layout_width="48dp"
                 android:layout_height="48dp"
                 android:scaleType="center"
                 android:src="@drawable/icon_back" />
    
             <TextView
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:gravity="center"
                 android:text="@string/custom_title"
                 android:textColor="#ffffff"
                 android:textSize="16sp" />
    
             <ImageView
                 android:id="@+id/gallery"
                 android:layout_width="34dp"
                 android:layout_height="34dp"
                 android:layout_marginEnd="10dp"
                 android:layout_marginRight="10dp"
                 android:scaleType="fitXY"
                 android:src="@drawable/selector_scan_from_gallery" />
    
             <ImageView
                 android:id="@+id/torch"
                 android:layout_width="34dp"
                 android:layout_height="34dp"
                 android:layout_marginEnd="10dp"
                 android:layout_marginRight="10dp"
                 android:scaleType="fitXY"
                 android:src="@drawable/selector_torch" />
         </LinearLayout>
    
         <com.example.custom.widget.RayView
             android:id="@+id/ray_view"
             android:layout_width="270dp"
             android:layout_height="280dp"
             android:layout_centerInParent="true"
             android:background="@drawable/custom_scan_ray" />
    
         <TextView
             android:id="@+id/tip_tv"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@+id/ray_view"
             android:layout_centerHorizontal="true"
             android:layout_marginTop="10dp"
             android:includeFontPadding="false"
             android:text="@string/scan_tip"
             android:textColor="#7fffffff"
             android:textSize="14sp" />
    
     </merge>
  9. In the widget package, add the ScanView class that inherits from the RelativeLayout class. Add the following code. This code implements the interaction between the scan-related view and the scan engine.

    public class ScanView extends RelativeLayout {
    
     private RayView mRayView;
    
     public ScanView(Context context) {
         super(context);
         init(context);
     }
    
     public ScanView(Context context, AttributeSet attrs) {
         super(context, attrs);
         init(context);
     }
    
     public ScanView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         init(context);
     }
    
     private void init(Context ctx) {
         LayoutInflater.from(ctx).inflate(R.layout.view_scan, this, true);
         FinderView finderView = (FinderView) findViewById(R.id.finder_view);
         mRayView = (RayView) findViewById(R.id.ray_view);
         mRayView.setFinderView(finderView);
     }
    
     public void onStartScan() {
         mRayView.startScanAnimation();
     }
    
     public void onStopScan() {
         mRayView.stopScanAnimation();
     }
    
     public float getCropWidth() {
         return mRayView.getWidth() * 1.1f;
     }
    
     public Rect getScanRect(Camera camera, int previewWidth, int previewHeight) {
         if (camera == null) {
             return null;
         }
         int[] location = new int[2];
         mRayView.getLocationOnScreen(location);
         Rect r = new Rect(location[0], location[1],
                 location[0] + mRayView.getWidth(), location[1] + mRayView.getHeight());
         Camera.Size size;
         try {
             size = camera.getParameters().getPreviewSize();
         } catch (Exception e) {
             return null;
         }
         if (size == null) {
             return null;
         }
         double rateX = (double) size.height / (double) previewWidth;
         double rateY = (double) size.width / (double) previewHeight;
         // The size of the crop box = The size of the grid animation box × 1.1
         int expandX = (int) (mRayView.getWidth() * 0.05);
         int expandY = (int) (mRayView.getHeight() * 0.05);
         Rect resRect = new Rect(
                 (int) ((r.top - expandY) * rateY),
                 (int) ((r.left - expandX) * rateX),
                 (int) ((r.bottom + expandY) * rateY),
                 (int) ((r.right + expandX) * rateX));
    
         Rect finalRect = new Rect(
                 resRect.left < 0 ? 0 : resRect.left,
                 resRect.top < 0 ? 0 : resRect.top,
                 resRect.width() > size.width ? size.width : resRect.width(),
                 resRect.height() > size.height ? size.height : resRect.height());
    
         Rect rect1 = new Rect(
                 finalRect.left / 4 * 4,
                 finalRect.top / 4 * 4,
                 finalRect.right / 4 * 4,
                 finalRect.bottom / 4 * 4);
    
         int max = Math.max(rect1.right, rect1.bottom);
         int diff = Math.abs(rect1.right - rect1.bottom) / 8 * 4;
    
         Rect rect2;
         if (rect1.right > rect1.bottom) {
             rect2 = new Rect(rect1.left, rect1.top - diff, max, max);
         } else {
             rect2 = new Rect(rect1.left - diff, rect1.top, max, max);
         }
         return rect2;
     }
    }
  10. Create an activity_custom_scan.xml file under the res > layout folder, and add the following code to the file. This code draws the main page of the custom scan feature.

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
    
       <com.mpaas.aar.demo.custom.widget.APTextureView
           android:id="@+id/surface_view"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
    
       <com.mpaas.aar.demo.custom.widget.ScanView
           android:id="@+id/scan_view"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />
    
    </FrameLayout>

Use the scan feature in the dependency project

  1. In the com.example.custom package of the custom module, add a ScanHelper class and add the following code. This code calls the scan feature and obtains the callback result of a scan result.

    public class ScanHelper {
    
     private static class Holder {
         private static ScanHelper instance = new ScanHelper();
     }
    
     private ScanCallback scanCallback;
    
     private ScanHelper() {
     }
    
     public static ScanHelper getInstance() {
         return Holder.instance;
     }
    
     public void scan(Context context, ScanCallback scanCallback) {
         if (context == null) {
             return;
         }
         this.scanCallback = scanCallback;
         context.startActivity(new Intent(context, CustomScanActivity.class));
     }
    
     void notifyScanResult(boolean isProcessed, Intent resultData) {
         if (scanCallback != null) {
             scanCallback.onScanResult(isProcessed, resultData);
             scanCallback = null;
         }
     }
    
     public interface ScanCallback {
         void onScanResult(boolean isProcessed, Intent result);
     }
    }
  2. In the com.example.custom package of the custom module, add the CustomScanActivity class that inherits from the Activity class. Set the UI immersive mode and create the View and Button corresponding to the resource file.

    public class CustomScanActivity extends Activity {
     private final String TAG = CustomScanActivity.class.getSimpleName();
     private static final int REQUEST_CODE_PERMISSION = 1;
     private static final int REQUEST_CODE_PHOTO = 2;
     private ImageView mTorchBtn;
     private APTextureView mTextureView;
     private ScanView mScanView;
     private boolean isFirstStart = true;
     private boolean isPermissionGranted;
     private boolean isScanning;
     private boolean isPaused;
     private Rect scanRect;
     private MPScanner mpScanner;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_custom_scan);
    
         // Sets the immersive mode.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
             getWindow().setFlags(
                     WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                     WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
         }
    
         mTextureView = findViewById(R.id.surface_view);
         mScanView = findViewById(R.id.scan_view);
         mTorchBtn = findViewById(R.id.torch);
    
     }
    
      @Override
     public void onPause() {
         super.onPause();
    
     }
    
     @Override
     public void onResume() {
         super.onResume();
    
     }
    
     @Override
     public void onDestroy() {
         super.onDestroy();
    
     }
    
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
     }
      @Override
     public void onBackPressed() {
         super.onBackPressed();
    
     }
    
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
    
     }
    }
  3. Follow the operations below to realize the function of opening mobile phone gallery.

    1. Create a pickImageFromGallery method in CustomScanActivity.

      private void pickImageFromGallery() {
       Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
       intent.setType("image/*");
       startActivityForResult(intent, REQUEST_CODE_PHOTO);
      }
    2. In the onCreate method in CustomScanActivity, add the click event of gallery, and call the pickImageFromGallery method.

           findViewById(R.id.gallery).setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   pickImageFromGallery();
               }
           });
  4. Follow the operations below to realize the function of turning on or off torch.

    1. Create a switchTorch method in CustomScanActivity.

       private void switchTorch() {
           boolean torchOn = mpScanner.switchTorch();
           mTorchBtn.setSelected(torchOn);
       }
    2. In the onCreate method in CustomScanActivity, add the click event of mTorchBtn and call the switchTorch method.

        mTorchBtn.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   switchTorch();
               }
           });
  5. In CustomScanActivity, create a notifyScanResult method, and call the notifyScanResult method in the onBackPressed method.

     private void notifyScanResult(boolean isProcessed, Intent resultData) {
         ScanHelper.getInstance().notifyScanResult(isProcessed, resultData);
     }
    
     @Override
     public void onBackPressed() {
         super.onBackPressed();
         notifyScanResult(false, null);
     }
  6. In the onCreate method in CustomScanActivity, add the click event of back and call the onBackPressed method.

         findViewById(R.id.back).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 onBackPressed();
             }
         });
  7. Create an initMPScanner method in CustomScanActivity, and use the setRecognizeType method in the mpScanner object to set the type of the identification code.

    private void initMPScanner() {       
         mpScanner = new MPScanner(this);
         mpScanner.setRecognizeType(
                 MPRecognizeType.QR_CODE,
                 MPRecognizeType.BAR_CODE,
                 MPRecognizeType.DM_CODE,
                 MPRecognizeType.PDF417_CODE
         );
    }
  8. Create an onScanSuccess method in CustomScanActivity and implement the following code:

     private void onScanSuccess(final MPScanResult result) {
         runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 if (result == null) {
                     notifyScanResult(true, null);
                 } else {
                     Intent intent = new Intent();
                     intent.setData(Uri.parse(result.getText()));
                     notifyScanResult(true, intent);
                 }
                 CustomScanActivity.this.finish();
             }
         });
     }
  9. Create an initScanRect method in CustomScanActivity to initialize the scan feature.

    1. Call the getCamera method in the mpScanner object to get the Camera object. Call the setScanRegion method in the mpScanner object to set scan area.

      private void initScanRect() {
       if (scanRect == null) {
           scanRect = mScanView.getScanRect(
                   mpScanner.getCamera(), mTextureView.getWidth(), mTextureView.getHeight());
      
           float cropWidth = mScanView.getCropWidth();
           LoggerFactory.getTraceLogger().debug(TAG, "cropWidth: " + cropWidth);
           if (cropWidth > 0) {
               // Maximum preview window width = Screen width/Crop box width
               WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
               float screenWith = wm.getDefaultDisplay().getWidth();
               float screenHeight = wm.getDefaultDisplay().getHeight();
               float previewScale = screenWith / cropWidth;
               if (previewScale < 1.0f) {
                   previewScale = 1.0f;
               }
               if (previewScale > 1.5f) {
                   previewScale = 1.5f;
               }
               LoggerFactory.getTraceLogger().debug(TAG, "previewScale: " + previewScale);
               Matrix transform = new Matrix();
               transform.setScale(previewScale, previewScale, screenWith / 2, screenHeight / 2);
               mTextureView.setTransform(transform);
           }
       }
       mpScanner.setScanRegion(scanRect);
      }
    2. In the initMPScanner method, call the setMPScanListener method in the mpScanner object to scan the listener.

      mpScanner.setMPScanListener(new MPScanListener() {
           @Override
           public void onConfiguration() {
               mpScanner.setDisplayView(mTextureView);
           }
      
           @Override
           public void onStart() {
               if (!isPaused) {
                   runOnUiThread(new Runnable() {
                       @Override
                       public void run() {
                           if (!isFinishing()) {
                               initScanRect();
                               mScanView.onStartScan();
                           }
                       }
                   });
               }
           }
      
           @Override
           public void onSuccess(MPScanResult mpScanResult) {
               mpScanner.beep();
               onScanSuccess(mpScanResult);
           }
      
           @Override
           public void onError(MPScanError mpScanError) {
               if (!isPaused) {
                   runOnUiThread(new Runnable() {
                       @Override
                       public void run() {
                           Utils.toast(CustomScanActivity.this, getString(R.string.camera_open_error));
                       }
                   });
               }
           }
       });
    3. In the initMPScanner method, call the setMPImageGrayListener method in the mpScanner object to listen to the gray value of a recognized image.

       mpScanner.setMPImageGrayListener(new MPImageGrayListener() {
           @Override
           public void onGetImageGray(int gray) {
               // Note: This callback may be executed multiple consecutive times in dark environments.
               if (gray < MPImageGrayListener.LOW_IMAGE_GRAY) {
                   runOnUiThread(new Runnable() {
                       @Override
                       public void run() {
                           Utils.toast(CustomScanActivity.this, "The light is too dark, please turn on the flashlight");
                       }
                   });
               }
           }
       });
      }
  10. In CustomScanActivity, create a startScan method to enable the scan feature of the camera, and create a stopScan method to disable the scan feature of the camera.

    private void startScan() {
        try {
            mpScanner.openCameraAndStartScan();
            isScanning = true;
        } catch (Exception e) {
            isScanning = false;
            LoggerFactory.getTraceLogger().error(TAG, "startScan: Exception " + e.getMessage());
        }
    }
    
    private void stopScan() {
        mpScanner.closeCameraAndStopScan();
        mScanView.onStopScan();
        isScanning = false;
        if (isFirstStart) {
            isFirstStart = false;
        }
    }
  11. In CustomScanActivity, create onPermissionGranted, checkCameraPermission and scanFromUri methods.

    private void onPermissionGranted() {
        isPermissionGranted = true;
        startScan();
    }
    
    private void checkCameraPermission() {
        if (PermissionChecker.checkSelfPermission(
                this, Manifest.permission.CAMERA) != PermissionChecker.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_PERMISSION);
        } else {
            onPermissionGranted();
        }
    }
    
    private void scanFromUri(Uri uri) {
        final Bitmap bitmap = Utils.uri2Bitmap(this, uri);
        if (bitmap == null) {
            notifyScanResult(true, null);
            finish();
        } else {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    MPScanResult mpScanResult = mpScanner.scanFromBitmap(bitmap);
                    mpScanner.beep();
                    onScanSuccess(mpScanResult);
                }
            }, "scanFromUri").start();
        }
    }
  12. Call the checkCameraPermission method in the onCreate method in CustomScanActivity to check camera permissions.

    checkCameraPermission();
  13. Separately add the following content to the onPause, onResume, onDestroy, onRequestPermissionsResult, and onActivityResult methods in CustomScanActivity:

    @Override
    public void onPause() {
        super.onPause();
        isPaused = true;
        if (isScanning) {
            stopScan();
        }
    }
    
    @Override
    public void onResume() {
        super.onResume();
        isPaused = false;
        if (!isFirstStart && isPermissionGranted) {
            startScan();
        }
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        mpScanner.release();
    }   
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSION) {
            int length = Math.min(permissions.length, grantResults.length);
            for (int i = 0; i < length; i++) {
                if (TextUtils.equals(permissions[i], Manifest.permission.CAMERA)) {
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        Utils.toast(this, getString(R.string.camera_no_permission));
                    } else {
                        onPermissionGranted();
                    }
                    break;
                }
            }
        }
          @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null) {
            return;
        }
        if (requestCode == REQUEST_CODE_PHOTO) {
            scanFromUri(data.getData());
        }
    }
    }
  14. In the AndroidManifest.xml file in the custom module, set CustomScanActivity as the main entry of custom.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.mpaas.aar.demo.custom">
    
       <application>
           <meta-data
               android:name="com.google.android.actions"
               android:resource="@xml/actions" />
    
           <activity
               android:name=".CustomScanActivity"
               android:configChanges="orientation|keyboardHidden|navigation"
               android:exported="false"
               android:launchMode="singleTask"
               android:screenOrientation="portrait"
               android:theme="@android:style/Theme.NoTitleBar"
               android:windowSoftInputMode="adjustResize|stateHidden" />
       </application>
    
    </manifest>

Call the scan feature in your custom UI in the main project

  1. In the activity_main.xml file, add a button and set the ID of the button to custom_ui_btn.

     <Button
         android:id="@+id/custom_ui_btn"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="208dp"
         android:background="#108EE9"
         android:gravity="center"
         android:text="Use Scan in the Custom UI"
         android:textColor="#ffffff"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
  2. Edit code in the MainActivity class. Add a click event to the custom_ui_btn button. Obtain the custom UI and then use the scan feature in the custom UI. The code is as follows:

    findViewById(R.id.custom_ui_btn).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 ScanHelper.getInstance().scan(MainActivity.this, new ScanHelper.ScanCallback() {
                     @Override
                     public void onScanResult(boolean isProcessed, Intent result) {
                         if (!isProcessed) {
                             // In the scan page, click the physical back button or the back button in the upper left corner.
                             return;
                         }
    
                         if (result == null || result.getData() == null) {
                             Toast.makeText(MainActivity.this, "Scan failed, try again.", Toast.LENGTH_SHORT).show();
                             return;
                         }
                         new AlertDialog.Builder(MainActivity.this)
                                 .setMessage(result.getData().toString())
                                 .setPositiveButton(R.string.confirm, null)
                                 .create()
                                 .show();
                     }
                 });
             }
         });
  3. After you compile and run the project, click Use Scan in the Custom UI to use the scan feature in the custom UI.

  4. Scan the QR code below, then the information about the QR code will be displayed.21