android13修改WiFi扫描二维码识别识别成功率不高的问题

Android13 Setting扫描二维码主要用到了WifiDppQrCodeScannerFragment

WifiDppQrCodeScannerFragment 依赖 QrCamera 类。

QrCamera 使用了 Camera1 的API。

开发了新类 ModernQrScanner ,采用了Camera2和更新了最新的Zxing包。

添加一个新的二维码扫描的处理类,放在com.android.settings.wifi.dpp 包下面。

复制代码
package com.android.settings.wifi.dpp;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 现代化QR码扫描工具类,基于Camera2 API
 * 提供更好的兼容性、稳定性和识别率
 */
public class ModernQrScanner {
    private static final String TAG = "ModernQrScanner";

    // 相机相关
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;
    private ImageReader mImageReader;
    private Size mPreviewSize;
    private String mCameraId;

    // 线程管理
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;
    private volatile Handler mMainHandler;

    // 同步控制
    private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
    private final AtomicBoolean mIsScanning = new AtomicBoolean(false);
    private final AtomicBoolean mIsCameraOpen = new AtomicBoolean(false);

    // QR码解码
    private MultiFormatReader mMultiFormatReader;
    private static final Map<DecodeHintType, Object> DECODE_HINTS = new EnumMap<>(DecodeHintType.class);

    // 配置参数
    private static final int MAX_PREVIEW_WIDTH = 1920;
    private static final int MAX_PREVIEW_HEIGHT = 1080;
    private static final int MIN_PREVIEW_WIDTH = 640;
    private static final int MIN_PREVIEW_HEIGHT = 480;
    private static final long SCAN_INTERVAL_MS = 200;

    // 回调接口
    private volatile ScanCallback mScanCallback;
    private Context mContext;

    static {
        // 配置ZXing解码参数
        DECODE_HINTS.put(DecodeHintType.POSSIBLE_FORMATS, EnumSet.of(BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX, BarcodeFormat.AZTEC, BarcodeFormat.PDF_417));
        DECODE_HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
        DECODE_HINTS.put(DecodeHintType.CHARACTER_SET, "UTF-8");
        DECODE_HINTS.put(DecodeHintType.ALSO_INVERTED, Boolean.TRUE);
    }

    public interface ScanCallback {
        /**
         * 扫描成功回调
         *
         * @param result QR码内容
         * @param format 条码格式
         */
        void onScanSuccess(String result, BarcodeFormat format);

        /**
         * 扫描失败回调
         *
         * @param error 错误信息
         */
        void onScanError(String error);

        /**
         * 相机状态回调
         *
         * @param isOpen 相机是否已开启
         */
        void onCameraStateChanged(boolean isOpen);

        /**
         * 获取扫描区域
         *
         * @param previewSize 预览尺寸
         * @return 扫描区域,null表示全屏扫描
         */
        Rect getScanRect(Size previewSize);

        void testGetScan(Bitmap bitmap);
    }

    public ModernQrScanner(@NonNull Context context) {
        mContext = context.getApplicationContext();
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        mMainHandler = new Handler(Looper.getMainLooper());
        initializeReader();
    }

    private void initializeReader() {
        mMultiFormatReader = new MultiFormatReader();
        mMultiFormatReader.setHints(DECODE_HINTS);
    }

