From c2e997a596820a51ae05786a187456457f20058f Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Tue, 14 Jul 2026 12:11:53 -0400 Subject: [PATCH 01/14] chore: Migration Swift package manager & Kotlin --- .gitignore | 4 + CHANGELOG.md | 33 +++++++ packages/wifi_iot/CHANGELOG.md | 7 ++ packages/wifi_iot/android/build.gradle | 36 ------- packages/wifi_iot/android/build.gradle.kts | 43 +++++++++ packages/wifi_iot/android/gradle.properties | 2 - packages/wifi_iot/android/settings.gradle | 1 - packages/wifi_iot/android/settings.gradle.kts | 1 + .../wifi_iot/example/android/app/build.gradle | 74 --------------- .../example/android/app/build.gradle.kts | 41 ++++++++ .../wifi_iot/example/android/build.gradle | 19 ---- .../wifi_iot/example/android/build.gradle.kts | 24 +++++ .../example/android/gradle.properties | 9 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../wifi_iot/example/android/settings.gradle | 25 ----- .../example/android/settings.gradle.kts | 26 +++++ .../example/android/settings_aar.gradle | 1 - packages/wifi_iot/example/ios/.gitignore | 2 + .../ios/Flutter/AppFrameworkInfo.plist | 2 - packages/wifi_iot/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 68 +++++++++----- .../xcshareddata/xcschemes/Runner.xcscheme | 21 ++++- .../example/ios/Runner/AppDelegate.swift | 2 +- .../wifi_iot/example/ios/Runner/Info.plist | 4 + packages/wifi_iot/example/lib/main.dart | 46 ++++----- packages/wifi_iot/example/pubspec.yaml | 5 +- packages/wifi_iot/ios/Assets/.gitkeep | 0 packages/wifi_iot/ios/Classes/WifiIotPlugin.h | 4 - packages/wifi_iot/ios/Classes/WifiIotPlugin.m | 8 -- packages/wifi_iot/ios/wifi_iot.podspec | 20 ++-- packages/wifi_iot/ios/wifi_iot/Package.swift | 36 +++++++ .../Sources/wifi_iot/WifiIotPlugin.swift} | 4 +- packages/wifi_iot/pubspec.yaml | 7 +- packages/wifi_scan/CHANGELOG.md | 8 ++ packages/wifi_scan/android/build.gradle | 52 ---------- packages/wifi_scan/android/build.gradle.kts | 51 ++++++++++ packages/wifi_scan/android/gradle.properties | 2 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/wifi_scan/android/settings.gradle | 1 - .../wifi_scan/android/settings.gradle.kts | 1 + .../example/android/app/build.gradle | 66 ------------- .../example/android/app/build.gradle.kts | 41 ++++++++ .../wifi_scan/example/android/build.gradle | 16 ---- .../example/android/build.gradle.kts | 24 +++++ .../example/android/gradle.properties | 9 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../wifi_scan/example/android/settings.gradle | 25 ----- .../example/android/settings.gradle.kts | 26 +++++ .../ios/Flutter/AppFrameworkInfo.plist | 2 - packages/wifi_scan/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 59 +++++++----- .../xcshareddata/xcschemes/Runner.xcscheme | 21 ++++- .../example/ios/Runner/AppDelegate.swift | 11 ++- .../wifi_scan/example/ios/Runner/Info.plist | 25 +++++ packages/wifi_scan/example/lib/main.dart | 7 +- packages/wifi_scan/example/pubspec.yaml | 6 +- packages/wifi_scan/ios/Assets/.gitkeep | 0 .../wifi_scan/ios/Classes/WifiScanPlugin.h | 4 - .../wifi_scan/ios/Classes/WifiScanPlugin.m | 15 --- packages/wifi_scan/ios/wifi_scan.podspec | 4 +- .../wifi_scan/ios/wifi_scan/Package.swift | 36 +++++++ .../Sources/wifi_scan/WifiScanPlugin.swift} | 4 +- packages/wifi_scan/pubspec.yaml | 10 +- pubspec.lock | 94 ++++++------------- pubspec.yaml | 7 +- 65 files changed, 661 insertions(+), 551 deletions(-) delete mode 100644 packages/wifi_iot/android/build.gradle create mode 100644 packages/wifi_iot/android/build.gradle.kts delete mode 100644 packages/wifi_iot/android/settings.gradle create mode 100644 packages/wifi_iot/android/settings.gradle.kts delete mode 100644 packages/wifi_iot/example/android/app/build.gradle create mode 100644 packages/wifi_iot/example/android/app/build.gradle.kts delete mode 100644 packages/wifi_iot/example/android/build.gradle create mode 100644 packages/wifi_iot/example/android/build.gradle.kts delete mode 100644 packages/wifi_iot/example/android/settings.gradle create mode 100644 packages/wifi_iot/example/android/settings.gradle.kts delete mode 100644 packages/wifi_iot/example/android/settings_aar.gradle delete mode 100644 packages/wifi_iot/ios/Assets/.gitkeep delete mode 100644 packages/wifi_iot/ios/Classes/WifiIotPlugin.h delete mode 100644 packages/wifi_iot/ios/Classes/WifiIotPlugin.m create mode 100644 packages/wifi_iot/ios/wifi_iot/Package.swift rename packages/wifi_iot/ios/{Classes/SwiftWifiIotPlugin.swift => wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift} (99%) delete mode 100644 packages/wifi_scan/android/build.gradle create mode 100644 packages/wifi_scan/android/build.gradle.kts delete mode 100644 packages/wifi_scan/android/settings.gradle create mode 100644 packages/wifi_scan/android/settings.gradle.kts delete mode 100644 packages/wifi_scan/example/android/app/build.gradle create mode 100644 packages/wifi_scan/example/android/app/build.gradle.kts delete mode 100644 packages/wifi_scan/example/android/build.gradle create mode 100644 packages/wifi_scan/example/android/build.gradle.kts delete mode 100644 packages/wifi_scan/example/android/settings.gradle create mode 100644 packages/wifi_scan/example/android/settings.gradle.kts delete mode 100644 packages/wifi_scan/ios/Assets/.gitkeep delete mode 100644 packages/wifi_scan/ios/Classes/WifiScanPlugin.h delete mode 100644 packages/wifi_scan/ios/Classes/WifiScanPlugin.m create mode 100644 packages/wifi_scan/ios/wifi_scan/Package.swift rename packages/wifi_scan/ios/{Classes/SwiftWifiScanPlugin.swift => wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift} (93%) diff --git a/.gitignore b/.gitignore index e96dabcc..c554b15e 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,10 @@ unlinked_spec.ds .fvmrc .cxx/ +# Swift Package Manager +.build/ +.swiftpm/ + # Coverage coverage/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 11aca603..efee9828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2026-07-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`wifi_iot` - `v0.3.19+3`](#wifi_iot---v03193) + - [`wifi_scan` - `v0.4.1+3`](#wifi_scan---v0413) + +--- + +#### `wifi_iot` - `v0.3.19+3` + + - **CHORE**: Flutter 3.44 / Dart 3.12 SDK constraints. + - **FEAT**: Add Swift Package Manager support (CocoaPods retained). + - **CHORE**: Migrate Android Gradle scripts from Groovy to Kotlin DSL; bump AGP, Gradle, and Java toolchain. + - **CHORE**: Update dependencies. + +#### `wifi_scan` - `v0.4.1+3` + + - **CHORE**: Flutter 3.44 / Dart 3.12 SDK constraints. + - **FEAT**: Add Swift Package Manager support (CocoaPods retained). + - **CHORE**: Migrate Android Gradle scripts from Groovy to Kotlin DSL; bump AGP, Gradle, and Java toolchain. + - **CHORE**: Migrate to built-in Kotlin. + - **CHORE**: Update dependencies. + + ## 2025-02-18 ### Changes diff --git a/packages/wifi_iot/CHANGELOG.md b/packages/wifi_iot/CHANGELOG.md index 0ec3468f..2c000b61 100644 --- a/packages/wifi_iot/CHANGELOG.md +++ b/packages/wifi_iot/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.3.19+3 + + - **CHORE**: Flutter 3.44 / Dart 3.12 SDK constraints. + - **FEAT**: Add Swift Package Manager support (CocoaPods retained). + - **CHORE**: Migrate Android Gradle scripts from Groovy to Kotlin DSL; bump AGP, Gradle, and Java toolchain. + - **CHORE**: Update dependencies. + ## 0.3.19+2 - **FIX**(wifi_iot): iOS isConnected always true even connection is failed - synced with master (#407). diff --git a/packages/wifi_iot/android/build.gradle b/packages/wifi_iot/android/build.gradle deleted file mode 100644 index c4d62eb2..00000000 --- a/packages/wifi_iot/android/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -group 'com.alternadom.wifiiot' -version '1.0-SNAPSHOT' - -buildscript { - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.2.0' - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - } -} - -apply plugin: 'com.android.library' - -android { - namespace 'com.alternadom.wifiiot' - - compileSdkVersion 34 - - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } -} diff --git a/packages/wifi_iot/android/build.gradle.kts b/packages/wifi_iot/android/build.gradle.kts new file mode 100644 index 00000000..2753a52e --- /dev/null +++ b/packages/wifi_iot/android/build.gradle.kts @@ -0,0 +1,43 @@ +group = "com.alternadom.wifiiot" +version = "1.0" + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:9.0.1") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +plugins { + id("com.android.library") +} + +android { + namespace = "com.alternadom.wifiiot" + + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + minSdk = 16 + } + + lint { + disable += "InvalidPackage" + } +} diff --git a/packages/wifi_iot/android/gradle.properties b/packages/wifi_iot/android/gradle.properties index 876bcc0e..94adc3a3 100644 --- a/packages/wifi_iot/android/gradle.properties +++ b/packages/wifi_iot/android/gradle.properties @@ -1,5 +1,3 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -android.enableR8=true -android.enableR8.fullMode=false diff --git a/packages/wifi_iot/android/settings.gradle b/packages/wifi_iot/android/settings.gradle deleted file mode 100644 index 4ae80feb..00000000 --- a/packages/wifi_iot/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'wifi_iot' diff --git a/packages/wifi_iot/android/settings.gradle.kts b/packages/wifi_iot/android/settings.gradle.kts new file mode 100644 index 00000000..6977d66d --- /dev/null +++ b/packages/wifi_iot/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "wifi_iot" diff --git a/packages/wifi_iot/example/android/app/build.gradle b/packages/wifi_iot/example/android/app/build.gradle deleted file mode 100644 index 00249e52..00000000 --- a/packages/wifi_iot/example/android/app/build.gradle +++ /dev/null @@ -1,74 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace 'com.alternadom.wifiiotexample' - compileSdk flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.alternadom.wifiiotexample" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - - - -dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' -} - - diff --git a/packages/wifi_iot/example/android/app/build.gradle.kts b/packages/wifi_iot/example/android/app/build.gradle.kts new file mode 100644 index 00000000..434853fb --- /dev/null +++ b/packages/wifi_iot/example/android/app/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.alternadom.wifiiotexample" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + applicationId = "com.alternadom.wifiiotexample" + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/packages/wifi_iot/example/android/build.gradle b/packages/wifi_iot/example/android/build.gradle deleted file mode 100644 index 42dc81d9..00000000 --- a/packages/wifi_iot/example/android/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/packages/wifi_iot/example/android/build.gradle.kts b/packages/wifi_iot/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/packages/wifi_iot/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/wifi_iot/example/android/gradle.properties b/packages/wifi_iot/example/android/gradle.properties index 25ac3744..e96108cf 100644 --- a/packages/wifi_iot/example/android/gradle.properties +++ b/packages/wifi_iot/example/android/gradle.properties @@ -1,5 +1,6 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true -android.enableR8=true -android.enableR8.fullMode=false \ No newline at end of file +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/packages/wifi_iot/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/wifi_iot/example/android/gradle/wrapper/gradle-wrapper.properties index 3acc4a6f..5237affd 100644 --- a/packages/wifi_iot/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/wifi_iot/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip \ No newline at end of file diff --git a/packages/wifi_iot/example/android/settings.gradle b/packages/wifi_iot/example/android/settings.gradle deleted file mode 100644 index f2c26252..00000000 --- a/packages/wifi_iot/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.2.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.10" apply false -} - -include ":app" \ No newline at end of file diff --git a/packages/wifi_iot/example/android/settings.gradle.kts b/packages/wifi_iot/example/android/settings.gradle.kts new file mode 100644 index 00000000..c21f0c5b --- /dev/null +++ b/packages/wifi_iot/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/packages/wifi_iot/example/android/settings_aar.gradle b/packages/wifi_iot/example/android/settings_aar.gradle deleted file mode 100644 index e7b4def4..00000000 --- a/packages/wifi_iot/example/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/packages/wifi_iot/example/ios/.gitignore b/packages/wifi_iot/example/ios/.gitignore index 79cc4da8..4dab8bdd 100644 --- a/packages/wifi_iot/example/ios/.gitignore +++ b/packages/wifi_iot/example/ios/.gitignore @@ -39,6 +39,8 @@ Icon? /Flutter/App.framework /Flutter/Flutter.framework /Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh /ServiceDefinitions.json Pods/ diff --git a/packages/wifi_iot/example/ios/Flutter/AppFrameworkInfo.plist b/packages/wifi_iot/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..391a902b 100644 --- a/packages/wifi_iot/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/wifi_iot/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 9.0 diff --git a/packages/wifi_iot/example/ios/Podfile b/packages/wifi_iot/example/ios/Podfile index 1e8c3c90..e72e0b48 100644 --- a/packages/wifi_iot/example/ios/Podfile +++ b/packages/wifi_iot/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj index 3949901f..b292f06b 100644 --- a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -13,6 +13,7 @@ 17E7A1E020C98264008E10CD /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17E7A1DF20C98264008E10CD /* NetworkExtension.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -44,6 +45,9 @@ 3B4AEEECCF2871CD8E0FE17E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* wifi_iot */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = wifi_iot; path = ../../ios/wifi_iot; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -60,6 +64,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 17E7A1DE20C98260008E10CD /* ExternalAccessory.framework in Frameworks */, 17E7A1E020C98264008E10CD /* NetworkExtension.framework in Frameworks */, 176B4CC4D709866371F34648 /* Pods_Runner.framework in Frameworks */, @@ -91,6 +96,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* wifi_iot */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -156,13 +164,15 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 7BB0D1ACFC8A0AF1B10F1DCC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -173,7 +183,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -203,6 +213,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -231,10 +244,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -243,26 +258,9 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 7BB0D1ACFC8A0AF1B10F1DCC /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/wifi_iot/wifi_iot.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wifi_iot.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -372,7 +370,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -419,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -443,7 +441,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -473,7 +474,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -509,6 +513,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/wifi_iot/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/wifi_iot/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1263ac84..de94d244 100644 --- a/packages/wifi_iot/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/wifi_iot/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + diff --git a/packages/wifi_iot/example/ios/Runner/AppDelegate.swift b/packages/wifi_iot/example/ios/Runner/AppDelegate.swift index 71cc41e3..f1dc4867 100644 --- a/packages/wifi_iot/example/ios/Runner/AppDelegate.swift +++ b/packages/wifi_iot/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/wifi_iot/example/ios/Runner/Info.plist b/packages/wifi_iot/example/ios/Runner/Info.plist index db61027a..d7299d96 100644 --- a/packages/wifi_iot/example/ios/Runner/Info.plist +++ b/packages/wifi_iot/example/ios/Runner/Info.plist @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/wifi_iot/example/lib/main.dart b/packages/wifi_iot/example/lib/main.dart index 5818deb7..5d83f249 100755 --- a/packages/wifi_iot/example/lib/main.dart +++ b/packages/wifi_iot/example/lib/main.dart @@ -1,4 +1,4 @@ -// ignore_for_file: deprecated_member_use, package_api_docs, public_member_api_docs +// ignore_for_file: deprecated_member_use, package_api_docs, public_member_api_docs, constant_identifier_names, avoid_print, avoid_function_literals_in_foreach_calls import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:wifi_iot/wifi_iot.dart'; @@ -14,8 +14,10 @@ const String AP_DEFAULT_PASSWORD = "AP_PASSWORD"; void main() => runApp(FlutterWifiIoT()); class FlutterWifiIoT extends StatefulWidget { + const FlutterWifiIoT({super.key}); + @override - _FlutterWifiIoTState createState() => _FlutterWifiIoTState(); + State createState() => _FlutterWifiIoTState(); } class _FlutterWifiIoTState extends State { @@ -23,7 +25,7 @@ class _FlutterWifiIoTState extends State { String? _sPreviousPreSharedKey = ""; List? _htResultNetwork; - Map? _htIsNetworkRegistered = Map(); + final Map _htIsNetworkRegistered = {}; bool _isEnabled = false; bool _isConnected = false; @@ -54,13 +56,13 @@ class _FlutterWifiIoTState extends State { super.initState(); } - storeAndConnect(String psSSID, String psKey) async { + Future storeAndConnect(String psSSID, String psKey) async { await storeAPInfos(); await WiFiForIoTPlugin.setWiFiAPSSID(psSSID); await WiFiForIoTPlugin.setWiFiAPPreSharedKey(psKey); } - storeAPInfos() async { + Future storeAPInfos() async { String? sAPSSID; String? sPreSharedKey; @@ -82,7 +84,7 @@ class _FlutterWifiIoTState extends State { }); } - restoreAPInfos() async { + Future restoreAPInfos() async { WiFiForIoTPlugin.setWiFiAPSSID(_sPreviousAPSSID!); WiFiForIoTPlugin.setWiFiAPPreSharedKey(_sPreviousPreSharedKey!); } @@ -158,7 +160,7 @@ class _FlutterWifiIoTState extends State { return htResultNetwork; } - isRegisteredWifiNetwork(String ssid) async { + Future isRegisteredWifiNetwork(String ssid) async { bool bIsRegistered; try { @@ -168,7 +170,7 @@ class _FlutterWifiIoTState extends State { } setState(() { - _htIsNetworkRegistered![ssid] = bIsRegistered; + _htIsNetworkRegistered[ssid] = bIsRegistered; }); } @@ -197,10 +199,10 @@ class _FlutterWifiIoTState extends State { _htResultNetwork = null; } - if (_htResultNetwork != null && _htResultNetwork!.length > 0) { + if (_htResultNetwork != null && _htResultNetwork!.isNotEmpty) { final List htNetworks = []; - _htResultNetwork!.forEach((oNetwork) { + for (var oNetwork in _htResultNetwork!) { final PopupCommand oCmdConnect = PopupCommand("Connect", oNetwork!.ssid!); final PopupCommand oCmdRemove = PopupCommand("Remove", oNetwork.ssid!); @@ -216,8 +218,8 @@ class _FlutterWifiIoTState extends State { setState(() { isRegisteredWifiNetwork(oNetwork.ssid!); - if (_htIsNetworkRegistered!.containsKey(oNetwork.ssid) && - _htIsNetworkRegistered![oNetwork.ssid]!) { + if (_htIsNetworkRegistered.containsKey(oNetwork.ssid) && + _htIsNetworkRegistered[oNetwork.ssid]!) { htPopupMenuItems.add( PopupMenuItem( value: oCmdRemove, @@ -228,12 +230,10 @@ class _FlutterWifiIoTState extends State { htNetworks.add( ListTile( - title: Text("" + - oNetwork.ssid! + - ((_htIsNetworkRegistered!.containsKey(oNetwork.ssid) && - _htIsNetworkRegistered![oNetwork.ssid]!) + title: Text("${oNetwork.ssid!}${(_htIsNetworkRegistered.containsKey(oNetwork.ssid) && + _htIsNetworkRegistered[oNetwork.ssid]!) ? " *" - : "")), + : ""}"), trailing: PopupMenuButton( padding: EdgeInsets.zero, onSelected: (PopupCommand poCommand) { @@ -256,7 +256,7 @@ class _FlutterWifiIoTState extends State { ), ); }); - }); + } return ListView( padding: kMaterialListPadding, @@ -505,7 +505,7 @@ class _FlutterWifiIoTState extends State { WiFiForIoTPlugin.setWiFiAPSSIDHidden(false); }, ) - : Container(width: 0, height: 0); + : SizedBox(width: 0, height: 0); } else { htPrimaryWidgets.add(Text("SSID is visible")); !_isWiFiAPEnabled @@ -516,7 +516,7 @@ class _FlutterWifiIoTState extends State { WiFiForIoTPlugin.setWiFiAPSSIDHidden(true); }, ) - : Container(width: 0, height: 0); + : SizedBox(width: 0, height: 0); } FutureBuilder( @@ -576,7 +576,7 @@ class _FlutterWifiIoTState extends State { _isConnected = val; })); - String? _sSSID; + String? sSSID; if (_isConnected) { htPrimaryWidgets.addAll([ @@ -585,13 +585,13 @@ class _FlutterWifiIoTState extends State { future: WiFiForIoTPlugin.getSSID(), initialData: "Loading..", builder: (BuildContext context, AsyncSnapshot ssid) { - _sSSID = ssid.data; + sSSID = ssid.data; return Text("SSID: ${ssid.data}"); }), ]); - if (_sSSID == STA_DEFAULT_SSID) { + if (sSSID == STA_DEFAULT_SSID) { htPrimaryWidgets.addAll([ MaterialButton( color: Colors.blue, diff --git a/packages/wifi_iot/example/pubspec.yaml b/packages/wifi_iot/example/pubspec.yaml index 20d81b54..622ac2f0 100644 --- a/packages/wifi_iot/example/pubspec.yaml +++ b/packages/wifi_iot/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.1.0+1 publish_to: none environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.12.0 dependencies: flutter: @@ -15,11 +15,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.6 + cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/packages/wifi_iot/ios/Assets/.gitkeep b/packages/wifi_iot/ios/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/wifi_iot/ios/Classes/WifiIotPlugin.h b/packages/wifi_iot/ios/Classes/WifiIotPlugin.h deleted file mode 100644 index 3ed21275..00000000 --- a/packages/wifi_iot/ios/Classes/WifiIotPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface WifiIotPlugin : NSObject -@end diff --git a/packages/wifi_iot/ios/Classes/WifiIotPlugin.m b/packages/wifi_iot/ios/Classes/WifiIotPlugin.m deleted file mode 100644 index b75f236f..00000000 --- a/packages/wifi_iot/ios/Classes/WifiIotPlugin.m +++ /dev/null @@ -1,8 +0,0 @@ -#import "WifiIotPlugin.h" -#import - -@implementation WifiIotPlugin -+ (void)registerWithRegistrar:(NSObject *)registrar { - [SwiftWifiIotPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/packages/wifi_iot/ios/wifi_iot.podspec b/packages/wifi_iot/ios/wifi_iot.podspec index df11c99a..2689478f 100644 --- a/packages/wifi_iot/ios/wifi_iot.podspec +++ b/packages/wifi_iot/ios/wifi_iot.podspec @@ -4,19 +4,19 @@ Pod::Spec.new do |s| s.name = 'wifi_iot' s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' + s.summary = 'Flutter plugin which can handle WiFi connections and hotspot (AP, STA).' s.description = <<-DESC -A new flutter plugin project. +Flutter plugin which can handle WiFi connections and hotspot (AP, STA). DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/flutternetwork/WiFiFlutter' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'WiFiFlutter' => 'contact@flutternetwork.dev' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' + s.source_files = 'wifi_iot/Sources/wifi_iot/**/*.swift' s.dependency 'Flutter' - - s.ios.deployment_target = '8.0' - s.swift_version = '4.0' -end + s.platform = :ios, '13.0' + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/packages/wifi_iot/ios/wifi_iot/Package.swift b/packages/wifi_iot/ios/wifi_iot/Package.swift new file mode 100644 index 00000000..1b18d1ad --- /dev/null +++ b/packages/wifi_iot/ios/wifi_iot/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "wifi_iot", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "wifi-iot", targets: ["wifi_iot"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "wifi_iot", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ], + resources: [ + // If your plugin requires a privacy manifest, for example if it uses any required + // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's + // privacy impact, and then uncomment these lines. For more information, see + // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + // .process("PrivacyInfo.xcprivacy"), + + // If you have other resources that need to be bundled with your plugin, refer to + // the following instructions to add them: + // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package + ] + ) + ] +) diff --git a/packages/wifi_iot/ios/Classes/SwiftWifiIotPlugin.swift b/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift similarity index 99% rename from packages/wifi_iot/ios/Classes/SwiftWifiIotPlugin.swift rename to packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift index 5638700a..263800c1 100755 --- a/packages/wifi_iot/ios/Classes/SwiftWifiIotPlugin.swift +++ b/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift @@ -3,10 +3,10 @@ import UIKit import SystemConfiguration.CaptiveNetwork import NetworkExtension -public class SwiftWifiIotPlugin: NSObject, FlutterPlugin { +public class WifiIotPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "wifi_iot", binaryMessenger: registrar.messenger()) - let instance = SwiftWifiIotPlugin() + let instance = WifiIotPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } diff --git a/packages/wifi_iot/pubspec.yaml b/packages/wifi_iot/pubspec.yaml index d20135ec..4b6e69b8 100644 --- a/packages/wifi_iot/pubspec.yaml +++ b/packages/wifi_iot/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_iot description: Flutter plugin which can handle WiFi connections and hotspot (AP, STA) -version: 0.3.19+2 +version: 0.3.19+3 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_iot flutter: @@ -14,9 +14,8 @@ flutter: pluginClass: WifiIotPlugin environment: - sdk: '>=2.12.0 <3.0.0' - # Flutter versions prior to 1.10 did not support the flutter.plugin.platforms map. - flutter: ">=1.10.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: flutter: diff --git a/packages/wifi_scan/CHANGELOG.md b/packages/wifi_scan/CHANGELOG.md index b23f7945..f4ac2f9e 100644 --- a/packages/wifi_scan/CHANGELOG.md +++ b/packages/wifi_scan/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.4.1+3 + + - **CHORE**: Flutter 3.44 / Dart 3.12 SDK constraints. + - **FEAT**: Add Swift Package Manager support (CocoaPods retained). + - **CHORE**: Migrate Android Gradle scripts from Groovy to Kotlin DSL; bump AGP, Gradle, and Java toolchain. + - **CHORE**: Migrate to built-in Kotlin. + - **CHORE**: Update dependencies. + ## 0.4.1+2 - **FIX**: Update compileSdkVersion to fix Android release build with flutter 3.24 (#398). diff --git a/packages/wifi_scan/android/build.gradle b/packages/wifi_scan/android/build.gradle deleted file mode 100644 index 24ba46ad..00000000 --- a/packages/wifi_scan/android/build.gradle +++ /dev/null @@ -1,52 +0,0 @@ -group 'dev.flutternetwork.wifi.wifi_scan' -version '1.0-SNAPSHOT' - -buildscript { - ext.kotlin_version = '1.8.21' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - namespace 'dev.flutternetwork.wifi.wifi_scan' - - compileSdkVersion 34 - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - minSdkVersion 16 - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/wifi_scan/android/build.gradle.kts b/packages/wifi_scan/android/build.gradle.kts new file mode 100644 index 00000000..87125adc --- /dev/null +++ b/packages/wifi_scan/android/build.gradle.kts @@ -0,0 +1,51 @@ +group = "dev.flutternetwork.wifi.wifi_scan" +version = "1.0-SNAPSHOT" + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:9.0.1") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +plugins { + id("com.android.library") +} + +android { + namespace = "dev.flutternetwork.wifi.wifi_scan" + + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + } + } + + defaultConfig { + minSdk = 16 + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} diff --git a/packages/wifi_scan/android/gradle.properties b/packages/wifi_scan/android/gradle.properties index 876bcc0e..94adc3a3 100644 --- a/packages/wifi_scan/android/gradle.properties +++ b/packages/wifi_scan/android/gradle.properties @@ -1,5 +1,3 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -android.enableR8=true -android.enableR8.fullMode=false diff --git a/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties b/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties index b5fc5a7b..2d428bfb 100644 --- a/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/packages/wifi_scan/android/settings.gradle b/packages/wifi_scan/android/settings.gradle deleted file mode 100644 index a528fecb..00000000 --- a/packages/wifi_scan/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'wifi_scan' diff --git a/packages/wifi_scan/android/settings.gradle.kts b/packages/wifi_scan/android/settings.gradle.kts new file mode 100644 index 00000000..578988da --- /dev/null +++ b/packages/wifi_scan/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "wifi_scan" diff --git a/packages/wifi_scan/example/android/app/build.gradle b/packages/wifi_scan/example/android/app/build.gradle deleted file mode 100644 index 042d84f1..00000000 --- a/packages/wifi_scan/example/android/app/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace 'dev.flutternetwork.wifi.wifi_scan_example' - - compileSdkVersion flutter.compileSdkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "dev.flutternetwork.wifi.wifi_scan_example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { -} diff --git a/packages/wifi_scan/example/android/app/build.gradle.kts b/packages/wifi_scan/example/android/app/build.gradle.kts new file mode 100644 index 00000000..7c67b7b1 --- /dev/null +++ b/packages/wifi_scan/example/android/app/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "dev.flutternetwork.wifi.wifi_scan_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + applicationId = "dev.flutternetwork.wifi.wifi_scan_example" + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/packages/wifi_scan/example/android/build.gradle b/packages/wifi_scan/example/android/build.gradle deleted file mode 100644 index b42eb3a7..00000000 --- a/packages/wifi_scan/example/android/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/packages/wifi_scan/example/android/build.gradle.kts b/packages/wifi_scan/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/packages/wifi_scan/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/wifi_scan/example/android/gradle.properties b/packages/wifi_scan/example/android/gradle.properties index 25ac3744..e96108cf 100644 --- a/packages/wifi_scan/example/android/gradle.properties +++ b/packages/wifi_scan/example/android/gradle.properties @@ -1,5 +1,6 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true -android.enableR8=true -android.enableR8.fullMode=false \ No newline at end of file +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/packages/wifi_scan/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/wifi_scan/example/android/gradle/wrapper/gradle-wrapper.properties index d07c7fcf..a97e89ca 100644 --- a/packages/wifi_scan/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/wifi_scan/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/packages/wifi_scan/example/android/settings.gradle b/packages/wifi_scan/example/android/settings.gradle deleted file mode 100644 index 5b99b39c..00000000 --- a/packages/wifi_scan/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.2.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.21" apply false -} - -include ":app" \ No newline at end of file diff --git a/packages/wifi_scan/example/android/settings.gradle.kts b/packages/wifi_scan/example/android/settings.gradle.kts new file mode 100644 index 00000000..c21f0c5b --- /dev/null +++ b/packages/wifi_scan/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/packages/wifi_scan/example/ios/Flutter/AppFrameworkInfo.plist b/packages/wifi_scan/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..391a902b 100644 --- a/packages/wifi_scan/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/wifi_scan/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 9.0 diff --git a/packages/wifi_scan/example/ios/Podfile b/packages/wifi_scan/example/ios/Podfile index 1e8c3c90..e72e0b48 100644 --- a/packages/wifi_scan/example/ios/Podfile +++ b/packages/wifi_scan/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj index 1a83e33e..34e0ddd6 100644 --- a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,13 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -36,6 +37,9 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* wifi_scan */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = wifi_scan; path = ../../ios/wifi_scan; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 82A6DCFC7EA9D7C1336EF2EA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -54,6 +58,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ED879899797BD40D00C7B393 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,6 +88,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* wifi_scan */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -139,13 +147,15 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 9119DAADF478773DD2355190 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -156,7 +166,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -174,6 +184,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -200,10 +213,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -212,25 +227,9 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9119DAADF478773DD2355190 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -340,7 +339,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -418,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -467,7 +466,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -547,6 +546,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/wifi_scan/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/wifi_scan/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..128d5765 100644 --- a/packages/wifi_scan/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/wifi_scan/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + diff --git a/packages/wifi_scan/example/ios/Runner/AppDelegate.swift b/packages/wifi_scan/example/ios/Runner/AppDelegate.swift index 70693e4a..c30b367e 100644 --- a/packages/wifi_scan/example/ios/Runner/AppDelegate.swift +++ b/packages/wifi_scan/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/packages/wifi_scan/example/ios/Runner/Info.plist b/packages/wifi_scan/example/ios/Runner/Info.plist index 83f86d25..3ba3efc3 100644 --- a/packages/wifi_scan/example/ios/Runner/Info.plist +++ b/packages/wifi_scan/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/wifi_scan/example/lib/main.dart b/packages/wifi_scan/example/lib/main.dart index 9a409ab3..c3a7ffb2 100644 --- a/packages/wifi_scan/example/lib/main.dart +++ b/packages/wifi_scan/example/lib/main.dart @@ -11,7 +11,7 @@ void main() { /// Example app for wifi_scan plugin. class MyApp extends StatefulWidget { /// Default constructor for [MyApp] widget. - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override State createState() => _MyAppState(); @@ -96,7 +96,7 @@ class _MyAppState extends State { Row( children: [ if (label != null) Text(label), - Switch(value: value, onChanged: onChanged, activeColor: activeColor), + Switch(value: value, onChanged: onChanged, activeThumbColor: activeColor), ], ); @@ -169,8 +169,7 @@ class _MyAppState extends State { class _AccessPointTile extends StatelessWidget { final WiFiAccessPoint accessPoint; - const _AccessPointTile({Key? key, required this.accessPoint}) - : super(key: key); + const _AccessPointTile({required this.accessPoint}); // build row that can display info, based on label: value pair. Widget _buildInfo(String label, dynamic value) => Container( diff --git a/packages/wifi_scan/example/pubspec.yaml b/packages/wifi_scan/example/pubspec.yaml index 20ed8aed..3c9abeea 100644 --- a/packages/wifi_scan/example/pubspec.yaml +++ b/packages/wifi_scan/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the wifi_scan plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.12.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -28,7 +28,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 + cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: @@ -39,7 +39,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/wifi_scan/ios/Assets/.gitkeep b/packages/wifi_scan/ios/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/wifi_scan/ios/Classes/WifiScanPlugin.h b/packages/wifi_scan/ios/Classes/WifiScanPlugin.h deleted file mode 100644 index 8e0a099e..00000000 --- a/packages/wifi_scan/ios/Classes/WifiScanPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface WifiScanPlugin : NSObject -@end diff --git a/packages/wifi_scan/ios/Classes/WifiScanPlugin.m b/packages/wifi_scan/ios/Classes/WifiScanPlugin.m deleted file mode 100644 index ac168139..00000000 --- a/packages/wifi_scan/ios/Classes/WifiScanPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "WifiScanPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "wifi_scan-Swift.h" -#endif - -@implementation WifiScanPlugin -+ (void)registerWithRegistrar:(NSObject *)registrar { - [SwiftWifiScanPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/packages/wifi_scan/ios/wifi_scan.podspec b/packages/wifi_scan/ios/wifi_scan.podspec index 2c085d1d..0ff10130 100644 --- a/packages/wifi_scan/ios/wifi_scan.podspec +++ b/packages/wifi_scan/ios/wifi_scan.podspec @@ -13,9 +13,9 @@ Flutter plugin to scan for nearby visible WiFi access points. s.license = { :file => '../LICENSE' } s.author = { 'WiFiFlutter' => 'contact@flutternetwork.dev' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'wifi_scan/Sources/wifi_scan/**/*.swift' s.dependency 'Flutter' - s.platform = :ios, '9.0' + s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/wifi_scan/ios/wifi_scan/Package.swift b/packages/wifi_scan/ios/wifi_scan/Package.swift new file mode 100644 index 00000000..b6662df0 --- /dev/null +++ b/packages/wifi_scan/ios/wifi_scan/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "wifi_scan", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "wifi-scan", targets: ["wifi_scan"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "wifi_scan", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ], + resources: [ + // If your plugin requires a privacy manifest, for example if it uses any required + // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's + // privacy impact, and then uncomment these lines. For more information, see + // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + // .process("PrivacyInfo.xcprivacy"), + + // If you have other resources that need to be bundled with your plugin, refer to + // the following instructions to add them: + // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package + ] + ) + ] +) diff --git a/packages/wifi_scan/ios/Classes/SwiftWifiScanPlugin.swift b/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift similarity index 93% rename from packages/wifi_scan/ios/Classes/SwiftWifiScanPlugin.swift rename to packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift index 10e40084..3457a20f 100644 --- a/packages/wifi_scan/ios/Classes/SwiftWifiScanPlugin.swift +++ b/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift @@ -3,9 +3,9 @@ import Flutter // Since no API for scanning or getting scanned results in iOS. // This class is just a "stub" implementation with sane returns. // It is maintained to avoid `MissingPluginException`. -public class SwiftWifiScanPlugin: NSObject, FlutterPlugin { +public class WifiScanPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { - let instance = SwiftWifiScanPlugin() + let instance = WifiScanPlugin() // set Flutter channels - 1 for method, 1 for event let channel = FlutterMethodChannel( name: "wifi_scan", binaryMessenger: registrar.messenger() diff --git a/packages/wifi_scan/pubspec.yaml b/packages/wifi_scan/pubspec.yaml index 6fc5bd72..7927bfb7 100644 --- a/packages/wifi_scan/pubspec.yaml +++ b/packages/wifi_scan/pubspec.yaml @@ -1,11 +1,11 @@ name: wifi_scan description: Flutter plugin to scan for nearby visible WiFi access points. -version: 0.4.1+2 +version: 0.4.1+3 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_scan environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=3.0.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: flutter: @@ -14,7 +14,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -24,7 +24,7 @@ flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' and Android 'package' identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when - # adding or updating assets for this project. + # adding or updating assets for the project. plugin: platforms: android: diff --git a/pubspec.lock b/pubspec.lock index 4968248f..bce4efd8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,14 +25,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" charcode: dependency: transitive description: @@ -41,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" cli_launcher: dependency: transitive description: @@ -69,26 +69,26 @@ packages: dependency: transitive description: name: conventional_commit - sha256: dec15ad1118f029c618651a4359eb9135d8b88f761aa24e4016d061cd45948f2 + sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3 url: "https://pub.dev" source: hosted - version: "0.6.0+1" + version: "0.6.1+1" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "6.0.0" glob: dependency: transitive description: @@ -141,26 +141,18 @@ packages: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "2.1.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" + version: "6.1.0" melos: dependency: "direct dev" description: name: melos - sha256: "96e64bbade5712c3f010137e195bca9f1b351fac34ab1f322af492ae34032067" + sha256: "4280dc46bd5b741887cce1e67e5c1a6aaf3c22310035cf5bd33dceeeda62ed22" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "6.3.3" meta: dependency: transitive description: @@ -205,10 +197,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.5" prompts: dependency: transitive description: @@ -229,26 +221,18 @@ packages: dependency: transitive description: name: pub_updater - sha256: b06600619c8c219065a548f8f7c192b3e080beff95488ed692780f48f69c0625 + sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" url: "https://pub.dev" source: hosted - version: "0.3.1" - pubspec: + version: "0.5.0" + pubspec_parse: dependency: transitive description: - name: pubspec - sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "2.3.0" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" + version: "1.5.0" source_span: dependency: transitive description: @@ -265,14 +249,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" string_scanner: dependency: transitive description: @@ -289,14 +265,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" typed_data: dependency: transitive description: @@ -305,14 +273,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uri: - dependency: transitive - description: - name: uri - sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" - url: "https://pub.dev" - source: hosted - version: "1.0.0" web: dependency: transitive description: @@ -338,4 +298,4 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.12.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 001f4043..68bb384b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,7 @@ name: wifi_flutter environment: - sdk: '>=2.18.0 <3.0.0' + sdk: ^3.12.0 dev_dependencies: - melos: ^3.1.1 - flutter_lints: ^2.0.0 - + melos: ^6.3.3 + flutter_lints: ^6.0.0 From 7a5a61c4358390d47da0f7f583cff08f4f521928 Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sat, 5 Sep 2026 13:41:47 +0530 Subject: [PATCH 02/14] format --- packages/wifi_iot/example/lib/main.dart | 414 +++++++++++--------- packages/wifi_iot/lib/wifi_iot.dart | 109 +++--- packages/wifi_scan/example/lib/main.dart | 65 +-- packages/wifi_scan/lib/src/can.dart | 3 +- packages/wifi_scan/lib/wifi_scan.dart | 36 +- packages/wifi_scan/test/wifi_scan_test.dart | 54 +-- 6 files changed, 375 insertions(+), 306 deletions(-) diff --git a/packages/wifi_iot/example/lib/main.dart b/packages/wifi_iot/example/lib/main.dart index 5d83f249..70b54dae 100755 --- a/packages/wifi_iot/example/lib/main.dart +++ b/packages/wifi_iot/example/lib/main.dart @@ -47,11 +47,13 @@ class _FlutterWifiIoTState extends State { _isConnected = val; }); - WiFiForIoTPlugin.isWiFiAPEnabled().then((val) { - _isWiFiAPEnabled = val; - }).catchError((val) { - _isWifiAPSupported = false; - }); + WiFiForIoTPlugin.isWiFiAPEnabled() + .then((val) { + _isWiFiAPEnabled = val; + }) + .catchError((val) { + _isWifiAPSupported = false; + }); super.initState(); } @@ -136,12 +138,16 @@ class _FlutterWifiIoTState extends State { } Future> getClientList( - bool onlyReachables, int reachableTimeout) async { + bool onlyReachables, + int reachableTimeout, + ) async { List htResultClient; try { htResultClient = await WiFiForIoTPlugin.getClientList( - onlyReachables, reachableTimeout); + onlyReachables, + reachableTimeout, + ); } on PlatformException { htResultClient = []; } @@ -176,15 +182,17 @@ class _FlutterWifiIoTState extends State { void showClientList() async { /// Refresh the list and show in console - getClientList(false, 300).then((val) => val.forEach((oClient) { - print("************************"); - print("Client :"); - print("ipAddr = '${oClient.ipAddr}'"); - print("hwAddr = '${oClient.hwAddr}'"); - print("device = '${oClient.device}'"); - print("isReachable = '${oClient.isReachable}'"); - print("************************"); - })); + getClientList(false, 300).then( + (val) => val.forEach((oClient) { + print("************************"); + print("Client :"); + print("ipAddr = '${oClient.ipAddr}'"); + print("hwAddr = '${oClient.hwAddr}'"); + print("device = '${oClient.device}'"); + print("isReachable = '${oClient.isReachable}'"); + print("************************"); + }), + ); } Widget getWidgets() { @@ -203,8 +211,10 @@ class _FlutterWifiIoTState extends State { final List htNetworks = []; for (var oNetwork in _htResultNetwork!) { - final PopupCommand oCmdConnect = - PopupCommand("Connect", oNetwork!.ssid!); + final PopupCommand oCmdConnect = PopupCommand( + "Connect", + oNetwork!.ssid!, + ); final PopupCommand oCmdRemove = PopupCommand("Remove", oNetwork.ssid!); final List> htPopupMenuItems = []; @@ -230,19 +240,20 @@ class _FlutterWifiIoTState extends State { htNetworks.add( ListTile( - title: Text("${oNetwork.ssid!}${(_htIsNetworkRegistered.containsKey(oNetwork.ssid) && - _htIsNetworkRegistered[oNetwork.ssid]!) - ? " *" - : ""}"), + title: Text( + "${oNetwork.ssid!}${(_htIsNetworkRegistered.containsKey(oNetwork.ssid) && _htIsNetworkRegistered[oNetwork.ssid]!) ? " *" : ""}", + ), trailing: PopupMenuButton( padding: EdgeInsets.zero, onSelected: (PopupCommand poCommand) { switch (poCommand.command) { case "Connect": - WiFiForIoTPlugin.connect(STA_DEFAULT_SSID, - password: STA_DEFAULT_PASSWORD, - joinOnce: true, - security: STA_DEFAULT_SECURITY); + WiFiForIoTPlugin.connect( + STA_DEFAULT_SSID, + password: STA_DEFAULT_PASSWORD, + joinOnce: true, + security: STA_DEFAULT_SECURITY, + ); break; case "Remove": WiFiForIoTPlugin.removeWifiNetwork(poCommand.argument); @@ -258,10 +269,7 @@ class _FlutterWifiIoTState extends State { }); } - return ListView( - padding: kMaterialListPadding, - children: htNetworks, - ); + return ListView(padding: kMaterialListPadding, children: htNetworks); } else { return SingleChildScrollView( child: SafeArea( @@ -292,8 +300,10 @@ class _FlutterWifiIoTState extends State { color: Colors.blue, child: Text("Disable", style: textStyle), onPressed: () { - WiFiForIoTPlugin.setEnabled(false, - shouldOpenSettings: _isWifiDisableOpenSettings); + WiFiForIoTPlugin.setEnabled( + false, + shouldOpenSettings: _isWifiDisableOpenSettings, + ); }, ), ]); @@ -308,35 +318,40 @@ class _FlutterWifiIoTState extends State { htPrimaryWidgets.addAll([ Text("Connected"), FutureBuilder( - future: WiFiForIoTPlugin.getSSID(), - initialData: "Loading..", - builder: (BuildContext context, AsyncSnapshot ssid) { - return Text("SSID: ${ssid.data}"); - }), + future: WiFiForIoTPlugin.getSSID(), + initialData: "Loading..", + builder: (BuildContext context, AsyncSnapshot ssid) { + return Text("SSID: ${ssid.data}"); + }, + ), FutureBuilder( - future: WiFiForIoTPlugin.getBSSID(), - initialData: "Loading..", - builder: (BuildContext context, AsyncSnapshot bssid) { - return Text("BSSID: ${bssid.data}"); - }), + future: WiFiForIoTPlugin.getBSSID(), + initialData: "Loading..", + builder: (BuildContext context, AsyncSnapshot bssid) { + return Text("BSSID: ${bssid.data}"); + }, + ), FutureBuilder( - future: WiFiForIoTPlugin.getCurrentSignalStrength(), - initialData: 0, - builder: (BuildContext context, AsyncSnapshot signal) { - return Text("Signal: ${signal.data}"); - }), + future: WiFiForIoTPlugin.getCurrentSignalStrength(), + initialData: 0, + builder: (BuildContext context, AsyncSnapshot signal) { + return Text("Signal: ${signal.data}"); + }, + ), FutureBuilder( - future: WiFiForIoTPlugin.getFrequency(), - initialData: 0, - builder: (BuildContext context, AsyncSnapshot freq) { - return Text("Frequency : ${freq.data}"); - }), + future: WiFiForIoTPlugin.getFrequency(), + initialData: 0, + builder: (BuildContext context, AsyncSnapshot freq) { + return Text("Frequency : ${freq.data}"); + }, + ), FutureBuilder( - future: WiFiForIoTPlugin.getIP(), - initialData: "Loading..", - builder: (BuildContext context, AsyncSnapshot ip) { - return Text("IP : ${ip.data}"); - }), + future: WiFiForIoTPlugin.getIP(), + initialData: "Loading..", + builder: (BuildContext context, AsyncSnapshot ip) { + return Text("IP : ${ip.data}"); + }, + ), MaterialButton( color: Colors.blue, child: Text("Disconnect", style: textStyle), @@ -345,16 +360,17 @@ class _FlutterWifiIoTState extends State { }, ), CheckboxListTile( - title: const Text("Disable WiFi on settings"), - subtitle: const Text("Available only on android API level >= 29"), - value: _isWifiDisableOpenSettings, - onChanged: (bool? setting) { - if (setting != null) { - setState(() { - _isWifiDisableOpenSettings = setting; - }); - } - }) + title: const Text("Disable WiFi on settings"), + subtitle: const Text("Available only on android API level >= 29"), + value: _isWifiDisableOpenSettings, + onChanged: (bool? setting) { + if (setting != null) { + setState(() { + _isWifiDisableOpenSettings = setting; + }); + } + }, + ), ]); } else { htPrimaryWidgets.addAll([ @@ -388,16 +404,17 @@ class _FlutterWifiIoTState extends State { ], ), CheckboxListTile( - title: const Text("Disable WiFi on settings"), - subtitle: const Text("Available only on android API level >= 29"), - value: _isWifiDisableOpenSettings, - onChanged: (bool? setting) { - if (setting != null) { - setState(() { - _isWifiDisableOpenSettings = setting; - }); - } - }) + title: const Text("Disable WiFi on settings"), + subtitle: const Text("Available only on android API level >= 29"), + value: _isWifiDisableOpenSettings, + onChanged: (bool? setting) { + if (setting != null) { + setState(() { + _isWifiDisableOpenSettings = setting; + }); + } + }, + ), ]); } } else { @@ -409,62 +426,68 @@ class _FlutterWifiIoTState extends State { child: Text("Enable", style: textStyle), onPressed: () { setState(() { - WiFiForIoTPlugin.setEnabled(true, - shouldOpenSettings: _isWifiEnableOpenSettings); + WiFiForIoTPlugin.setEnabled( + true, + shouldOpenSettings: _isWifiEnableOpenSettings, + ); }); }, ), CheckboxListTile( - title: const Text("Enable WiFi on settings"), - subtitle: const Text("Available only on android API level >= 29"), - value: _isWifiEnableOpenSettings, - onChanged: (bool? setting) { - if (setting != null) { - setState(() { - _isWifiEnableOpenSettings = setting; - }); - } - }) + title: const Text("Enable WiFi on settings"), + subtitle: const Text("Available only on android API level >= 29"), + value: _isWifiEnableOpenSettings, + onChanged: (bool? setting) { + if (setting != null) { + setState(() { + _isWifiEnableOpenSettings = setting; + }); + } + }, + ), ]); } - htPrimaryWidgets.add(Divider( - height: 32.0, - )); + htPrimaryWidgets.add(Divider(height: 32.0)); if (_isWifiAPSupported) { htPrimaryWidgets.addAll([ Text("WiFi AP State"), FutureBuilder( - future: getWiFiAPState(), - initialData: WIFI_AP_STATE.WIFI_AP_STATE_DISABLED, - builder: (BuildContext context, - AsyncSnapshot wifiState) { - final List widgets = []; - - if (wifiState.data == WIFI_AP_STATE.WIFI_AP_STATE_ENABLED) { - widgets.add(MaterialButton( - color: Colors.blue, - child: Text("Get Client List", style: textStyle), - onPressed: () { - showClientList(); - }, - )); - } + future: getWiFiAPState(), + initialData: WIFI_AP_STATE.WIFI_AP_STATE_DISABLED, + builder: + (BuildContext context, AsyncSnapshot wifiState) { + final List widgets = []; + + if (wifiState.data == WIFI_AP_STATE.WIFI_AP_STATE_ENABLED) { + widgets.add( + MaterialButton( + color: Colors.blue, + child: Text("Get Client List", style: textStyle), + onPressed: () { + showClientList(); + }, + ), + ); + } - widgets.add(Text(wifiState.data.toString())); + widgets.add(Text(wifiState.data.toString())); - return Column(children: widgets); - }), + return Column(children: widgets); + }, + ), ]); WiFiForIoTPlugin.isWiFiAPEnabled() - .then((val) => setState(() { - _isWiFiAPEnabled = val; - })) + .then( + (val) => setState(() { + _isWiFiAPEnabled = val; + }), + ) .catchError((val) { - _isWiFiAPEnabled = false; - }); + _isWiFiAPEnabled = false; + }); if (_isWiFiAPEnabled) { htPrimaryWidgets.addAll([ @@ -491,9 +514,11 @@ class _FlutterWifiIoTState extends State { } WiFiForIoTPlugin.isWiFiAPSSIDHidden() - .then((val) => setState(() { - _isWiFiAPSSIDHidden = val; - })) + .then( + (val) => setState(() { + _isWiFiAPSSIDHidden = val; + }), + ) .catchError((val) => _isWiFiAPSSIDHidden = false); if (_isWiFiAPSSIDHidden) { htPrimaryWidgets.add(Text("SSID is hidden")); @@ -520,44 +545,47 @@ class _FlutterWifiIoTState extends State { } FutureBuilder( - future: getWiFiAPInfos(), - initialData: [], - builder: (BuildContext context, AsyncSnapshot> info) { - htPrimaryWidgets.addAll([ - Text("SSID : ${info.data![0]}"), - Text("KEY : ${info.data![1]}"), - MaterialButton( - color: Colors.blue, - child: Text( - "Set AP info ($AP_DEFAULT_SSID/$AP_DEFAULT_PASSWORD)", - style: textStyle), - onPressed: () { - storeAndConnect(AP_DEFAULT_SSID, AP_DEFAULT_PASSWORD); - }, - ), - Text("AP SSID stored : $_sPreviousAPSSID"), - Text("KEY stored : $_sPreviousPreSharedKey"), - MaterialButton( - color: Colors.blue, - child: Text("Store AP infos", style: textStyle), - onPressed: () { - storeAPInfos(); - }, - ), - MaterialButton( - color: Colors.blue, - child: Text("Restore AP infos", style: textStyle), - onPressed: () { - restoreAPInfos(); - }, + future: getWiFiAPInfos(), + initialData: [], + builder: (BuildContext context, AsyncSnapshot> info) { + htPrimaryWidgets.addAll([ + Text("SSID : ${info.data![0]}"), + Text("KEY : ${info.data![1]}"), + MaterialButton( + color: Colors.blue, + child: Text( + "Set AP info ($AP_DEFAULT_SSID/$AP_DEFAULT_PASSWORD)", + style: textStyle, ), - ]); + onPressed: () { + storeAndConnect(AP_DEFAULT_SSID, AP_DEFAULT_PASSWORD); + }, + ), + Text("AP SSID stored : $_sPreviousAPSSID"), + Text("KEY stored : $_sPreviousPreSharedKey"), + MaterialButton( + color: Colors.blue, + child: Text("Store AP infos", style: textStyle), + onPressed: () { + storeAPInfos(); + }, + ), + MaterialButton( + color: Colors.blue, + child: Text("Restore AP infos", style: textStyle), + onPressed: () { + restoreAPInfos(); + }, + ), + ]); - return Text("SSID : ${info.data![0]}"); - }); + return Text("SSID : ${info.data![0]}"); + }, + ); } else { htPrimaryWidgets.add( - Center(child: Text("Wifi AP probably not supported by your device"))); + Center(child: Text("Wifi AP probably not supported by your device")), + ); } return htPrimaryWidgets; @@ -566,15 +594,19 @@ class _FlutterWifiIoTState extends State { List getButtonWidgetsForiOS() { final List htPrimaryWidgets = []; - WiFiForIoTPlugin.isEnabled().then((val) => setState(() { - _isEnabled = val; - })); + WiFiForIoTPlugin.isEnabled().then( + (val) => setState(() { + _isEnabled = val; + }), + ); if (_isEnabled) { htPrimaryWidgets.add(Text("Wifi Enabled")); - WiFiForIoTPlugin.isConnected().then((val) => setState(() { - _isConnected = val; - })); + WiFiForIoTPlugin.isConnected().then( + (val) => setState(() { + _isConnected = val; + }), + ); String? sSSID; @@ -582,13 +614,14 @@ class _FlutterWifiIoTState extends State { htPrimaryWidgets.addAll([ Text("Connected"), FutureBuilder( - future: WiFiForIoTPlugin.getSSID(), - initialData: "Loading..", - builder: (BuildContext context, AsyncSnapshot ssid) { - sSSID = ssid.data; + future: WiFiForIoTPlugin.getSSID(), + initialData: "Loading..", + builder: (BuildContext context, AsyncSnapshot ssid) { + sSSID = ssid.data; - return Text("SSID: ${ssid.data}"); - }), + return Text("SSID: ${ssid.data}"); + }, + ), ]); if (sSSID == STA_DEFAULT_SSID) { @@ -607,10 +640,12 @@ class _FlutterWifiIoTState extends State { color: Colors.blue, child: Text("Connect to '$AP_DEFAULT_SSID'", style: textStyle), onPressed: () { - WiFiForIoTPlugin.connect(STA_DEFAULT_SSID, - password: STA_DEFAULT_PASSWORD, - joinOnce: true, - security: NetworkSecurity.WPA); + WiFiForIoTPlugin.connect( + STA_DEFAULT_SSID, + password: STA_DEFAULT_PASSWORD, + joinOnce: true, + security: NetworkSecurity.WPA, + ); }, ), ]); @@ -622,10 +657,12 @@ class _FlutterWifiIoTState extends State { color: Colors.blue, child: Text("Connect to '$AP_DEFAULT_SSID'", style: textStyle), onPressed: () { - WiFiForIoTPlugin.connect(STA_DEFAULT_SSID, - password: STA_DEFAULT_PASSWORD, - joinOnce: true, - security: NetworkSecurity.WPA); + WiFiForIoTPlugin.connect( + STA_DEFAULT_SSID, + password: STA_DEFAULT_PASSWORD, + joinOnce: true, + security: NetworkSecurity.WPA, + ); }, ), ]); @@ -637,10 +674,12 @@ class _FlutterWifiIoTState extends State { color: Colors.blue, child: Text("Connect to '$AP_DEFAULT_SSID'", style: textStyle), onPressed: () { - WiFiForIoTPlugin.connect(STA_DEFAULT_SSID, - password: STA_DEFAULT_PASSWORD, - joinOnce: true, - security: NetworkSecurity.WPA); + WiFiForIoTPlugin.connect( + STA_DEFAULT_SSID, + password: STA_DEFAULT_PASSWORD, + joinOnce: true, + security: NetworkSecurity.WPA, + ); }, ), ]); @@ -669,8 +708,9 @@ class _FlutterWifiIoTState extends State { WiFiForIoTPlugin.disconnect(); break; case "remove": - WiFiForIoTPlugin.getSSID().then((val) => - WiFiForIoTPlugin.removeWifiNetwork(val!)); + WiFiForIoTPlugin.getSSID().then( + (val) => WiFiForIoTPlugin.removeWifiNetwork(val!), + ); break; default: break; @@ -678,15 +718,15 @@ class _FlutterWifiIoTState extends State { }, itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: "disconnect", - child: const Text('Disconnect'), - ), - PopupMenuItem( - value: "remove", - child: const Text('Remove'), - ), - ], + PopupMenuItem( + value: "disconnect", + child: const Text('Disconnect'), + ), + PopupMenuItem( + value: "remove", + child: const Text('Remove'), + ), + ], ), ] : null, diff --git a/packages/wifi_iot/lib/wifi_iot.dart b/packages/wifi_iot/lib/wifi_iot.dart index 3cda5388..0125e4a8 100755 --- a/packages/wifi_iot/lib/wifi_iot.dart +++ b/packages/wifi_iot/lib/wifi_iot.dart @@ -10,7 +10,7 @@ enum WIFI_AP_STATE { WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_ENABLING, WIFI_AP_STATE_ENABLED, - WIFI_AP_STATE_FAILED + WIFI_AP_STATE_FAILED, } enum NetworkSecurity { WPA, WEP, NONE } @@ -23,10 +23,12 @@ const serializeNetworkSecurityMap = { const MethodChannel _channel = const MethodChannel('wifi_iot'); @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") -const EventChannel _eventChannel = - const EventChannel('plugins.wififlutter.io/wifi_scan'); + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", +) +const EventChannel _eventChannel = const EventChannel( + 'plugins.wififlutter.io/wifi_scan', +); class WiFiForIoTPlugin { /// Returns whether the WiFi AP is enabled or not @@ -119,7 +121,9 @@ class WiFiForIoTPlugin { /// Get WiFi AP clients @Deprecated("This is will only work with < Android SDK 26.") static Future> getClientList( - bool onlyReachables, int reachableTimeout) async { + bool onlyReachables, + int reachableTimeout, + ) async { final Map htArguments = Map(); htArguments["onlyReachables"] = onlyReachables; htArguments["reachableTimeout"] = reachableTimeout; @@ -171,8 +175,10 @@ class WiFiForIoTPlugin { final Map htArguments = Map(); String? sResult; try { - sResult = - await _channel.invokeMethod('getWiFiAPPreSharedKey', htArguments); + sResult = await _channel.invokeMethod( + 'getWiFiAPPreSharedKey', + htArguments, + ); } on MissingPluginException catch (e) { print("MissingPluginException : ${e.toString()}"); } @@ -192,25 +198,28 @@ class WiFiForIoTPlugin { } @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", + ) static Stream>? _onWifiScanResultReady; @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", + ) static Stream> get onWifiScanResultReady { if (_onWifiScanResultReady == null) { - _onWifiScanResultReady = _eventChannel - .receiveBroadcastStream() - .map((dynamic event) => WifiNetwork.parse(event)); + _onWifiScanResultReady = _eventChannel.receiveBroadcastStream().map( + (dynamic event) => WifiNetwork.parse(event), + ); } return _onWifiScanResultReady!; } @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", + ) static Future>? _loadWifiList() async { final Map htArguments = Map(); String? sResult; @@ -225,8 +234,9 @@ class WiFiForIoTPlugin { } @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", + ) static Future> loadWifiList() async { final List result = (await _loadWifiList() ?? []); if (result.length >= 1) return result; @@ -554,8 +564,10 @@ class WiFiForIoTPlugin { final Map htArguments = Map(); int? iResult; try { - iResult = - await _channel.invokeMethod('getCurrentSignalStrength', htArguments); + iResult = await _channel.invokeMethod( + 'getCurrentSignalStrength', + htArguments, + ); } on MissingPluginException catch (e) { print("MissingPluginException : ${e.toString()}"); } @@ -604,8 +616,10 @@ class WiFiForIoTPlugin { htArguments["ssid"] = ssid; bool? bResult; try { - bResult = - await _channel.invokeMethod('isRegisteredWifiNetwork', htArguments); + bResult = await _channel.invokeMethod( + 'isRegisteredWifiNetwork', + htArguments, + ); } on MissingPluginException catch (e) { print("MissingPluginException : ${e.toString()}"); } @@ -627,17 +641,17 @@ class APClient { bool? isReachable; APClient.fromJson(Map json) - : ipAddr = json['IPAddr'], - hwAddr = json['HWAddr'], - device = json['Device'], - isReachable = json['isReachable']; + : ipAddr = json['IPAddr'], + hwAddr = json['HWAddr'], + device = json['Device'], + isReachable = json['isReachable']; Map toJson() => { - 'IPAddr': ipAddr, - 'HWAddr': hwAddr, - 'Device': device, - 'isReachable': isReachable, - }; + 'IPAddr': ipAddr, + 'HWAddr': hwAddr, + 'Device': device, + 'isReachable': isReachable, + }; static List parse(String psString) { final List htList = []; @@ -653,8 +667,9 @@ class APClient { } @Deprecated( - "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " - "Check - https://pub.dev/packages/wifi_scan") + "This is discontinued, switch to new `wifi_scan` plugin by WiFiFlutter. " + "Check - https://pub.dev/packages/wifi_scan", +) class WifiNetwork { String? ssid; String? bssid; @@ -665,21 +680,21 @@ class WifiNetwork { String? password; WifiNetwork.fromJson(Map json) - : ssid = json['SSID'], - bssid = json['BSSID'], - capabilities = json['capabilities'], - frequency = json['frequency'], - level = json['level'], - timestamp = json['timestamp']; + : ssid = json['SSID'], + bssid = json['BSSID'], + capabilities = json['capabilities'], + frequency = json['frequency'], + level = json['level'], + timestamp = json['timestamp']; Map toJson() => { - 'SSID': ssid, - 'BSSID': bssid, - 'capabilities': capabilities, - 'frequency': frequency, - 'level': level, - 'timestamp': timestamp, - }; + 'SSID': ssid, + 'BSSID': bssid, + 'capabilities': capabilities, + 'frequency': frequency, + 'level': level, + 'timestamp': timestamp, + }; static List parse(String psString) { /// [{"SSID":"Florian","BSSID":"30:7e:cb:8c:48:e4","capabilities":"[WPA-PSK-CCMP+TKIP][ESS]","frequency":2462,"level":-64,"timestamp":201307720907},{"SSID":"Pi3-AP","BSSID":"b8:27:eb:b1:fa:e1","capabilities":"[WPA2-PSK-CCMP][ESS]","frequency":2437,"level":-66,"timestamp":201307720892},{"SSID":"AlternaDom-SonOff","BSSID":"b8:27:eb:98:b4:81","capabilities":"[WPA2-PSK-CCMP][ESS]","frequency":2437,"level":-86,"timestamp":201307720897},{"SSID":"SFR_1CF0_2GEXT","BSSID":"9c:3d:cf:58:98:07","capabilities":"[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]","frequency":2412,"level":-87,"timestamp":201307720887},{"SSID":"Freebox-5CC952","BSSID":"f4:ca:e5:96:71:c4","capabilities":"[WPA-PSK-CCMP][ESS]","frequency":2442,"level":-90,"timestamp":201307720902}] diff --git a/packages/wifi_scan/example/lib/main.dart b/packages/wifi_scan/example/lib/main.dart index c3a7ffb2..bda586f1 100644 --- a/packages/wifi_scan/example/lib/main.dart +++ b/packages/wifi_scan/example/lib/main.dart @@ -69,8 +69,9 @@ class _MyAppState extends State { Future _startListeningToScanResults(BuildContext context) async { if (await _canGetScannedResults(context)) { - subscription = WiFiScan.instance.onScannedResultsAvailable - .listen((result) => setState(() => accessPoints = result)); + subscription = WiFiScan.instance.onScannedResultsAvailable.listen( + (result) => setState(() => accessPoints = result), + ); } } @@ -92,13 +93,12 @@ class _MyAppState extends State { bool value = false, ValueChanged? onChanged, Color? activeColor, - }) => - Row( - children: [ - if (label != null) Text(label), - Switch(value: value, onChanged: onChanged, activeThumbColor: activeColor), - ], - ); + }) => Row( + children: [ + if (label != null) Text(label), + Switch(value: value, onChanged: onChanged, activeThumbColor: activeColor), + ], + ); @override Widget build(BuildContext context) { @@ -108,10 +108,11 @@ class _MyAppState extends State { title: const Text('Plugin example app'), actions: [ _buildToggle( - label: "Check can?", - value: shouldCheckCan, - onChanged: (v) => setState(() => shouldCheckCan = v), - activeColor: Colors.purple) + label: "Check can?", + value: shouldCheckCan, + onChanged: (v) => setState(() => shouldCheckCan = v), + activeColor: Colors.purple, + ), ], ), body: Builder( @@ -151,7 +152,8 @@ class _MyAppState extends State { : ListView.builder( itemCount: accessPoints.length, itemBuilder: (context, i) => - _AccessPointTile(accessPoint: accessPoints[i])), + _AccessPointTile(accessPoint: accessPoints[i]), + ), ), ), ], @@ -173,19 +175,16 @@ class _AccessPointTile extends StatelessWidget { // build row that can display info, based on label: value pair. Widget _buildInfo(String label, dynamic value) => Container( - decoration: const BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey)), - ), - child: Row( - children: [ - Text( - "$label: ", - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Expanded(child: Text(value.toString())) - ], - ), - ); + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey)), + ), + child: Row( + children: [ + Text("$label: ", style: const TextStyle(fontWeight: FontWeight.bold)), + Expanded(child: Text(value.toString())), + ], + ), + ); @override Widget build(BuildContext context) { @@ -211,13 +210,19 @@ class _AccessPointTile extends StatelessWidget { _buildInfo("level", accessPoint.level), _buildInfo("standard", accessPoint.standard), _buildInfo( - "centerFrequency0", "${accessPoint.centerFrequency0}MHz"), + "centerFrequency0", + "${accessPoint.centerFrequency0}MHz", + ), _buildInfo( - "centerFrequency1", "${accessPoint.centerFrequency1}MHz"), + "centerFrequency1", + "${accessPoint.centerFrequency1}MHz", + ), _buildInfo("channelWidth", accessPoint.channelWidth), _buildInfo("isPasspoint", accessPoint.isPasspoint), _buildInfo( - "operatorFriendlyName", accessPoint.operatorFriendlyName), + "operatorFriendlyName", + accessPoint.operatorFriendlyName, + ), _buildInfo("venueName", accessPoint.venueName), _buildInfo("is80211mcResponder", accessPoint.is80211mcResponder), ], diff --git a/packages/wifi_scan/lib/src/can.dart b/packages/wifi_scan/lib/src/can.dart index 8adef95a..2cd1ffbe 100644 --- a/packages/wifi_scan/lib/src/can.dart +++ b/packages/wifi_scan/lib/src/can.dart @@ -91,5 +91,6 @@ CanGetScannedResults _deserializeCanGetScannedResults(int? canCode) { return CanGetScannedResults.noLocationServiceDisabled; } throw UnsupportedError( - "$canCode cannot be serialized to CanGetScannedNetworks"); + "$canCode cannot be serialized to CanGetScannedNetworks", + ); } diff --git a/packages/wifi_scan/lib/wifi_scan.dart b/packages/wifi_scan/lib/wifi_scan.dart index f52c8f5d..c57ca95d 100644 --- a/packages/wifi_scan/lib/wifi_scan.dart +++ b/packages/wifi_scan/lib/wifi_scan.dart @@ -15,8 +15,9 @@ class WiFiScan { static final instance = WiFiScan._(); final _channel = const MethodChannel('wifi_scan'); - final _scannedResultsAvailableChannel = - const EventChannel('wifi_scan/onScannedResultsAvailable'); + final _scannedResultsAvailableChannel = const EventChannel( + 'wifi_scan/onScannedResultsAvailable', + ); Stream>? _onScannedResultsAvailable; /// Checks if it is ok to invoke [startScan]. @@ -48,8 +49,9 @@ class WiFiScan { /// configuration, etc are checked. /// /// Set [askPermissions] flag to ask user for necessary permissions. - Future canGetScannedResults( - {bool askPermissions = true}) async { + Future canGetScannedResults({ + bool askPermissions = true, + }) async { final canCode = await _channel.invokeMethod("canGetScannedResults", { "askPermissions": askPermissions, }); @@ -62,8 +64,9 @@ class WiFiScan { /// /// Should call [canGetScannedResults] as a check before calling this method. Future> getScannedResults() async { - final scannedResults = - await _channel.invokeListMethod("getScannedResults"); + final scannedResults = await _channel.invokeListMethod( + "getScannedResults", + ); return scannedResults! .map((map) => WiFiAccessPoint._fromMap(map)) .toList(growable: false); @@ -76,14 +79,15 @@ class WiFiScan { /// /// Should call [canGetScannedResults] as a check before calling this method. Stream> get onScannedResultsAvailable => - _onScannedResultsAvailable ??= - _scannedResultsAvailableChannel.receiveBroadcastStream().map((event) { - if (event is Error) throw event; - if (event is List) { - return event - .map((map) => WiFiAccessPoint._fromMap(map)) - .toList(growable: false); - } - return const []; - }); + _onScannedResultsAvailable ??= _scannedResultsAvailableChannel + .receiveBroadcastStream() + .map((event) { + if (event is Error) throw event; + if (event is List) { + return event + .map((map) => WiFiAccessPoint._fromMap(map)) + .toList(growable: false); + } + return const []; + }); } diff --git a/packages/wifi_scan/test/wifi_scan_test.dart b/packages/wifi_scan/test/wifi_scan_test.dart index 2eaf579b..4febc9eb 100644 --- a/packages/wifi_scan/test/wifi_scan_test.dart +++ b/packages/wifi_scan/test/wifi_scan_test.dart @@ -11,10 +11,10 @@ void main() { setUp(() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (call) { - final result = mockHandlers[call.method]?.call(call.arguments); - if (result is Future) return result; - return Future.value(result); - }); + final result = mockHandlers[call.method]?.call(call.arguments); + if (result is Future) return result; + return Future.value(result); + }); }); tearDown(() { @@ -40,8 +40,10 @@ void main() { final badCanCodes = [null, -1, 6, 7]; for (int i = 0; i < badCanCodes.length; i++) { mockHandlers["canStartScan"] = (_) => badCanCodes[i]; - expect(() async => await WiFiScan.instance.canStartScan(), - throwsUnsupportedError); + expect( + () async => await WiFiScan.instance.canStartScan(), + throwsUnsupportedError, + ); } }); @@ -69,30 +71,32 @@ void main() { final badCanCodes = [null, -1, 6, 7]; for (int i = 0; i < badCanCodes.length; i++) { mockHandlers["canGetScannedResults"] = (_) => badCanCodes[i]; - expect(() async => await WiFiScan.instance.canGetScannedResults(), - throwsUnsupportedError); + expect( + () async => await WiFiScan.instance.canGetScannedResults(), + throwsUnsupportedError, + ); } }); test("getScannedResults", () async { mockHandlers["getScannedResults"] = (_) => [ - { - "ssid": "my-ssid", - "bssid": "00:00:00:12", - "capabilities": "Unknown", - "frequency": 600, - "level": 5, - "timestamp": null, - "standard": null, - "centerFrequency0": null, - "centerFrequency1": null, - "channelWidth": null, - "isPasspoint": null, - "operatorFriendlyName": null, - "venueName": null, - "is80211mcResponder": null, - } - ]; + { + "ssid": "my-ssid", + "bssid": "00:00:00:12", + "capabilities": "Unknown", + "frequency": 600, + "level": 5, + "timestamp": null, + "standard": null, + "centerFrequency0": null, + "centerFrequency1": null, + "channelWidth": null, + "isPasspoint": null, + "operatorFriendlyName": null, + "venueName": null, + "is80211mcResponder": null, + }, + ]; final scannedNetworks = await WiFiScan.instance.getScannedResults(); expect(scannedNetworks.length, 1); }); From 5966f45546dc8c57f6c577729c8a82103f0b6d36 Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sat, 5 Sep 2026 13:46:28 +0530 Subject: [PATCH 03/14] chore(dart): analyzer added exclude rules --- analysis_options.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2d3f7d19..e6b4cb4f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,13 @@ analyzer: # Ignore generated files - '**/*.g.dart' - 'lib/src/generated/*.dart' + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** linter: rules: From 16114c2a3c9da9cb5ead427c57b3d2245c42cb05 Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sat, 5 Sep 2026 18:07:36 +0530 Subject: [PATCH 04/14] formatting java, kotlin and swift --- .gitignore | 1 + melos.yaml | 2 +- .../com/alternadom/wifiiot/WifiIotPlugin.java | 25 +- .../hotspotmanager/WifiApManager.java | 3 +- .../example/ios/Runner/AppDelegate.swift | 2 +- packages/wifi_iot/ios/wifi_iot/Package.swift | 56 +- .../Sources/wifi_iot/WifiIotPlugin.swift | 774 +++++++++--------- .../wifi/wifi_scan/WifiScanPlugin.kt | 491 +++++------ .../wifi/wifi_scan_example/MainActivity.kt | 3 +- .../wifi_scan/ios/wifi_scan/Package.swift | 56 +- .../Sources/wifi_scan/WifiScanPlugin.swift | 45 +- 11 files changed, 739 insertions(+), 719 deletions(-) diff --git a/.gitignore b/.gitignore index c554b15e..b7db5663 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ unlinked_spec.ds .fvm/ .fvmrc .cxx/ +.repo_tool_cache/ # Swift Package Manager .build/ diff --git a/melos.yaml b/melos.yaml index 613ec65d..cbc064da 100644 --- a/melos.yaml +++ b/melos.yaml @@ -23,7 +23,7 @@ scripts: - Note: you can also rely on your IDEs Dart Analysis / Issues window. format: - run: melos exec -c 5 -- dart format . && dart pub global run flutter_plugin_tools format + run: melos exec -c 5 -- dart format . && dart pub global run flutter_plugin_tools format --base-branch=master description: | activate flutter_plugin_tools - Requires `flutter_plugin_tools` (`pub global activate flutter_plugin_tools`). diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 9f82228e..455839e1 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -337,7 +337,8 @@ private void getWiFiAPSSID(Result poResult) { } else { poResult.error( "Exception [getWiFiAPSSID]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + "Security type is not WifiConfiguration.KeyMgmt.None or" + + " WifiConfiguration.KeyMgmt.WPA2_PSK", null); } } else { @@ -395,7 +396,8 @@ private void isSSIDHidden(Result poResult) { } else { poResult.error( "Exception [isSSIDHidden]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + "Security type is not WifiConfiguration.KeyMgmt.None or" + + " WifiConfiguration.KeyMgmt.WPA2_PSK", null); } } @@ -450,7 +452,8 @@ private void getWiFiAPPreSharedKey(Result poResult) { } else { poResult.error( "Exception [getWiFiAPPreSharedKey]", - "Security type is not WifiConfiguration.KeyMgmt.None or WifiConfiguration.KeyMgmt.WPA2_PSK", + "Security type is not WifiConfiguration.KeyMgmt.None or" + + " WifiConfiguration.KeyMgmt.WPA2_PSK", null); } } else { @@ -714,11 +717,11 @@ JSONArray handleNetworkScanResult() { wifiObject.put("timestamp", 0); } /// Other fields not added - //wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); - //wifiObject.put("venueName", result.venueName); - //wifiObject.put("centerFreq0", result.centerFreq0); - //wifiObject.put("centerFreq1", result.centerFreq1); - //wifiObject.put("channelWidth", result.channelWidth); + // wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); + // wifiObject.put("venueName", result.venueName); + // wifiObject.put("centerFreq0", result.centerFreq0); + // wifiObject.put("centerFreq1", result.centerFreq1); + // wifiObject.put("channelWidth", result.channelWidth); wifiArray.put(wifiObject); } @@ -1191,7 +1194,7 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { List mWifiConfigList = moWiFi.getConfiguredNetworks(); for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - String comparableSSID = ('"' + prefix_ssid); //Add quotes because wifiConfig.SSID has them + String comparableSSID = ('"' + prefix_ssid); // Add quotes because wifiConfig.SSID has them if (wifiConfig.SSID.startsWith(comparableSSID)) { moWiFi.removeNetwork(wifiConfig.networkId); moWiFi.saveConfiguration(); @@ -1223,7 +1226,7 @@ private void isRegisteredWifiNetwork(MethodCall poCall, Result poResult) { String ssid = poCall.argument("ssid"); List mWifiConfigList = moWiFi.getConfiguredNetworks(); - String comparableSSID = ('"' + ssid + '"'); //Add quotes because wifiConfig.SSID has them + String comparableSSID = ('"' + ssid + '"'); // Add quotes because wifiConfig.SSID has them if (mWifiConfigList != null) { for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { if (wifiConfig.SSID.equals(comparableSSID)) { @@ -1319,7 +1322,7 @@ public void run() { moWiFi.removeNetworkSuggestions(networkSuggestions); } - //builder.setIsAppInteractionRequired(true); + // builder.setIsAppInteractionRequired(true); final WifiNetworkSuggestion suggestion = builder.build(); networkSuggestions = new ArrayList<>(); diff --git a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java index 8a427b01..908c732f 100644 --- a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java +++ b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java @@ -67,7 +67,8 @@ public void showWritePermissionSettings(boolean force) { */ public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { try { - // Calling setWifiApEnabled requires MANAGE_WRITE_SETTINGS permissions, so check and request if needed + // Calling setWifiApEnabled requires MANAGE_WRITE_SETTINGS permissions, so check and request + // if needed showWritePermissionSettings(false); if (enabled) { // disable WiFi in any case diff --git a/packages/wifi_iot/example/ios/Runner/AppDelegate.swift b/packages/wifi_iot/example/ios/Runner/AppDelegate.swift index f1dc4867..f5917576 100644 --- a/packages/wifi_iot/example/ios/Runner/AppDelegate.swift +++ b/packages/wifi_iot/example/ios/Runner/AppDelegate.swift @@ -1,5 +1,5 @@ -import UIKit import Flutter +import UIKit @main @objc class AppDelegate: FlutterAppDelegate { diff --git a/packages/wifi_iot/ios/wifi_iot/Package.swift b/packages/wifi_iot/ios/wifi_iot/Package.swift index 1b18d1ad..d6bba092 100644 --- a/packages/wifi_iot/ios/wifi_iot/Package.swift +++ b/packages/wifi_iot/ios/wifi_iot/Package.swift @@ -4,33 +4,33 @@ import PackageDescription let package = Package( - name: "wifi_iot", - platforms: [ - .iOS("13.0") - ], - products: [ - .library(name: "wifi-iot", targets: ["wifi_iot"]) - ], - dependencies: [ - .package(name: "FlutterFramework", path: "../FlutterFramework") - ], - targets: [ - .target( - name: "wifi_iot", - dependencies: [ - .product(name: "FlutterFramework", package: "FlutterFramework") - ], - resources: [ - // If your plugin requires a privacy manifest, for example if it uses any required - // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's - // privacy impact, and then uncomment these lines. For more information, see - // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - // .process("PrivacyInfo.xcprivacy"), + name: "wifi_iot", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "wifi-iot", targets: ["wifi_iot"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "wifi_iot", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ], + resources: [ + // If your plugin requires a privacy manifest, for example if it uses any required + // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's + // privacy impact, and then uncomment these lines. For more information, see + // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + // .process("PrivacyInfo.xcprivacy"), - // If you have other resources that need to be bundled with your plugin, refer to - // the following instructions to add them: - // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package - ] - ) - ] + // If you have other resources that need to be bundled with your plugin, refer to + // the following instructions to add them: + // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package + ] + ) + ] ) diff --git a/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift b/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift index 220a76d7..65073906 100755 --- a/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift +++ b/packages/wifi_iot/ios/wifi_iot/Sources/wifi_iot/WifiIotPlugin.swift @@ -1,454 +1,458 @@ import Flutter -import UIKit -import SystemConfiguration.CaptiveNetwork import NetworkExtension +import SystemConfiguration.CaptiveNetwork +import UIKit public class WifiIotPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "wifi_iot", binaryMessenger: registrar.messenger()) - let instance = WifiIotPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch (call.method) { - /// Stand Alone - case "loadWifiList": - loadWifiList(result: result) - break; - case "forceWifiUsage": - forceWifiUsage(call: call, result: result) - break; - case "isEnabled": - isEnabled(result: result) - break; - case "setEnabled": - setEnabled(call: call, result: result) - break; - case "findAndConnect": // OK - findAndConnect(call: call, result: result) - break; - case "connect": // OK - connect(call: call, result: result) - break; - case "isConnected": // OK - isConnected(result: result) - break; - case "disconnect": // OK - disconnect(result: result) - break; - case "getSSID": - getSSID { (sSSID) in - result(sSSID) - } - break; - case "getBSSID": - getBSSID { (bSSID) in - result(bSSID) - } - break; - case "getCurrentSignalStrength": - getCurrentSignalStrength(result: result) - break; - case "getFrequency": - getFrequency(result: result) - break; - case "getIP": - getIP(result: result) - break; - case "removeWifiNetwork": // OK - removeWifiNetwork(call: call, result: result) - break; - case "isRegisteredWifiNetwork": - isRegisteredWifiNetwork(call: call, result: result) - break; - /// Access Point - case "isWiFiAPEnabled": - isWiFiAPEnabled(result: result) - break; - case "setWiFiAPEnabled": - setWiFiAPEnabled(call: call, result: result) - break; - case "getWiFiAPState": - getWiFiAPState(result: result) - break; - case "getClientList": - getClientList(result: result) - break; - case "getWiFiAPSSID": - getWiFiAPSSID(result: result) - break; - case "setWiFiAPSSID": - setWiFiAPSSID(call: call, result: result) - break; - case "isSSIDHidden": - isSSIDHidden(result: result) - break; - case "setSSIDHidden": - setSSIDHidden(call: call, result: result) - break; - case "getWiFiAPPreSharedKey": - getWiFiAPPreSharedKey(result: result) - break; - case "setWiFiAPPreSharedKey": - setWiFiAPPreSharedKey(call: call, result: result) - break; - default: - result(FlutterMethodNotImplemented); - break; - } - } + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "wifi_iot", binaryMessenger: registrar.messenger()) + let instance = WifiIotPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } - private func loadWifiList(result: FlutterResult) { - result(FlutterMethodNotImplemented) + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + /// Stand Alone + case "loadWifiList": + loadWifiList(result: result) + break + case "forceWifiUsage": + forceWifiUsage(call: call, result: result) + break + case "isEnabled": + isEnabled(result: result) + break + case "setEnabled": + setEnabled(call: call, result: result) + break + case "findAndConnect": // OK + findAndConnect(call: call, result: result) + break + case "connect": // OK + connect(call: call, result: result) + break + case "isConnected": // OK + isConnected(result: result) + break + case "disconnect": // OK + disconnect(result: result) + break + case "getSSID": + getSSID { (sSSID) in + result(sSSID) + } + break + case "getBSSID": + getBSSID { (bSSID) in + result(bSSID) + } + break + case "getCurrentSignalStrength": + getCurrentSignalStrength(result: result) + break + case "getFrequency": + getFrequency(result: result) + break + case "getIP": + getIP(result: result) + break + case "removeWifiNetwork": // OK + removeWifiNetwork(call: call, result: result) + break + case "isRegisteredWifiNetwork": + isRegisteredWifiNetwork(call: call, result: result) + break + /// Access Point + case "isWiFiAPEnabled": + isWiFiAPEnabled(result: result) + break + case "setWiFiAPEnabled": + setWiFiAPEnabled(call: call, result: result) + break + case "getWiFiAPState": + getWiFiAPState(result: result) + break + case "getClientList": + getClientList(result: result) + break + case "getWiFiAPSSID": + getWiFiAPSSID(result: result) + break + case "setWiFiAPSSID": + setWiFiAPSSID(call: call, result: result) + break + case "isSSIDHidden": + isSSIDHidden(result: result) + break + case "setSSIDHidden": + setSSIDHidden(call: call, result: result) + break + case "getWiFiAPPreSharedKey": + getWiFiAPPreSharedKey(result: result) + break + case "setWiFiAPPreSharedKey": + setWiFiAPPreSharedKey(call: call, result: result) + break + default: + result(FlutterMethodNotImplemented) + break } + } - private func forceWifiUsage(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let useWifi = (arguments as! [String : Bool])["useWifi"] - print("Forcing WiFi usage : %s", ((useWifi ?? false) ? "Use WiFi" : "Use 3G/4G Data")) - if #available(iOS 14.0, *) { - if(useWifi ?? false){ - // trigger access for local network - triggerLocalNetworkPrivacyAlert(); - } - result(true) - } else { - result(FlutterMethodNotImplemented) - } + private func loadWifiList(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func forceWifiUsage(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let useWifi = (arguments as! [String: Bool])["useWifi"] + print("Forcing WiFi usage : %s", ((useWifi ?? false) ? "Use WiFi" : "Use 3G/4G Data")) + if #available(iOS 14.0, *) { + if useWifi ?? false { + // trigger access for local network + triggerLocalNetworkPrivacyAlert() + } + result(true) + } else { + result(FlutterMethodNotImplemented) } + } + + private func connect(call: FlutterMethodCall, result: @escaping FlutterResult) { + let sSSID = (call.arguments as? [String: AnyObject])?["ssid"] as! String + let _ = (call.arguments as? [String: AnyObject])?["bssid"] as? String? // not used + let sPassword = (call.arguments as? [String: AnyObject])?["password"] as? String? ?? nil + let bJoinOnce = (call.arguments as? [String: AnyObject])?["join_once"] as! Bool? + let sSecurity = (call.arguments as? [String: AnyObject])?["security"] as! String? - private func connect(call: FlutterMethodCall, result: @escaping FlutterResult) { - let sSSID = (call.arguments as? [String : AnyObject])?["ssid"] as! String - let _ = (call.arguments as? [String : AnyObject])?["bssid"] as? String? // not used - let sPassword = (call.arguments as? [String : AnyObject])?["password"] as? String? ?? nil - let bJoinOnce = (call.arguments as? [String : AnyObject])?["join_once"] as! Bool? - let sSecurity = (call.arguments as? [String : AnyObject])?["security"] as! String? - - if #available(iOS 11.0, *) { - let configuration = initHotspotConfiguration(ssid: sSSID, passphrase: sPassword, security: sSecurity) - configuration.joinOnce = bJoinOnce ?? false - - NEHotspotConfigurationManager.shared.apply(configuration) { [weak self] (error) in - guard let this = self else { - print("WiFi network not found") - result(false) - return - } - this.getSSID { (connectedSSID) -> () in - if (error != nil) { - if (error?.localizedDescription == "already associated.") { - print("Connected to '\(connectedSSID ?? "")'") - result(true) - } else { - print("Not Connected") - result(false) - } - } else if let connectedSSID = connectedSSID { - print("Connected to " + connectedSSID) - // Emit result of [isConnected] by checking if targetSSID is the same as connectedSSID. - result(sSSID == connectedSSID) - } else { - print("WiFi network not found") - result(false) - } - } + if #available(iOS 11.0, *) { + let configuration = initHotspotConfiguration( + ssid: sSSID, passphrase: sPassword, security: sSecurity) + configuration.joinOnce = bJoinOnce ?? false + + NEHotspotConfigurationManager.shared.apply(configuration) { [weak self] (error) in + guard let this = self else { + print("WiFi network not found") + result(false) + return + } + this.getSSID { (connectedSSID) -> Void in + if error != nil { + if error?.localizedDescription == "already associated." { + print("Connected to '\(connectedSSID ?? "")'") + result(true) + } else { + print("Not Connected") + result(false) } - } else { - print("Not Connected") - result(nil) - return + } else if let connectedSSID = connectedSSID { + print("Connected to " + connectedSSID) + // Emit result of [isConnected] by checking if targetSSID is the same as connectedSSID. + result(sSSID == connectedSSID) + } else { + print("WiFi network not found") + result(false) + } } + } + } else { + print("Not Connected") + result(nil) + return } + } - private func findAndConnect(call: FlutterMethodCall, result: @escaping FlutterResult) { - result(FlutterMethodNotImplemented) - } + private func findAndConnect(call: FlutterMethodCall, result: @escaping FlutterResult) { + result(FlutterMethodNotImplemented) + } - @available(iOS 11.0, *) - private func initHotspotConfiguration(ssid: String, passphrase: String?, security: String? = nil) -> NEHotspotConfiguration { - switch security?.uppercased() { - case "WPA": - return NEHotspotConfiguration.init(ssid: ssid, passphrase: passphrase!, isWEP: false) - case "WEP": - return NEHotspotConfiguration.init(ssid: ssid, passphrase: passphrase!, isWEP: true) - default: - return NEHotspotConfiguration.init(ssid: ssid) - } + @available(iOS 11.0, *) + private func initHotspotConfiguration(ssid: String, passphrase: String?, security: String? = nil) + -> NEHotspotConfiguration + { + switch security?.uppercased() { + case "WPA": + return NEHotspotConfiguration.init(ssid: ssid, passphrase: passphrase!, isWEP: false) + case "WEP": + return NEHotspotConfiguration.init(ssid: ssid, passphrase: passphrase!, isWEP: true) + default: + return NEHotspotConfiguration.init(ssid: ssid) } + } - private func isEnabled(result: @escaping FlutterResult) { - // For now.. - getSSID { (sSSID) in - if (sSSID != nil) { - result(true) - } else { - result(nil) - } - } + private func isEnabled(result: @escaping FlutterResult) { + // For now.. + getSSID { (sSSID) in + if sSSID != nil { + result(true) + } else { + result(nil) + } } + } - private func setEnabled(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let state = (arguments as! [String : Bool])["state"] - if (state != nil) { - print("Setting WiFi Enable : \(((state ?? false) ? "enable" : "disable"))") - result(FlutterMethodNotImplemented) - } else { - result(nil) - } + private func setEnabled(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let state = (arguments as! [String: Bool])["state"] + if state != nil { + print("Setting WiFi Enable : \(((state ?? false) ? "enable" : "disable"))") + result(FlutterMethodNotImplemented) + } else { + result(nil) } + } - private func isConnected(result: @escaping FlutterResult) { - // For now.. - getSSID { (sSSID) in - if (sSSID != nil) { - result(true) - } else { - result(false) - } - } + private func isConnected(result: @escaping FlutterResult) { + // For now.. + getSSID { (sSSID) in + if sSSID != nil { + result(true) + } else { + result(false) + } } + } - private func disconnect(result: @escaping FlutterResult) { - if #available(iOS 11.0, *) { - getSSID { (sSSID) in - if (sSSID != nil) { - print("Trying to disconnect from '\(sSSID!)'") - NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: sSSID ?? "") - result(true) - } else { - print("Not connected to a network") - result(false) - } - } + private func disconnect(result: @escaping FlutterResult) { + if #available(iOS 11.0, *) { + getSSID { (sSSID) in + if sSSID != nil { + print("Trying to disconnect from '\(sSSID!)'") + NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: sSSID ?? "") + result(true) } else { - print("disconnect not available on this iOS version") - result(nil) + print("Not connected to a network") + result(false) } + } + } else { + print("disconnect not available on this iOS version") + result(nil) } + } - private func getSSID(result: @escaping (String?) -> ()) { - if #available(iOS 14.0, *) { - NEHotspotNetwork.fetchCurrent(completionHandler: { currentNetwork in - result(currentNetwork?.ssid); - }) - } else { - if let interfaces = CNCopySupportedInterfaces() as NSArray? { - for interface in interfaces { - if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { - result(interfaceInfo[kCNNetworkInfoKeySSID as String] as? String) - return - } - } - } - result(nil) + private func getSSID(result: @escaping (String?) -> Void) { + if #available(iOS 14.0, *) { + NEHotspotNetwork.fetchCurrent(completionHandler: { currentNetwork in + result(currentNetwork?.ssid) + }) + } else { + if let interfaces = CNCopySupportedInterfaces() as NSArray? { + for interface in interfaces { + if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { + result(interfaceInfo[kCNNetworkInfoKeySSID as String] as? String) + return + } } + } + result(nil) } + } - private func getBSSID(result: @escaping (String?) -> ()) { - if #available(iOS 14.0, *) { - NEHotspotNetwork.fetchCurrent(completionHandler: { currentNetwork in - result(currentNetwork?.bssid); - }) - } else { - if let interfaces = CNCopySupportedInterfaces() as NSArray? { - for interface in interfaces { - if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { - result(interfaceInfo[kCNNetworkInfoKeyBSSID as String] as? String) - return - } - } - } - result(nil) + private func getBSSID(result: @escaping (String?) -> Void) { + if #available(iOS 14.0, *) { + NEHotspotNetwork.fetchCurrent(completionHandler: { currentNetwork in + result(currentNetwork?.bssid) + }) + } else { + if let interfaces = CNCopySupportedInterfaces() as NSArray? { + for interface in interfaces { + if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { + result(interfaceInfo[kCNNetworkInfoKeyBSSID as String] as? String) + return + } } + } + result(nil) } - - private func getCurrentSignalStrength(result: FlutterResult) { - result(FlutterMethodNotImplemented) - } + } - private func getFrequency(result: FlutterResult) { - result(FlutterMethodNotImplemented) - } + private func getCurrentSignalStrength(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } - private func getIP(result: FlutterResult) { - guard let interface = getNetworkInterface(family: AF_INET) else { - return result(nil) - } - - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, socklen_t(hostname.count), - nil, socklen_t(0), NI_NUMERICHOST) + private func getFrequency(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } - result(String(cString: hostname)) - } - - private func getNetworkInterface(family: Int32) -> ifaddrs? { - var ifaddr : UnsafeMutablePointer? - guard getifaddrs(&ifaddr) == 0 else { return nil } - guard let firstAddr = ifaddr else { return nil } - - for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { - if ifptr.pointee.ifa_addr.pointee.sa_family == UInt8(family) { - if String(cString: ifptr.pointee.ifa_name) == "en0" { - freeifaddrs(ifaddr) - return ifptr.pointee - } - } - } - freeifaddrs(ifaddr) - return nil + private func getIP(result: FlutterResult) { + guard let interface = getNetworkInterface(family: AF_INET) else { + return result(nil) } - private func removeWifiNetwork(call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments - let sPrefixSSID = (arguments as! [String : String])["ssid"] ?? "" - if (sPrefixSSID == "") { - print("No prefix SSID was given!") - result(nil) - } - - if #available(iOS 11.0, *) { - NEHotspotConfigurationManager.shared.getConfiguredSSIDs { (htSSID) in - for sIncSSID in htSSID { - if (sPrefixSSID != "" && sIncSSID.hasPrefix(sPrefixSSID)) { - NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: sIncSSID) - } - } - } - result(true) - } else { - print("Not removed") - result(nil) - } - } + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + getnameinfo( + interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), + &hostname, socklen_t(hostname.count), + nil, socklen_t(0), NI_NUMERICHOST) - private func isRegisteredWifiNetwork(call: FlutterMethodCall, result: FlutterResult) { - result(FlutterMethodNotImplemented) - } + result(String(cString: hostname)) + } - private func isWiFiAPEnabled(result: FlutterResult) { - result(FlutterMethodNotImplemented) - } + private func getNetworkInterface(family: Int32) -> ifaddrs? { + var ifaddr: UnsafeMutablePointer? + guard getifaddrs(&ifaddr) == 0 else { return nil } + guard let firstAddr = ifaddr else { return nil } - private func setWiFiAPEnabled(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let state = (arguments as! [String : Bool])["state"] - if (state != nil) { - print("Setting AP WiFi Enable : \(state ?? false ? "enable" : "disable")") - result(FlutterMethodNotImplemented) - } else { - result(nil) + for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { + if ifptr.pointee.ifa_addr.pointee.sa_family == UInt8(family) { + if String(cString: ifptr.pointee.ifa_name) == "en0" { + freeifaddrs(ifaddr) + return ifptr.pointee } + } } + freeifaddrs(ifaddr) + return nil + } - private func getWiFiAPState(result: FlutterResult) { - result(FlutterMethodNotImplemented) + private func removeWifiNetwork(call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments + let sPrefixSSID = (arguments as! [String: String])["ssid"] ?? "" + if sPrefixSSID == "" { + print("No prefix SSID was given!") + result(nil) } - private func getClientList(result: FlutterResult) { - result(FlutterMethodNotImplemented) + if #available(iOS 11.0, *) { + NEHotspotConfigurationManager.shared.getConfiguredSSIDs { (htSSID) in + for sIncSSID in htSSID { + if sPrefixSSID != "" && sIncSSID.hasPrefix(sPrefixSSID) { + NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: sIncSSID) + } + } + } + result(true) + } else { + print("Not removed") + result(nil) } + } - private func getWiFiAPSSID(result: FlutterResult) { - result(FlutterMethodNotImplemented) - } + private func isRegisteredWifiNetwork(call: FlutterMethodCall, result: FlutterResult) { + result(FlutterMethodNotImplemented) + } - private func setWiFiAPSSID(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let ssid = (arguments as! [String : String])["ssid"] - if (ssid != nil) { - print("Setting AP WiFi SSID : '\(ssid ?? "")'") - result(FlutterMethodNotImplemented) - } else { - result(nil) - } - } + private func isWiFiAPEnabled(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } - private func isSSIDHidden(result: FlutterResult) { - result(FlutterMethodNotImplemented) + private func setWiFiAPEnabled(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let state = (arguments as! [String: Bool])["state"] + if state != nil { + print("Setting AP WiFi Enable : \(state ?? false ? "enable" : "disable")") + result(FlutterMethodNotImplemented) + } else { + result(nil) } + } - private func setSSIDHidden(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let hidden = (arguments as! [String : Bool])["hidden"] - if (hidden != nil) { - print("Setting AP WiFi Visibility : \(((hidden ?? false) ? "hidden" : "visible"))") - result(FlutterMethodNotImplemented) - } else { - result(nil) - } + private func getWiFiAPState(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func getClientList(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func getWiFiAPSSID(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func setWiFiAPSSID(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let ssid = (arguments as! [String: String])["ssid"] + if ssid != nil { + print("Setting AP WiFi SSID : '\(ssid ?? "")'") + result(FlutterMethodNotImplemented) + } else { + result(nil) } + } - private func getWiFiAPPreSharedKey(result: FlutterResult) { - result(FlutterMethodNotImplemented) + private func isSSIDHidden(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func setSSIDHidden(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let hidden = (arguments as! [String: Bool])["hidden"] + if hidden != nil { + print("Setting AP WiFi Visibility : \(((hidden ?? false) ? "hidden" : "visible"))") + result(FlutterMethodNotImplemented) + } else { + result(nil) } + } - private func setWiFiAPPreSharedKey(call: FlutterMethodCall, result: FlutterResult) { - let arguments = call.arguments - let preSharedKey = (arguments as! [String : String])["preSharedKey"] - if (preSharedKey != nil) { - print("Setting AP WiFi PreSharedKey : '\(preSharedKey ?? "")'") - result(FlutterMethodNotImplemented) - } else { - result(nil) - } + private func getWiFiAPPreSharedKey(result: FlutterResult) { + result(FlutterMethodNotImplemented) + } + + private func setWiFiAPPreSharedKey(call: FlutterMethodCall, result: FlutterResult) { + let arguments = call.arguments + let preSharedKey = (arguments as! [String: String])["preSharedKey"] + if preSharedKey != nil { + print("Setting AP WiFi PreSharedKey : '\(preSharedKey ?? "")'") + result(FlutterMethodNotImplemented) + } else { + result(nil) } + } } /// Used to enforce local network usage for iOSv14+ /// For more background on this, see [Triggering the Local Network Privacy Alert](https://developer.apple.com/forums/thread/663768). func triggerLocalNetworkPrivacyAlert() { - let sock4 = socket(AF_INET, SOCK_DGRAM, 0) - guard sock4 >= 0 else { return } - defer { close(sock4) } - let sock6 = socket(AF_INET6, SOCK_DGRAM, 0) - guard sock6 >= 0 else { return } - defer { close(sock6) } - - let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces() - var message = [UInt8]("!".utf8) - for address in addresses { - address.withUnsafeBytes { buf in - let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self) - let saLen = socklen_t(buf.count) - let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6 - _ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen) - } + let sock4 = socket(AF_INET, SOCK_DGRAM, 0) + guard sock4 >= 0 else { return } + defer { close(sock4) } + let sock6 = socket(AF_INET6, SOCK_DGRAM, 0) + guard sock6 >= 0 else { return } + defer { close(sock6) } + + let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces() + var message = [UInt8]("!".utf8) + for address in addresses { + address.withUnsafeBytes { buf in + let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self) + let saLen = socklen_t(buf.count) + let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6 + _ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen) } + } } /// Returns the addresses of the discard service (port 9) on every /// broadcast-capable interface. /// /// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`. private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] { - var addrList: UnsafeMutablePointer? = nil - let err = getifaddrs(&addrList) - guard err == 0, let start = addrList else { return [] } - defer { freeifaddrs(start) } - return sequence(first: start, next: { $0.pointee.ifa_next }) - .compactMap { i -> Data? in - guard - (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0, - let sa = i.pointee.ifa_addr - else { return nil } - var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len))) - switch CInt(sa.pointee.sa_family) { - case AF_INET: - result.withUnsafeMutableBytes { buf in - let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self) - sin.pointee.sin_port = UInt16(9).bigEndian - } - case AF_INET6: - result.withUnsafeMutableBytes { buf in - let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self) - sin6.pointee.sin6_port = UInt16(9).bigEndian - } - default: - return nil - } - return result + var addrList: UnsafeMutablePointer? = nil + let err = getifaddrs(&addrList) + guard err == 0, let start = addrList else { return [] } + defer { freeifaddrs(start) } + return sequence(first: start, next: { $0.pointee.ifa_next }) + .compactMap { i -> Data? in + guard + (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0, + let sa = i.pointee.ifa_addr + else { return nil } + var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len))) + switch CInt(sa.pointee.sa_family) { + case AF_INET: + result.withUnsafeMutableBytes { buf in + let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self) + sin.pointee.sin_port = UInt16(9).bigEndian + } + case AF_INET6: + result.withUnsafeMutableBytes { buf in + let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self) + sin6.pointee.sin6_port = UInt16(9).bigEndian } + default: + return nil + } + return result + } } diff --git a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt index 0dc46c78..b6c1b6ec 100644 --- a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt +++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt @@ -50,291 +50,302 @@ private const val CAN_GET_RESULTS_NO_LOC_DISABLED = 5 /** Magic codes */ private const val ASK_FOR_LOC_PERM = -1 -/** WifiScanPlugin +/** + * WifiScanPlugin * * Useful links: * - https://developer.android.com/guide/topics/connectivity/wifi-scan * - https://developer.android.com/reference/android/net/wifi/WifiManager * - https://developer.android.com/reference/android/net/wifi/ScanResult * - https://developer.android.com/training/location/permissions - * */ -class WifiScanPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, - PluginRegistry.RequestPermissionsResultListener, EventChannel.StreamHandler { - private val logTag = javaClass.simpleName - private lateinit var context: Context - private var activity: Activity? = null - private var wifi: WifiManager? = null - private var wifiScanReceiver: BroadcastReceiver? = null - private val requestPermissionCookie = mutableMapOf Boolean>() - private val locationPermissionCoarse = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION) - private val locationPermissionFine = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) - private val locationPermissionBoth = locationPermissionCoarse + locationPermissionFine + */ +class WifiScanPlugin : + FlutterPlugin, + MethodCallHandler, + ActivityAware, + PluginRegistry.RequestPermissionsResultListener, + EventChannel.StreamHandler { + private val logTag = javaClass.simpleName + private lateinit var context: Context + private var activity: Activity? = null + private var wifi: WifiManager? = null + private var wifiScanReceiver: BroadcastReceiver? = null + private val requestPermissionCookie = mutableMapOf Boolean>() + private val locationPermissionCoarse = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION) + private val locationPermissionFine = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) + private val locationPermissionBoth = locationPermissionCoarse + locationPermissionFine - // plugin interfaces - private lateinit var channel: MethodChannel - private lateinit var eventChannel: EventChannel + // plugin interfaces + private lateinit var channel: MethodChannel + private lateinit var eventChannel: EventChannel - // single sink - to send - private var eventSink: EventSink? = null + // single sink - to send + private var eventSink: EventSink? = null - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - context = flutterPluginBinding.applicationContext - wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - // set broadcast receiver - listening for new scannedResults - wifiScanReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) { - onScannedResultsAvailable() - } + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + context = flutterPluginBinding.applicationContext + wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + // set broadcast receiver - listening for new scannedResults + wifiScanReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) { + onScannedResultsAvailable() } + } } - val intentFilter = IntentFilter() - intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) - context.registerReceiver(wifiScanReceiver, intentFilter) - - // set Flutter channels - 1 for method, 1 for event - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wifi_scan") - channel.setMethodCallHandler(this) - eventChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - "wifi_scan/onScannedResultsAvailable" - ) - eventChannel.setStreamHandler(this) - } + val intentFilter = IntentFilter() + intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + context.registerReceiver(wifiScanReceiver, intentFilter) - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - eventChannel.setStreamHandler(null) - eventSink?.endOfStream() - eventSink = null - wifi = null - context.unregisterReceiver(wifiScanReceiver) - wifiScanReceiver = null - } + // set Flutter channels - 1 for method, 1 for event + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wifi_scan") + channel.setMethodCallHandler(this) + eventChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "wifi_scan/onScannedResultsAvailable") + eventChannel.setStreamHandler(this) + } + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + eventChannel.setStreamHandler(null) + eventSink?.endOfStream() + eventSink = null + wifi = null + context.unregisterReceiver(wifiScanReceiver) + wifiScanReceiver = null + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - binding.addRequestPermissionsResultListener(this) - } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addRequestPermissionsResultListener(this) + } - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - activity = binding.activity - binding.addRequestPermissionsResultListener(this) - } + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addRequestPermissionsResultListener(this) + } - override fun onDetachedFromActivity() { - activity = null - } + override fun onDetachedFromActivity() { + activity = null + } - override fun onListen(arguments: Any?, events: EventSink?) { - eventSink = events - // put the current available results - to start with - onScannedResultsAvailable() - } + override fun onListen(arguments: Any?, events: EventSink?) { + eventSink = events + // put the current available results - to start with + onScannedResultsAvailable() + } - override fun onCancel(arguments: Any?) { - eventSink?.endOfStream() - eventSink = null - } + override fun onCancel(arguments: Any?) { + eventSink?.endOfStream() + eventSink = null + } - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ): Boolean { - Log.d( - logTag, - "onRequestPermissionsResult: arguments ($requestCode, $permissions, $grantResults)" - ) - Log.d(logTag, "requestPermissionCookie: $requestPermissionCookie") - return requestPermissionCookie[requestCode]?.invoke(grantResults) ?: false - } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ): Boolean { + Log.d( + logTag, "onRequestPermissionsResult: arguments ($requestCode, $permissions, $grantResults)") + Log.d(logTag, "requestPermissionCookie: $requestPermissionCookie") + return requestPermissionCookie[requestCode]?.invoke(grantResults) ?: false + } - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - when (call.method) { - "canStartScan" -> { - val askPermission = call.argument("askPermissions") ?: return result.error( - ERROR_INVALID_ARGS, - "askPermissions argument is null", - null - ) - // if not ASK_FOR_LOC_PERM, send result - // else ask for permission - wait for user action - return result based on it - when (val canCode = canStartScan(askPermission)) { - ASK_FOR_LOC_PERM -> askForLocationPermission { askResult -> - when (askResult) { - AskLocPermResult.GRANTED -> { - result.success(canStartScan(askPermission = false)) - } - AskLocPermResult.UPGRADE_TO_FINE -> { - result.success(CAN_START_SCAN_NO_LOC_PERM_UPGRADE_ACCURACY) - } - AskLocPermResult.DENIED -> { - result.success(CAN_START_SCAN_NO_LOC_PERM_DENIED) - } - AskLocPermResult.ERROR_NO_ACTIVITY -> { - result.error( - ERROR_NULL_ACTIVITY, - "Cannot ask for location permission.", - "Looks like called from non-Activity." - ) - } - } - } - else -> result.success(canCode) + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + when (call.method) { + "canStartScan" -> { + val askPermission = + call.argument("askPermissions") + ?: return result.error(ERROR_INVALID_ARGS, "askPermissions argument is null", null) + // if not ASK_FOR_LOC_PERM, send result + // else ask for permission - wait for user action - return result based on it + when (val canCode = canStartScan(askPermission)) { + ASK_FOR_LOC_PERM -> + askForLocationPermission { askResult -> + when (askResult) { + AskLocPermResult.GRANTED -> { + result.success(canStartScan(askPermission = false)) + } + AskLocPermResult.UPGRADE_TO_FINE -> { + result.success(CAN_START_SCAN_NO_LOC_PERM_UPGRADE_ACCURACY) + } + AskLocPermResult.DENIED -> { + result.success(CAN_START_SCAN_NO_LOC_PERM_DENIED) + } + AskLocPermResult.ERROR_NO_ACTIVITY -> { + result.error( + ERROR_NULL_ACTIVITY, + "Cannot ask for location permission.", + "Looks like called from non-Activity.") + } } - } - "startScan" -> result.success(startScan()) - "canGetScannedResults" -> { - val askPermission = call.argument("askPermissions") ?: return result.error( - ERROR_INVALID_ARGS, - "askPermissions argument is null", - null - ) - when(val canCode = canGetScannedResults(askPermission)){ - ASK_FOR_LOC_PERM -> askForLocationPermission { askResult -> - when (askResult) { - AskLocPermResult.GRANTED -> { - result.success(canGetScannedResults(askPermission = false)) - } - AskLocPermResult.UPGRADE_TO_FINE -> { - result.success(CAN_GET_RESULTS_NO_LOC_PERM_UPGRADE_ACCURACY) - } - AskLocPermResult.DENIED -> { - result.success(CAN_GET_RESULTS_NO_LOC_PERM_DENIED) - } - AskLocPermResult.ERROR_NO_ACTIVITY -> { - result.error( - ERROR_NULL_ACTIVITY, - "Cannot ask for location permission.", - "Looks like called from non-Activity." - ) - } - } - } - else -> result.success(canCode) + } + else -> result.success(canCode) + } + } + "startScan" -> result.success(startScan()) + "canGetScannedResults" -> { + val askPermission = + call.argument("askPermissions") + ?: return result.error(ERROR_INVALID_ARGS, "askPermissions argument is null", null) + when (val canCode = canGetScannedResults(askPermission)) { + ASK_FOR_LOC_PERM -> + askForLocationPermission { askResult -> + when (askResult) { + AskLocPermResult.GRANTED -> { + result.success(canGetScannedResults(askPermission = false)) + } + AskLocPermResult.UPGRADE_TO_FINE -> { + result.success(CAN_GET_RESULTS_NO_LOC_PERM_UPGRADE_ACCURACY) + } + AskLocPermResult.DENIED -> { + result.success(CAN_GET_RESULTS_NO_LOC_PERM_DENIED) + } + AskLocPermResult.ERROR_NO_ACTIVITY -> { + result.error( + ERROR_NULL_ACTIVITY, + "Cannot ask for location permission.", + "Looks like called from non-Activity.") + } } - } - "getScannedResults" -> result.success(getScannedResults()) - else -> result.notImplemented() + } + else -> result.success(canCode) } + } + "getScannedResults" -> result.success(getScannedResults()) + else -> result.notImplemented() } + } - private fun canStartScan(askPermission: Boolean): Int { - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - // for SDK < P[28] : Not in guide, should not require any additional permissions - Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES - // for SDK >= Q[29]: CHANGE_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" - hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES - hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED - } + private fun canStartScan(askPermission: Boolean): Int { + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + // for SDK < P[28] : Not in guide, should not require any additional permissions + Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES + // for SDK >= Q[29]: CHANGE_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" + hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES + hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED } + } - private fun startScan(): Boolean = wifi!!.startScan() + private fun startScan(): Boolean = wifi!!.startScan() - private fun canGetScannedResults(askPermission: Boolean): Int { - // check all prerequisite conditions - // ACCESS_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES - hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED - } + private fun canGetScannedResults(askPermission: Boolean): Int { + // check all prerequisite conditions + // ACCESS_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES + hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED } + } - private fun getScannedResults(): List> = wifi!!.scanResults.map { ap -> + private fun getScannedResults(): List> = + wifi!!.scanResults.map { ap -> mapOf( "ssid" to ap.SSID, "bssid" to ap.BSSID, "capabilities" to ap.capabilities, "frequency" to ap.frequency, "level" to ap.level, - "timestamp" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp else null, - "standard" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, - "centerFrequency0" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, - "centerFrequency1" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, - "channelWidth" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, - "isPasspoint" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, - "operatorFriendlyName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.operatorFriendlyName else null, - "venueName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.venueName else null, - "is80211mcResponder" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null - ) - } + "timestamp" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp + else null, + "standard" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, + "centerFrequency0" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, + "centerFrequency1" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, + "channelWidth" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, + "isPasspoint" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, + "operatorFriendlyName" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.operatorFriendlyName + else null, + "venueName" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.venueName else null, + "is80211mcResponder" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null) + } - private fun onScannedResultsAvailable() { - eventSink?.success(getScannedResults()) - } + private fun onScannedResultsAvailable() { + eventSink?.success(getScannedResults()) + } - /** - * ACCESS_FINE_LOCATION required for: SDK >= Q[29] and tSDK >= Q[29] - */ - private fun requiresFineLocation(): Boolean = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q + /** ACCESS_FINE_LOCATION required for: SDK >= Q[29] and tSDK >= Q[29] */ + private fun requiresFineLocation(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q - private fun hasLocationPermission(): Boolean { - val permissions = when { - requiresFineLocation() -> locationPermissionFine - else -> locationPermissionBoth - } - return permissions.any { permission -> - ContextCompat.checkSelfPermission( - context, - permission - ) == PackageManager.PERMISSION_GRANTED + private fun hasLocationPermission(): Boolean { + val permissions = + when { + requiresFineLocation() -> locationPermissionFine + else -> locationPermissionBoth } + return permissions.any { permission -> + ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED } + } - private enum class AskLocPermResult { - GRANTED, UPGRADE_TO_FINE, DENIED, ERROR_NO_ACTIVITY - } + private enum class AskLocPermResult { + GRANTED, + UPGRADE_TO_FINE, + DENIED, + ERROR_NO_ACTIVITY + } - private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { - // check if has activity - return error if null - if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) - // make permissions - val requiresFine = requiresFineLocation() - // - for SDK > R[30] - cannot only ask for FINE - val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R - val permissions = when { - requiresFineButAskBoth -> locationPermissionBoth - requiresFine -> locationPermissionFine - else -> locationPermissionCoarse - } - // request permission - add result-handler in requestPermissionCookie - val permissionCode = 6567800 + Random.Default.nextInt(100) - requestPermissionCookie[permissionCode] = { grantArray -> - // invoke callback with proper askResult - Log.d(logTag, "permissionResultCallback: args($grantArray)") - callback.invoke( - when { - // GRANTED: if all granted - grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { - AskLocPermResult.GRANTED - } - // UPGRADE_TO_FINE: if requiresFineButAskBoth and COARSE granted - requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { - AskLocPermResult.UPGRADE_TO_FINE - } - else -> AskLocPermResult.DENIED - } - ) - true + private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { + // check if has activity - return error if null + if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) + // make permissions + val requiresFine = requiresFineLocation() + // - for SDK > R[30] - cannot only ask for FINE + val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R + val permissions = + when { + requiresFineButAskBoth -> locationPermissionBoth + requiresFine -> locationPermissionFine + else -> locationPermissionCoarse } - ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) + // request permission - add result-handler in requestPermissionCookie + val permissionCode = 6567800 + Random.Default.nextInt(100) + requestPermissionCookie[permissionCode] = { grantArray -> + // invoke callback with proper askResult + Log.d(logTag, "permissionResultCallback: args($grantArray)") + callback.invoke( + when { + // GRANTED: if all granted + grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { + AskLocPermResult.GRANTED + } + // UPGRADE_TO_FINE: if requiresFineButAskBoth and COARSE granted + requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { + AskLocPermResult.UPGRADE_TO_FINE + } + else -> AskLocPermResult.DENIED + }) + true } + ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) + } - private fun isLocationEnabled(): Boolean = - LocationManagerCompat.isLocationEnabled( - context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager - ) + private fun isLocationEnabled(): Boolean = + LocationManagerCompat.isLocationEnabled( + context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager) } diff --git a/packages/wifi_scan/example/android/app/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan_example/MainActivity.kt b/packages/wifi_scan/example/android/app/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan_example/MainActivity.kt index 6ea3a678..905f4ea2 100644 --- a/packages/wifi_scan/example/android/app/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan_example/MainActivity.kt +++ b/packages/wifi_scan/example/android/app/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan_example/MainActivity.kt @@ -2,5 +2,4 @@ package dev.flutternetwork.wifi.wifi_scan_example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() {} diff --git a/packages/wifi_scan/ios/wifi_scan/Package.swift b/packages/wifi_scan/ios/wifi_scan/Package.swift index b6662df0..d9215e94 100644 --- a/packages/wifi_scan/ios/wifi_scan/Package.swift +++ b/packages/wifi_scan/ios/wifi_scan/Package.swift @@ -4,33 +4,33 @@ import PackageDescription let package = Package( - name: "wifi_scan", - platforms: [ - .iOS("13.0") - ], - products: [ - .library(name: "wifi-scan", targets: ["wifi_scan"]) - ], - dependencies: [ - .package(name: "FlutterFramework", path: "../FlutterFramework") - ], - targets: [ - .target( - name: "wifi_scan", - dependencies: [ - .product(name: "FlutterFramework", package: "FlutterFramework") - ], - resources: [ - // If your plugin requires a privacy manifest, for example if it uses any required - // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's - // privacy impact, and then uncomment these lines. For more information, see - // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - // .process("PrivacyInfo.xcprivacy"), + name: "wifi_scan", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "wifi-scan", targets: ["wifi_scan"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "wifi_scan", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ], + resources: [ + // If your plugin requires a privacy manifest, for example if it uses any required + // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's + // privacy impact, and then uncomment these lines. For more information, see + // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + // .process("PrivacyInfo.xcprivacy"), - // If you have other resources that need to be bundled with your plugin, refer to - // the following instructions to add them: - // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package - ] - ) - ] + // If you have other resources that need to be bundled with your plugin, refer to + // the following instructions to add them: + // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package + ] + ) + ] ) diff --git a/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift b/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift index 3457a20f..96096298 100644 --- a/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift +++ b/packages/wifi_scan/ios/wifi_scan/Sources/wifi_scan/WifiScanPlugin.swift @@ -8,38 +8,39 @@ public class WifiScanPlugin: NSObject, FlutterPlugin { let instance = WifiScanPlugin() // set Flutter channels - 1 for method, 1 for event let channel = FlutterMethodChannel( - name: "wifi_scan", binaryMessenger: registrar.messenger() + name: "wifi_scan", binaryMessenger: registrar.messenger() ) registrar.addMethodCallDelegate(instance, channel: channel) let eventChannel = FlutterEventChannel( - name: "wifi_scan/onScannedResultsAvailable", - binaryMessenger: registrar.messenger() + name: "wifi_scan/onScannedResultsAvailable", + binaryMessenger: registrar.messenger() ) eventChannel.setStreamHandler(StubStreamHandler()) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch(call.method){ - case "canStartScan": - return result(0) // not supported - case "startScan": - return result(false) // always fails - case "canGetScannedResults": - return result(0) // not supported - case "getScannedResults": - return result([]) // empty results - default: - return result(FlutterMethodNotImplemented) + switch call.method { + case "canStartScan": + return result(0) // not supported + case "startScan": + return result(false) // always fails + case "canGetScannedResults": + return result(0) // not supported + case "getScannedResults": + return result([]) // empty results + default: + return result(FlutterMethodNotImplemented) } } } - -class StubStreamHandler: NSObject, FlutterStreamHandler{ - func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - return nil - } - func onCancel(withArguments arguments: Any?) -> FlutterError? { - return nil - } +class StubStreamHandler: NSObject, FlutterStreamHandler { + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + return nil + } + func onCancel(withArguments arguments: Any?) -> FlutterError? { + return nil + } } From 06b0e808b695e48e505a83060ccce0aeceea160c Mon Sep 17 00:00:00 2001 From: Harsh Bhikadia Date: Sat, 5 Sep 2026 18:16:53 +0530 Subject: [PATCH 05/14] fix: remove blank gradle files --- packages/wifi_iot/android/build.gradle | 0 packages/wifi_iot/example/android/build.gradle | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/wifi_iot/android/build.gradle delete mode 100644 packages/wifi_iot/example/android/build.gradle diff --git a/packages/wifi_iot/android/build.gradle b/packages/wifi_iot/android/build.gradle deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/wifi_iot/example/android/build.gradle b/packages/wifi_iot/example/android/build.gradle deleted file mode 100644 index e69de29b..00000000 From 6060d65fd185ef34227c44282a7ca7c849b2cef0 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Mon, 17 Aug 2026 11:33:40 -0400 Subject: [PATCH 06/14] feat: Update API --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 175 ++++++++++++++++-- .../wifi/wifi_scan/WifiScanPlugin.kt | 69 ++++--- 2 files changed, 201 insertions(+), 43 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 455839e1..8f3dcf3a 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -8,10 +8,13 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.TransportInfo; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; import android.net.wifi.SupplicantState; @@ -20,6 +23,7 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiNetworkSuggestion; +import android.net.wifi.WifiSsid; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -27,7 +31,10 @@ import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import java.net.Inet4Address; +import java.net.InetAddress; import info.whitebyte.hotspotmanager.ClientScanResult; import info.whitebyte.hotspotmanager.FinishScanListener; import info.whitebyte.hotspotmanager.WIFI_AP_STATE; @@ -676,8 +683,18 @@ public void onListen(Object o, EventChannel.EventSink eventSink) { private void _onListen(EventChannel.EventSink eventSink) { receiver = createReceiver(eventSink); - moContext.registerReceiver( - receiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + registerScanResultsReceiver(receiver); + } + + @SuppressWarnings("deprecation") + private void registerScanResultsReceiver(BroadcastReceiver broadcastReceiver) { + IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API 33+ + moContext.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + moContext.registerReceiver(broadcastReceiver, filter); + } } @Override @@ -704,9 +721,10 @@ JSONArray handleNetworkScanResult() { try { for (ScanResult result : results) { JSONObject wifiObject = new JSONObject(); - if (!result.SSID.equals("")) { + String ssid = ssidFromScanResult(result); + if (ssid != null && !ssid.isEmpty()) { - wifiObject.put("SSID", result.SSID); + wifiObject.put("SSID", ssid); wifiObject.put("BSSID", result.BSSID); wifiObject.put("capabilities", result.capabilities); wifiObject.put("frequency", result.frequency); @@ -1014,7 +1032,7 @@ public void run() { String security = null; List results = moWiFi.getScanResults(); for (ScanResult result : results) { - String resultString = "" + result.SSID; + String resultString = ssidFromScanResult(result); if (ssid.equals(resultString) && (result.BSSID == null || bssid == null || result.BSSID.equals(bssid))) { security = getSecurityType(result); @@ -1136,11 +1154,11 @@ private void disconnect(Result poResult) { /// This method will return current ssid private void getSSID(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); + WifiInfo info = getCurrentWifiInfo(); // This value should be wrapped in double quotes, so we need to unwrap it. String ssid = info.getSSID(); - if (ssid.startsWith("\"") && ssid.endsWith("\"")) { + if (ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"")) { ssid = ssid.substring(1, ssid.length() - 1); } @@ -1149,12 +1167,12 @@ private void getSSID(Result poResult) { /// This method will return the basic service set identifier (BSSID) of the current access point private void getBSSID(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); + WifiInfo info = getCurrentWifiInfo(); String bssid = info.getBSSID(); try { - poResult.success(bssid.toUpperCase()); + poResult.success(bssid != null ? bssid.toUpperCase() : null); } catch (Exception e) { poResult.error("Exception", e.getMessage(), null); } @@ -1162,13 +1180,12 @@ private void getBSSID(Result poResult) { /// This method will return current WiFi signal strength private void getCurrentSignalStrength(Result poResult) { - int linkSpeed = moWiFi.getConnectionInfo().getRssi(); - poResult.success(linkSpeed); + poResult.success(getCurrentWifiInfo().getRssi()); } /// This method will return current WiFi frequency private void getFrequency(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); + WifiInfo info = getCurrentWifiInfo(); int frequency = 0; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { frequency = info.getFrequency(); @@ -1178,8 +1195,7 @@ private void getFrequency(Result poResult) { /// This method will return current IP private void getIP(Result poResult) { - WifiInfo info = moWiFi.getConnectionInfo(); - String stringip = longToIP(info.getIpAddress()); + String stringip = getCurrentWifiIpv4(); poResult.success(stringip); } @@ -1533,7 +1549,7 @@ private Boolean connectToDeprecated( boolean connected = false; for (int i = 0; i < 20; i++) { - WifiInfo currentNet = moWiFi.getConnectionInfo(); + WifiInfo currentNet = getCurrentWifiInfo(); int networkId = currentNet.getNetworkId(); SupplicantState netState = currentNet.getSupplicantState(); @@ -1552,4 +1568,133 @@ private Boolean connectToDeprecated( return connected; } + + @NonNull + @SuppressWarnings("deprecation") + private WifiInfo getCurrentWifiInfo() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // API 31+ + WifiInfo wifiInfo = wifiInfoFromConnectivityManager(); + if (wifiInfo != null) { + return wifiInfo; + } + } + return moWiFi.getConnectionInfo(); + } + + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private WifiInfo wifiInfoFromConnectivityManager() { + ConnectivityManager connectivityManager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { + return null; + } + + if (joinedNetwork != null) { + WifiInfo joinedInfo = wifiInfoFromNetwork(connectivityManager, joinedNetwork); + if (joinedInfo != null) { + return joinedInfo; + } + } + + Network activeNetwork = connectivityManager.getActiveNetwork(); + if (activeNetwork != null) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork); + if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return wifiInfoFromCapabilities(capabilities); + } + } + + return null; + } + + @Nullable + @RequiresApi(Build.VERSION_CODES.Q) + private WifiInfo wifiInfoFromNetwork( + ConnectivityManager connectivityManager, Network network) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + return capabilities != null ? wifiInfoFromCapabilities(capabilities) : null; + } + + @Nullable + @RequiresApi(Build.VERSION_CODES.Q) + private WifiInfo wifiInfoFromCapabilities(NetworkCapabilities capabilities) { + TransportInfo transportInfo = capabilities.getTransportInfo(); + if (transportInfo instanceof WifiInfo) { + return (WifiInfo) transportInfo; + } + return null; + } + + @Nullable + @SuppressWarnings("deprecation") + private String getCurrentWifiIpv4() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // API 31+ + String ipv4 = ipv4FromLinkProperties(); + if (ipv4 != null) { + return ipv4; + } + } + return longToIP(getCurrentWifiInfo().getIpAddress()); + } + + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private String ipv4FromLinkProperties() { + ConnectivityManager connectivityManager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { + return null; + } + + Network wifiNetwork = joinedNetwork; + if (wifiNetwork == null) { + Network activeNetwork = connectivityManager.getActiveNetwork(); + if (activeNetwork != null) { + NetworkCapabilities capabilities = + connectivityManager.getNetworkCapabilities(activeNetwork); + if (capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + wifiNetwork = activeNetwork; + } + } + } + + if (wifiNetwork == null) { + return null; + } + + LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); + if (linkProperties == null) { + return null; + } + + for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { + InetAddress address = linkAddress.getAddress(); + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } + } + return null; + } + + @Nullable + @SuppressWarnings("deprecation") + private static String ssidFromScanResult(ScanResult result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API 33+ + WifiSsid wifiSsid = result.getWifiSsid(); + if (wifiSsid == null) { + return null; + } + String ssid = wifiSsid.toString(); + if (ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length() >= 2) { + return ssid.substring(1, ssid.length() - 1); + } + return ssid; + } + return result.SSID; + } } diff --git a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt index b6c1b6ec..e9c2c3b4 100644 --- a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt +++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt @@ -96,9 +96,18 @@ class WifiScanPlugin : } } } - val intentFilter = IntentFilter() - intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) - context.registerReceiver(wifiScanReceiver, intentFilter) + val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API 33+ + context.registerReceiver( + wifiScanReceiver, + intentFilter, + Context.RECEIVER_NOT_EXPORTED + ) + } else { + @Suppress("DEPRECATION") + context.registerReceiver(wifiScanReceiver, intentFilter) + } // set Flutter channels - 1 for method, 1 for event channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wifi_scan") @@ -256,36 +265,40 @@ class WifiScanPlugin : private fun getScannedResults(): List> = wifi!!.scanResults.map { ap -> mapOf( - "ssid" to ap.SSID, + "ssid" to ssidFromScanResult(ap), "bssid" to ap.BSSID, "capabilities" to ap.capabilities, "frequency" to ap.frequency, "level" to ap.level, - "timestamp" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp - else null, - "standard" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, - "centerFrequency0" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, - "centerFrequency1" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, - "channelWidth" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, - "isPasspoint" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, - "operatorFriendlyName" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.operatorFriendlyName - else null, - "venueName" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.venueName else null, - "is80211mcResponder" to - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null) - } + "timestamp" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp else null, + "standard" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, + "centerFrequency0" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, + "centerFrequency1" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, + "channelWidth" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, + "isPasspoint" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, + "operatorFriendlyName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") ap.operatorFriendlyName?.toString() + } else null, + "venueName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") ap.venueName?.toString() + } else null, + "is80211mcResponder" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null + ) + } - private fun onScannedResultsAvailable() { - eventSink?.success(getScannedResults()) - } + private fun ssidFromScanResult(ap: android.net.wifi.ScanResult): String? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // API 33+ + val wifiSsid = ap.wifiSsid ?: return null + return wifiSsid.toString().trim('"') + } + @Suppress("DEPRECATION") + return ap.SSID + } + + private fun onScannedResultsAvailable() { + eventSink?.success(getScannedResults()) + } /** ACCESS_FINE_LOCATION required for: SDK >= Q[29] and tSDK >= Q[29] */ private fun requiresFineLocation(): Boolean = From 828f3528184c0d4aa95a7be49bab0b7b3a20f4d3 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Tue, 25 Aug 2026 10:50:15 -0400 Subject: [PATCH 07/14] fix(wifi_iot): resolve SSID from all Wi-Fi networks on Android 12+ --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 95 ++++++++++++------- packages/wifi_iot/pubspec.yaml | 2 +- packages/wifi_scan/pubspec.yaml | 2 +- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 8f3dcf3a..a557829f 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -35,6 +35,8 @@ import androidx.annotation.RequiresApi; import java.net.Inet4Address; import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; import info.whitebyte.hotspotmanager.ClientScanResult; import info.whitebyte.hotspotmanager.FinishScanListener; import info.whitebyte.hotspotmanager.WIFI_AP_STATE; @@ -1162,6 +1164,12 @@ private void getSSID(Result poResult) { ssid = ssid.substring(1, ssid.length() - 1); } + // Android returns this sentinel when the SSID is not readable (permissions / OEM). + if (ssid == null || ssid.equals("") || ssid.equals("0x")) { + poResult.success(null); + return; + } + poResult.success(ssid); } @@ -1591,22 +1599,57 @@ private WifiInfo wifiInfoFromConnectivityManager() { return null; } - if (joinedNetwork != null) { - WifiInfo joinedInfo = wifiInfoFromNetwork(connectivityManager, joinedNetwork); - if (joinedInfo != null) { - return joinedInfo; + for (Network network : wifiNetworksOrdered(connectivityManager)) { + WifiInfo wifiInfo = wifiInfoFromNetwork(connectivityManager, network); + if (wifiInfo != null && hasReadableSsid(wifiInfo)) { + return wifiInfo; } } + return null; + } + + @RequiresApi(Build.VERSION_CODES.M) + private List wifiNetworksOrdered(ConnectivityManager connectivityManager) { + List ordered = new ArrayList<>(); + if (joinedNetwork != null) { + ordered.add(joinedNetwork); + } + + List activeWifi = new ArrayList<>(); + List rest = new ArrayList<>(); Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork != null) { - NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork); - if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - return wifiInfoFromCapabilities(capabilities); + + for (Network network : connectivityManager.getAllNetworks()) { + if (joinedNetwork != null && network.equals(joinedNetwork)) { + continue; + } + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null || !capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + continue; + } + if (activeNetwork != null && network.equals(activeNetwork)) { + activeWifi.add(network); + } else { + rest.add(network); } } - return null; + ordered.addAll(activeWifi); + ordered.addAll(rest); + return ordered; + } + + @SuppressWarnings("deprecation") + private boolean hasReadableSsid(@NonNull WifiInfo wifiInfo) { + String ssid = wifiInfo.getSSID(); + if (ssid == null) { + return false; + } + if (ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length() >= 2) { + ssid = ssid.substring(1, ssid.length() - 1); + } + return !ssid.isEmpty() && !ssid.equals("") && !ssid.equals("0x"); } @Nullable @@ -1649,32 +1692,16 @@ private String ipv4FromLinkProperties() { return null; } - Network wifiNetwork = joinedNetwork; - if (wifiNetwork == null) { - Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork != null) { - NetworkCapabilities capabilities = - connectivityManager.getNetworkCapabilities(activeNetwork); - if (capabilities != null - && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - wifiNetwork = activeNetwork; - } + for (Network wifiNetwork : wifiNetworksOrdered(connectivityManager)) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); + if (linkProperties == null) { + continue; } - } - - if (wifiNetwork == null) { - return null; - } - - LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); - if (linkProperties == null) { - return null; - } - - for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { - InetAddress address = linkAddress.getAddress(); - if (address instanceof Inet4Address) { - return address.getHostAddress(); + for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { + InetAddress address = linkAddress.getAddress(); + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } } } return null; diff --git a/packages/wifi_iot/pubspec.yaml b/packages/wifi_iot/pubspec.yaml index 4b6e69b8..3ed0a935 100644 --- a/packages/wifi_iot/pubspec.yaml +++ b/packages/wifi_iot/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_iot description: Flutter plugin which can handle WiFi connections and hotspot (AP, STA) -version: 0.3.19+3 +version: 0.3.19+4 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_iot flutter: diff --git a/packages/wifi_scan/pubspec.yaml b/packages/wifi_scan/pubspec.yaml index 7927bfb7..ea87e414 100644 --- a/packages/wifi_scan/pubspec.yaml +++ b/packages/wifi_scan/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_scan description: Flutter plugin to scan for nearby visible WiFi access points. -version: 0.4.1+3 +version: 0.4.1+4 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_scan environment: From 7eb0981abe85542476c0d1b9510013e14baf3cdd Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Mon, 7 Sep 2026 23:10:32 -0400 Subject: [PATCH 08/14] feat(wifi_iot): Implement Android modern API --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 911 +++--------------- .../wifiiot/wifi/LegacyWifiPlatform.java | 228 +++++ .../wifiiot/wifi/ModernWifiPlatform.java | 412 ++++++++ .../wifiiot/wifi/WifiConnectCallback.java | 9 + .../wifiiot/wifi/WifiConnectRequest.java | 34 + .../alternadom/wifiiot/wifi/WifiPlatform.java | 53 + .../hotspotmanager/WifiApManager.java | 1 + 7 files changed, 868 insertions(+), 780 deletions(-) create mode 100644 packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java create mode 100644 packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java create mode 100644 packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java create mode 100644 packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java create mode 100644 packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index a557829f..d3c44382 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -8,35 +8,26 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.ConnectivityManager; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -import android.net.TransportInfo; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; -import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiNetworkSuggestion; import android.net.wifi.WifiSsid; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.List; +import com.alternadom.wifiiot.wifi.WifiConnectCallback; +import com.alternadom.wifiiot.wifi.WifiConnectRequest; +import com.alternadom.wifiiot.wifi.WifiPlatform; import info.whitebyte.hotspotmanager.ClientScanResult; import info.whitebyte.hotspotmanager.FinishScanListener; import info.whitebyte.hotspotmanager.WIFI_AP_STATE; @@ -56,19 +47,18 @@ import org.json.JSONException; import org.json.JSONObject; -/** WifiIotPlugin */ +/** WifiIotPlugin — Flutter binding; STA logic in {@link WifiPlatform} (legacy / modern). */ +@SuppressWarnings("deprecation") // API < 29 SoftAp / WifiConfiguration public class WifiIotPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.RequestPermissionsResultListener { - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; private EventChannel eventChannel; - private Network joinedNetwork; private WifiManager moWiFi; private Context moContext; private WifiApManager moWiFiAPManager; @@ -76,12 +66,9 @@ public class WifiIotPlugin private BroadcastReceiver receiver; private WifiManager.LocalOnlyHotspotReservation apReservation; private WIFI_AP_STATE localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; - private ConnectivityManager.NetworkCallback networkCallback; - private List networkSuggestions; - private List ssidsToBeRemovedOnExit = new ArrayList(); - private List suggestionsToBeRemovedOnExit = new ArrayList<>(); - // Permission request management + private WifiPlatform wifiPlatform; + private boolean requestingPermission = false; private Result permissionRequestResultCallback = null; private ArrayList permissionRequestCookie = new ArrayList<>(); @@ -91,88 +78,76 @@ public class WifiIotPlugin 65655437; private static final int PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED = 65655438; - // initialize members of this class with Context private void initWithContext(Context context) { moContext = context; moWiFi = (WifiManager) moContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE); moWiFiAPManager = new WifiApManager(moContext.getApplicationContext()); + ConnectivityManager connectivityManager = + (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); + wifiPlatform = WifiPlatform.create(moContext, moWiFi, connectivityManager); } - // initialize members of this class with Activity private void initWithActivity(Activity activity) { moActivity = activity; } - // cleanup private void cleanup() { - if (!ssidsToBeRemovedOnExit.isEmpty()) { - List wifiConfigList = moWiFi.getConfiguredNetworks(); - for (String ssid : ssidsToBeRemovedOnExit) { - for (WifiConfiguration wifiConfig : wifiConfigList) { - if (wifiConfig.SSID.equals(ssid)) { - moWiFi.removeNetwork(wifiConfig.networkId); - } - } - } + unregisterScanReceiver(); + if (apReservation != null) { + apReservation.close(); + apReservation = null; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !suggestionsToBeRemovedOnExit.isEmpty()) { - moWiFi.removeNetworkSuggestions(suggestionsToBeRemovedOnExit); + if (wifiPlatform != null) { + wifiPlatform.close(); } - // setting all members to null to avoid memory leaks + permissionRequestResultCallback = null; + permissionRequestCookie.clear(); + requestingPermission = false; channel = null; eventChannel = null; moActivity = null; moContext = null; moWiFi = null; moWiFiAPManager = null; + wifiPlatform = null; } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - // initialize method and event channel and set handlers channel = new MethodChannel(binding.getBinaryMessenger(), "wifi_iot"); eventChannel = new EventChannel(binding.getBinaryMessenger(), "plugins.wififlutter.io/wifi_scan"); channel.setMethodCallHandler(this); eventChannel.setStreamHandler(this); - - // initializeWithContext initWithContext(binding.getApplicationContext()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - // set null as channel handlers channel.setMethodCallHandler(null); eventChannel.setStreamHandler(null); - - // set member to null cleanup(); } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - // init with activity initWithActivity(binding.getActivity()); binding.addRequestPermissionsResultListener(this); } @Override public void onDetachedFromActivityForConfigChanges() { - // set activity to null moActivity = null; } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - // init with activity initWithActivity(binding.getActivity()); binding.addRequestPermissionsResultListener(this); } @Override public void onDetachedFromActivity() { - // set activity to null moActivity = null; } @@ -322,11 +297,6 @@ public void onMethodCall(MethodCall poCall, Result poResult) { } } - /** - * The network's SSID. Can either be an ASCII string, which must be enclosed in double quotation - * marks (e.g., {@code "MyNetwork"}), or a string of hex digits, which are not enclosed in quotes - * (e.g., {@code 01a243f405}). - */ private void getWiFiAPSSID(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); @@ -365,11 +335,8 @@ private void setWiFiAPSSID(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - oWiFiConfig.SSID = sAPSSID; - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); } else { poResult.error( @@ -379,10 +346,6 @@ private void setWiFiAPSSID(MethodCall poCall, Result poResult) { } } - /** - * This is a network that does not broadcast its SSID, so an SSID-specific probe request must be - * used for scans. - */ private void isSSIDHidden(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); @@ -419,13 +382,9 @@ private void isSSIDHidden(Result poResult) { private void setSSIDHidden(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { boolean isSSIDHidden = poCall.argument("hidden"); - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - oWiFiConfig.hiddenSSID = isSSIDHidden; - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); } else { poResult.error( @@ -435,13 +394,6 @@ private void setSSIDHidden(MethodCall poCall, Result poResult) { } } - /** - * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in double quotation marks - * (e.g., {@code "abcdefghij"} for PSK passphrase or a string of 64 hex digits for raw PSK. - * - *

