zhaoyadi преди 8 месеца
родител
ревизия
1f43099d9f

+ 1 - 0
.idea/codeStyles/Project.xml

@@ -138,6 +138,7 @@
     </codeStyleSettings>
     <codeStyleSettings language="kotlin">
       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+      <option name="RIGHT_MARGIN" value="160" />
       <option name="KEEP_LINE_BREAKS" value="false" />
       <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
       <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />

+ 6 - 0
xiaodou/src/main/AndroidManifest.xml

@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
     <uses-feature
         android:name="android.hardware.bluetooth"
         android:required="true" />
@@ -37,6 +39,10 @@
         <activity
             android:name=".XDScanWifiActivity"
             android:theme="@style/XDTheme" />
+
+        <service
+            android:name=".XDScanBLEService"
+            android:foregroundServiceType="connectedDevice" />
     </application>
 
 </manifest>

+ 8 - 8
xiaodou/src/main/java/com/luojigou/xiaodou/XDScanBLEActivity.kt

@@ -89,6 +89,7 @@ class XDScanBLEActivity : AppCompatActivity(), XDScanBLEStatus.Host {
     private val bleConnection = object : ServiceConnection {
         override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
             bluetoothBinder = service as XDScanBLEService.XDScanBLEBinder?
+            checkBluetoothPermissionAndEnable()
         }
 
         override fun onServiceDisconnected(name: ComponentName?) {
@@ -101,14 +102,17 @@ class XDScanBLEActivity : AppCompatActivity(), XDScanBLEStatus.Host {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_scanble)
 
-        checkBluetoothPermissionAndEnable()
-        bindService(Intent(this, XDScanBLEService::class.java), bleConnection, 0)
+        val intent = Intent(this, XDScanBLEService::class.java)
+        startService(intent)
+        bindService(intent, bleConnection, 0)
     }
 
     override fun onResume() {
         super.onResume()
         registerReceiver(broadcastReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
-        checkBluetoothPermissionAndEnable()
+        if(bluetoothBinder != null){
+            checkBluetoothPermissionAndEnable()
+        }
     }
 
     override fun onPause() {
@@ -155,10 +159,6 @@ class XDScanBLEActivity : AppCompatActivity(), XDScanBLEStatus.Host {
     }
 
     override fun onStatusChanged(status: XDScanBLEStatus) = checkBluetoothPermissionAndEnable()
-    override fun getBluetooth(): BluetoothLe =
-        bluetoothBinder?.getBluetooth() ?: throw IllegalStateException("蓝牙初始化失败")
 
-    override suspend fun connectBluetooth(device: BluetoothDevice): Boolean {
-        bluetoothBinder?.connectGatt(device)
-    }
+    override fun getManager(): XDScanBLEStatus.Manager  = bluetoothBinder ?: throw IllegalStateException("蓝牙不存在")
 }

+ 73 - 13
xiaodou/src/main/java/com/luojigou/xiaodou/XDScanBLEService.kt

@@ -6,41 +6,101 @@ import android.content.Intent
 import android.os.Binder
 import android.os.IBinder
 import android.util.ArraySet
+import android.util.Log
 import androidx.bluetooth.BluetoothDevice
 import androidx.bluetooth.BluetoothLe
 import androidx.bluetooth.ScanFilter
 import androidx.bluetooth.ScanResult
+import com.luojigou.xiaodou.ble.XDBLEConnectStatus
+import com.luojigou.xiaodou.ble.XDBLEUtils
 import com.luojigou.xiaodou.ble.XDScanBLEDevice
+import com.luojigou.xiaodou.ble.XDScanBLEStatus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+import java.util.UUID
+
 
 @SuppressLint("MissingPermission")
 class XDScanBLEService : Service() {
+    companion object {
+        const val ACTION_START_SCAN = "com.luojigou.xiaodou.ble.ACTION_START_SCAN"
+        const val ACTION_STOP_SCAN = "com.luojigou.xiaodou.ble.ACTION_STOP_SCAN"
+
+        const val ACTION_CONNECT_WIFI = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI"
+        const val ACTION_CONNECT_WIFI_RESULT = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT"
+        const val ACTION_CONNECT_WIFI_RESULT_DATA = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_DATA"
+        const val ACTION_CONNECT_WIFI_RESULT_ERROR = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_ERROR"
+        const val ACTION_CONNECT_WIFI_RESULT_SUCCESS = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_SUCCESS"
+        const val ACTION_CONNECT_WIFI_RESULT_FAILED = "com.luojigou.xiaodou.ble.ACTION_CONNECT_WIFI_RESULT_FAILED"
+
+        private val wifiConnectServiceUUID = UUID.fromString("0000ae80-0000-1000-8000-00805f9b34fb")
+        private val wifiConnectWriteUUID = UUID.fromString("0000ae81-0000-1000-8000-00805f9b34fb")
+        private val wifiConnectNotifyUUID = UUID.fromString("0000ae82-0000-1000-8000-00805f9b34fb")
+        private val filter: List<ScanFilter> = listOf(ScanFilter())
+    }
+
     private val bluetoothLe: BluetoothLe by lazy { BluetoothLe(this) }
 
     private val deviceSet: MutableSet<XDScanBLEDevice> = ArraySet()
 
-    private val deviceListFlow = MutableSharedFlow<List<XDScanBLEDevice>(listOf())
+    private val deviceListFlow = MutableStateFlow<List<XDScanBLEDevice>>(listOf())
+
+    private val connectStatus = MutableStateFlow<XDBLEConnectStatus>(XDBLEConnectStatus.Waiting)
+    override fun onCreate() {
+        super.onCreate()
+    }
+
     override fun onBind(intent: Intent?): IBinder {
         return XDScanBLEBinder(this)
     }
 
-    class XDScanBLEBinder(private val service: XDScanBLEService) : Binder() {
-        companion object {
-            private val filter: List<ScanFilter> = listOf(
-                ScanFilter()
-            )
-        }
-
-        fun getBluetooth(): BluetoothLe = service.bluetoothLe
-
-        suspend fun scanBle(): Flow<ScanResult> {
+    class XDScanBLEBinder(private val service: XDScanBLEService) : Binder(), XDScanBLEStatus.Manager {
+        override suspend fun scanBle(): Flow<ScanResult> {
             return service.bluetoothLe.scan(filter)
         }
 
-        suspend fun connectGatt(device: BluetoothDevice) {
+        override suspend fun connectWifi(device: BluetoothDevice, ssid: String, password: String): Unit = withContext(Dispatchers.IO) {
+            service.connectStatus.emit(XDBLEConnectStatus.Connecting(device))
             service.bluetoothLe.connectGatt(device) {
+                val connection = this
+
+                val bleService = this.getService(wifiConnectServiceUUID)
+                if (bleService != null) {
+                    service.connectStatus.emit(XDBLEConnectStatus.Connected(device))
+
+                    val write = bleService.getCharacteristic(wifiConnectWriteUUID)
+                    val notify = bleService.getCharacteristic(wifiConnectNotifyUUID)
+
+                    if (notify != null) {
+                        connection
+                            .subscribeToCharacteristic(notify)
+                            .onStart { Log.d("XDScanBLEBinder", "subscribeToCharacteristic: ") }
+                            .onEach { XDBLEUtils.parseConnectResponse(it) }
+                            .onCompletion { Log.d("XDScanBLEBinder", "onCompletion: ") }
+                            .launchIn(CoroutineScope(currentCoroutineContext()))
+                    }
 
+                    if (write != null) {
+                        val request = XDBLEUtils.convertToConnectRequest(ssid, password)
+                        val result = connection.writeCharacteristic(write, request)
+                        if (result.isSuccess) {
+                            Log.d("XDScanBLEBinder", "connectWifi: success")
+                        } else {
+                            Log.d("XDScanBLEBinder", "connectWifi: failed")
+                        }
+                    }
+                } else {
+                    service.connectStatus.emit(XDBLEConnectStatus.Failed(device, "蓝牙无服务"))
+                }
             }
         }
     }

+ 0 - 5
xiaodou/src/main/java/com/luojigou/xiaodou/ble/BLEConnectStatus.kt

@@ -1,5 +0,0 @@
-package com.luojigou.xiaodou.ble
-
-class BLEConnectStatus {
-
-}

+ 12 - 0
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDBLEConnectStatus.kt

@@ -0,0 +1,12 @@
+package com.luojigou.xiaodou.ble
+
+import androidx.bluetooth.BluetoothDevice
+
+sealed class XDBLEConnectStatus() {
+    object Waiting : XDBLEConnectStatus()
+    data class Connecting(val device: BluetoothDevice) : XDBLEConnectStatus()
+    data class Connected(val device: BluetoothDevice) : XDBLEConnectStatus()
+    data class Disconnected(val device: BluetoothDevice) : XDBLEConnectStatus()
+    data class Unknown(val device: BluetoothDevice) : XDBLEConnectStatus()
+    data class Failed(val device: BluetoothDevice, val reason: String) : XDBLEConnectStatus()
+}

+ 27 - 0
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDBLEUtils.kt

@@ -0,0 +1,27 @@
+package com.luojigou.xiaodou.ble
+
+import android.util.Log
+
+object XDBLEUtils {
+    fun parseConnectResponse(data: ByteArray) {
+        Log.d("XDBLEUtils", "parseConnectResponse: ${data.decodeToString()}")
+    }
+
+    fun convertToConnectRequest(ssid: String, password: String): ByteArray {
+        val stringBuffer = StringBuffer()
+
+        stringBuffer.append(Char(0x7E))
+        stringBuffer.append(Char(0x01))
+        stringBuffer.append(Char(ssid.length))
+        ssid.toCharArray().forEach {
+            stringBuffer.append(it)
+        }
+        stringBuffer.append(Char(password.length))
+        password.toCharArray().forEach {
+            stringBuffer.append(it)
+        }
+        stringBuffer.append(Char(0xFF))
+        val byteArray = stringBuffer.toString().toByteArray()
+        return byteArray
+    }
+}

+ 4 - 4
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDScanBLEAdapter.kt

@@ -13,9 +13,9 @@ import com.luojigou.xiaodou.R
 
 @SuppressLint("MissingPermission")
 class XDScanBLEAdapter(
-    private val itemClickListener: (Item) -> Unit,
+    private val itemClickListener: (XDScanBLEDevice) -> Unit,
 ) : RecyclerView.Adapter<XDScanBLEAdapter.ViewHolder>() {
-    private val list: MutableSet<Item> = ArraySet()
+    private val list: MutableSet<XDScanBLEDevice> = ArraySet()
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
         val itemView: View =
@@ -43,13 +43,13 @@ class XDScanBLEAdapter(
     }
 
     @SuppressLint("NotifyDataSetChanged")
-    fun setValues(values: List<Item>) {
+    fun setValues(values: List<XDScanBLEDevice>) {
         list.clear()
         list.addAll(values)
         notifyDataSetChanged()
     }
 
-    fun addValue(value: Item) {
+    fun addValue(value: XDScanBLEDevice) {
         list.add(value)
         notifyItemChanged(list.size - 1)
     }

+ 4 - 0
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDScanBLEDevice.kt

@@ -1,4 +1,7 @@
 package com.luojigou.xiaodou.ble
+
+import androidx.bluetooth.BluetoothDevice
+
 data class XDScanBLEDevice(
     val id: String,
     val name: String,
@@ -6,6 +9,7 @@ data class XDScanBLEDevice(
     val rssi: String,
     val address: String,
     val addressType: String,
+    val device: BluetoothDevice,
 ) {
     override fun hashCode(): Int {
         return address.hashCode()

+ 19 - 9
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDScanBLENormalFragment.kt

@@ -4,10 +4,10 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.Button
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.bluetooth.ScanFilter
@@ -22,10 +22,11 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 @SuppressLint("MissingPermission")
-class XDScanBLENormalFragment(private val host: XDScanBLEStatus.Host) : Fragment(),
-    CoroutineScope by MainScope() {
+class XDScanBLENormalFragment(private val host: XDScanBLEStatus.Host) : Fragment(), CoroutineScope by MainScope() {
     private val adapter = XDScanBLEAdapter {
-
+        launch {
+            host.getManager().connectWifi(it.device, "zdzh", "zdzh00000")
+        }
     }
 
     private val scanFilter = listOf<ScanFilter>(
@@ -43,9 +44,12 @@ class XDScanBLENormalFragment(private val host: XDScanBLEStatus.Host) : Fragment
         super.onViewCreated(view, savedInstanceState)
 
         selectWifi = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-            host.onStatusChanged(XDScanBLEStatus.Disabled)
+
         }
 
+        val button = view.findViewById<Button>(R.id.scan_again)
+        button.setOnClickListener { scanBle() }
+
         val recyclerView = view.findViewById<RecyclerView>(R.id.scan_list)
         val layoutManager = GridLayoutManager(requireContext(), 2, RecyclerView.VERTICAL, false)
 
@@ -55,9 +59,14 @@ class XDScanBLENormalFragment(private val host: XDScanBLEStatus.Host) : Fragment
 
     override fun onAttach(context: Context) {
         super.onAttach(context)
+        scanBle()
+    }
+
+    private fun scanBle() {
+        adapter.setValues(mutableListOf())
 
         launch {
-            host.getBluetooth().scan(scanFilter).collect {
+            host.getManager().scanBle().collect {
                 val device = it.device
                 val rssi = it.rssi
                 val serviceUuids = it.serviceUuids
@@ -73,18 +82,19 @@ class XDScanBLENormalFragment(private val host: XDScanBLEStatus.Host) : Fragment
                 val address = deviceAddress.address
                 val addressType = deviceAddress.addressType
 
-                Log.d("XDScanBLENormalFragment", "onAttach: ")
+                //                Log.d("XDScanBLENormalFragment", "onAttach: ")
 
                 if (name != null) {
                     withContext(Dispatchers.Main) {
                         adapter.addValue(
-                            XDScanBLEAdapter.Item(
+                            XDScanBLEDevice(
                                 id = id.toString(),
                                 name = name.toString(),
                                 bondState = bondState.toString(),
                                 rssi = rssi.toString(),
                                 address = address,
-                                addressType = addressType.toString()
+                                addressType = addressType.toString(),
+                                device = device,
                             )
                         )
                     }

+ 7 - 3
xiaodou/src/main/java/com/luojigou/xiaodou/ble/XDScanBLEStatus.kt

@@ -1,7 +1,8 @@
 package com.luojigou.xiaodou.ble
 
 import androidx.bluetooth.BluetoothDevice
-import androidx.bluetooth.BluetoothLe
+import androidx.bluetooth.ScanResult
+import kotlinx.coroutines.flow.Flow
 
 sealed class XDScanBLEStatus {
     object Disabled : XDScanBLEStatus()
@@ -12,8 +13,11 @@ sealed class XDScanBLEStatus {
     interface Host {
         fun onStatusChanged(status: XDScanBLEStatus)
 
-        fun getBluetooth(): BluetoothLe
+        fun getManager(): Manager
+    }
 
-        suspend fun connectBluetooth(device: BluetoothDevice): Boolean
+    interface Manager {
+        suspend fun scanBle(): Flow<ScanResult>
+        suspend fun connectWifi(device: BluetoothDevice, ssid: String, password: String)
     }
 }

+ 12 - 1
xiaodou/src/main/res/layout/fragment_scanble_normal.xml

@@ -1,9 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/scan_list"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
+        android:layout_height="match_parent" />
+
+
+    <Button
+        android:id="@+id/scan_again"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="重新扫描"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent" />
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
xiaodou/src/main/res/layout/fragment_scanwifi_normal.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">