    /**
     * 开始扫描
     *
     * @param surface  预览Surface
     * @param callback 扫描回调
     */
    public void startScanning(@NonNull Surface surface, @NonNull ScanCallback callback) {
        if (!checkCameraPermission()) {
            callback.onScanError("Camera permission not granted");
            return;
        }

        mScanCallback = callback;
        startBackgroundThread();

        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                callback.onScanError("Time out waiting to lock camera opening.");
                return;
            }
            openCamera(surface);
        } catch (InterruptedException e) {
            callback.onScanError("Interrupted while trying to lock camera opening: " + e.getMessage());
        }
    }

    public void startScanning(TextureView textureView, ScanCallback callback) {
        if (!checkCameraPermission() && textureView != null) {
            callback.onScanError("Camera permission not granted");
            return;
        }

        mScanCallback = callback;
        startBackgroundThread();

        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                callback.onScanError("Time out waiting to lock camera opening.");
                return;
            }
            if (textureView.isAvailable()) {
                openCamera(new Surface(textureView.getSurfaceTexture()));
            } else {
                textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                    @Override
                    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
                        openCamera(new Surface(surfaceTexture));
                    }

                    @Override
                    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {

                    }

                    @Override
                    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
                        closeCamera();
                        return false;
                    }

                    @Override
                    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {

                    }
                });
            }

        } catch (InterruptedException e) {
            callback.onScanError("Interrupted while trying to lock camera opening: " + e.getMessage());
        }
    }

    /**
     * 停止扫描
     */
    public void stopScanning() {
        mIsScanning.set(false);
        closeCamera();
        stopBackgroundThread();
//        if (mScanCallback != null && mMainHandler != null) {
//            mMainHandler.post(() -> mScanCallback.onCameraStateChanged(false));
//        }
    }

    private boolean checkCameraPermission() {
        return ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
    }

    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    private void stopBackgroundThread() {
        if (mBackgroundThread != null) {
            mBackgroundThread.quitSafely();
            try {
                mBackgroundThread.join();
                mBackgroundThread = null;
                mBackgroundHandler = null;
            } catch (InterruptedException e) {
                Log.e(TAG, "Error stopping background thread", e);
            }
        }
    }

    @SuppressLint("MissingPermission")
    private void openCamera(@NonNull Surface surface) {
        try {
//            Log.e(TAG, " openCamera ");
            setupCameraParameters();
            setupImageReader();

            mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    mCameraOpenCloseLock.release();
                    mCameraDevice = camera;
                    mIsCameraOpen.set(true);
                    createCameraPreviewSession(surface);

                    if (mScanCallback != null) {
                        mMainHandler.post(() -> mScanCallback.onCameraStateChanged(true));
                    }
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    mCameraOpenCloseLock.release();
                    camera.close();
                    mCameraDevice = null;
                    mIsCameraOpen.set(false);

                    if (mScanCallback != null) {
                        mMainHandler.post(() -> mScanCallback.onCameraStateChanged(false));
                    }
                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    mCameraOpenCloseLock.release();
                    camera.close();
                    mCameraDevice = null;
                    mIsCameraOpen.set(false);

                    if (mScanCallback != null) {
                        String errorMsg = "Camera error: " + error;
                        mMainHandler.post(() -> {
                            mScanCallback.onScanError(errorMsg);
                            mScanCallback.onCameraStateChanged(false);
                        });
                    }
                }
            }, mBackgroundHandler);

        } catch (Exception e) {
            Log.e(TAG, "Failed to open camera", e);
            if (mScanCallback != null) {
                mMainHandler.post(() -> mScanCallback.onScanError("Failed to open camera: " + e.getMessage()));
            }
        }
    }

    private void setupCameraParameters() throws CameraAccessException {
        mCameraId = selectBestCamera();
        if (mCameraId == null) {
            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
        }

        CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        if (map == null) {
            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
        }

        // 选择最佳预览尺寸
        Size[] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
        mPreviewSize = chooseBestSize(outputSizes, MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT);

        Log.d(TAG, "Selected camera: " + mCameraId + ", preview size: " + mPreviewSize);
    }

    private String selectBestCamera() throws CameraAccessException {
        String[] cameraIds = mCameraManager.getCameraIdList();

        // 优先选择后置摄像头
        for (String cameraId : cameraIds) {
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
            Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);

            if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
                // 检查是否支持自动对焦
                int[] afModes = characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
                if (afModes != null && afModes.length > 0) {
                    return cameraId;
                }
            }
        }

        // 如果没有后置摄像头,选择第一个可用的
        return cameraIds.length > 0 ? cameraIds[0] : null;
    }

    private Size chooseBestSize(Size[] choices, int maxWidth, int maxHeight) {
        List<Size> bigEnough = new ArrayList<>();
        List<Size> notBigEnough = new ArrayList<>();

        for (Size option : choices) {

            if (option.getWidth() >= MIN_PREVIEW_WIDTH && option.getHeight() >= MIN_PREVIEW_HEIGHT) {
                if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight) {
                    bigEnough.add(option);
                } else {
                    notBigEnough.add(option);
                }
            }
        }

        // 选择满足条件的最大尺寸,或者最小的可用尺寸
        if (bigEnough.size() > 0) {
            return Collections.max(bigEnough, new CompareSizesByArea());
        } else if (notBigEnough.size() > 0) {
            return Collections.min(notBigEnough, new CompareSizesByArea());
        } else {
            Log.w(TAG, "Couldn't find any suitable preview size");
            return choices[0];
        }
    }

    private void setupImageReader() {

//        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 3);

        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                if (mIsScanning.get()) {
                    processImage(reader.acquireLatestImage());
                }
            }
        }, mBackgroundHandler);