When the value of this key is read, the actual key is not returned, just a "*" if the key - * has a value, or the null string otherwise. - */ private void getWiFiAPPreSharedKey(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); @@ -478,13 +430,9 @@ private void getWiFiAPPreSharedKey(Result poResult) { private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { String sPreSharedKey = poCall.argument("preSharedKey"); - android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); - oWiFiConfig.preSharedKey = sPreSharedKey; - moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); } else { poResult.error( @@ -494,12 +442,6 @@ private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { } } - /** - * Gets a list of the clients connected to the Hotspot *** getClientList : param onlyReachables - * {@code false} if the list should contain unreachable (probably disconnected) clients, {@code - * true} otherwise param reachableTimeout Reachable Timout in miliseconds, 300 is default param - * finishListener, Interface called when the scan method finishes - */ private void getClientList(MethodCall poCall, final Result poResult) { Boolean onlyReachables = false; if (poCall.argument("onlyReachables") != null) { @@ -555,12 +497,7 @@ public void onFinishScan(final ArrayList clients) { } } - /** - * Return whether Wi-Fi AP is enabled or disabled. *** isWifiApEnabled : return {@code true} if - * Wi-Fi AP is enabled - */ private void isWiFiAPEnabled(Result poResult) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { try { poResult.success(moWiFiAPManager.isWifiApEnabled()); @@ -573,17 +510,9 @@ private void isWiFiAPEnabled(Result poResult) { } } - /** - * Start AccessPoint mode with the specified configuration. If the radio is already running in AP - * mode, update the new configuration Note that starting in access point mode disables station - * mode operation *** setWifiApEnabled : param wifiConfig SSID, security and channel details as - * part of WifiConfiguration return {@code true} if the operation succeeds, {@code false} - * otherwise - */ private void setWiFiAPEnabled(MethodCall poCall, final Result poResult) { boolean enabled = poCall.argument("state"); - /** Using LocalOnlyHotspotCallback when setting WiFi AP state on API level >= 29 */ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { final boolean result = moWiFiAPManager.setWifiApEnabled(null, enabled); poResult.success(result); @@ -642,19 +571,12 @@ public void onFailed(int reason) { } } - /** - * Show write permission settings page to user Depending on Android version and application these - * may be needed to perform certain WiFi configurations that require WRITE_SETTINGS which require - * a double opt-in, not just presence in manifest. *** showWritePermissionSettings : param boolean - * force, if true shows always, if false only if permissions are not already granted - */ private void showWritePermissionSettings(MethodCall poCall, Result poResult) { boolean force = poCall.argument("force"); moWiFiAPManager.showWritePermissionSettings(force); poResult.success(null); } - /** Gets the Wi-Fi enabled state. *** getWifiApState : return {link WIFI_AP_STATE} */ private void getWiFiAPState(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { poResult.success(moWiFiAPManager.getWifiApState().ordinal()); @@ -677,34 +599,42 @@ public void onListen(Object o, EventChannel.EventSink eventSink) { moActivity.requestPermissions( new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); - // actual call will be handled in [onRequestPermissionsResult] } else { _onListen(eventSink); } } private void _onListen(EventChannel.EventSink eventSink) { + unregisterScanReceiver(); receiver = createReceiver(eventSink); - registerScanResultsReceiver(receiver); + IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + registerScanResultsReceiver(receiver, filter); } - @SuppressWarnings("deprecation") - private void registerScanResultsReceiver(BroadcastReceiver broadcastReceiver) { - IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // API 33+ + @SuppressWarnings("deprecation") // API < 33 + private void registerScanResultsReceiver(BroadcastReceiver broadcastReceiver, IntentFilter filter) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33+ moContext.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); } else { moContext.registerReceiver(broadcastReceiver, filter); } } - @Override - public void onCancel(Object o) { - if (receiver != null) { + private void unregisterScanReceiver() { + if (receiver == null || moContext == null) { + return; + } + try { moContext.unregisterReceiver(receiver); - receiver = null; + } catch (IllegalArgumentException ignored) { + // already unregistered } + receiver = null; + } + + @Override + public void onCancel(Object o) { + unregisterScanReceiver(); } private BroadcastReceiver createReceiver(final EventChannel.EventSink eventSink) { @@ -725,7 +655,6 @@ JSONArray handleNetworkScanResult() { JSONObject wifiObject = new JSONObject(); String ssid = ssidFromScanResult(result); if (ssid != null && !ssid.isEmpty()) { - wifiObject.put("SSID", ssid); wifiObject.put("BSSID", result.BSSID); wifiObject.put("capabilities", result.capabilities); @@ -736,13 +665,6 @@ JSONArray handleNetworkScanResult() { } else { wifiObject.put("timestamp", 0); } - /// Other fields not added - // wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); - // wifiObject.put("venueName", result.venueName); - // wifiObject.put("centerFreq0", result.centerFreq0); - // wifiObject.put("centerFreq1", result.centerFreq1); - // wifiObject.put("channelWidth", result.channelWidth); - wifiArray.put(wifiObject); } } @@ -753,7 +675,6 @@ JSONArray handleNetworkScanResult() { } } - /// Method to load wifi list into string via Callback. Returns a stringified JSONArray private void loadWifiList(final Result poResult) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) @@ -768,7 +689,6 @@ private void loadWifiList(final Result poResult) { moActivity.requestPermissions( new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); - // actual call will be handled in [onRequestPermissionsResult] } else { _loadWifiList(poResult); } @@ -783,6 +703,7 @@ private void _loadWifiList(final Result poResult) { } } + @SuppressWarnings("deprecation") // API < 23 private boolean selectNetwork(final Network network, final ConnectivityManager manager) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return manager.bindProcessToNetwork(network); @@ -804,14 +725,6 @@ public void run() { }); } - /// Method to force wifi usage if the user needs to send requests via wifi - /// if it does not have internet connection. Useful for IoT applications, when - /// the app needs to communicate and send requests to a device that have no - /// internet connection via wifi. - - /// Receives a boolean to enable forceWifiUsage if true, and disable if false. - /// Is important to enable only when communicating with the device via wifi - /// and remember to disable it when disconnecting from device. private void forceWifiUsage(final MethodCall poCall, final Result poResult) { boolean useWifi = poCall.argument("useWifi"); @@ -822,15 +735,11 @@ private void forceWifiUsage(final MethodCall poCall, final Result poResult) { boolean shouldReply = true; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP && manager != null) { if (useWifi) { - // SDK-31 If not previously in a disconnected state, select the joinedNetwork to ensure - // the correct network is used for communications, else fallback to network manager network. - // https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections + Network joinedNetwork = wifiPlatform.getJoinedNetwork(); if (joinedNetwork != null) { success = selectNetwork(joinedNetwork, manager); } else { - NetworkRequest.Builder builder; - builder = new NetworkRequest.Builder(); - /// set the transport type do WIFI + NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); shouldReply = false; manager.requestNetwork( @@ -853,22 +762,18 @@ public void onAvailable(Network network) { } } - /// Method to check if wifi is enabled private void isEnabled(Result poResult) { poResult.success(moWiFi.isWifiEnabled()); } - /// Method to connect/disconnect wifi service + @SuppressWarnings("deprecation") // API < 29 private void setEnabled(MethodCall poCall, Result poResult) { Boolean enabled = poCall.argument("state"); Boolean shouldOpenSettings = poCall.argument("shouldOpenSettings"); - // Enable or Disable WiFi programmatically if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { moWiFi.setWifiEnabled(enabled); - } - // Whether to open native WiFi settings or not - else { + } else { if (shouldOpenSettings != null) { if (shouldOpenSettings) { Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); @@ -889,116 +794,66 @@ private void setEnabled(MethodCall poCall, Result poResult) { private void connect(final MethodCall poCall, final Result poResult) { new Thread() { public void run() { - String ssid = poCall.argument("ssid"); - String bssid = poCall.argument("bssid"); - String password = poCall.argument("password"); - String security = poCall.argument("security"); - Boolean joinOnce = poCall.argument("join_once"); - Boolean withInternet = poCall.argument("with_internet"); - Boolean isHidden = poCall.argument("is_hidden"); - Integer timeoutInSeconds = poCall.argument("timeout_in_seconds"); - - connectTo( - poResult, - ssid, - bssid, - password, - security, - joinOnce, - withInternet, - isHidden, - timeoutInSeconds); + WifiConnectRequest request = + new WifiConnectRequest( + poCall.argument("ssid"), + poCall.argument("bssid"), + poCall.argument("password"), + poCall.argument("security"), + poCall.argument("join_once"), + poCall.argument("with_internet"), + poCall.argument("is_hidden"), + poCall.argument("timeout_in_seconds")); + connectWithResult(request, poResult); } }.start(); } - /// Transform a string based bssid into a MacAdress. - /// Return null in case of error. - @RequiresApi(Build.VERSION_CODES.P) - private static MacAddress macAddressFromBssid(String bssid) { - if (bssid == null) { - return null; - } + private void connectWithResult(WifiConnectRequest request, final Result poResult) { + final Handler handler = new Handler(Looper.getMainLooper()); + wifiPlatform.connect( + request, + new WifiConnectCallback() { + @Override + public void onSuccess(final boolean connected) { + if (Looper.myLooper() == Looper.getMainLooper()) { + poResult.success(connected); + } else { + handler.post(() -> poResult.success(connected)); + } + } - try { - return MacAddress.fromString(bssid); - } catch (IllegalArgumentException invalidRepresentation) { - Log.e( - WifiIotPlugin.class.getSimpleName(), - "Mac address parsing failed for bssid: " + bssid, - invalidRepresentation); - return null; - } + @Override + public void onError(final String code, final String message, final Object details) { + if (Looper.myLooper() == Looper.getMainLooper()) { + poResult.error(code, message, details); + } else { + handler.post(() -> poResult.error(code, message, details)); + } + } + }); } - /** - * Registers a wifi network in the device wireless networks For API >= 30 uses intent to - * permanently store such network in user configuration For API <= 29 uses deprecated functions - * that manipulate directly *** registerWifiNetwork : param ssid, SSID to register param password, - * passphrase to use param security, security mode (WPA or null) to use return {@code true} if the - * operation succeeds, {@code false} otherwise - */ private void registerWifiNetwork(final MethodCall poCall, final Result poResult) { - String ssid = poCall.argument("ssid"); - String bssid = poCall.argument("bssid"); - String password = poCall.argument("password"); - String security = poCall.argument("security"); - Boolean isHidden = poCall.argument("is_hidden"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - final WifiNetworkSuggestion.Builder suggestedNet = new WifiNetworkSuggestion.Builder(); - suggestedNet.setSsid(ssid); - suggestedNet.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - poResult.error("Error", "Invalid BSSID representation", ""); - return; - } - suggestedNet.setBssid(macAddress); - } - - if (security != null && security.toUpperCase().equals("WPA")) { - suggestedNet.setWpa2Passphrase(password); - } else if (security != null && security.toUpperCase().equals("WEP")) { - // WEP is not supported - poResult.error( - "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); - return; - } - - final ArrayList suggestionsList = - new ArrayList(); - suggestionsList.add(suggestedNet.build()); - - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList( - android.provider.Settings.EXTRA_WIFI_NETWORK_LIST, suggestionsList); - Intent intent = new Intent(android.provider.Settings.ACTION_WIFI_ADD_NETWORKS); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - moContext.startActivity(intent); - - poResult.success(null); - } else { - // Deprecated version - android.net.wifi.WifiConfiguration conf = - generateConfiguration(ssid, bssid, password, security, isHidden); - - int updateNetwork = registerWifiNetworkDeprecated(conf); + wifiPlatform.registerNetwork( + poCall.argument("ssid"), + poCall.argument("bssid"), + poCall.argument("password"), + poCall.argument("security"), + poCall.argument("is_hidden"), + new WifiPlatform.RegisterCallback() { + @Override + public void onSuccess() { + poResult.success(null); + } - if (updateNetwork == -1) { - poResult.error("Error", "Error updating network configuration", ""); - } else { - poResult.success(null); - } - } + @Override + public void onError(String code, String message, Object details) { + poResult.error(code, message, details); + } + }); } - /// Send the ssid and password of a Wifi network into this to connect to the network. - /// Example: wifi.findAndConnect(ssid, password); - /// After 10 seconds, a post telling you whether you are connected will pop up. - /// Callback returns true if ssid is in the range private void findAndConnect(final MethodCall poCall, final Result poResult) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) @@ -1015,7 +870,6 @@ private void findAndConnect(final MethodCall poCall, final Result poResult) { moActivity.requestPermissions( new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); - // actual call will be handled in [onRequestPermissionsResult] } else { _findAndConnect(poCall, poResult); } @@ -1037,42 +891,21 @@ public void run() { String resultString = ssidFromScanResult(result); if (ssid.equals(resultString) && (result.BSSID == null || bssid == null || result.BSSID.equals(bssid))) { - security = getSecurityType(result); + security = securityTypeFromScanResult(result); if (bssid == null) { bssid = result.BSSID; } } } - connectTo( - poResult, - ssid, - bssid, - password, - security, - joinOnce, - withInternet, - false, - timeoutInSeconds); + WifiConnectRequest request = + new WifiConnectRequest( + ssid, bssid, password, security, joinOnce, withInternet, false, timeoutInSeconds); + connectWithResult(request, poResult); } }.start(); } - private static String getSecurityType(ScanResult scanResult) { - String capabilities = scanResult.capabilities; - - if (capabilities.contains("WPA") - || capabilities.contains("WPA2") - || capabilities.contains("WPA/WPA2 PSK")) { - return "WPA"; - } else if (capabilities.contains("WEP")) { - return "WEP"; - } else { - return null; - } - } - - /// Use this method to check if the device is currently connected to Wifi. private void isConnected(Result poResult) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { isConnectedDeprecated(poResult); @@ -1089,7 +922,6 @@ private void isConnected(Result poResult) { moActivity.requestPermissions( new String[] {Manifest.permission.ACCESS_NETWORK_STATE}, PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED); - // actual call will be handled in [onRequestPermissionsResult] } else { _isConnected(poResult); } @@ -1101,8 +933,6 @@ private void _isConnected(Result poResult) { (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); boolean result = false; if (connManager != null) { - // `connManager.getActiveNetwork` only return if the network has internet - // therefore using `connManager.getAllNetworks()` to check all networks for (final Network network : connManager.getAllNetworks()) { final NetworkCapabilities capabilities = network != null ? connManager.getNetworkCapabilities(network) : null; @@ -1118,7 +948,7 @@ private void _isConnected(Result poResult) { poResult.success(result); } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // API < 23 private void isConnectedDeprecated(Result poResult) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -1128,57 +958,17 @@ private void isConnectedDeprecated(Result poResult) { poResult.success(mWifi != null && mWifi.isConnected()); } - /// Disconnect current Wifi. private void disconnect(Result poResult) { - boolean disconnected = false; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - //noinspection deprecation - disconnected = moWiFi.disconnect(); - } else { - if (networkCallback != null) { - final ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - connectivityManager.unregisterNetworkCallback(networkCallback); - networkCallback = null; - disconnected = true; - joinedNetwork = null; - } else if (networkSuggestions != null) { - final int networksRemoved = moWiFi.removeNetworkSuggestions(networkSuggestions); - disconnected = networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; - } else { - Log.e( - WifiIotPlugin.class.getSimpleName(), - "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); - } - } - poResult.success(disconnected); + poResult.success(wifiPlatform.disconnect()); } - /// This method will return current ssid private void getSSID(Result poResult) { - WifiInfo info = getCurrentWifiInfo(); - - // This value should be wrapped in double quotes, so we need to unwrap it. - String ssid = info.getSSID(); - if (ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"")) { - ssid = ssid.substring(1, ssid.length() - 1); - } - - // Android returns this sentinel when the SSID is not readable (permissions / OEM). - if (ssid == null || ssid.equals("") || ssid.equals("0x")) { - poResult.success(null); - return; - } - - poResult.success(ssid); + poResult.success(wifiPlatform.getSsid()); } - /// This method will return the basic service set identifier (BSSID) of the current access point private void getBSSID(Result poResult) { - WifiInfo info = getCurrentWifiInfo(); - + WifiInfo info = wifiPlatform.getWifiInfo(); String bssid = info.getBSSID(); - try { poResult.success(bssid != null ? bssid.toUpperCase() : null); } catch (Exception e) { @@ -1186,28 +976,24 @@ private void getBSSID(Result poResult) { } } - /// This method will return current WiFi signal strength private void getCurrentSignalStrength(Result poResult) { - poResult.success(getCurrentWifiInfo().getRssi()); + poResult.success(wifiPlatform.getWifiInfo().getRssi()); } - /// This method will return current WiFi frequency private void getFrequency(Result poResult) { - WifiInfo info = getCurrentWifiInfo(); + WifiInfo info = wifiPlatform.getWifiInfo(); int frequency = 0; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { frequency = info.getFrequency(); } poResult.success(frequency); } - /// This method will return current IP private void getIP(Result poResult) { - String stringip = getCurrentWifiIpv4(); - poResult.success(stringip); + poResult.success(wifiPlatform.getIpv4()); } - /// This method will remove the WiFi network as per the passed SSID from the device list + @SuppressWarnings("deprecation") // API < 29 private void removeWifiNetwork(MethodCall poCall, Result poResult) { String prefix_ssid = poCall.argument("ssid"); if (prefix_ssid.equals("")) { @@ -1216,9 +1002,9 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { boolean removed = false; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - List mWifiConfigList = moWiFi.getConfiguredNetworks(); - for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - String comparableSSID = ('"' + prefix_ssid); // Add quotes because wifiConfig.SSID has them + List mWifiConfigList = moWiFi.getConfiguredNetworks(); + for (WifiConfiguration wifiConfig : mWifiConfigList) { + String comparableSSID = ('"' + prefix_ssid); if (wifiConfig.SSID.startsWith(comparableSSID)) { moWiFi.removeNetwork(wifiConfig.networkId); moWiFi.saveConfiguration(); @@ -1228,10 +1014,9 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { } } - // remove network suggestion if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { List suggestions = moWiFi.getNetworkSuggestions(); - List removeSuggestions = new ArrayList(); + List removeSuggestions = new ArrayList<>(); for (int i = 0, suggestionsSize = suggestions.size(); i < suggestionsSize; i++) { WifiNetworkSuggestion suggestion = suggestions.get(i); if (suggestion.getSsid().startsWith(prefix_ssid)) { @@ -1244,15 +1029,13 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { poResult.success(removed); } - /// This method will remove the WiFi network as per the passed SSID from the device list + @SuppressWarnings("deprecation") // API < 29 private void isRegisteredWifiNetwork(MethodCall poCall, Result poResult) { - String ssid = poCall.argument("ssid"); - - List mWifiConfigList = moWiFi.getConfiguredNetworks(); - String comparableSSID = ('"' + ssid + '"'); // Add quotes because wifiConfig.SSID has them + List mWifiConfigList = moWiFi.getConfiguredNetworks(); + String comparableSSID = ('"' + ssid + '"'); if (mWifiConfigList != null) { - for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { + for (WifiConfiguration wifiConfig : mWifiConfigList) { if (wifiConfig.SSID.equals(comparableSSID)) { poResult.success(true); return; @@ -1262,456 +1045,11 @@ private void isRegisteredWifiNetwork(MethodCall poCall, Result poResult) { poResult.success(false); } - private static String longToIP(int longIp) { - StringBuilder sb = new StringBuilder(""); - String[] strip = new String[4]; - strip[3] = String.valueOf((longIp >>> 24)); - strip[2] = String.valueOf((longIp & 0x00FFFFFF) >>> 16); - strip[1] = String.valueOf((longIp & 0x0000FFFF) >>> 8); - strip[0] = String.valueOf((longIp & 0x000000FF)); - sb.append(strip[0]); - sb.append("."); - sb.append(strip[1]); - sb.append("."); - sb.append(strip[2]); - sb.append("."); - sb.append(strip[3]); - return sb.toString(); - } - - /// Method to connect to WIFI Network - private void connectTo( - final Result poResult, - final String ssid, - final String bssid, - final String password, - final String security, - final Boolean joinOnce, - final Boolean withInternet, - final Boolean isHidden, - final Integer timeoutInSeconds) { - final Handler handler = new Handler(Looper.getMainLooper()); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - final boolean connected = - connectToDeprecated(ssid, bssid, password, security, joinOnce, isHidden); - handler.post( - new Runnable() { - @Override - public void run() { - poResult.success(connected); - } - }); - } else { - // error if WEP security, since not supported - if (security != null && security.toUpperCase().equals("WEP")) { - handler.post( - new Runnable() { - @Override - public void run() { - poResult.error( - "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); - } - }); - return; - } - - if (withInternet != null && withInternet) { - // create network suggestion - final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); - // set ssid - builder.setSsid(ssid); - builder.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); - return; - } - builder.setBssid(macAddress); - } - - // set password - if (security != null && security.toUpperCase().equals("WPA")) { - builder.setWpa2Passphrase(password); - } - - // remove suggestions if already existing - if (networkSuggestions != null) { - moWiFi.removeNetworkSuggestions(networkSuggestions); - } - - // builder.setIsAppInteractionRequired(true); - final WifiNetworkSuggestion suggestion = builder.build(); - - networkSuggestions = new ArrayList<>(); - networkSuggestions.add(suggestion); - if (joinOnce != null && joinOnce) { - suggestionsToBeRemovedOnExit.add(suggestion); - } - - final int status = moWiFi.addNetworkSuggestions(networkSuggestions); - Log.e(WifiIotPlugin.class.getSimpleName(), "status: " + status); - - handler.post( - new Runnable() { - @Override - public void run() { - poResult.success(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); - } - }); - } else { - // Make new network specifier - final WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder(); - // set ssid - builder.setSsid(ssid); - builder.setIsHiddenSsid(isHidden != null ? isHidden : false); - if (bssid != null) { - final MacAddress macAddress = macAddressFromBssid(bssid); - if (macAddress == null) { - handler.post( - new Runnable() { - @Override - public void run() { - poResult.error("Error", "Invalid BSSID representation", ""); - } - }); - return; - } - builder.setBssid(macAddress); - } - - // set security - if (security != null && security.toUpperCase().equals("WPA")) { - builder.setWpa2Passphrase(password); - } - - final NetworkRequest networkRequest = - new NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .setNetworkSpecifier(builder.build()) - .build(); - - final ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - - if (networkCallback != null) connectivityManager.unregisterNetworkCallback(networkCallback); - - networkCallback = - new ConnectivityManager.NetworkCallback() { - boolean resultSent = false; - - @Override - public void onAvailable(@NonNull Network network) { - super.onAvailable(network); - if (!resultSent) { - joinedNetwork = network; - poResult.success(true); - resultSent = true; - } - } - - @Override - public void onUnavailable() { - super.onUnavailable(); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - connectivityManager.unregisterNetworkCallback(this); - } - if (!resultSent) { - poResult.success(false); - resultSent = true; - } - } - - @Override - public void onLost(Network network) { - super.onLost(network); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - connectivityManager.unregisterNetworkCallback(this); - } - } - }; - - connectivityManager.requestNetwork( - networkRequest, networkCallback, handler, timeoutInSeconds * 1000); - } - } - } - - @SuppressWarnings("deprecation") - private int registerWifiNetworkDeprecated(android.net.wifi.WifiConfiguration conf) { - int updateNetwork = -1; - int registeredNetwork = -1; - - /// Remove the existing configuration for this netwrok - List mWifiConfigList = moWiFi.getConfiguredNetworks(); - - if (mWifiConfigList != null) { - for (android.net.wifi.WifiConfiguration wifiConfig : mWifiConfigList) { - if (wifiConfig.SSID.equals(conf.SSID) - && (wifiConfig.BSSID == null - || conf.BSSID == null - || wifiConfig.BSSID.equals(conf.BSSID))) { - conf.networkId = wifiConfig.networkId; - registeredNetwork = wifiConfig.networkId; - updateNetwork = moWiFi.updateNetwork(conf); - } - } - } - - /// If network not already in configured networks add new network - if (updateNetwork == -1) { - updateNetwork = moWiFi.addNetwork(conf); - moWiFi.saveConfiguration(); - } - - // Try returning last known valid network id - if (updateNetwork == -1) { - return registeredNetwork; - } - - return updateNetwork; - } - - private android.net.wifi.WifiConfiguration generateConfiguration( - String ssid, String bssid, String password, String security, Boolean isHidden) { - android.net.wifi.WifiConfiguration conf = new android.net.wifi.WifiConfiguration(); - conf.SSID = "\"" + ssid + "\""; - conf.hiddenSSID = isHidden != null ? isHidden : false; - if (bssid != null) { - conf.BSSID = bssid; - } - - if (security != null) security = security.toUpperCase(); - else security = "NONE"; - - if (security.toUpperCase().equals("WPA")) { - - /// appropriate ciper is need to set according to security type used, - /// ifcase of not added it will not be able to connect - conf.preSharedKey = "\"" + password + "\""; - - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); - - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); - - conf.status = android.net.wifi.WifiConfiguration.Status.ENABLED; - - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.TKIP); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.CCMP); - - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK); - - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.TKIP); - conf.allowedPairwiseCiphers.set(android.net.wifi.WifiConfiguration.PairwiseCipher.CCMP); - - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.RSN); - conf.allowedProtocols.set(android.net.wifi.WifiConfiguration.Protocol.WPA); - } else if (security.equals("WEP")) { - conf.wepKeys[0] = "\"" + password + "\""; - conf.wepTxKeyIndex = 0; - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); - conf.allowedGroupCiphers.set(android.net.wifi.WifiConfiguration.GroupCipher.WEP40); - } else { - conf.allowedKeyManagement.set(android.net.wifi.WifiConfiguration.KeyMgmt.NONE); - } - - return conf; - } - - @SuppressWarnings("deprecation") - private Boolean connectToDeprecated( - String ssid, - String bssid, - String password, - String security, - Boolean joinOnce, - Boolean isHidden) { - /// Make new configuration - android.net.wifi.WifiConfiguration conf = - generateConfiguration(ssid, bssid, password, security, isHidden); - - int updateNetwork = registerWifiNetworkDeprecated(conf); - - if (updateNetwork == -1) { - return false; - } - - if (joinOnce != null && joinOnce.booleanValue()) { - ssidsToBeRemovedOnExit.add(conf.SSID); - } - - boolean disconnect = moWiFi.disconnect(); - if (!disconnect) { - return false; - } - - boolean enabled = moWiFi.enableNetwork(updateNetwork, true); - if (!enabled) return false; - - boolean connected = false; - for (int i = 0; i < 20; i++) { - WifiInfo currentNet = getCurrentWifiInfo(); - int networkId = currentNet.getNetworkId(); - SupplicantState netState = currentNet.getSupplicantState(); - - // Wait for connection to reach state completed - // to discard false positives like auth error - if (networkId != -1 && netState == SupplicantState.COMPLETED) { - connected = networkId == updateNetwork; - break; - } - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - break; - } - } - - return connected; - } - - @NonNull - @SuppressWarnings("deprecation") - private WifiInfo getCurrentWifiInfo() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // API 31+ - WifiInfo wifiInfo = wifiInfoFromConnectivityManager(); - if (wifiInfo != null) { - return wifiInfo; - } - } - return moWiFi.getConnectionInfo(); - } - - @Nullable - @RequiresApi(Build.VERSION_CODES.S) - private WifiInfo wifiInfoFromConnectivityManager() { - ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - return null; - } - - for (Network network : wifiNetworksOrdered(connectivityManager)) { - WifiInfo wifiInfo = wifiInfoFromNetwork(connectivityManager, network); - if (wifiInfo != null && hasReadableSsid(wifiInfo)) { - return wifiInfo; - } - } - - return null; - } - - @RequiresApi(Build.VERSION_CODES.M) - private List wifiNetworksOrdered(ConnectivityManager connectivityManager) { - List ordered = new ArrayList<>(); - if (joinedNetwork != null) { - ordered.add(joinedNetwork); - } - - List activeWifi = new ArrayList<>(); - List rest = new ArrayList<>(); - Network activeNetwork = connectivityManager.getActiveNetwork(); - - for (Network network : connectivityManager.getAllNetworks()) { - if (joinedNetwork != null && network.equals(joinedNetwork)) { - continue; - } - NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); - if (capabilities == null || !capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - continue; - } - if (activeNetwork != null && network.equals(activeNetwork)) { - activeWifi.add(network); - } else { - rest.add(network); - } - } - - ordered.addAll(activeWifi); - ordered.addAll(rest); - return ordered; - } - - @SuppressWarnings("deprecation") - private boolean hasReadableSsid(@NonNull WifiInfo wifiInfo) { - String ssid = wifiInfo.getSSID(); - if (ssid == null) { - return false; - } - if (ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length() >= 2) { - ssid = ssid.substring(1, ssid.length() - 1); - } - return !ssid.isEmpty() && !ssid.equals("") && !ssid.equals("0x"); - } - - @Nullable - @RequiresApi(Build.VERSION_CODES.Q) - private WifiInfo wifiInfoFromNetwork( - ConnectivityManager connectivityManager, Network network) { - NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); - return capabilities != null ? wifiInfoFromCapabilities(capabilities) : null; - } - - @Nullable - @RequiresApi(Build.VERSION_CODES.Q) - private WifiInfo wifiInfoFromCapabilities(NetworkCapabilities capabilities) { - TransportInfo transportInfo = capabilities.getTransportInfo(); - if (transportInfo instanceof WifiInfo) { - return (WifiInfo) transportInfo; - } - return null; - } - - @Nullable - @SuppressWarnings("deprecation") - private String getCurrentWifiIpv4() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // API 31+ - String ipv4 = ipv4FromLinkProperties(); - if (ipv4 != null) { - return ipv4; - } - } - return longToIP(getCurrentWifiInfo().getIpAddress()); - } - + /** API 33+: WifiSsid; API < 33: SSID. */ @Nullable - @RequiresApi(Build.VERSION_CODES.S) - private String ipv4FromLinkProperties() { - ConnectivityManager connectivityManager = - (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - return null; - } - - for (Network wifiNetwork : wifiNetworksOrdered(connectivityManager)) { - LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); - if (linkProperties == null) { - continue; - } - for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { - InetAddress address = linkAddress.getAddress(); - if (address instanceof Inet4Address) { - return address.getHostAddress(); - } - } - } - return null; - } - - @Nullable - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // API < 33 private static String ssidFromScanResult(ScanResult result) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // API 33+ WifiSsid wifiSsid = result.getWifiSsid(); if (wifiSsid == null) { return null; @@ -1724,4 +1062,17 @@ private static String ssidFromScanResult(ScanResult result) { } return result.SSID; } + + @Nullable + private static String securityTypeFromScanResult(ScanResult scanResult) { + String capabilities = scanResult.capabilities; + if (capabilities.contains("WPA") + || capabilities.contains("WPA2") + || capabilities.contains("WPA/WPA2 PSK")) { + return "WPA"; + } else if (capabilities.contains("WEP")) { + return "WEP"; + } + return null; + } } diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java new file mode 100644 index 00000000..32e6e551 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/LegacyWifiPlatform.java @@ -0,0 +1,228 @@ +package com.alternadom.wifiiot.wifi; + +import android.net.Network; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** Legacy STA backend for API < 29. */ +@SuppressWarnings("deprecation") // API < 29 +public final class LegacyWifiPlatform implements WifiPlatform { + private static final String UNKNOWN_SSID = ""; + private static final String HEX_EMPTY_SSID = "0x"; + + private final WifiManager wifiManager; + private final List ssidsToRemoveOnClose = new ArrayList<>(); + + public LegacyWifiPlatform(WifiManager wifiManager) { + this.wifiManager = wifiManager; + } + + @Override + public void connect(WifiConnectRequest request, WifiConnectCallback callback) { + callback.onSuccess( + connectLegacy( + request.ssid, + request.bssid, + request.password, + request.security, + request.joinOnce, + request.isHidden)); + } + + @Override + public boolean disconnect() { + return wifiManager.disconnect(); + } + + @Override + public void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + WifiConfiguration conf = createConfiguration(ssid, bssid, password, security, isHidden); + if (updateOrCreateNetwork(conf) == -1) { + callback.onError("Error", "Error updating network configuration", ""); + } else { + callback.onSuccess(); + } + } + + @Override + @NonNull + public WifiInfo getWifiInfo() { + return wifiManager.getConnectionInfo(); + } + + @Override + @Nullable + public String getSsid() { + return normalizeSsid(getWifiInfo().getSSID()); + } + + @Override + @Nullable + public String getIpv4() { + return formatIpv4(getWifiInfo().getIpAddress()); + } + + @Override + @Nullable + public Network getJoinedNetwork() { + return null; + } + + @Override + public void close() { + if (ssidsToRemoveOnClose.isEmpty()) { + return; + } + List configs = wifiManager.getConfiguredNetworks(); + if (configs == null) { + return; + } + for (String ssid : ssidsToRemoveOnClose) { + for (WifiConfiguration config : configs) { + if (config.SSID.equals(ssid)) { + wifiManager.removeNetwork(config.networkId); + } + } + } + ssidsToRemoveOnClose.clear(); + } + + private boolean connectLegacy( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean joinOnce, + @Nullable Boolean isHidden) { + WifiConfiguration conf = createConfiguration(ssid, bssid, password, security, isHidden); + int networkId = updateOrCreateNetwork(conf); + if (networkId == -1) { + return false; + } + + if (joinOnce != null && joinOnce) { + ssidsToRemoveOnClose.add(conf.SSID); + } + + if (!wifiManager.disconnect()) { + return false; + } + if (!wifiManager.enableNetwork(networkId, true)) { + return false; + } + + for (int i = 0; i < 20; i++) { + WifiInfo info = getWifiInfo(); + if (info.getNetworkId() != -1 && info.getSupplicantState() == SupplicantState.COMPLETED) { + return info.getNetworkId() == networkId; + } + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + break; + } + } + return false; + } + + private WifiConfiguration createConfiguration( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden) { + WifiConfiguration conf = new WifiConfiguration(); + conf.SSID = "\"" + ssid + "\""; + conf.hiddenSSID = isHidden != null ? isHidden : false; + if (bssid != null) { + conf.BSSID = bssid; + } + + if (security != null) { + security = security.toUpperCase(); + } else { + security = "NONE"; + } + + if (security.equals("WPA")) { + conf.preSharedKey = "\"" + password + "\""; + conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + conf.status = WifiConfiguration.Status.ENABLED; + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + conf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + conf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + conf.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + } else if (security.equals("WEP")) { + conf.wepKeys[0] = "\"" + password + "\""; + conf.wepTxKeyIndex = 0; + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); + } else { + conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } + return conf; + } + + private int updateOrCreateNetwork(WifiConfiguration conf) { + int updateNetwork = -1; + int registeredNetwork = -1; + List configs = wifiManager.getConfiguredNetworks(); + if (configs != null) { + for (WifiConfiguration existing : configs) { + if (existing.SSID.equals(conf.SSID) + && (existing.BSSID == null + || conf.BSSID == null + || existing.BSSID.equals(conf.BSSID))) { + conf.networkId = existing.networkId; + registeredNetwork = existing.networkId; + updateNetwork = wifiManager.updateNetwork(conf); + } + } + } + if (updateNetwork == -1) { + updateNetwork = wifiManager.addNetwork(conf); + wifiManager.saveConfiguration(); + } + return updateNetwork == -1 ? registeredNetwork : updateNetwork; + } + + @Nullable + static String normalizeSsid(@Nullable String ssid) { + if (ssid == null) { + return null; + } + if (ssid.length() >= 2 && ssid.charAt(0) == '"' && ssid.charAt(ssid.length() - 1) == '"') { + ssid = ssid.substring(1, ssid.length() - 1); + } + if (ssid.isEmpty() || UNKNOWN_SSID.equals(ssid) || HEX_EMPTY_SSID.equals(ssid)) { + return null; + } + return ssid; + } + + static String formatIpv4(int longIp) { + return (longIp & 0xff) + + "." + + ((longIp >> 8) & 0xff) + + "." + + ((longIp >> 16) & 0xff) + + "." + + ((longIp >> 24) & 0xff); + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java new file mode 100644 index 00000000..38b1fff2 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java @@ -0,0 +1,412 @@ +package com.alternadom.wifiiot.wifi; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MacAddress; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.TransportInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiNetworkSpecifier; +import android.net.wifi.WifiNetworkSuggestion; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +/** Modern STA backend for API 29+. */ +@RequiresApi(api = Build.VERSION_CODES.Q) // API 29+ +@SuppressWarnings("deprecation") // API < 31 +public final class ModernWifiPlatform implements WifiPlatform { + private static final String TAG = "ModernWifiPlatform"; + + private final Context context; + private final WifiManager wifiManager; + private final ConnectivityManager connectivityManager; + + private ConnectivityManager.NetworkCallback networkCallback; + private List networkSuggestions; + @Nullable private Network joinedNetwork; + private final List suggestionsToRemoveOnClose = new ArrayList<>(); + + public ModernWifiPlatform( + Context context, WifiManager wifiManager, ConnectivityManager connectivityManager) { + this.context = context; + this.wifiManager = wifiManager; + this.connectivityManager = connectivityManager; + } + + @Override + public void connect(WifiConnectRequest request, WifiConnectCallback callback) { + final Handler handler = new Handler(Looper.getMainLooper()); + + if (request.security != null && request.security.toUpperCase().equals("WEP")) { + handler.post( + () -> + callback.onError( + "Error", + "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, + "")); + return; + } + + if (request.withInternet != null && request.withInternet) { + connectWithSuggestion(request, callback, handler); + } else { + connectWithSpecifier(request, callback, handler); + } + } + + private void connectWithSuggestion( + WifiConnectRequest request, WifiConnectCallback callback, Handler handler) { + final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); + builder.setSsid(request.ssid); + builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); + if (!applyBssid(builder, request.bssid, callback, handler)) { + return; + } + if (request.security != null && request.security.toUpperCase().equals("WPA")) { + builder.setWpa2Passphrase(request.password); + } + + if (networkSuggestions != null) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + } + + final WifiNetworkSuggestion suggestion = builder.build(); + networkSuggestions = new ArrayList<>(); + networkSuggestions.add(suggestion); + if (request.joinOnce != null && request.joinOnce) { + suggestionsToRemoveOnClose.add(suggestion); + } + + final int status = wifiManager.addNetworkSuggestions(networkSuggestions); + Log.e(TAG, "status: " + status); + handler.post( + () -> callback.onSuccess(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS)); + } + + private void connectWithSpecifier( + WifiConnectRequest request, WifiConnectCallback callback, Handler handler) { + final WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder(); + builder.setSsid(request.ssid); + builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); + if (!applyBssid(builder, request.bssid, callback, handler)) { + return; + } + if (request.security != null && request.security.toUpperCase().equals("WPA")) { + builder.setWpa2Passphrase(request.password); + } + + final NetworkRequest networkRequest = + new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .setNetworkSpecifier(builder.build()) + .build(); + + if (networkCallback != null) { + connectivityManager.unregisterNetworkCallback(networkCallback); + } + + Integer timeoutInSeconds = request.timeoutInSeconds; + int timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : 30000; + + networkCallback = + new ConnectivityManager.NetworkCallback() { + boolean resultSent = false; + + @Override + public void onAvailable(@NonNull Network network) { + super.onAvailable(network); + if (!resultSent) { + joinedNetwork = network; + callback.onSuccess(true); + resultSent = true; + } + } + + @Override + public void onUnavailable() { + super.onUnavailable(); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // API 29 only + connectivityManager.unregisterNetworkCallback(this); + } + if (!resultSent) { + callback.onSuccess(false); + resultSent = true; + } + } + + @Override + public void onLost(Network network) { + super.onLost(network); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // API 29 only + connectivityManager.unregisterNetworkCallback(this); + } + } + }; + + connectivityManager.requestNetwork(networkRequest, networkCallback, handler, timeoutMs); + } + + private boolean applyBssid( + WifiNetworkSuggestion.Builder builder, + @Nullable String bssid, + WifiConnectCallback callback, + Handler handler) { + if (bssid == null) { + return true; + } + MacAddress mac = parseMacAddress(bssid); + if (mac == null) { + handler.post(() -> callback.onError("Error", "Invalid BSSID representation", "")); + return false; + } + builder.setBssid(mac); + return true; + } + + private boolean applyBssid( + WifiNetworkSpecifier.Builder builder, + @Nullable String bssid, + WifiConnectCallback callback, + Handler handler) { + if (bssid == null) { + return true; + } + MacAddress mac = parseMacAddress(bssid); + if (mac == null) { + handler.post(() -> callback.onError("Error", "Invalid BSSID representation", "")); + return false; + } + builder.setBssid(mac); + return true; + } + + @Override + public boolean disconnect() { + if (networkCallback != null) { + connectivityManager.unregisterNetworkCallback(networkCallback); + networkCallback = null; + joinedNetwork = null; + return true; + } + if (networkSuggestions != null) { + final int networksRemoved = wifiManager.removeNetworkSuggestions(networkSuggestions); + networkSuggestions = null; + return networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; + } + Log.e(TAG, "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); + return false; + } + + @Override + public void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // API < 30 + new LegacyWifiPlatform(wifiManager) + .registerNetwork(ssid, bssid, password, security, isHidden, callback); + return; + } + createNetworkViaSettings(ssid, bssid, password, security, isHidden, callback); + } + + @RequiresApi(api = Build.VERSION_CODES.R) // API 30+ + private void createNetworkViaSettings( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback) { + final WifiNetworkSuggestion.Builder suggestedNet = new WifiNetworkSuggestion.Builder(); + suggestedNet.setSsid(ssid); + suggestedNet.setIsHiddenSsid(isHidden != null ? isHidden : false); + if (bssid != null) { + MacAddress macAddress = parseMacAddress(bssid); + if (macAddress == null) { + callback.onError("Error", "Invalid BSSID representation", ""); + return; + } + suggestedNet.setBssid(macAddress); + } + + if (security != null && security.toUpperCase().equals("WPA")) { + suggestedNet.setWpa2Passphrase(password); + } else if (security != null && security.toUpperCase().equals("WEP")) { + callback.onError( + "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, ""); + return; + } + + final ArrayList suggestionsList = new ArrayList<>(); + suggestionsList.add(suggestedNet.build()); + + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST, suggestionsList); + Intent intent = new Intent(Settings.ACTION_WIFI_ADD_NETWORKS); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + callback.onSuccess(); + } + + @Override + @NonNull + public WifiInfo getWifiInfo() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // API 31+ + WifiInfo wifiInfo = fetchWifiInfoFromConnectivityManager(); + if (wifiInfo != null) { + return wifiInfo; + } + } + return wifiManager.getConnectionInfo(); + } + + @Override + @Nullable + public String getSsid() { + return LegacyWifiPlatform.normalizeSsid(getWifiInfo().getSSID()); + } + + @Override + @Nullable + public String getIpv4() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // API 31+ + String ipv4 = fetchIpv4FromLinkProperties(); + if (ipv4 != null) { + return ipv4; + } + } + return LegacyWifiPlatform.formatIpv4(getWifiInfo().getIpAddress()); + } + + @Override + @Nullable + public Network getJoinedNetwork() { + return joinedNetwork; + } + + @Override + public void close() { + if (networkCallback != null) { + try { + connectivityManager.unregisterNetworkCallback(networkCallback); + } catch (IllegalArgumentException ignored) { + // already unregistered + } + networkCallback = null; + } + joinedNetwork = null; + networkSuggestions = null; + + if (!suggestionsToRemoveOnClose.isEmpty()) { + wifiManager.removeNetworkSuggestions(suggestionsToRemoveOnClose); + suggestionsToRemoveOnClose.clear(); + } + } + + @Nullable + private static MacAddress parseMacAddress(@Nullable String bssid) { + if (bssid == null) { + return null; + } + try { + return MacAddress.fromString(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Mac address parsing failed for bssid: " + bssid, e); + return null; + } + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.S) // API 31+ + private WifiInfo fetchWifiInfoFromConnectivityManager() { + for (Network network : orderedWifiNetworks()) { + WifiInfo wifiInfo = fetchWifiInfoFromNetwork(network); + if (wifiInfo != null && LegacyWifiPlatform.normalizeSsid(wifiInfo.getSSID()) != null) { + return wifiInfo; + } + } + return null; + } + + @RequiresApi(api = Build.VERSION_CODES.M) // API 23+ + private List orderedWifiNetworks() { + List ordered = new ArrayList<>(); + if (joinedNetwork != null) { + ordered.add(joinedNetwork); + } + + List activeWifi = new ArrayList<>(); + List rest = new ArrayList<>(); + Network activeNetwork = connectivityManager.getActiveNetwork(); + + for (Network network : connectivityManager.getAllNetworks()) { + if (joinedNetwork != null && network.equals(joinedNetwork)) { + continue; + } + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null || !capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + continue; + } + if (activeNetwork != null && network.equals(activeNetwork)) { + activeWifi.add(network); + } else { + rest.add(network); + } + } + ordered.addAll(activeWifi); + ordered.addAll(rest); + return ordered; + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.Q) // API 29+ + private WifiInfo fetchWifiInfoFromNetwork(Network network) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null) { + return null; + } + TransportInfo transportInfo = capabilities.getTransportInfo(); + return transportInfo instanceof WifiInfo ? (WifiInfo) transportInfo : null; + } + + @Nullable + @RequiresApi(api = Build.VERSION_CODES.S) // API 31+ + private String fetchIpv4FromLinkProperties() { + for (Network wifiNetwork : orderedWifiNetworks()) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(wifiNetwork); + if (linkProperties == null) { + continue; + } + for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { + InetAddress address = linkAddress.getAddress(); + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } + } + } + return null; + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java new file mode 100644 index 00000000..71433342 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectCallback.java @@ -0,0 +1,9 @@ +package com.alternadom.wifiiot.wifi; + +import androidx.annotation.Nullable; + +public interface WifiConnectCallback { + void onSuccess(boolean connected); + + void onError(String code, String message, @Nullable Object details); +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java new file mode 100644 index 00000000..f68233c6 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiConnectRequest.java @@ -0,0 +1,34 @@ +package com.alternadom.wifiiot.wifi; + +import androidx.annotation.Nullable; + +/** Parameters for a STA connect attempt. */ +public final class WifiConnectRequest { + public final String ssid; + @Nullable public final String bssid; + @Nullable public final String password; + @Nullable public final String security; + @Nullable public final Boolean joinOnce; + @Nullable public final Boolean withInternet; + @Nullable public final Boolean isHidden; + @Nullable public final Integer timeoutInSeconds; + + public WifiConnectRequest( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean joinOnce, + @Nullable Boolean withInternet, + @Nullable Boolean isHidden, + @Nullable Integer timeoutInSeconds) { + this.ssid = ssid; + this.bssid = bssid; + this.password = password; + this.security = security; + this.joinOnce = joinOnce; + this.withInternet = withInternet; + this.isHidden = isHidden; + this.timeoutInSeconds = timeoutInSeconds; + } +} diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java new file mode 100644 index 00000000..028623c1 --- /dev/null +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/WifiPlatform.java @@ -0,0 +1,53 @@ +package com.alternadom.wifiiot.wifi; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** STA Wi‑Fi backend: {@link LegacyWifiPlatform} (API < 29) or {@link ModernWifiPlatform} (API 29+). */ +public interface WifiPlatform { + void connect(WifiConnectRequest request, WifiConnectCallback callback); + + boolean disconnect(); + + void registerNetwork( + String ssid, + @Nullable String bssid, + @Nullable String password, + @Nullable String security, + @Nullable Boolean isHidden, + RegisterCallback callback); + + @NonNull + WifiInfo getWifiInfo(); + + @Nullable + String getSsid(); + + @Nullable + String getIpv4(); + + @Nullable + Network getJoinedNetwork(); + + void close(); + + interface RegisterCallback { + void onSuccess(); + + void onError(String code, String message, @Nullable Object details); + } + + static WifiPlatform create( + Context context, WifiManager wifiManager, ConnectivityManager connectivityManager) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return new LegacyWifiPlatform(wifiManager); + } + return new ModernWifiPlatform(context, wifiManager, connectivityManager); + } +} diff --git a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java index 908c732f..663d0842 100644 --- a/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java +++ b/packages/wifi_iot/android/src/main/java/info/whitebyte/hotspotmanager/WifiApManager.java @@ -32,6 +32,7 @@ import java.net.InetAddress; import java.util.ArrayList; +@SuppressWarnings("deprecation") // API < 29 public class WifiApManager { private final WifiManager mWifiManager; private Context context; From 35adf5c4ce0457908dba43219f62199fd65e4201 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Mon, 7 Sep 2026 23:10:46 -0400 Subject: [PATCH 09/14] feat(wifi_scan): Implement Android modern API --- .../wifi/wifi_scan/WifiScanPlugin.kt | 122 +++++++++--------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt index e9c2c3b4..67c3e5f7 100644 --- a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt +++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt @@ -97,15 +97,15 @@ class WifiScanPlugin : } } val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + // API 33+: RECEIVER_NOT_EXPORTED if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // API 33+ context.registerReceiver( wifiScanReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED ) } else { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION") // API < 33 context.registerReceiver(wifiScanReceiver, intentFilter) } @@ -233,32 +233,33 @@ class WifiScanPlugin : } } - private fun canStartScan(askPermission: Boolean): Int { - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - // for SDK < P[28] : Not in guide, should not require any additional permissions - Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES - // for SDK >= Q[29]: CHANGE_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" - hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES - hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED + private fun canStartScan(askPermission: Boolean): Int { + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + // API < 28 + Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES + // API 29+ + hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES + hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED + } } } - private fun startScan(): Boolean = wifi!!.startScan() + @Suppress("DEPRECATION") // API 29+ throttled + private fun startScan(): Boolean = wifi!!.startScan() - private fun canGetScannedResults(askPermission: Boolean): Int { - // check all prerequisite conditions - // ACCESS_WIFI_STATE & ACCESS_x_LOCATION & "Location enabled" - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES - hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED + private fun canGetScannedResults(askPermission: Boolean): Int { + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES + hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED + } } } @@ -277,22 +278,24 @@ class WifiScanPlugin : "channelWidth" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, "isPasspoint" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, "operatorFriendlyName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - @Suppress("DEPRECATION") ap.operatorFriendlyName?.toString() + @Suppress("DEPRECATION") // API < 31 + ap.operatorFriendlyName?.toString() } else null, "venueName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - @Suppress("DEPRECATION") ap.venueName?.toString() + @Suppress("DEPRECATION") // API < 31 + ap.venueName?.toString() } else null, "is80211mcResponder" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null ) } + /** API 33+: wifiSsid; API < 33: SSID. */ private fun ssidFromScanResult(ap: android.net.wifi.ScanResult): String? { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // API 33+ val wifiSsid = ap.wifiSsid ?: return null return wifiSsid.toString().trim('"') } - @Suppress("DEPRECATION") + @Suppress("DEPRECATION") // API < 33 return ap.SSID } @@ -300,10 +303,9 @@ class WifiScanPlugin : eventSink?.success(getScannedResults()) } - /** ACCESS_FINE_LOCATION required for: SDK >= Q[29] and tSDK >= Q[29] */ - private fun requiresFineLocation(): Boolean = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && - context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q + /** API 29+ and targetSdk 29+: fine location required. */ + private fun requiresFineLocation(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q private fun hasLocationPermission(): Boolean { val permissions = @@ -323,37 +325,33 @@ class WifiScanPlugin : ERROR_NO_ACTIVITY } - private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { - // check if has activity - return error if null - if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) - // make permissions - val requiresFine = requiresFineLocation() - // - for SDK > R[30] - cannot only ask for FINE - val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R - val permissions = - when { - requiresFineButAskBoth -> locationPermissionBoth - requiresFine -> locationPermissionFine - else -> locationPermissionCoarse + private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { + if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) + val requiresFine = requiresFineLocation() + // API > 30: ask FINE + COARSE together + val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R + val permissions = when { + requiresFineButAskBoth -> locationPermissionBoth + requiresFine -> locationPermissionFine + else -> locationPermissionCoarse } - // request permission - add result-handler in requestPermissionCookie - val permissionCode = 6567800 + Random.Default.nextInt(100) - requestPermissionCookie[permissionCode] = { grantArray -> - // invoke callback with proper askResult - Log.d(logTag, "permissionResultCallback: args($grantArray)") - callback.invoke( - when { - // GRANTED: if all granted - grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { - AskLocPermResult.GRANTED - } - // UPGRADE_TO_FINE: if requiresFineButAskBoth and COARSE granted - requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { - AskLocPermResult.UPGRADE_TO_FINE - } - else -> AskLocPermResult.DENIED - }) - true + val permissionCode = 6567800 + Random.Default.nextInt(100) + requestPermissionCookie[permissionCode] = { grantArray -> + Log.d(logTag, "permissionResultCallback: args($grantArray)") + callback.invoke( + when { + grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { + AskLocPermResult.GRANTED + } + requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { + AskLocPermResult.UPGRADE_TO_FINE + } + else -> AskLocPermResult.DENIED + } + ) + true + } + ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) } ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) } From cd15e10fca515facf275d80bf6e79f177a386779 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Tue, 8 Sep 2026 12:50:03 -0400 Subject: [PATCH 10/14] refactor(wifi_iot): Update Android modern API --- analysis_options.yaml | 1 - .../com/alternadom/wifiiot/WifiIotPlugin.java | 338 ++++++++++-------- .../wifiiot/wifi/ModernWifiPlatform.java | 143 +++++--- 3 files changed, 273 insertions(+), 209 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index e6b4cb4f..f002263f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,7 +22,6 @@ analyzer: linter: rules: # doc-style related: https://dart.dev/guides/language/effective-dart/documentation - - package_api_docs - public_member_api_docs - comment_references - slash_for_doc_comments diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index d3c44382..82ec44b9 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -78,6 +78,33 @@ public class WifiIotPlugin 65655437; private static final int PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED = 65655438; + /** API ≤ 32: FINE_LOCATION; API ≥ 33: NEARBY_WIFI_DEVICES (SSID required). */ + private String[] wifiScanPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[] {Manifest.permission.NEARBY_WIFI_DEVICES}; + } + return new String[] {Manifest.permission.ACCESS_FINE_LOCATION}; + } + + private boolean hasWifiScanPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return moContext.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) + == PackageManager.PERMISSION_GRANTED; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } + return true; + } + + private String wifiScanPermissionDeniedMessage() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return "NEARBY_WIFI_DEVICES permission denied"; + } + return "Fine location permission denied"; + } + private void initWithContext(Context context) { moContext = context; moWiFi = (WifiManager) moContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE); @@ -162,7 +189,7 @@ public boolean onRequestPermissionsResult( _loadWifiList(permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); } requestingPermission = false; return true; @@ -182,7 +209,7 @@ public boolean onRequestPermissionsResult( _findAndConnect(poCall, permissionRequestResultCallback); } else { permissionRequestResultCallback.error( - "WifiIotPlugin.Permission", "Fine location permission denied", null); + "WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); } requestingPermission = false; return true; @@ -202,119 +229,119 @@ public boolean onRequestPermissionsResult( } @Override - public void onMethodCall(MethodCall poCall, Result poResult) { + public void onMethodCall(MethodCall poCall, Result result) { switch (poCall.method) { case "loadWifiList": - loadWifiList(poResult); + loadWifiList(result); break; case "forceWifiUsage": - forceWifiUsage(poCall, poResult); + forceWifiUsage(poCall, result); break; case "isEnabled": - isEnabled(poResult); + isEnabled(result); break; case "setEnabled": - setEnabled(poCall, poResult); + setEnabled(poCall, result); break; case "connect": - connect(poCall, poResult); + connect(poCall, result); break; case "registerWifiNetwork": - registerWifiNetwork(poCall, poResult); + registerWifiNetwork(poCall, result); break; case "findAndConnect": - findAndConnect(poCall, poResult); + findAndConnect(poCall, result); break; case "isConnected": - isConnected(poResult); + isConnected(result); break; case "disconnect": - disconnect(poResult); + disconnect(result); break; case "getSSID": - getSSID(poResult); + getSSID(result); break; case "getBSSID": - getBSSID(poResult); + getBSSID(result); break; case "getCurrentSignalStrength": - getCurrentSignalStrength(poResult); + getCurrentSignalStrength(result); break; case "getFrequency": - getFrequency(poResult); + getFrequency(result); break; case "getIP": - getIP(poResult); + getIP(result); break; case "removeWifiNetwork": - removeWifiNetwork(poCall, poResult); + removeWifiNetwork(poCall, result); break; case "isRegisteredWifiNetwork": if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) - isRegisteredWifiNetwork(poCall, poResult); + isRegisteredWifiNetwork(poCall, result); else - poResult.error( + result.error( "Error", "isRegisteredWifiNetwork not supported for Android SDK " + Build.VERSION.SDK_INT, null); break; case "isWiFiAPEnabled": - isWiFiAPEnabled(poResult); + isWiFiAPEnabled(result); break; case "setWiFiAPEnabled": - setWiFiAPEnabled(poCall, poResult); + setWiFiAPEnabled(poCall, result); break; case "getWiFiAPState": - getWiFiAPState(poResult); + getWiFiAPState(result); break; case "getClientList": - getClientList(poCall, poResult); + getClientList(poCall, result); break; case "getWiFiAPSSID": - getWiFiAPSSID(poResult); + getWiFiAPSSID(result); break; case "setWiFiAPSSID": - setWiFiAPSSID(poCall, poResult); + setWiFiAPSSID(poCall, result); break; case "isSSIDHidden": - isSSIDHidden(poResult); + isSSIDHidden(result); break; case "setSSIDHidden": - setSSIDHidden(poCall, poResult); + setSSIDHidden(poCall, result); break; case "getWiFiAPPreSharedKey": - getWiFiAPPreSharedKey(poResult); + getWiFiAPPreSharedKey(result); break; case "setWiFiAPPreSharedKey": - setWiFiAPPreSharedKey(poCall, poResult); + setWiFiAPPreSharedKey(poCall, result); break; case "showWritePermissionSettings": - showWritePermissionSettings(poCall, poResult); + showWritePermissionSettings(poCall, result); break; default: - poResult.notImplemented(); + result.notImplemented(); break; } } - private void getWiFiAPSSID(Result poResult) { + private void getWiFiAPSSID(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); if (oWiFiConfig != null && oWiFiConfig.SSID != null) { - poResult.success(oWiFiConfig.SSID); + result.success(oWiFiConfig.SSID); return; } - poResult.error("Exception [getWiFiAPSSID]", "SSID not found", null); + result.error("Exception [getWiFiAPSSID]", "SSID not found", null); } else { if (apReservation != null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.SSID); + result.success(wifiConfiguration.SSID); } else { - poResult.error( + result.error( "Exception [getWiFiAPSSID]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -322,51 +349,51 @@ private void getWiFiAPSSID(Result poResult) { } } else { SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getSsid()); + result.success(softApConfiguration.getSsid()); } } else { - poResult.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); + result.error("Exception [getWiFiAPSSID]", "Hotspot is not enabled.", null); } } } - private void setWiFiAPSSID(MethodCall poCall, Result poResult) { + private void setWiFiAPSSID(MethodCall poCall, Result result) { String sAPSSID = poCall.argument("ssid"); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); oWiFiConfig.SSID = sAPSSID; moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); + result.success(null); } else { - poResult.error( + result.error( "Exception [setWiFiAPSSID]", "Setting SSID name is not supported on API level >= 26", null); } } - private void isSSIDHidden(Result poResult) { + private void isSSIDHidden(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); if (oWiFiConfig != null && oWiFiConfig.hiddenSSID) { - poResult.success(oWiFiConfig.hiddenSSID); + result.success(oWiFiConfig.hiddenSSID); return; } - poResult.error("Exception [isSSIDHidden]", "Wifi AP not Supported", null); + result.error("Exception [isSSIDHidden]", "Wifi AP not Supported", null); } else { if (apReservation != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.isHiddenSsid()); + result.success(softApConfiguration.isHiddenSsid()); } else { WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.hiddenSSID); + result.success(wifiConfiguration.hiddenSSID); } else { - poResult.error( + result.error( "Exception [isSSIDHidden]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -374,44 +401,44 @@ private void isSSIDHidden(Result poResult) { } } } else { - poResult.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); + result.error("Exception [isSSIDHidden]", "Hotspot is not enabled.", null); } } } - private void setSSIDHidden(MethodCall poCall, Result poResult) { + private void setSSIDHidden(MethodCall poCall, Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { boolean isSSIDHidden = poCall.argument("hidden"); android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); oWiFiConfig.hiddenSSID = isSSIDHidden; moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); + result.success(null); } else { - poResult.error( + result.error( "Exception [setSSIDHidden]", "Setting SSID visibility is not supported on API level >= 26", null); } } - private void getWiFiAPPreSharedKey(Result poResult) { + private void getWiFiAPPreSharedKey(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); if (oWiFiConfig != null && oWiFiConfig.preSharedKey != null) { - poResult.success(oWiFiConfig.preSharedKey); + result.success(oWiFiConfig.preSharedKey); return; } - poResult.error("Exception", "Wifi AP not Supported", null); + result.error("Exception", "Wifi AP not Supported", null); } else { if (apReservation != null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { WifiConfiguration wifiConfiguration = apReservation.getWifiConfiguration(); if (wifiConfiguration != null) { - poResult.success(wifiConfiguration.preSharedKey); + result.success(wifiConfiguration.preSharedKey); } else { - poResult.error( + result.error( "Exception [getWiFiAPPreSharedKey]", "Security type is not WifiConfiguration.KeyMgmt.None or" + " WifiConfiguration.KeyMgmt.WPA2_PSK", @@ -419,30 +446,30 @@ private void getWiFiAPPreSharedKey(Result poResult) { } } else { SoftApConfiguration softApConfiguration = apReservation.getSoftApConfiguration(); - poResult.success(softApConfiguration.getPassphrase()); + result.success(softApConfiguration.getPassphrase()); } } else { - poResult.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); + result.error("Exception [getWiFiAPPreSharedKey]", "Hotspot is not enabled.", null); } } } - private void setWiFiAPPreSharedKey(MethodCall poCall, Result poResult) { + private void setWiFiAPPreSharedKey(MethodCall poCall, Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { String sPreSharedKey = poCall.argument("preSharedKey"); android.net.wifi.WifiConfiguration oWiFiConfig = moWiFiAPManager.getWifiApConfiguration(); oWiFiConfig.preSharedKey = sPreSharedKey; moWiFiAPManager.setWifiApConfiguration(oWiFiConfig); - poResult.success(null); + result.success(null); } else { - poResult.error( + result.error( "Exception [setWiFiAPPreSharedKey]", "Setting WiFi password is not supported on API level >= 26", null); } } - private void getClientList(MethodCall poCall, final Result poResult) { + private void getClientList(MethodCall poCall, final Result result) { Boolean onlyReachables = false; if (poCall.argument("onlyReachables") != null) { onlyReachables = poCall.argument("onlyReachables"); @@ -478,14 +505,14 @@ public void onFinishScan(final ArrayList clients) { clientObject.put("Device", client.getDevice()); clientObject.put("isReachable", client.isReachable()); } catch (JSONException e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } clientArray.put(clientObject); } } - poResult.success(clientArray.toString()); + result.success(clientArray.toString()); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } }; @@ -497,25 +524,25 @@ public void onFinishScan(final ArrayList clients) { } } - private void isWiFiAPEnabled(Result poResult) { + private void isWiFiAPEnabled(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { try { - poResult.success(moWiFiAPManager.isWifiApEnabled()); + result.success(moWiFiAPManager.isWifiApEnabled()); } catch (SecurityException e) { Log.e(WifiIotPlugin.class.getSimpleName(), e.getMessage(), null); - poResult.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); + result.error("Exception [isWiFiAPEnabled]", e.getMessage(), null); } } else { - poResult.success(apReservation != null); + result.success(apReservation != null); } } - private void setWiFiAPEnabled(MethodCall poCall, final Result poResult) { + private void setWiFiAPEnabled(MethodCall poCall, final Result result) { boolean enabled = poCall.argument("state"); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { final boolean result = moWiFiAPManager.setWifiApEnabled(null, enabled); - poResult.success(result); + result.success(result); } else { if (enabled) { localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLING; @@ -526,7 +553,7 @@ public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { super.onStarted(reservation); apReservation = reservation; localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLED; - poResult.success(true); + result.success(true); } @Override @@ -551,7 +578,7 @@ public void onFailed(int reason) { Log.d( WifiIotPlugin.class.getSimpleName(), "LocalHotspot failed with code: " + String.valueOf(reason)); - poResult.success(false); + result.success(false); } }, new Handler()); @@ -560,36 +587,34 @@ public void onFailed(int reason) { if (apReservation != null) { apReservation.close(); apReservation = null; - poResult.success(true); + result.success(true); } else { Log.e( WifiIotPlugin.class.getSimpleName(), "Can't disable WiFi AP, apReservation is null."); - poResult.success(false); + result.success(false); } localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_DISABLED; } } } - private void showWritePermissionSettings(MethodCall poCall, Result poResult) { + private void showWritePermissionSettings(MethodCall poCall, Result result) { boolean force = poCall.argument("force"); moWiFiAPManager.showWritePermissionSettings(force); - poResult.success(null); + result.success(null); } - private void getWiFiAPState(Result poResult) { + private void getWiFiAPState(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - poResult.success(moWiFiAPManager.getWifiApState().ordinal()); + result.success(moWiFiAPManager.getWifiApState().ordinal()); } else { - poResult.success(localOnlyHotspotState); + result.success(localOnlyHotspotState); } } @Override public void onListen(Object o, EventChannel.EventSink eventSink) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { + if (!hasWifiScanPermission()) { if (requestingPermission) { return; } @@ -597,8 +622,7 @@ public void onListen(Object o, EventChannel.EventSink eventSink) { permissionRequestCookie.clear(); permissionRequestCookie.add(eventSink); moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_ON_LISTEN); } else { _onListen(eventSink); } @@ -675,31 +699,28 @@ JSONArray handleNetworkScanResult() { } } - private void loadWifiList(final Result poResult) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { + private void loadWifiList(final Result result) { + if (!hasWifiScanPermission()) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_LOAD_WIFI_LIST); } else { - _loadWifiList(poResult); + _loadWifiList(result); } } - private void _loadWifiList(final Result poResult) { + private void _loadWifiList(final Result result) { try { moWiFi.startScan(); - poResult.success(handleNetworkScanResult().toString()); + result.success(handleNetworkScanResult().toString()); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } @@ -713,19 +734,19 @@ private boolean selectNetwork(final Network network, final ConnectivityManager m } private void onAvailableNetwork( - final ConnectivityManager manager, final Network network, final Result poResult) { + final ConnectivityManager manager, final Network network, final Result result) { final boolean result = selectNetwork(network, manager); final Handler handler = new Handler(Looper.getMainLooper()); handler.post( new Runnable() { @Override public void run() { - poResult.success(result); + result.success(result); } }); } - private void forceWifiUsage(final MethodCall poCall, final Result poResult) { + private void forceWifiUsage(final MethodCall poCall, final Result result) { boolean useWifi = poCall.argument("useWifi"); final ConnectivityManager manager = @@ -749,7 +770,7 @@ private void forceWifiUsage(final MethodCall poCall, final Result poResult) { public void onAvailable(Network network) { super.onAvailable(network); manager.unregisterNetworkCallback(this); - onAvailableNetwork(manager, network, poResult); + onAvailableNetwork(manager, network, result); } }); } @@ -758,16 +779,16 @@ public void onAvailable(Network network) { } } if (shouldReply) { - poResult.success(success); + result.success(success); } } - private void isEnabled(Result poResult) { - poResult.success(moWiFi.isWifiEnabled()); + private void isEnabled(Result result) { + result.success(moWiFi.isWifiEnabled()); } @SuppressWarnings("deprecation") // API < 29 - private void setEnabled(MethodCall poCall, Result poResult) { + private void setEnabled(MethodCall poCall, Result result) { Boolean enabled = poCall.argument("state"); Boolean shouldOpenSettings = poCall.argument("shouldOpenSettings"); @@ -788,10 +809,10 @@ private void setEnabled(MethodCall poCall, Result poResult) { } } - poResult.success(null); + result.success(null); } - private void connect(final MethodCall poCall, final Result poResult) { + private void connect(final MethodCall poCall, final Result result) { new Thread() { public void run() { WifiConnectRequest request = @@ -804,12 +825,12 @@ public void run() { poCall.argument("with_internet"), poCall.argument("is_hidden"), poCall.argument("timeout_in_seconds")); - connectWithResult(request, poResult); + connectWithResult(request, result); } }.start(); } - private void connectWithResult(WifiConnectRequest request, final Result poResult) { + private void connectWithResult(WifiConnectRequest request, final Result result) { final Handler handler = new Handler(Looper.getMainLooper()); wifiPlatform.connect( request, @@ -817,24 +838,24 @@ private void connectWithResult(WifiConnectRequest request, final Result poResult @Override public void onSuccess(final boolean connected) { if (Looper.myLooper() == Looper.getMainLooper()) { - poResult.success(connected); + result.success(connected); } else { - handler.post(() -> poResult.success(connected)); + handler.post(() -> result.success(connected)); } } @Override public void onError(final String code, final String message, final Object details) { if (Looper.myLooper() == Looper.getMainLooper()) { - poResult.error(code, message, details); + result.error(code, message, details); } else { - handler.post(() -> poResult.error(code, message, details)); + handler.post(() -> result.error(code, message, details)); } } }); } - private void registerWifiNetwork(final MethodCall poCall, final Result poResult) { + private void registerWifiNetwork(final MethodCall poCall, final Result result) { wifiPlatform.registerNetwork( poCall.argument("ssid"), poCall.argument("bssid"), @@ -844,38 +865,35 @@ private void registerWifiNetwork(final MethodCall poCall, final Result poResult) new WifiPlatform.RegisterCallback() { @Override public void onSuccess() { - poResult.success(null); + result.success(null); } @Override public void onError(String code, String message, Object details) { - poResult.error(code, message, details); + result.error(code, message, details); } }); } - private void findAndConnect(final MethodCall poCall, final Result poResult) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && moContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { + private void findAndConnect(final MethodCall poCall, final Result result) { + if (!hasWifiScanPermission()) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; permissionRequestCookie.clear(); permissionRequestCookie.add(poCall); moActivity.requestPermissions( - new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, - PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); + wifiScanPermissions(), PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION_FIND_AND_CONNECT); } else { - _findAndConnect(poCall, poResult); + _findAndConnect(poCall, result); } } - private void _findAndConnect(final MethodCall poCall, final Result poResult) { + private void _findAndConnect(final MethodCall poCall, final Result result) { new Thread() { public void run() { String ssid = poCall.argument("ssid"); @@ -901,34 +919,34 @@ public void run() { WifiConnectRequest request = new WifiConnectRequest( ssid, bssid, password, security, joinOnce, withInternet, false, timeoutInSeconds); - connectWithResult(request, poResult); + connectWithResult(request, result); } }.start(); } - private void isConnected(Result poResult) { + private void isConnected(Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - isConnectedDeprecated(poResult); + isConnectedDeprecated(result); } else { if (moContext.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED) { if (requestingPermission) { - poResult.error( + result.error( "WifiIotPlugin.Permission", "Only one permission can be requested at a time", null); return; } requestingPermission = true; - permissionRequestResultCallback = poResult; + permissionRequestResultCallback = result; moActivity.requestPermissions( new String[] {Manifest.permission.ACCESS_NETWORK_STATE}, PERMISSIONS_REQUEST_CODE_ACCESS_NETWORK_STATE_IS_CONNECTED); } else { - _isConnected(poResult); + _isConnected(result); } } } - private void _isConnected(Result poResult) { + private void _isConnected(Result result) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); boolean result = false; @@ -945,59 +963,67 @@ private void _isConnected(Result poResult) { } } - poResult.success(result); + result.success(result); } @SuppressWarnings("deprecation") // API < 23 - private void isConnectedDeprecated(Result poResult) { + private void isConnectedDeprecated(Result result) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); android.net.NetworkInfo mWifi = connManager != null ? connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI) : null; - poResult.success(mWifi != null && mWifi.isConnected()); + result.success(mWifi != null && mWifi.isConnected()); } - private void disconnect(Result poResult) { - poResult.success(wifiPlatform.disconnect()); + private void disconnect(Result result) { + result.success(wifiPlatform.disconnect()); } - private void getSSID(Result poResult) { - poResult.success(wifiPlatform.getSsid()); + private void getSSID(Result result) { + if (!hasWifiScanPermission()) { + result.error("WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); + return; + } + result.success(wifiPlatform.getSsid()); } - private void getBSSID(Result poResult) { + private void getBSSID(Result result) { + if (!hasWifiScanPermission()) { + result.error("WifiIotPlugin.Permission", wifiScanPermissionDeniedMessage(), null); + return; + } WifiInfo info = wifiPlatform.getWifiInfo(); String bssid = info.getBSSID(); try { - poResult.success(bssid != null ? bssid.toUpperCase() : null); + result.success(bssid != null ? bssid.toUpperCase() : null); } catch (Exception e) { - poResult.error("Exception", e.getMessage(), null); + result.error("Exception", e.getMessage(), null); } } - private void getCurrentSignalStrength(Result poResult) { - poResult.success(wifiPlatform.getWifiInfo().getRssi()); + private void getCurrentSignalStrength(Result result) { + result.success(wifiPlatform.getWifiInfo().getRssi()); } - private void getFrequency(Result poResult) { + private void getFrequency(Result result) { WifiInfo info = wifiPlatform.getWifiInfo(); int frequency = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { frequency = info.getFrequency(); } - poResult.success(frequency); + result.success(frequency); } - private void getIP(Result poResult) { - poResult.success(wifiPlatform.getIpv4()); + private void getIP(Result result) { + result.success(wifiPlatform.getIpv4()); } @SuppressWarnings("deprecation") // API < 29 - private void removeWifiNetwork(MethodCall poCall, Result poResult) { + private void removeWifiNetwork(MethodCall poCall, Result result) { String prefix_ssid = poCall.argument("ssid"); if (prefix_ssid.equals("")) { - poResult.error("Error", "No prefix SSID was given!", null); + result.error("Error", "No prefix SSID was given!", null); } boolean removed = false; @@ -1026,23 +1052,23 @@ private void removeWifiNetwork(MethodCall poCall, Result poResult) { final int networksRemoved = moWiFi.removeNetworkSuggestions(removeSuggestions); removed = networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; } - poResult.success(removed); + result.success(removed); } @SuppressWarnings("deprecation") // API < 29 - private void isRegisteredWifiNetwork(MethodCall poCall, Result poResult) { + private void isRegisteredWifiNetwork(MethodCall poCall, Result result) { String ssid = poCall.argument("ssid"); List mWifiConfigList = moWiFi.getConfiguredNetworks(); String comparableSSID = ('"' + ssid + '"'); if (mWifiConfigList != null) { for (WifiConfiguration wifiConfig : mWifiConfigList) { if (wifiConfig.SSID.equals(comparableSSID)) { - poResult.success(true); + result.success(true); return; } } } - poResult.success(false); + result.success(false); } /** API 33+: WifiSsid; API < 33: SSID. */ diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java index 38b1fff2..846d9ac3 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java @@ -27,16 +27,19 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** Modern STA backend for API 29+. */ @RequiresApi(api = Build.VERSION_CODES.Q) // API 29+ @SuppressWarnings("deprecation") // API < 31 public final class ModernWifiPlatform implements WifiPlatform { private static final String TAG = "ModernWifiPlatform"; + private static final int DEFAULT_TIMEOUT_MS = 30000; private final Context context; private final WifiManager wifiManager; private final ConnectivityManager connectivityManager; + private final Handler handler = new Handler(Looper.getMainLooper()); private ConnectivityManager.NetworkCallback networkCallback; private List networkSuggestions; @@ -52,8 +55,6 @@ public ModernWifiPlatform( @Override public void connect(WifiConnectRequest request, WifiConnectCallback callback) { - final Handler handler = new Handler(Looper.getMainLooper()); - if (request.security != null && request.security.toUpperCase().equals("WEP")) { handler.post( () -> @@ -65,18 +66,19 @@ public void connect(WifiConnectRequest request, WifiConnectCallback callback) { } if (request.withInternet != null && request.withInternet) { - connectWithSuggestion(request, callback, handler); + connectWithSuggestion(request, callback); } else { - connectWithSpecifier(request, callback, handler); + connectWithSpecifier(request, callback); } } private void connectWithSuggestion( WifiConnectRequest request, WifiConnectCallback callback, Handler handler) { + private void connectWithSuggestion(WifiConnectRequest request, WifiConnectCallback callback) { final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); builder.setSsid(request.ssid); builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); - if (!applyBssid(builder, request.bssid, callback, handler)) { + if (!applyBssid(builder, request.bssid, callback)) { return; } if (request.security != null && request.security.toUpperCase().equals("WPA")) { @@ -94,18 +96,21 @@ private void connectWithSuggestion( suggestionsToRemoveOnClose.add(suggestion); } - final int status = wifiManager.addNetworkSuggestions(networkSuggestions); - Log.e(TAG, "status: " + status); - handler.post( - () -> callback.onSuccess(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS)); + int status = wifiManager.addNetworkSuggestions(networkSuggestions); + if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + status = wifiManager.addNetworkSuggestions(networkSuggestions); + } + Log.d(TAG, "addNetworkSuggestions status: " + status + " (suggestion_added_async if SUCCESS)"); + final boolean added = status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; + handler.post(() -> callback.onSuccess(added)); } - private void connectWithSpecifier( - WifiConnectRequest request, WifiConnectCallback callback, Handler handler) { + private void connectWithSpecifier(WifiConnectRequest request, WifiConnectCallback callback) { final WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder(); builder.setSsid(request.ssid); builder.setIsHiddenSsid(request.isHidden != null ? request.isHidden : false); - if (!applyBssid(builder, request.bssid, callback, handler)) { + if (!applyBssid(builder, request.bssid, callback)) { return; } if (request.security != null && request.security.toUpperCase().equals("WPA")) { @@ -119,44 +124,57 @@ private void connectWithSpecifier( .setNetworkSpecifier(builder.build()) .build(); - if (networkCallback != null) { - connectivityManager.unregisterNetworkCallback(networkCallback); - } + unregisterNetwork(networkCallback); + networkCallback = null; Integer timeoutInSeconds = request.timeoutInSeconds; - int timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : 30000; + int timeoutMs = + timeoutInSeconds != null ? timeoutInSeconds * 1000 : DEFAULT_TIMEOUT_MS; + + final AtomicBoolean done = new AtomicBoolean(false); networkCallback = new ConnectivityManager.NetworkCallback() { - boolean resultSent = false; - @Override public void onAvailable(@NonNull Network network) { super.onAvailable(network); - if (!resultSent) { - joinedNetwork = network; - callback.onSuccess(true); - resultSent = true; + if (!done.compareAndSet(false, true)) { + return; } + joinedNetwork = network; + connectivityManager.bindProcessToNetwork(network); + handler.post(() -> callback.onSuccess(true)); } @Override public void onUnavailable() { super.onUnavailable(); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // API 29 only - connectivityManager.unregisterNetworkCallback(this); + if (!done.compareAndSet(false, true)) { + return; } - if (!resultSent) { - callback.onSuccess(false); - resultSent = true; + // Keep callback registered on API 30+ so a later user approval can still deliver + // onAvailable; on API 29, unregister to avoid leaks after terminal failure. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + unregisterNetwork(this); + if (networkCallback == this) { + networkCallback = null; + } } + handler.post(() -> callback.onSuccess(false)); } @Override public void onLost(Network network) { super.onLost(network); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { // API 29 only - connectivityManager.unregisterNetworkCallback(this); + if (joinedNetwork != null && joinedNetwork.equals(network)) { + connectivityManager.bindProcessToNetwork(null); + joinedNetwork = null; + } + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + unregisterNetwork(this); + if (networkCallback == this) { + networkCallback = null; + } } } }; @@ -167,8 +185,7 @@ public void onLost(Network network) { private boolean applyBssid( WifiNetworkSuggestion.Builder builder, @Nullable String bssid, - WifiConnectCallback callback, - Handler handler) { + WifiConnectCallback callback) { if (bssid == null) { return true; } @@ -184,8 +201,7 @@ private boolean applyBssid( private boolean applyBssid( WifiNetworkSpecifier.Builder builder, @Nullable String bssid, - WifiConnectCallback callback, - Handler handler) { + WifiConnectCallback callback) { if (bssid == null) { return true; } @@ -200,19 +216,31 @@ private boolean applyBssid( @Override public boolean disconnect() { - if (networkCallback != null) { - connectivityManager.unregisterNetworkCallback(networkCallback); - networkCallback = null; - joinedNetwork = null; - return true; - } + connectivityManager.bindProcessToNetwork(null); + joinedNetwork = null; + + boolean hasCallback = networkCallback != null; + unregisterNetwork(networkCallback); + networkCallback = null; + + boolean hasSuggestions = networkSuggestions != null; + boolean removed = true; if (networkSuggestions != null) { - final int networksRemoved = wifiManager.removeNetworkSuggestions(networkSuggestions); + removed = + wifiManager.removeNetworkSuggestions(networkSuggestions) + == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; networkSuggestions = null; - return networksRemoved == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; } - Log.e(TAG, "Can't disconnect from WiFi, networkCallback and networkSuggestions is null."); - return false; + if (!suggestionsToRemoveOnClose.isEmpty()) { + wifiManager.removeNetworkSuggestions(suggestionsToRemoveOnClose); + suggestionsToRemoveOnClose.clear(); + } + + if (!hasCallback && !hasSuggestions) { + Log.e(TAG, "Can't disconnect from WiFi, no active callback/suggestions."); + return false; + } + return hasCallback || removed; } @Override @@ -309,16 +337,16 @@ public Network getJoinedNetwork() { @Override public void close() { - if (networkCallback != null) { - try { - connectivityManager.unregisterNetworkCallback(networkCallback); - } catch (IllegalArgumentException ignored) { - // already unregistered - } - networkCallback = null; - } + connectivityManager.bindProcessToNetwork(null); joinedNetwork = null; - networkSuggestions = null; + + unregisterNetwork(networkCallback); + networkCallback = null; + + if (networkSuggestions != null) { + wifiManager.removeNetworkSuggestions(networkSuggestions); + networkSuggestions = null; + } if (!suggestionsToRemoveOnClose.isEmpty()) { wifiManager.removeNetworkSuggestions(suggestionsToRemoveOnClose); @@ -326,6 +354,17 @@ public void close() { } } + private void unregisterNetwork(@Nullable ConnectivityManager.NetworkCallback callback) { + if (callback == null) { + return; + } + try { + connectivityManager.unregisterNetworkCallback(callback); + } catch (IllegalArgumentException ignored) { + // already unregistered + } + } + @Nullable private static MacAddress parseMacAddress(@Nullable String bssid) { if (bssid == null) { From 55ecbcec5e93a33986635b33bc8e8ae4601065b8 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Thu, 10 Sep 2026 10:02:24 -0400 Subject: [PATCH 11/14] chore(wifi_iot): Update AndroidManifest.xml --- packages/wifi_iot/android/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wifi_iot/android/src/main/AndroidManifest.xml b/packages/wifi_iot/android/src/main/AndroidManifest.xml index 6cd7e26d..99d05271 100644 --- a/packages/wifi_iot/android/src/main/AndroidManifest.xml +++ b/packages/wifi_iot/android/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + From f4e5f550d9df548e00e66c0e1d39dc6af65bca52 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Mon, 14 Sep 2026 09:14:45 -0400 Subject: [PATCH 12/14] style: dart analyzer --- .../main/java/com/alternadom/wifiiot/WifiIotPlugin.java | 7 +++++++ packages/wifi_iot/pubspec.yaml | 2 +- packages/wifi_scan/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 82ec44b9..bb37f717 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -689,6 +689,13 @@ JSONArray handleNetworkScanResult() { } else { wifiObject.put("timestamp", 0); } + /// Other fields not added + // wifiObject.put("operatorFriendlyName", result.operatorFriendlyName); + // wifiObject.put("venueName", result.venueName); + // wifiObject.put("centerFreq0", result.centerFreq0); + // wifiObject.put("centerFreq1", result.centerFreq1); + // wifiObject.put("channelWidth", result.channelWidth); + wifiArray.put(wifiObject); } } diff --git a/packages/wifi_iot/pubspec.yaml b/packages/wifi_iot/pubspec.yaml index 3ed0a935..e3bfb4e6 100644 --- a/packages/wifi_iot/pubspec.yaml +++ b/packages/wifi_iot/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_iot description: Flutter plugin which can handle WiFi connections and hotspot (AP, STA) -version: 0.3.19+4 +version: 0.4.1-dev.0 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_iot flutter: diff --git a/packages/wifi_scan/pubspec.yaml b/packages/wifi_scan/pubspec.yaml index ea87e414..b117addb 100644 --- a/packages/wifi_scan/pubspec.yaml +++ b/packages/wifi_scan/pubspec.yaml @@ -1,6 +1,6 @@ name: wifi_scan description: Flutter plugin to scan for nearby visible WiFi access points. -version: 0.4.1+4 +version: 0.5.1-dev.0 homepage: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_scan environment: From 3572ce585e9ffbdff6c8a59a04cd37aa2ce9532b Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Tue, 15 Sep 2026 13:31:14 -0400 Subject: [PATCH 13/14] style: dart analyzer --- .../com/alternadom/wifiiot/WifiIotPlugin.java | 18 +- .../wifiiot/wifi/ModernWifiPlatform.java | 17 +- packages/wifi_iot/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 4 +- .../wifi/wifi_scan/WifiScanPlugin.kt | 186 +++++++++--------- packages/wifi_scan/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- 7 files changed, 114 insertions(+), 121 deletions(-) diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java index 5c4c12f3..5576f7e9 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/WifiIotPlugin.java @@ -540,8 +540,8 @@ private void setWiFiAPEnabled(MethodCall poCall, final Result result) { boolean enabled = poCall.argument("state"); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - final boolean result = moWiFiAPManager.setWifiApEnabled(null, enabled); - result.success(result); + final boolean success = moWiFiAPManager.setWifiApEnabled(null, enabled); + result.success(success); } else { if (enabled) { localOnlyHotspotState = WIFI_AP_STATE.WIFI_AP_STATE_ENABLING; @@ -735,13 +735,13 @@ private boolean selectNetwork(final Network network, final ConnectivityManager m private void onAvailableNetwork( final ConnectivityManager manager, final Network network, final Result result) { - final boolean result = selectNetwork(network, manager); + final boolean success = selectNetwork(network, manager); final Handler handler = new Handler(Looper.getMainLooper()); handler.post( new Runnable() { @Override public void run() { - result.success(result); + result.success(success); } }); } @@ -949,21 +949,21 @@ private void isConnected(Result result) { private void _isConnected(Result result) { ConnectivityManager connManager = (ConnectivityManager) moContext.getSystemService(Context.CONNECTIVITY_SERVICE); - boolean result = false; + boolean isConnected = false; if (connManager != null) { for (final Network network : connManager.getAllNetworks()) { final NetworkCapabilities capabilities = network != null ? connManager.getNetworkCapabilities(network) : null; - final boolean isConnected = + final boolean hasWifi = capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); - if (isConnected) { - result = true; + if (hasWifi) { + isConnected = true; break; } } } - result.success(result); + result.success(isConnected); } @SuppressWarnings("deprecation") // API < 23 diff --git a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java index 846d9ac3..3f894a64 100644 --- a/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java +++ b/packages/wifi_iot/android/src/main/java/com/alternadom/wifiiot/wifi/ModernWifiPlatform.java @@ -59,9 +59,7 @@ public void connect(WifiConnectRequest request, WifiConnectCallback callback) { handler.post( () -> callback.onError( - "Error", - "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, - "")); + "Error", "WEP is not supported for Android SDK " + Build.VERSION.SDK_INT, "")); return; } @@ -72,8 +70,6 @@ public void connect(WifiConnectRequest request, WifiConnectCallback callback) { } } - private void connectWithSuggestion( - WifiConnectRequest request, WifiConnectCallback callback, Handler handler) { private void connectWithSuggestion(WifiConnectRequest request, WifiConnectCallback callback) { final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder(); builder.setSsid(request.ssid); @@ -128,8 +124,7 @@ private void connectWithSpecifier(WifiConnectRequest request, WifiConnectCallbac networkCallback = null; Integer timeoutInSeconds = request.timeoutInSeconds; - int timeoutMs = - timeoutInSeconds != null ? timeoutInSeconds * 1000 : DEFAULT_TIMEOUT_MS; + int timeoutMs = timeoutInSeconds != null ? timeoutInSeconds * 1000 : DEFAULT_TIMEOUT_MS; final AtomicBoolean done = new AtomicBoolean(false); @@ -183,9 +178,7 @@ public void onLost(Network network) { } private boolean applyBssid( - WifiNetworkSuggestion.Builder builder, - @Nullable String bssid, - WifiConnectCallback callback) { + WifiNetworkSuggestion.Builder builder, @Nullable String bssid, WifiConnectCallback callback) { if (bssid == null) { return true; } @@ -199,9 +192,7 @@ private boolean applyBssid( } private boolean applyBssid( - WifiNetworkSpecifier.Builder builder, - @Nullable String bssid, - WifiConnectCallback callback) { + WifiNetworkSpecifier.Builder builder, @Nullable String bssid, WifiConnectCallback callback) { if (bssid == null) { return true; } diff --git a/packages/wifi_iot/example/ios/Podfile b/packages/wifi_iot/example/ios/Podfile index e72e0b48..17ed9861 100644 --- a/packages/wifi_iot/example/ios/Podfile +++ b/packages/wifi_iot/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +# platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj index b292f06b..438e5b97 100644 --- a/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_iot/example/ios/Runner.xcodeproj/project.pbxproj @@ -370,7 +370,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt index 67c3e5f7..1655796e 100644 --- a/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt +++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt @@ -96,18 +96,14 @@ class WifiScanPlugin : } } } - val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) - // API 33+: RECEIVER_NOT_EXPORTED - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver( - wifiScanReceiver, - intentFilter, - Context.RECEIVER_NOT_EXPORTED - ) - } else { - @Suppress("DEPRECATION") // API < 33 - context.registerReceiver(wifiScanReceiver, intentFilter) - } + val intentFilter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + // API 33+: RECEIVER_NOT_EXPORTED + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(wifiScanReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + } else { + @Suppress("DEPRECATION") // API < 33 + context.registerReceiver(wifiScanReceiver, intentFilter) + } // set Flutter channels - 1 for method, 1 for event channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wifi_scan") @@ -233,33 +229,31 @@ class WifiScanPlugin : } } - private fun canStartScan(askPermission: Boolean): Int { - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - // API < 28 - Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES - // API 29+ - hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES - hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED - } + private fun canStartScan(askPermission: Boolean): Int { + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + // API < 28 + Build.VERSION.SDK_INT < Build.VERSION_CODES.P -> CAN_START_SCAN_YES + // API 29+ + hasLocPerm && isLocEnabled -> CAN_START_SCAN_YES + hasLocPerm -> CAN_START_SCAN_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_START_SCAN_NO_LOC_PERM_REQUIRED } } - @Suppress("DEPRECATION") // API 29+ throttled - private fun startScan(): Boolean = wifi!!.startScan() + @Suppress("DEPRECATION") // API 29+ throttled + private fun startScan(): Boolean = wifi!!.startScan() - private fun canGetScannedResults(askPermission: Boolean): Int { - val hasLocPerm = hasLocationPermission() - val isLocEnabled = isLocationEnabled() - return when { - hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES - hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED - askPermission -> ASK_FOR_LOC_PERM - else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED - } + private fun canGetScannedResults(askPermission: Boolean): Int { + val hasLocPerm = hasLocationPermission() + val isLocEnabled = isLocationEnabled() + return when { + hasLocPerm && isLocEnabled -> CAN_GET_RESULTS_YES + hasLocPerm -> CAN_GET_RESULTS_NO_LOC_DISABLED + askPermission -> ASK_FOR_LOC_PERM + else -> CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED } } @@ -271,41 +265,51 @@ class WifiScanPlugin : "capabilities" to ap.capabilities, "frequency" to ap.frequency, "level" to ap.level, - "timestamp" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp else null, - "standard" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, - "centerFrequency0" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, - "centerFrequency1" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, - "channelWidth" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, - "isPasspoint" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, - "operatorFriendlyName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - @Suppress("DEPRECATION") // API < 31 - ap.operatorFriendlyName?.toString() - } else null, - "venueName" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - @Suppress("DEPRECATION") // API < 31 - ap.venueName?.toString() - } else null, - "is80211mcResponder" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null - ) - } + "timestamp" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ap.timestamp + else null, + "standard" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ap.wifiStandard else null, + "centerFrequency0" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq0 else null, + "centerFrequency1" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.centerFreq1 else null, + "channelWidth" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.channelWidth else null, + "isPasspoint" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.isPasspointNetwork else null, + "operatorFriendlyName" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") // API < 31 + ap.operatorFriendlyName?.toString() + } else null, + "venueName" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + @Suppress("DEPRECATION") // API < 31 + ap.venueName?.toString() + } else null, + "is80211mcResponder" to + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ap.is80211mcResponder else null) + } - /** API 33+: wifiSsid; API < 33: SSID. */ - private fun ssidFromScanResult(ap: android.net.wifi.ScanResult): String? { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - val wifiSsid = ap.wifiSsid ?: return null - return wifiSsid.toString().trim('"') - } - @Suppress("DEPRECATION") // API < 33 - return ap.SSID + /** API 33+: wifiSsid; API < 33: SSID. */ + private fun ssidFromScanResult(ap: android.net.wifi.ScanResult): String? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val wifiSsid = ap.wifiSsid ?: return null + return wifiSsid.toString().trim('"') } + @Suppress("DEPRECATION") // API < 33 + return ap.SSID + } - private fun onScannedResultsAvailable() { - eventSink?.success(getScannedResults()) - } + private fun onScannedResultsAvailable() { + eventSink?.success(getScannedResults()) + } - /** API 29+ and targetSdk 29+: fine location required. */ - private fun requiresFineLocation(): Boolean = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q + /** API 29+ and targetSdk 29+: fine location required. */ + private fun requiresFineLocation(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q private fun hasLocationPermission(): Boolean { val permissions = @@ -325,33 +329,31 @@ class WifiScanPlugin : ERROR_NO_ACTIVITY } - private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { - if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) - val requiresFine = requiresFineLocation() - // API > 30: ask FINE + COARSE together - val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R - val permissions = when { - requiresFineButAskBoth -> locationPermissionBoth - requiresFine -> locationPermissionFine - else -> locationPermissionCoarse - } - val permissionCode = 6567800 + Random.Default.nextInt(100) - requestPermissionCookie[permissionCode] = { grantArray -> - Log.d(logTag, "permissionResultCallback: args($grantArray)") - callback.invoke( - when { - grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { - AskLocPermResult.GRANTED - } - requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { - AskLocPermResult.UPGRADE_TO_FINE - } - else -> AskLocPermResult.DENIED - } - ) - true + private fun askForLocationPermission(callback: (AskLocPermResult) -> Unit) { + if (activity == null) return callback.invoke(AskLocPermResult.ERROR_NO_ACTIVITY) + val requiresFine = requiresFineLocation() + // API > 30: ask FINE + COARSE together + val requiresFineButAskBoth = requiresFine && Build.VERSION.SDK_INT > Build.VERSION_CODES.R + val permissions = + when { + requiresFineButAskBoth -> locationPermissionBoth + requiresFine -> locationPermissionFine + else -> locationPermissionCoarse } - ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) + val permissionCode = 6567800 + Random.Default.nextInt(100) + requestPermissionCookie[permissionCode] = { grantArray -> + Log.d(logTag, "permissionResultCallback: args($grantArray)") + callback.invoke( + when { + grantArray.all { it == PackageManager.PERMISSION_GRANTED } -> { + AskLocPermResult.GRANTED + } + requiresFineButAskBoth && grantArray.first() == PackageManager.PERMISSION_GRANTED -> { + AskLocPermResult.UPGRADE_TO_FINE + } + else -> AskLocPermResult.DENIED + }) + true } ActivityCompat.requestPermissions(activity!!, permissions, permissionCode) } diff --git a/packages/wifi_scan/example/ios/Podfile b/packages/wifi_scan/example/ios/Podfile index e72e0b48..17ed9861 100644 --- a/packages/wifi_scan/example/ios/Podfile +++ b/packages/wifi_scan/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +# platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj index 34e0ddd6..f4760929 100644 --- a/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/wifi_scan/example/ios/Runner.xcodeproj/project.pbxproj @@ -339,7 +339,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -466,7 +466,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; From 62745fb06a49b63a6cdf0b9ef83d9f87279580c1 Mon Sep 17 00:00:00 2001 From: Florentin Bouchez Date: Tue, 15 Sep 2026 14:24:27 -0400 Subject: [PATCH 14/14] docs: Update CHANGELOG.md --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ packages/wifi_iot/CHANGELOG.md | 6 ++++++ packages/wifi_scan/CHANGELOG.md | 4 ++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78afcfdd..2502494e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2026-09-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`wifi_iot` - `v0.4.1-dev.0`](#wifi_iot---v041-dev0) + - [`wifi_scan` - `v0.5.1-dev.0`](#wifi_scan---v051-dev0) + +--- + +#### `wifi_iot` - `v0.4.1-dev.0` + + - **FEAT**(wifi_iot): Implement Android modern Wi‑Fi API with Legacy/Modern platform split. + - **FIX**(wifi_iot): Resolve SSID from all Wi‑Fi networks on Android 12+. + - **FIX**(wifi_iot): Fix Android compile errors from shadowed `result` locals. + +#### `wifi_scan` - `v0.5.1-dev.0` + + - **FEAT**(wifi_scan): Implement Android modern Wi‑Fi scan API. + + ## 2026-09-05 ### Changes diff --git a/packages/wifi_iot/CHANGELOG.md b/packages/wifi_iot/CHANGELOG.md index b5d64a66..fdd75fd6 100644 --- a/packages/wifi_iot/CHANGELOG.md +++ b/packages/wifi_iot/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.4.1-dev.0 + + - **FEAT**(wifi_iot): Implement Android modern Wi‑Fi API with Legacy/Modern platform split. + - **FIX**(wifi_iot): Resolve SSID from all Wi‑Fi networks on Android 12+. + - **FIX**(wifi_iot): Fix Android compile errors from shadowed `result` locals. + ## 0.4.0 > Note: This release has breaking changes. diff --git a/packages/wifi_scan/CHANGELOG.md b/packages/wifi_scan/CHANGELOG.md index 12952c96..b54b3c7c 100644 --- a/packages/wifi_scan/CHANGELOG.md +++ b/packages/wifi_scan/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.1-dev.0 + + - **FEAT**(wifi_scan): Implement Android modern Wi‑Fi scan API. + ## 0.5.0 > Note: This release has breaking changes.