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() {@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {openCamera(new Surface(surfaceTexture));}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {closeCamera();return false;}@Overridepublic 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() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraOpenCloseLock.release();mCameraDevice = camera;mIsCameraOpen.set(true);createCameraPreviewSession(surface);if (mScanCallback != null) {mMainHandler.post(() -> mScanCallback.onCameraStateChanged(true));}}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {mCameraOpenCloseLock.release();camera.close();mCameraDevice = null;mIsCameraOpen.set(false);if (mScanCallback != null) {mMainHandler.post(() -> mScanCallback.onCameraStateChanged(false));}}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {if (mCameraDevice == null) {return;}mCaptureSession = cameraCaptureSession;startRepeatingRequest(surface);}@Overridepublic 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() {@Overridepublic 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;// 添加扫描间隔,避免过度消耗CPUif (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> {@Overridepublic 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 implementsSurfaceTextureListener,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 usageprivate 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() {@Overridepublic 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 errorremoveMessages(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 herereturn;}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 destroyedLog.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:}}};@UiThreadprivate 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;}@VisibleForTestingboolean 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;}@Overridepublic 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 endif (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 endif (model.isWifiDppHandshaking()) {return;}int code = statusCode.intValue();Log.d(TAG, "Easy connect enrollee callback onFailure " + code);new EasyConnectEnrolleeStatusCallback().onFailure(code);});}@Overridepublic void onPause() {try{if (mCamera != null) {mCamera.stopScanning();}} catch (Exception e) {e.printStackTrace();}super.onPause();}@Overridepublic void onResume() {super.onResume();if (!isWifiDppHandshaking()) {restartCamera();}}@Overridepublic int getMetricsCategory() {if (mIsConfiguratorMode) {return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;} else {return SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE;}}// Container Activity must implement this interfacepublic 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;}@Overridepublic 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) {@Overridepublic 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 TalkBackif (mIsConfiguratorMode) {getActivity().setTitle(R.string.wifi_dpp_add_device_to_network);} else {getActivity().setTitle(R.string.wifi_dpp_scan_qr_code);}}@Overridepublic void onAttach(Context context) {super.onAttach(context);mScanWifiDppSuccessListener = (OnScanWifiDppSuccessListener) context;}@Overridepublic void onDetach() {mScanWifiDppSuccessListener = null;super.onDetach();}@Overridepublic void onDestroyView() {mWorkerThread.quit();super.onDestroyView();}@Overridepublic final View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.wifi_dpp_qrcode_scanner_fragment, container,/* attachToRoot */ false);}@Overridepublic 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);}@Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {menu.removeItem(Menu.FIRST);super.onCreateOptionsMenu(menu, inflater);}@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {initCamera(surface);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {// Do nothing}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {destroyCamera();return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {// Do nothing}@Overridepublic Size getViewSize() {return new Size(mTextureView.getWidth(), mTextureView.getHeight());}@Overridepublic Rect getFramePosition(Size previewSize, int cameraOrientation) {return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());}@Overridepublic void setTransform(Matrix transform) {mTextureView.setTransform(transform);}@Overridepublic 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 formatfinal 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;*/@Overridepublic 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);}@Overridepublic 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();}@VisibleForTestingvoid showErrorMessageAndRestartCamera(@StringRes int messageResId) {final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,getString(messageResId));message.arg1 = ARG_RESTART_CAMERA;message.sendToTarget();}@Overridepublic 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 {@Overridepublic 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);}@Overridepublic void onConfiguratorSuccess(int code) {// Do nothing}@Overridepublic 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);}@Overridepublic void onProgress(int code) {// Do nothing}}private void startWifiDppEnrolleeInitiator(WifiQrCode wifiQrCode) {final WifiDppInitiatorViewModel model =ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);model.startEasyConnectAsEnrolleeInitiator(wifiQrCode.getQrCode());}@Overridepublic 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();}@Overridepublic 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 notprivate 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() {@Overridepublic void onScanSuccess(String result, BarcodeFormat format) {if (isValid(result)) {handleSuccessfulResult(result);} else {mCamera.resumeScanning();}}@Overridepublic void onScanError(String error) {}@Overridepublic void onCameraStateChanged(boolean isOpen) {}@Overridepublic Rect getScanRect(Size previewSize) {return null;}@Overridepublic 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);}}@VisibleForTestingprotected boolean isDecodeTaskAlive() {return mCamera != null && mCamera.isScanning();}@Overrideprotected 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",],