//        Log.e(TAG, " setupImageReader ===============  ok  ");
    }

    private void createCameraPreviewSession(@NonNull Surface surface) {
        try {
            List<Surface> surfaces = Arrays.asList(surface, mImageReader.getSurface());

            mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    if (mCameraDevice == null) {
                        return;
                    }

                    mCaptureSession = cameraCaptureSession;
                    startRepeatingRequest(surface);
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    if (mScanCallback != null) {
                        mMainHandler.post(() -> mScanCallback.onScanError("Failed to configure camera"));
                    }
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            Log.e(TAG, "Failed to create camera preview session", e);
            if (mScanCallback != null) {
                mMainHandler.post(() -> mScanCallback.onScanError("Failed to create preview session: " + e.getMessage()));
            }
        }
    }

    private void startRepeatingRequest(Surface surface) {
        try {
            CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mImageReader.getSurface());
            requestBuilder.addTarget(surface);

            // 设置自动对焦模式
            requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

            // 设置自动曝光模式
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);

            // 设置自动白平衡
            requestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);

            // 设置场景模式为条码扫描(如果支持)
            requestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, CaptureRequest.CONTROL_SCENE_MODE_BARCODE);

            mCaptureSession.setRepeatingRequest(requestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    // 开始扫描
                    mIsScanning.set(true);
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            Log.e(TAG, "Failed to start repeating request", e);
            if (mScanCallback != null) {
                mMainHandler.post(() -> mScanCallback.onScanError("Failed to start camera preview: " + e.getMessage()));
            }
        }
    }

    private volatile boolean inAction = false;

    private void processImage(Image image) {
//        Log.e(TAG, "processImage ... ");
        if (image == null || !mIsScanning.get()) {
            if (image != null) {
                image.close();
            }
            return;
        }
        try {
            // 转换Image到byte数组
            Image.Plane[] planes = image.getPlanes();
            ByteBuffer byteBuffer = planes[0].getBuffer();
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

            int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
            bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0,
                    bitmap.getWidth(), bitmap.getHeight());
            RGBLuminanceSource source = new RGBLuminanceSource(
                    bitmap.getWidth(), bitmap.getHeight(), pixels);
            BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));

            if (mScanCallback != null && !inAction) {
                inAction = true;
                mScanCallback.testGetScan(bitmap);
                inAction = false;
            }
            Result result = mMultiFormatReader.decode(binaryBitmap);
            if (result != null) {
                mIsScanning.set(false); // 停止扫描
                if (mScanCallback != null) {
                    final String text = result.getText();
                    final BarcodeFormat format = result.getBarcodeFormat();
                    mMainHandler.post(() -> mScanCallback.onScanSuccess(text, format));
                }
            }
        } catch (ReaderException e) {
            Log.e(TAG, "Error processing image", e);
            // 解码失败,继续扫描
            e.printStackTrace();
        } catch (Exception e) {
            Log.e(TAG, "Error processing image", e);
            e.printStackTrace();
        } finally {
            mMultiFormatReader.reset();
            image.close();
//            inAction = false;
            // 添加扫描间隔,避免过度消耗CPU
            if (mIsScanning.get() && mBackgroundHandler != null) {
                mBackgroundHandler.postDelayed(() -> {
                    // 延迟后继续扫描
                }, SCAN_INTERVAL_MS);
            }
        }
    }

    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();

            if (mCaptureSession != null) {
                mCaptureSession.close();
                mCaptureSession = null;
            }

            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }

            if (mImageReader != null) {
                mImageReader.close();
                mImageReader = null;
            }

            mIsCameraOpen.set(false);

        } catch (InterruptedException e) {
            Log.e(TAG, "Interrupted while trying to lock camera closing", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

    /**
     * 检查相机是否已开启
     */
    public boolean isCameraOpen() {
        return mIsCameraOpen.get();
    }

    /**
     * 检查是否正在扫描
     */
    public boolean isScanning() {
        return mIsScanning.get();
    }

    /**
     * 手动触发对焦
     */
    public void triggerFocus() {
        if (mCaptureSession != null && mCameraDevice != null) {
            try {
                CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                requestBuilder.addTarget(mImageReader.getSurface());
                requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);

                mCaptureSession.capture(requestBuilder.build(), null, mBackgroundHandler);

            } catch (CameraAccessException e) {
                Log.e(TAG, "Failed to trigger focus", e);
            }
        }
    }

    /**
     * 重新开始扫描
     */
    public void resumeScanning() {
        mIsScanning.set(true);
    }

    /**
     * 暂停扫描
     */
    public void pauseScanning() {
        mIsScanning.set(false);
    }

    /**
     * 尺寸比较器
     */
    private static class CompareSizesByArea implements Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
        }
    }

    /**
     * 资源清理
     */
    public void release() {
        stopScanning();
        mScanCallback = null;
        mContext = null;
    }
}

WifiDppQrCodeScannerFragment 做了修改适配了新的扫描类 ModernQrScanner。

复制代码
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.android.settings.wifi.dpp;

import static android.net.wifi.WifiInfo.sanitizeSsid;
import android.graphics.Bitmap;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import android.content.pm.ActivityInfo;
import com.google.zxing.BarcodeFormat;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewModelProviders;

import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.qrcode.QrCamera;
import com.android.settingslib.qrcode.QrDecorateView;
import com.android.settingslib.wifi.WifiPermissionChecker;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;

import android.view.Surface;

import java.time.Clock;
import java.time.ZoneOffset;
import java.util.List;

