From ddc4ee25f8e4fd5a13f010488b6a11a501cca96a Mon Sep 17 00:00:00 2001
From: wjc <1243177876@qq.com>
Date: 星期四, 12 三月 2026 18:08:04 +0800
Subject: [PATCH] 2026年03月12日18:07:47 蓝牙配网开发中

---
 app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java        |  158 ++++++++++++
 app/src/main/AndroidManifest.xml                                           |    7 
 app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java |  601 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 766 insertions(+), 0 deletions(-)

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8162f51..bd31fa4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -89,6 +89,13 @@
     <!-- 娣诲姞 dataSync 鍓嶅彴鏈嶅姟鏉冮檺 -->
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
 
+
+
+    <!-- Android 12+ 鎺ㄨ崘鐨勬柊鏉冮檺 -->
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <!-- Android 12+ 鍙娇鐢� NEARBY_WIFI_DEVICES 鏇夸唬浣嶇疆鏉冮檺锛堜粎闄愮壒瀹氬満鏅級 -->
+    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
     <queries>
         <package android:name="com.hdl.photovoltaic.services" />
     </queries>
diff --git a/app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java b/app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java
new file mode 100644
index 0000000..d705f58
--- /dev/null
+++ b/app/src/main/java/com/hdl/photovoltaic/other/HdlBluetoothLogic.java
@@ -0,0 +1,158 @@
+package com.hdl.photovoltaic.other;
+
+import android.Manifest;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.RequiresPermission;
+
+import com.hdl.photovoltaic.HDLApp;
+import com.hdl.photovoltaic.utils.BleWifiConfiguratorUtils;
+
+/**
+ * 钃濈墮閫昏緫
+ */
+public class HdlBluetoothLogic {
+    private static volatile HdlBluetoothLogic sHdlBluetoothLogic;
+    private BleWifiConfiguratorUtils configurator;
+
+    /**
+     * 鑾峰彇褰撳墠瀵硅薄
+     *
+     * @return HdlAccountLogic
+     */
+    public static synchronized HdlBluetoothLogic getInstance() {
+        if (sHdlBluetoothLogic == null) {
+            synchronized (HdlBluetoothLogic.class) {
+                if (sHdlBluetoothLogic == null) {
+                    sHdlBluetoothLogic = new HdlBluetoothLogic();
+                    // 鍦ㄦ瀯閫犳柟娉曚腑鍒濆鍖朿onfigurator
+                    sHdlBluetoothLogic.initConfigurator();
+                }
+            }
+
+        }
+        return sHdlBluetoothLogic;
+    }
+
+    /**
+     * 瀹炰緥鏂规硶锛屽垵濮嬪寲configurator
+     */
+    private void initConfigurator() {
+        this.configurator = new BleWifiConfiguratorUtils(HDLApp.getInstance());
+    }
+
+    /**
+     * 鎵弿璁惧
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void scanDevices() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.startScan(new BleWifiConfiguratorUtils.ScanListener() {
+            @Override
+            public void onDeviceFound(BluetoothDevice device, int rssi, byte[] scanRecord) {
+
+            }
+
+            @Override
+            public void onScanFailed(int errorCode) {
+
+            }
+        });
+    }
+
+    /**
+     * 杩炴帴璁惧
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    public void connect() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.connect("", new BleWifiConfiguratorUtils.ConnectListener() {
+            @Override
+            public void onConnected() {
+
+            }
+
+            @Override
+            public void onDisconnected() {
+
+            }
+
+            @Override
+            public void onConnectionFailed(String reason) {
+
+            }
+        });
+    }
+
+    /**
+     * 鍙戦�侀厤缃俊鎭�
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    public void sendConfig() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.writeCredentials("", "", new BleWifiConfiguratorUtils.WriteListener() {
+            @Override
+            public void onSsidWriteSuccess() {
+
+            }
+
+            @Override
+            public void onPasswordWriteSuccess() {
+
+            }
+
+            @Override
+            public void onWriteComplete(boolean success) {
+
+            }
+
+            @Override
+            public void onWriteFailed(int status) {
+
+            }
+
+            @Override
+            public void onDeviceResponse(String response) {
+
+            }
+        });
+    }
+
+    /**
+     * 鍋滄鎵弿
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void stopScan() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.stopScan();
+    }
+
+    /**
+     * 鏂紑璁惧杩炴帴
+     */
+    public void disconnect() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.disconnect();
+    }
+
+    /**
+     * 閲婃斁璧勬簮锛堝湪 Activity/Fragment 閿�姣佹椂璋冪敤锛�
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void release() {
+        if (this.configurator == null) {
+            return;
+        }
+        this.configurator.release();
+    }
+}
diff --git a/app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java b/app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java
new file mode 100644
index 0000000..f23abd4
--- /dev/null
+++ b/app/src/main/java/com/hdl/photovoltaic/utils/BleWifiConfiguratorUtils.java
@@ -0,0 +1,601 @@
+package com.hdl.photovoltaic.utils;
+
+import android.Manifest;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.RequiresPermission;
+import androidx.core.app.ActivityCompat;
+
+import com.google.gson.Gson;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * 鎵嬫満绔�氳繃 BLE 鍚戣澶囧啓鍏� Wi-Fi SSID 鍜屽瘑鐮佺殑宸ュ叿绫汇��
+ * 鍏稿瀷鐢ㄦ硶锛�
+ * 1. 鍒涘缓瀹炰緥 BleWifiConfigurator configurator = new BleWifiConfigurator(context);
+ * 2. 璁剧疆鑷畾涔� UUID锛堝彲閫夛紝榛樿浣跨敤甯歌 UUID锛�
+ * 3. 璋冪敤 startScan() 鎵弿璁惧
+ * 4. 鍦� onDeviceFound 鍥炶皟涓�夋嫨鐩爣璁惧锛岃皟鐢� connect() 杩炴帴
+ * 5. 杩炴帴鎴愬姛鍚庤皟鐢� writeCredentials(ssid, password) 鍐欏叆鍑瘉
+ * 6. 鐩戝惉鍥炶皟澶勭悊缁撴灉
+ */
+public class BleWifiConfiguratorUtils {
+
+    private static final String TAG = "BleWifiConfigurator";
+
+    // 榛樿鐨� UUID锛堣澶囩闇�瑕佸畾涔変互涓嬫湇鍔″拰鐗瑰緛锛�
+    //杩欐槸涓绘湇鍔★紝鍖呭惈鎵�鏈夐厤缃戠浉鍏崇殑鐗瑰緛銆�
+    public static final UUID DEFAULT_SERVICE_UUID = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
+    //鐗瑰緛鍊� 1: 0000fff1-0000-1000-8000-00805f9b34fb (SSID)
+    //鐢ㄩ�旓細鍐欏叆 Wi-Fi 鐨� SSID 鍚嶇О
+    //灞炴�э細Write (鍙啓)
+    //鏁版嵁绫诲瀷锛歎TF-8 瀛楃涓�
+    //绀轰緥锛�"MyWiFi_5G"
+    public static final UUID DEFAULT_SSID_CHAR_UUID = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
+    //鐗瑰緛鍊� 2: 0000fff2-0000-1000-8000-00805f9b34fb (瀵嗙爜)
+    // 鐢ㄩ�旓細鍐欏叆 Wi-Fi 瀵嗙爜
+    //灞炴�э細Write (鍙啓)
+    //鏁版嵁绫诲瀷锛歎TF-8 瀛楃涓�
+    //绀轰緥锛�"password123"
+    public static final UUID DEFAULT_PWD_CHAR_UUID = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");
+    //鐗瑰緛鍊� 3: 0000fff3-0000-1000-8000-00805f9b34fb (閫氱煡锛屽彲閫�)
+    //鐢ㄩ�旓細鎺ユ敹璁惧绔殑鍝嶅簲娑堟伅
+    //灞炴�э細Notify (閫氱煡)
+    //鏁版嵁绫诲瀷锛歎TF-8 瀛楃涓�
+    //绀轰緥鍝嶅簲锛�"SUCCESS", "FAIL: Invalid SSID"
+    public static final UUID DEFAULT_NOTIFY_CHAR_UUID = UUID.fromString("0000fff3-0000-1000-8000-00805f9b34fb"); // 鍙�夛紝鐢ㄤ簬鎺ユ敹璁惧鍝嶅簲
+
+    // 涓婁笅鏂�
+    private Context context;
+
+    // 钃濈墮閫傞厤鍣ㄥ拰鎵弿鍣�
+    private BluetoothAdapter bluetoothAdapter;
+    private BluetoothLeScanner bleScanner;
+
+    // GATT 鐩稿叧
+    private BluetoothGatt bluetoothGatt;
+    private BluetoothGattService targetService;
+    private BluetoothGattCharacteristic ssidCharacteristic;
+    private BluetoothGattCharacteristic pwdCharacteristic;
+    private BluetoothGattCharacteristic notifyCharacteristic; // 鍙��
+
+    // 鑷畾涔� UUID锛堝厑璁歌皟鐢ㄨ�呬慨鏀癸級
+    private UUID serviceUuid = DEFAULT_SERVICE_UUID;
+    private UUID ssidCharUuid = DEFAULT_SSID_CHAR_UUID;
+    private UUID pwdCharUuid = DEFAULT_PWD_CHAR_UUID;
+    private UUID notifyCharUuid = DEFAULT_NOTIFY_CHAR_UUID;
+
+    // 鍥炶皟鎺ュ彛
+    private ScanListener scanListener;
+    private ConnectListener connectListener;
+    private WriteListener writeListener;
+
+    // 鍐呴儴鐘舵��
+    private boolean isScanning = false;
+    private boolean isConnected = false;
+    private int writeState = 0; // 0: idle, 1: writing ssid, 2: writing pwd
+    private String ssidToWrite;
+    private String pwdToWrite;
+
+    // 鐢ㄤ簬灏� BLE 鍥炶皟鎶涘埌涓荤嚎绋�
+    private final Handler callbackHandler = new Handler(Looper.getMainLooper());
+
+    // ==================== 鏋勯�犲嚱鏁板拰璁剧疆 ====================
+
+    public BleWifiConfiguratorUtils(Context context) {
+        this.context = context.getApplicationContext();
+        initBluetooth();
+    }
+
+    private void initBluetooth() {
+        BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (bluetoothManager != null) {
+            bluetoothAdapter = bluetoothManager.getAdapter();
+        }
+    }
+
+    /**
+     * 璁剧疆鑷畾涔� Service UUID锛堝彲閫夛級
+     */
+    public void setServiceUuid(UUID serviceUuid) {
+        this.serviceUuid = serviceUuid;
+    }
+
+    /**
+     * 璁剧疆鑷畾涔� SSID 鐗瑰緛 UUID锛堝彲閫夛級
+     */
+    public void setSsidCharUuid(UUID ssidCharUuid) {
+        this.ssidCharUuid = ssidCharUuid;
+    }
+
+    /**
+     * 璁剧疆鑷畾涔夊瘑鐮佺壒寰� UUID锛堝彲閫夛級
+     */
+    public void setPwdCharUuid(UUID pwdCharUuid) {
+        this.pwdCharUuid = pwdCharUuid;
+    }
+
+    /**
+     * 璁剧疆鑷畾涔夐�氱煡鐗瑰緛 UUID锛堝彲閫夛紝鐢ㄤ簬鎺ユ敹璁惧鍝嶅簲锛�
+     */
+    public void setNotifyCharUuid(UUID notifyCharUuid) {
+        this.notifyCharUuid = notifyCharUuid;
+    }
+
+    // ==================== 鏉冮檺妫�鏌� ====================
+
+    /**
+     * 妫�鏌ユ槸鍚︽嫢鏈夊繀瑕佺殑杩愯鏃舵潈闄愶紙Android 12+ 闇�鍔ㄦ�佺敵璇锋柊鏉冮檺锛�
+     *
+     * @return 缂哄け鐨勬潈闄愬垪琛紝涓虹┖鍒欒〃绀哄叏閮ㄦ嫢鏈�
+     */
+    public List<String> getMissingPermissions() {
+        List<String> missing = new ArrayList<>();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            if (context.checkSelfPermission(android.Manifest.permission.BLUETOOTH_SCAN) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                missing.add(android.Manifest.permission.BLUETOOTH_SCAN);
+                Log.d(TAG, "No Permissions: android.Manifest.permission.BLUETOOTH_SCAN");
+            }
+            if (context.checkSelfPermission(android.Manifest.permission.BLUETOOTH_CONNECT) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                missing.add(android.Manifest.permission.BLUETOOTH_CONNECT);
+                Log.d(TAG, "No Permissions: android.Manifest.permission.BLUETOOTH_CONNECT");
+            }
+            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                missing.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
+                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_FINE_LOCATION");
+            }
+        } else {
+            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                missing.add(android.Manifest.permission.ACCESS_FINE_LOCATION);
+                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_FINE_LOCATION");
+            }
+            if (context.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+                missing.add(android.Manifest.permission.ACCESS_COARSE_LOCATION);
+                Log.d(TAG, "No Permissions: android.Manifest.permission.ACCESS_COARSE_LOCATION");
+            }
+
+        }
+        return missing;
+    }
+
+    // ==================== 鎵弿鐩稿叧 ====================
+
+    /**
+     * 寮�濮嬫壂鎻� BLE 璁惧锛岄粯璁や笉璁剧疆杩囨护锛堢敱 ScanListener 鑷鍒ゆ柇锛�
+     *
+     * @param listener 鎵弿鍥炶皟
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void startScan(ScanListener listener) {
+        try {
+            this.scanListener = listener;
+            if (!checkBluetoothEnabled()) {
+                notifyScanFailed(ScanListener.ERROR_BLUETOOTH_DISABLED);
+                return;
+            }
+            if (bleScanner == null) {
+                bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+                if (bleScanner == null) {
+                    notifyScanFailed(ScanListener.ERROR_SCANNER_NULL);
+                    return;
+                }
+            }
+            if (isScanning) {
+                stopScan();
+            }
+            ScanSettings settings = new ScanSettings.Builder()
+                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+//                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) //CALLBACK_TYPE_FIRST_MATCH 浠呭綋棣栨鍖归厤鍒拌澶囨椂鍥炶皟涓�娆�// 鎴� CALLBACK_TYPE_ALL_MATCHES 姣忔鏀跺埌骞挎挱閮藉洖璋冦��
+//                    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
+//                    .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
+                    .build();
+            // 鍙互娣诲姞鍩轰簬 serviceUuid 鐨勮繃婊わ紝浣嗕繚鐣欑敱 listener 鑷杩囨护鐨勭伒娲绘��
+            List<ScanFilter> filters = new ArrayList<>();
+            // 鍙�夛細娣诲姞鏈嶅姟 UUID 杩囨护锛堝鏋滈渶瑕侊級
+            // filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuid)).build());
+            if (!this.getMissingPermissions().isEmpty()) {
+                callbackHandler.post(() -> this.scanListener.onScanFailed(ScanListener.ERROR_No_Permissions)); // 娌℃湁鏉冮檺
+                return;
+            }
+            bleScanner.startScan(filters, settings, scanCallback);
+            isScanning = true;
+            Log.d(TAG, "BLE scan started");
+        } catch (Exception e) {
+
+        }
+    }
+
+    /**
+     * 鍋滄鎵弿
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void stopScan() {
+        if (isScanning && bleScanner != null) {
+            if (!this.getMissingPermissions().isEmpty()) {
+                callbackHandler.post(() -> this.scanListener.onScanFailed(ScanListener.ERROR_No_Permissions)); // 娌℃湁鏉冮檺
+                return;
+            }
+            bleScanner.stopScan(scanCallback);
+            isScanning = false;
+            Log.d(TAG, "BLE scan stopped");
+        }
+    }
+
+    private final ScanCallback scanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            BluetoothDevice device = result.getDevice();
+            Log.d("===", "onDeviceFound: " + Objects.requireNonNull(result.getScanRecord()).getDeviceName());
+//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+//                if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+//                    // TODO: Consider calling
+//                    //    ActivityCompat#requestPermissions
+//                    // here to request the missing permissions, and then overriding
+//                    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
+//                    //                                          int[] grantResults)
+//                    // to handle the case where the user grants the permission. See the documentation
+//                    // for ActivityCompat#requestPermissions for more details.
+//                    scanListener.onScanFailed(ScanListener.ERROR_No_Permissions);
+//                    return;
+//                }
+//            }
+            int rssi = result.getRssi();
+            byte[] scanRecord = result.getScanRecord() != null ? result.getScanRecord().getBytes() : null;
+            if (scanListener != null) {
+                callbackHandler.post(() -> scanListener.onDeviceFound(device, rssi, scanRecord));
+            }
+        }
+
+        @Override
+        public void onBatchScanResults(List<ScanResult> results) {
+            // 鎵归噺缁撴灉锛屽彲鏍规嵁闇�瑕佸鐞�
+            Log.d("=====", "onDeviceFound: " + new Gson().toJson(results));
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            Log.e(TAG, "Scan failed, error: " + errorCode);
+            notifyScanFailed(errorCode);
+        }
+    };
+
+    private void notifyScanFailed(int errorCode) {
+        if (this.scanListener != null) {
+            callbackHandler.post(() -> this.scanListener.onScanFailed(errorCode));
+        }
+        isScanning = false;
+    }
+
+    // ==================== 杩炴帴鐩稿叧 ====================
+
+    /**
+     * 杩炴帴鎸囧畾鐨� BLE 璁惧(绯荤粺榛樿杩炴帴瓒呮椂30绉�        )
+     *
+     * @param deviceAddress 鐩爣璁惧BluetoothDevice
+     * @param listener      杩炴帴鐘舵�佸洖璋�
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    public void connect(String deviceAddress, ConnectListener listener) {
+        this.connectListener = listener;
+        if (!checkBluetoothEnabled()) {
+            notifyConnectionFailed("Bluetooth disabled");
+            return;
+        }
+        if (!this.getMissingPermissions().isEmpty()) {
+            callbackHandler.post(() -> this.connectListener.onConnectionFailed("No permissions")); // 娌℃湁鏉冮檺
+            return;
+        }
+        if (bluetoothGatt != null) {
+            bluetoothGatt.close();
+            bluetoothGatt = null;
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
+        // 鑷姩杩炴帴 = false锛岄伩鍏嶇郴缁熺紦瀛樿繛鎺�
+        bluetoothGatt = device.connectGatt(context, false, gattCallback);
+        Log.d(TAG, "Connecting to " + device.getAddress());
+    }
+
+    /**
+     * 鏂紑褰撳墠杩炴帴
+     */
+    public void disconnect() {
+        if (bluetoothGatt != null) {
+            if (ActivityCompat.checkSelfPermission(this.context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+                // TODO: Consider calling
+                //    ActivityCompat#requestPermissions
+                // here to request the missing permissions, and then overriding
+                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                //                                          int[] grantResults)
+                // to handle the case where the user grants the permission. See the documentation
+                // for ActivityCompat#requestPermissions for more details.
+                return;
+            }
+            bluetoothGatt.disconnect();
+            bluetoothGatt.close();
+            bluetoothGatt = null;
+        }
+        isConnected = false;
+        targetService = null;
+        ssidCharacteristic = null;
+        pwdCharacteristic = null;
+        notifyCharacteristic = null;
+    }
+
+    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
+        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+        @Override
+        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+            //濡傛灉杩炴帴瓒呮椂绯荤粺浼氭姤133
+            if (newState == BluetoothProfile.STATE_CONNECTED) {
+                Log.d(TAG, "Connected to GATT server, attempting service discovery");
+                gatt.discoverServices();
+            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+                isConnected = false;
+                if (connectListener != null) {
+                    callbackHandler.post(() -> connectListener.onDisconnected());
+                }
+            }//
+        }
+
+        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                targetService = gatt.getService(serviceUuid);
+                if (targetService == null) {
+                    Log.e(TAG, "Service not found: " + serviceUuid);
+                    notifyConnectionFailed("Service not found");
+                    return;
+                }
+                ssidCharacteristic = targetService.getCharacteristic(ssidCharUuid);
+                pwdCharacteristic = targetService.getCharacteristic(pwdCharUuid);
+                notifyCharacteristic = targetService.getCharacteristic(notifyCharUuid);
+                if (ssidCharacteristic == null || pwdCharacteristic == null) {
+                    Log.e(TAG, "Required characteristics not found");
+                    notifyConnectionFailed("Characteristics not found");
+                    return;
+                }
+
+                // 濡傛灉鏀寔閫氱煡锛屽彲浠ュ紑鍚�氱煡锛堝彲閫夛級
+                if (notifyCharacteristic != null) {
+                    enableNotification(notifyCharacteristic);
+                }
+
+                isConnected = true;
+                if (connectListener != null) {
+                    callbackHandler.post(() -> connectListener.onConnected());
+                }
+            } else {
+                Log.e(TAG, "Service discovery failed, status: " + status);
+                notifyConnectionFailed("Service discovery failed");
+            }
+        }
+
+        @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+        @Override
+        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+            boolean success = status == BluetoothGatt.GATT_SUCCESS;
+            if (!success) {
+                Log.e(TAG, "Write failed, status: " + status);
+                writeState = 0;
+                if (writeListener != null) {
+                    callbackHandler.post(() -> writeListener.onWriteFailed(status));
+                }
+                return;
+            }
+
+            if (writeState == 1 && characteristic.getUuid().equals(ssidCharUuid)) {
+                // SSID 鍐欏叆鎴愬姛锛屽紑濮嬪啓瀵嗙爜
+                writeState = 2;
+                if (writeListener != null) {
+                    callbackHandler.post(() -> writeListener.onSsidWriteSuccess());
+                }
+                writeCharacteristic(pwdCharacteristic, pwdToWrite);
+            } else if (writeState == 2 && characteristic.getUuid().equals(pwdCharUuid)) {
+                // 瀵嗙爜鍐欏叆鎴愬姛
+                writeState = 0;
+                if (writeListener != null) {
+                    callbackHandler.post(() -> writeListener.onPasswordWriteSuccess());
+                    callbackHandler.post(() -> writeListener.onWriteComplete(true));
+                }
+            }
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+            if (characteristic.getUuid().equals(notifyCharUuid) && writeListener != null) {
+                byte[] value = characteristic.getValue();
+                String response = new String(value, StandardCharsets.UTF_8);
+                callbackHandler.post(() -> writeListener.onDeviceResponse(response));
+            }
+        }
+
+        @Override
+        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+            // 閫氱煡鎻忚堪绗﹀啓鍏ュ畬鎴愶紝鍙互蹇界暐
+//            if (status == BluetoothGatt.GATT_SUCCESS) {
+//                Log.d(TAG, "閫氱煡寮�鍚垚鍔燂紝鍙互鎺ユ敹璁惧鍝嶅簲浜�");
+//            } else {
+//                Log.e(TAG, "閫氱煡寮�鍚け璐ワ細" + status);
+//                if (writeListener != null) {
+//                    callbackHandler.post(() -> writeListener.onWriteFailed(status));
+//                }
+//            }
+        }
+    };
+
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    private void enableNotification(BluetoothGattCharacteristic characteristic) {
+        if (bluetoothGatt == null) return;
+        if (!this.getMissingPermissions().isEmpty()) {
+            return;
+        }
+        // 绗� 1 姝ワ細鍦ㄥ崗璁爤灞傞潰寮�鍚�氱煡
+        boolean enabled = bluetoothGatt.setCharacteristicNotification(characteristic, true);
+        if (enabled) {
+            // 绗� 2 姝ワ細鑾峰彇瀹㈡埛绔壒寰侀厤缃弿杩扮 (CCCD)
+            // 鈫� 00002902 鏄爣鍑嗙殑 CCCD 鎻忚堪绗� UUID
+            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                    UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
+            if (descriptor != null) {
+                // 绗� 3 姝ワ細璁剧疆鎻忚堪绗︾殑鍊间负 ENABLE_NOTIFICATION_VALUE锛�
+                // 鈫� 杩欎釜鍊兼槸 byte[]{0x01, 0x00}锛屽憡璇夎澶囩"鎴戣璁㈤槄閫氱煡"
+                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+                // 绗� 4 姝ワ細鍐欏叆鎻忚堪绗﹀埌璁惧
+                //鈫� 鍙湁鍐欏叆鎴愬姛鍚庯紝鍛婅瘔璁惧绔紝璁惧鎵嶄細寮�濮嬪彂閫侀�氱煡锛�
+                bluetoothGatt.writeDescriptor(descriptor);
+            }
+        }
+    }
+
+    private void notifyConnectionFailed(String reason) {
+        if (connectListener != null) {
+            callbackHandler.post(() -> connectListener.onConnectionFailed(reason));
+        }
+        disconnect();
+    }
+
+    // ==================== 鍐欏叆鍑瘉 ====================
+
+    /**
+     * 鍐欏叆 Wi-Fi SSID 鍜屽瘑鐮侊紙蹇呴』鍦ㄨ繛鎺ユ垚鍔熷悗璋冪敤锛�
+     *
+     * @param ssid     Wi-Fi SSID
+     * @param password Wi-Fi 瀵嗙爜
+     * @param listener 鍐欏叆缁撴灉鍥炶皟
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    public void writeCredentials(String ssid, String password, WriteListener listener) {
+        this.writeListener = listener;
+        if (!isConnected || ssidCharacteristic == null || pwdCharacteristic == null) {
+            if (listener != null) {
+                callbackHandler.post(() -> listener.onWriteFailed(-1)); // 鑷畾涔夐敊璇爜
+            }
+            return;
+        }
+        this.ssidToWrite = ssid;
+        this.pwdToWrite = password;
+
+        // 寮�濮嬪啓 SSID
+        writeState = 1;
+        writeCharacteristic(ssidCharacteristic, ssid);
+    }
+
+    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    private void writeCharacteristic(BluetoothGattCharacteristic characteristic, String value) {
+        if (!this.getMissingPermissions().isEmpty()) {
+            callbackHandler.post(() -> this.writeListener.onWriteFailed(-2)); // 娌℃湁鏉冮檺
+            return;
+        }
+        characteristic.setValue(value.getBytes(StandardCharsets.UTF_8));
+        characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+        bluetoothGatt.writeCharacteristic(characteristic);
+    }
+
+    // ==================== 宸ュ叿鏂规硶 ====================
+
+    /**
+     * 妫�鏌ヨ摑鐗欐槸鍚﹀紑鍚�
+     */
+    private boolean checkBluetoothEnabled() {
+        return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
+    }
+
+    /**
+     * 閲婃斁璧勬簮锛堝湪 Activity/Fragment 閿�姣佹椂璋冪敤锛�
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_SCAN)
+    public void release() {
+        stopScan();
+        disconnect();
+    }
+
+    // ==================== 鍥炶皟鎺ュ彛瀹氫箟 ====================
+
+    public interface ScanListener {
+        /**
+         * 钃濈墮娌℃湁寮�鍚�
+         */
+        int ERROR_BLUETOOTH_DISABLED = 1;
+        int ERROR_SCANNER_NULL = 2;
+        int ERROR_No_Permissions = 3;
+
+        /**
+         * 鍙戠幇璁惧锛堝彲鑳藉娆″洖璋冿級
+         */
+        void onDeviceFound(BluetoothDevice device, int rssi, byte[] scanRecord);
+
+        /**
+         * 鎵弿澶辫触
+         */
+        void onScanFailed(int errorCode);
+    }
+
+    public interface ConnectListener {
+        /**
+         * 杩炴帴鎴愬姛锛屾湇鍔″凡鍙戠幇锛岀壒寰佸凡鑾峰彇
+         */
+        void onConnected();
+
+        /**
+         * 杩炴帴鏂紑锛堝寘鎷富鍔ㄦ柇寮�鍜屾剰澶栨柇寮�锛�
+         */
+        void onDisconnected();
+
+        /**
+         * 杩炴帴澶辫触锛堟湇鍔℃湭鎵惧埌銆佺壒寰佺己澶便�丟ATT 閿欒绛夛級
+         */
+        void onConnectionFailed(String reason);
+    }
+
+    public interface WriteListener {
+        /**
+         * SSID 鍐欏叆鎴愬姛
+         */
+        void onSsidWriteSuccess();
+
+        /**
+         * 瀵嗙爜鍐欏叆鎴愬姛
+         */
+        void onPasswordWriteSuccess();
+
+        /**
+         * 鍏ㄩ儴鍐欏叆瀹屾垚锛堟垚鍔熶负 true锛�
+         */
+        void onWriteComplete(boolean success);
+
+        /**
+         * 鍐欏叆澶辫触
+         */
+        void onWriteFailed(int status);
+
+        /**
+         * 璁惧閫氳繃閫氱煡杩斿洖鐨勫搷搴旓紙濡傛灉鏈夛級
+         */
+        void onDeviceResponse(String response);
+    }
+}

--
Gitblit v1.8.0