public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment implements
        SurfaceTextureListener,
        QrCamera.ScannerCallback,
        WifiManager.ActionListener {
    private static final String TAG = "WifiDppQrCodeScanner";

    /**
     * Message sent to hide error message
     */
    private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;

    /**
     * Message sent to show error message
     */
    private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;

    /**
     * Message sent to manipulate Wi-Fi DPP QR code
     */
    private static final int MESSAGE_SCAN_WIFI_DPP_SUCCESS = 3;

    /**
     * Message sent to manipulate ZXing Wi-Fi QR code
     */
    private static final int MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS = 4;

    private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
    private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;

    // Key for Bundle usage
    private static final String KEY_IS_CONFIGURATOR_MODE = "key_is_configurator_mode";
    private static final String KEY_LATEST_ERROR_CODE = "key_latest_error_code";
    public static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration";

    private static final int ARG_RESTART_CAMERA = 1;

    // Max age of tracked WifiEntries.
    private static final long MAX_SCAN_AGE_MILLIS = 15_000;
    // Interval between initiating WifiPickerTracker scans.
    private static final long SCAN_INTERVAL_MILLIS = 10_000;

    //    private QrCamera mCamera;
    private TextureView mTextureView;
    private QrDecorateView mDecorateView;
    private ModernQrScanner mCamera;
    private TextView mErrorMessage;

    /**
     * true if the fragment working for configurator, false enrollee
     */
    private boolean mIsConfiguratorMode;

    /**
     * The SSID of the Wi-Fi network which the user specify to enroll
     */
    private String mSsid;

    /**
     * QR code data scanned by camera
     */
    private WifiQrCode mWifiQrCode;

    /**
     * The WifiConfiguration connecting for enrollee usage
     */
    private WifiConfiguration mEnrolleeWifiConfiguration;

    private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE;

    private WifiPickerTracker mWifiPickerTracker;
    private HandlerThread mWorkerThread;
    private WifiPermissionChecker mWifiPermissionChecker;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_HIDE_ERROR_MESSAGE:
                    mErrorMessage.setVisibility(View.INVISIBLE);
                    break;

                case MESSAGE_SHOW_ERROR_MESSAGE:
                    final String errorMessage = (String) msg.obj;

                    mErrorMessage.setVisibility(View.VISIBLE);
                    mErrorMessage.setText(errorMessage);
                    mErrorMessage.sendAccessibilityEvent(
                            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);

                    // Cancel any pending messages to hide error view and requeue the message so
                    // user has time to see error
                    removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
                    sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
                            SHOW_ERROR_MESSAGE_INTERVAL);

                    if (msg.arg1 == ARG_RESTART_CAMERA) {
                        setProgressBarShown(false);
                        mDecorateView.setFocused(false);
                        restartCamera();
                    }
                    break;

                case MESSAGE_SCAN_WIFI_DPP_SUCCESS:
                    if (mScanWifiDppSuccessListener == null) {
                        // mScanWifiDppSuccessListener may be null after onDetach(), do nothing here
                        return;
                    }
                    mScanWifiDppSuccessListener.onScanWifiDppSuccess((WifiQrCode) msg.obj);

                    if (!mIsConfiguratorMode) {
                        setProgressBarShown(true);
                        startWifiDppEnrolleeInitiator((WifiQrCode) msg.obj);
                        updateEnrolleeSummary();
                        mSummary.sendAccessibilityEvent(
                                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                    }

                    notifyUserForQrCodeRecognition();
                    break;

                case MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS:
                    final Context context = getContext();
                    if (context == null) {
                        // Context may be null if the message is received after the Activity has
                        // been destroyed
                        Log.d(TAG, "Scan success but context is null");
                        return;
                    }

                    // We may get 2 WifiConfiguration if the QR code has no password in it,
                    // one for open network and one for enhanced open network.
                    final WifiManager wifiManager =
                            context.getSystemService(WifiManager.class);
                    final WifiNetworkConfig qrCodeWifiNetworkConfig =
                            (WifiNetworkConfig) msg.obj;
                    final List<WifiConfiguration> qrCodeWifiConfigurations =
                            qrCodeWifiNetworkConfig.getWifiConfigurations();

                    // Adds all Wi-Fi networks in QR code to the set of configured networks and
                    // connects to it if it's reachable.
                    boolean hasHiddenOrReachableWifiNetwork = false;
                    for (WifiConfiguration qrCodeWifiConfiguration : qrCodeWifiConfigurations) {
                        final int id = wifiManager.addNetwork(qrCodeWifiConfiguration);
                        if (id == -1) {
                            continue;
                        }

                        if (!canConnectWifi(qrCodeWifiConfiguration.SSID)) return;

                        wifiManager.enableNetwork(id, /* attemptConnect */ false);
                        // WifiTracker only contains a hidden SSID Wi-Fi network if it's saved.
                        // We can't check if a hidden SSID Wi-Fi network is reachable in advance.
                        if (qrCodeWifiConfiguration.hiddenSSID ||
                                isReachableWifiNetwork(qrCodeWifiConfiguration)) {
                            hasHiddenOrReachableWifiNetwork = true;
                            mEnrolleeWifiConfiguration = qrCodeWifiConfiguration;
                            wifiManager.connect(id,
                                    /* listener */ WifiDppQrCodeScannerFragment.this);
                        }
                    }

                    if (!hasHiddenOrReachableWifiNetwork) {
                        showErrorMessageAndRestartCamera(
                                R.string.wifi_dpp_check_connection_try_again);
                        return;
                    }

                    mMetricsFeatureProvider.action(
                            mMetricsFeatureProvider.getAttribution(getActivity()),
                            SettingsEnums.ACTION_SETTINGS_ENROLL_WIFI_QR_CODE,
                            SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE,
                            /* key */ null,
                            /* value */ Integer.MIN_VALUE);

                    notifyUserForQrCodeRecognition();
                    break;

                default:
            }
        }
    };

    @UiThread
    private void notifyUserForQrCodeRecognition() {
        if (mCamera != null) {
            mCamera.stopScanning();
        }

        mDecorateView.setFocused(true);
        mErrorMessage.setVisibility(View.INVISIBLE);

        WifiDppUtils.triggerVibrationForQrCodeRecognition(getContext());
    }

    private boolean isReachableWifiNetwork(WifiConfiguration wifiConfiguration) {
        final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
        final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
        if (connectedWifiEntry != null) {
            // Add connected WifiEntry to prevent fail toast to users when it's connected.
            wifiEntries.add(connectedWifiEntry);
        }

        for (WifiEntry wifiEntry : wifiEntries) {
            if (!TextUtils.equals(sanitizeSsid(wifiEntry.getSsid()), sanitizeSsid(wifiConfiguration.SSID))) {
                continue;
            }
            final int security =
                    WifiDppUtils.getSecurityTypeFromWifiConfiguration(wifiConfiguration);
            if (security == wifiEntry.getSecurity()) {
                return true;
            }

            // Default security type of PSK/SAE transition mode WifiEntry is SECURITY_PSK and
            // there is no way to know if a WifiEntry is of transition mode. Give it a chance.
            if (security == WifiEntry.SECURITY_SAE
                    && wifiEntry.getSecurity() == WifiEntry.SECURITY_PSK) {
                return true;
            }
        }
        return false;
    }

    @VisibleForTesting
    boolean canConnectWifi(String ssid) {
        final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
        for (WifiEntry wifiEntry : wifiEntries) {
            if (!TextUtils.equals(wifiEntry.getSsid(), sanitizeSsid(ssid))) continue;

            if (!wifiEntry.canConnect()) {
                Log.w(TAG, "Wi-Fi is not allowed to connect by your organization. SSID:" + ssid);
                showErrorMessageAndRestartCamera(R.string.not_allowed_by_ent);
                return false;
            }
        }
        return true;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState != null) {
            mIsConfiguratorMode = savedInstanceState.getBoolean(KEY_IS_CONFIGURATOR_MODE);
            mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_ERROR_CODE);
            mEnrolleeWifiConfiguration = savedInstanceState.getParcelable(KEY_WIFI_CONFIGURATION);
        }

        final WifiDppInitiatorViewModel model =
                ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);

        model.getEnrolleeSuccessNetworkId().observe(this, networkId -> {
            // After configuration change, observe callback will be triggered,
            // do nothing for this case if a handshake does not end
            if (model.isWifiDppHandshaking()) {
                return;
            }

            new EasyConnectEnrolleeStatusCallback().onEnrolleeSuccess(networkId.intValue());
        });

        model.getStatusCode().observe(this, statusCode -> {
            // After configuration change, observe callback will be triggered,
            // do nothing for this case if a handshake does not end
            if (model.isWifiDppHandshaking()) {
                return;
            }

            int code = statusCode.intValue();
            Log.d(TAG, "Easy connect enrollee callback onFailure " + code);
            new EasyConnectEnrolleeStatusCallback().onFailure(code);
        });
    }

    @Override
    public void onPause() {
        try{
            if (mCamera != null) {
                mCamera.stopScanning();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();

        if (!isWifiDppHandshaking()) {
            restartCamera();
        }
    }

    @Override
    public int getMetricsCategory() {
        if (mIsConfiguratorMode) {
            return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
        } else {
            return SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE;
        }
    }

    // Container Activity must implement this interface
    public interface OnScanWifiDppSuccessListener {
        void onScanWifiDppSuccess(WifiQrCode wifiQrCode);
    }

    private OnScanWifiDppSuccessListener mScanWifiDppSuccessListener;

    /**
     * Configurator container activity of the fragment should create instance with this constructor.
     */
    public WifiDppQrCodeScannerFragment() {
        super();

        mIsConfiguratorMode = true;
    }

    public WifiDppQrCodeScannerFragment(WifiPickerTracker wifiPickerTracker,
                                        WifiPermissionChecker wifiPermissionChecker) {
        super();

        mIsConfiguratorMode = true;
        mWifiPickerTracker = wifiPickerTracker;
        mWifiPermissionChecker = wifiPermissionChecker;
    }

    /**
     * Enrollee container activity of the fragment should create instance with this constructor and
     * specify the SSID string of the WI-Fi network to be provisioned.
     */
    WifiDppQrCodeScannerFragment(String ssid) {
        super();

        mIsConfiguratorMode = false;
        mSsid = ssid;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

//        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        mWorkerThread = new HandlerThread(
                TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
                Process.THREAD_PRIORITY_BACKGROUND);
        mWorkerThread.start();
        final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
            @Override
            public long millis() {
                return SystemClock.elapsedRealtime();
            }
        };
        final Context context = getContext();
        mWifiPickerTracker = FeatureFactory.getFactory(context)
                .getWifiTrackerLibProvider()
                .createWifiPickerTracker(getSettingsLifecycle(), context,
                        new Handler(Looper.getMainLooper()),
                        mWorkerThread.getThreadHandler(),
                        elapsedRealtimeClock,
                        MAX_SCAN_AGE_MILLIS,
                        SCAN_INTERVAL_MILLIS,
                        null /* listener */);

        // setTitle for TalkBack
        if (mIsConfiguratorMode) {
            getActivity().setTitle(R.string.wifi_dpp_add_device_to_network);
        } else {
            getActivity().setTitle(R.string.wifi_dpp_scan_qr_code);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        mScanWifiDppSuccessListener = (OnScanWifiDppSuccessListener) context;
    }

    @Override
    public void onDetach() {
        mScanWifiDppSuccessListener = null;

        super.onDetach();
    }

    @Override
    public void onDestroyView() {
        mWorkerThread.quit();

        super.onDestroyView();
    }

    @Override
    public final View onCreateView(LayoutInflater inflater, ViewGroup container,
                                   Bundle savedInstanceState) {
        return inflater.inflate(R.layout.wifi_dpp_qrcode_scanner_fragment, container,
                /* attachToRoot */ false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mSummary = view.findViewById(R.id.sud_layout_subtitle);

        mTextureView = view.findViewById(R.id.preview_view);
        mTextureView.setSurfaceTextureListener(this);

        mDecorateView = view.findViewById(R.id.decorate_view);

        setProgressBarShown(isWifiDppHandshaking());

        if (mIsConfiguratorMode) {
            setHeaderTitle(R.string.wifi_dpp_add_device_to_network);

            WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity())
                    .getWifiNetworkConfig();
            if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
                throw new IllegalStateException("Invalid Wi-Fi network for configuring");
            }
            mSummary.setText(getString(R.string.wifi_dpp_center_qr_code,
                    wifiNetworkConfig.getSsid()));
        } else {
            setHeaderTitle(R.string.wifi_dpp_scan_qr_code);

            updateEnrolleeSummary();
        }

        mErrorMessage = view.findViewById(R.id.error_message);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.removeItem(Menu.FIRST);

        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        initCamera(surface);
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // Do nothing
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        destroyCamera();
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Do nothing
    }

    @Override
    public Size getViewSize() {
        return new Size(mTextureView.getWidth(), mTextureView.getHeight());
    }

    @Override
    public Rect getFramePosition(Size previewSize, int cameraOrientation) {
        return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
    }

    @Override
    public void setTransform(Matrix transform) {
        mTextureView.setTransform(transform);
    }

    @Override
    public boolean isValid(String qrCode) {
        try {
            mWifiQrCode = new WifiQrCode(qrCode);
        } catch (IllegalArgumentException e) {
            showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format);
            return false;
        }

        // It's impossible to provision other device with ZXing Wi-Fi Network config format
        final String scheme = mWifiQrCode.getScheme();
        if (mIsConfiguratorMode && WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG.equals(scheme)) {
            showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format);
            return false;
        }

        return true;
    }

    /**
     * This method is only called when QrCamera.ScannerCallback.isValid returns true;
     */
    @Override
    public void handleSuccessfulResult(String qrCode) {
        switch (mWifiQrCode.getScheme()) {
            case WifiQrCode.SCHEME_DPP:
                handleWifiDpp();
                break;

            case WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG:
                handleZxingWifiFormat();
                break;

            default:
                // continue below
        }
    }

    private void handleWifiDpp() {
        Message message = mHandler.obtainMessage(MESSAGE_SCAN_WIFI_DPP_SUCCESS);
        message.obj = new WifiQrCode(mWifiQrCode.getQrCode());

        mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
    }

    private void handleZxingWifiFormat() {
        Message message = mHandler.obtainMessage(MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS);
        message.obj = new WifiQrCode(mWifiQrCode.getQrCode()).getWifiNetworkConfig();

        mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
    }

    @Override
    public void handleCameraFailure() {
        destroyCamera();
    }

    private void initCamera(SurfaceTexture surface) {
        // Check if the camera has already created.
        if (mCamera == null) {
            mCamera = new ModernQrScanner(getContext());

            if (isWifiDppHandshaking()) {
                if (mDecorateView != null) {
                    mDecorateView.setFocused(true);
                }
            } else {

                mCamera.startScanning(new Surface(surface), scanListener);
            }
        }
    }

    private void destroyCamera() {
        if (mCamera != null) {
            mCamera.stopScanning();
            mCamera.release();
            mCamera = null;
        }
    }

    private void showErrorMessage(@StringRes int messageResId) {
        final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
                getString(messageResId));
        message.sendToTarget();
    }

    @VisibleForTesting
    void showErrorMessageAndRestartCamera(@StringRes int messageResId) {
        final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
                getString(messageResId));
        message.arg1 = ARG_RESTART_CAMERA;
        message.sendToTarget();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(KEY_IS_CONFIGURATOR_MODE, mIsConfiguratorMode);
        outState.putInt(KEY_LATEST_ERROR_CODE, mLatestStatusCode);
        outState.putParcelable(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration);

        super.onSaveInstanceState(outState);
    }

    private class EasyConnectEnrolleeStatusCallback extends EasyConnectStatusCallback {
        @Override
        public void onEnrolleeSuccess(int newNetworkId) {

            // Connect to the new network.
            final WifiManager wifiManager = getContext().getSystemService(WifiManager.class);
            final List<WifiConfiguration> wifiConfigs =
                    wifiManager.getPrivilegedConfiguredNetworks();
            for (WifiConfiguration wifiConfig : wifiConfigs) {
                if (wifiConfig.networkId == newNetworkId) {
                    mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS;
                    mEnrolleeWifiConfiguration = wifiConfig;
                    if (!canConnectWifi(wifiConfig.SSID)) return;
                    wifiManager.connect(wifiConfig, WifiDppQrCodeScannerFragment.this);
                    return;
                }
            }

            Log.e(TAG, "Invalid networkId " + newNetworkId);
            mLatestStatusCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
            updateEnrolleeSummary();
            showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again);
        }

        @Override
        public void onConfiguratorSuccess(int code) {
            // Do nothing
        }

        @Override
        public void onFailure(int code) {
            Log.d(TAG, "EasyConnectEnrolleeStatusCallback.onFailure " + code);

            int errorMessageResId;
            switch (code) {
                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
                    errorMessageResId = R.string.wifi_dpp_qr_code_is_not_valid_format;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION:
                    errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
                    errorMessageResId = R.string.wifi_dpp_failure_not_compatible;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION:
                    errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY:
                    if (code == mLatestStatusCode) {
                        throw (new IllegalStateException("stopEasyConnectSession and try again for"
                                + "EASY_CONNECT_EVENT_FAILURE_BUSY but still failed"));
                    }

                    mLatestStatusCode = code;
                    final WifiManager wifiManager =
                            getContext().getSystemService(WifiManager.class);
                    wifiManager.stopEasyConnectSession();
                    startWifiDppEnrolleeInitiator(mWifiQrCode);
                    return;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT:
                    errorMessageResId = R.string.wifi_dpp_failure_timeout;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC:
                    errorMessageResId = R.string.wifi_dpp_failure_generic;
                    break;

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED:
                    throw (new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED" +
                            " should be a configurator only error"));

                case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK:
                    throw (new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK" +
                            " should be a configurator only error"));

                default:
                    throw (new IllegalStateException("Unexpected Wi-Fi DPP error"));
            }

            mLatestStatusCode = code;
            updateEnrolleeSummary();
            showErrorMessageAndRestartCamera(errorMessageResId);
        }

        @Override
        public void onProgress(int code) {
            // Do nothing
        }
    }

    private void startWifiDppEnrolleeInitiator(WifiQrCode wifiQrCode) {
        final WifiDppInitiatorViewModel model =
                ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);

        model.startEasyConnectAsEnrolleeInitiator(wifiQrCode.getQrCode());
    }

    @Override
    public void onSuccess() {
        final Intent resultIntent = new Intent();
        resultIntent.putExtra(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration);

        final Activity hostActivity = getActivity();
        if (hostActivity == null) return;
        if (mWifiPermissionChecker == null) {
            mWifiPermissionChecker = new WifiPermissionChecker(hostActivity);
        }

        if (!mWifiPermissionChecker.canAccessWifiState()) {
            Log.w(TAG, "Calling package does not have ACCESS_WIFI_STATE permission for result.");
            EventLog.writeEvent(0x534e4554, "187176859",
                    mWifiPermissionChecker.getLaunchedPackage(), "no ACCESS_WIFI_STATE permission");
            hostActivity.finish();
            return;
        }

        if (!mWifiPermissionChecker.canAccessFineLocation()) {
            Log.w(TAG, "Calling package does not have ACCESS_FINE_LOCATION permission for result.");
            EventLog.writeEvent(0x534e4554, "187176859",
                    mWifiPermissionChecker.getLaunchedPackage(),
                    "no ACCESS_FINE_LOCATION permission");
            hostActivity.finish();
            return;
        }

        hostActivity.setResult(Activity.RESULT_OK, resultIntent);
        hostActivity.finish();
    }

    @Override
    public void onFailure(int reason) {
        Log.d(TAG, "Wi-Fi connect onFailure reason - " + reason);
        showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again);
    }

    // Check is Easy Connect handshaking or not
    private boolean isWifiDppHandshaking() {
        final WifiDppInitiatorViewModel model =
                ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);

        return model.isWifiDppHandshaking();
    }

    /**
     * To resume camera decoding task after handshake fail or Wi-Fi connection fail.
     */
    private void restartCamera() {
        if (mCamera == null) {
            Log.d(TAG, "mCamera is not available for restarting camera");
            return;
        }

        if (mCamera.isScanning()) {
            mCamera.stopScanning();
        }
        mCamera.release();
        mCamera = null;

        final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        if (surfaceTexture == null) {
            throw new IllegalStateException("SurfaceTexture is not ready for restarting camera");
        }
        mCamera = new ModernQrScanner(getContext());
        mCamera.startScanning(mTextureView, scanListener);
    }

    private ModernQrScanner.ScanCallback scanListener = new ModernQrScanner.ScanCallback() {
        @Override
        public void onScanSuccess(String result, BarcodeFormat format) {
            if (isValid(result)) {
                handleSuccessfulResult(result);
            } else {
                mCamera.resumeScanning();
            }
        }

        @Override
        public void onScanError(String error) {

        }

        @Override
        public void onCameraStateChanged(boolean isOpen) {

        }

        @Override
        public Rect getScanRect(Size previewSize) {
            return null;
        }

        @Override
        public void testGetScan(Bitmap bitmap) {

        }
    };

    private void updateEnrolleeSummary() {
        if (isWifiDppHandshaking()) {
            mSummary.setText(R.string.wifi_dpp_connecting);
        } else {
            String description;
            if (TextUtils.isEmpty(mSsid)) {
                description = getString(R.string.wifi_dpp_scan_qr_code_join_unknown_network, mSsid);
            } else {
                description = getString(R.string.wifi_dpp_scan_qr_code_join_network, mSsid);
            }
            mSummary.setText(description);
        }
    }

    @VisibleForTesting
    protected boolean isDecodeTaskAlive() {
        return mCamera != null && mCamera.isScanning();
    }

    @Override
    protected boolean isFooterAvailable() {
        return false;
    }
}

下载最新的Zxing包。放到Setting下的 libs文件夹。

添加Android.bp 对Zxing的引用。

复制代码
java_import {
    name: "zxing-core3.5.3",
    jars: ["libs/zxing_core_3.5.3.jar"],
}

修改引用包

复制代码
static_libs: [
        "androidx-constraintlayout_constraintlayout",
        "androidx.slice_slice-builders",
        "androidx.slice_slice-core",
        "androidx.slice_slice-view",
        "androidx.core_core",
        "androidx.appcompat_appcompat",
        "androidx.cardview_cardview",
        "androidx.preference_preference",
        "androidx.recyclerview_recyclerview",
        "androidx.window_window",
        "com.google.android.material_material",
        "setupcompat",
        "setupdesign",
        "androidx.lifecycle_lifecycle-runtime",
        "androidx.lifecycle_lifecycle-extensions",
        "guava",
        "jsr305",
        "net-utils-framework-common",
        "settings-contextual-card-protos-lite",
        "settings-log-bridge-protos-lite",
        "settings-telephony-protos-lite",
        "contextualcards",
        "settings-logtags",
        "statslog-settings",
        // "zxing-core-1.7",  这里进行了引用修改
        "zxing-core3.5.3",
        "android.hardware.dumpstate-V1.0-java",
        "android.hardware.dumpstate-V1.1-java",
        "lottie",
        "WifiTrackerLib",
        "SettingsLibActivityEmbedding",
    ],
相关推荐
Xu_youyaxianshen21 小时前
Git 零基础常用指令手册(Gitee / GitHub 通用 )
git·gitee·github
QN1幻化引擎4 天前
SFA 信号场注意力:用8KB参数换248x KV Cache压缩,边缘设备也能跑长序列
人工智能·算法·gitee·gitlab
J14818529147 天前
md文件图片在其他地方不能预览
gitee
Mark White9 天前
使用 Codex 自动完成 Gitee 仓库全托管
gitee
hkj880810 天前
vscode提交gitee远程仓库出现Permission denied (publickey)解决方法
ide·vscode·gitee
雪的季节11 天前
ffmpeg源码国内gitee下载
ffmpeg·gitee
御坂嘀喵13 天前
Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架
android·gitee
风信子IT人19 天前
【5天实战】从零构建AI-Native组织:飞书+Bot+Gitee全链路自动化实战指南—Day 5:完整场景实操验证
gitee·自动化·飞书·ai-native·bot开发
zakariyaa3319 天前
Android 绘制调度机制
android·gitee
m0_526119401 个月前
ssh key生成,gitee配置ssh
运维·gitee·ssh