diff --git a/.github/workflows/wifi_scan.yaml b/.github/workflows/wifi_scan.yaml
new file mode 100644
index 00000000..b58050af
--- /dev/null
+++ b/.github/workflows/wifi_scan.yaml
@@ -0,0 +1,91 @@
+# Build wifi_scan example
+name: wifi_scan
+
+on:
+ pull_request:
+ paths:
+ - "packages/wifi_scan/**"
+ - ".github/workflows/wifi_scan.yaml"
+
+env:
+ PLUGIN_SCOPE: "*wifi_scan*"
+ PLUGIN_EXAMPLE_SCOPE: "*wifi_scan_example*"
+
+jobs:
+ android:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: "Checkout repository"
+ uses: actions/checkout@v2
+ - name: "Install Flutter"
+ run: ./.github/workflows/scripts/install-flutter.sh stable
+ - name: "Install Tools"
+ run: ./.github/workflows/scripts/install-tools.sh
+ - name: "Build Example"
+ run: ./.github/workflows/scripts/build-examples.sh android ./lib/main.dart
+
+ ios:
+ runs-on: macos-latest
+ timeout-minutes: 30
+ steps:
+ - name: "Checkout repository"
+ uses: actions/checkout@v2
+ - name: "Install Flutter"
+ run: ./.github/workflows/scripts/install-flutter.sh stable
+ - name: "Install Tools"
+ run: ./.github/workflows/scripts/install-tools.sh
+ - name: "Build Example"
+ run: ./.github/workflows/scripts/build-examples.sh ios ./lib/main.dart
+
+# macos:
+# runs-on: macos-latest
+# timeout-minutes: 30
+# steps:
+# - name: "Checkout repository"
+# uses: actions/checkout@v2
+# - name: "Install Flutter"
+# run: ./.github/workflows/scripts/install-flutter.sh stable
+# - name: "Install Tools"
+# run: ./.github/workflows/scripts/install-tools.sh
+# - name: "Build Example"
+# run: ./.github/workflows/scripts/build-examples.sh macos ./lib/main.dart
+
+# linux:
+# runs-on: ubuntu-latest
+# timeout-minutes: 30
+# steps:
+# - name: "Checkout repository"
+# uses: actions/checkout@v2
+# - name: "Install Flutter"
+# run: ./.github/workflows/scripts/install-flutter.sh stable
+# - name: "Install Tools"
+# run: ./.github/workflows/scripts/install-tools.sh
+# - name: "Build Example"
+# run: ./.github/workflows/scripts/build-examples.sh linux ./lib/main.dart
+
+# windows:
+# runs-on: ubuntu-latest
+# timeout-minutes: 30
+# steps:
+# - name: "Checkout repository"
+# uses: actions/checkout@v2
+# - name: "Install Flutter"
+# run: ./.github/workflows/scripts/install-flutter.sh stable
+# - name: "Install Tools"
+# run: ./.github/workflows/scripts/install-tools.sh
+# - name: "Build Example"
+# run: ./.github/workflows/scripts/build-examples.sh windows ./lib/main.dart
+
+# web:
+# runs-on: ubuntu-latest
+# timeout-minutes: 30
+# steps:
+# - name: "Checkout repository"
+# uses: actions/checkout@v2
+# - name: "Install Flutter"
+# run: ./.github/workflows/scripts/install-flutter.sh stable
+# - name: "Install Tools"
+# run: ./.github/workflows/scripts/install-tools.sh
+# - name: "Build Example"
+# run: ./.github/workflows/scripts/build-examples.sh web ./lib/main.dart
\ No newline at end of file
diff --git a/README.md b/README.md
index 6f8433d8..344845c3 100644
--- a/README.md
+++ b/README.md
@@ -72,14 +72,14 @@ Flutter plugin for basic WiFi information and functionalities.
### `wifi_scan`
> [![wifi_scan][scan_workflow_badge]][scan_workflow] [![wifi_scan][scan_pub_badge]][scan_pub] [![pub points][scan_pub_points_badge]][scan_pub_points]
-Flutter plugin to scan for WiFi networks.
+Flutter plugin to scan for WiFi access points.
[[View Source][scan_code]]
#### Platform Support
| Android | iOS |
| :-----: | :-: |
-| ❌ | ➖ |
+| ✔️ | |
---
diff --git a/packages/wifi_scan/.gitignore b/packages/wifi_scan/.gitignore
new file mode 100644
index 00000000..e9dc58d3
--- /dev/null
+++ b/packages/wifi_scan/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+build/
diff --git a/packages/wifi_scan/.metadata b/packages/wifi_scan/.metadata
new file mode 100644
index 00000000..5bed5265
--- /dev/null
+++ b/packages/wifi_scan/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 18116933e77adc82f80866c928266a5b4f1ed645
+ channel: stable
+
+project_type: plugin
diff --git a/packages/wifi_scan/CHANGELOG.md b/packages/wifi_scan/CHANGELOG.md
new file mode 100644
index 00000000..41cc7d81
--- /dev/null
+++ b/packages/wifi_scan/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/packages/wifi_scan/LICENSE b/packages/wifi_scan/LICENSE
new file mode 100644
index 00000000..d36bc92e
--- /dev/null
+++ b/packages/wifi_scan/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 AlternaDom
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/wifi_scan/README.md b/packages/wifi_scan/README.md
new file mode 100644
index 00000000..4bad3c3e
--- /dev/null
+++ b/packages/wifi_scan/README.md
@@ -0,0 +1,144 @@
+
| wifi_scan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+This plugin allows Flutter apps to trigger WiFi scan and get scanned results.
+
+> This plugin is part of [WiFiFlutter][wf_home] suite, enabling various WiFi services for Flutter.
+
+## Platform Support
+
+| Platform | Status | Min. Version | API | Notes |
+| :------: | :----: |:------------:| :---: |:-----:|
+| **Android** | ✔️ | 16 (J) | Scan related APIs in [`WifiManager`][android_WifiManager] *[[Guide][android_guide]]* | For SDK >= 26(O) scans are [throttled][android_throttling]. |
+| **iOS** | ✔️ | 9.0 | No API available | Dummy implementation with [sane static returns][ios_dummy]. |
+
+## Usage
+The entry point for the plugin is the singleton instance `WiFiScan.instance`.
+
+### Start scan
+You can trigger full WiFi scan with `WiFiScan.startScan` API, as shown below:
+```dart
+// check if platform support and necessary requirements
+final can = await WiFiScan.instance.canStartScan(askPermissions: true);
+switch(can) {
+ case CanStartScan.yes:
+ // start full scan async-ly
+ final isScanning = await WiFiScan.instance.startScan();
+ //...
+ break;
+ // ... handle other cases of CanStartScan values
+}
+```
+
+For more details, you can read documentation of [`WiFiScan.startScan`][doc_startScan], [`WiFiScan.canStartScan`][doc_canStartScan] and [`CanStartScan`][doc_enum_CanStartScan].
+
+### Get scanned results
+You can get scanned results with `WiFiScan.getScannedResults` API, as shown below:
+```dart
+// check if platform support and necessary requirements
+final can = await WiFiScan.instance.canGetScannedResults(askPermissions: true);
+switch(can) {
+ case CanGetScannedResults.yes:
+ // get scanned results
+ final accessPoints = await WiFiScan.instance.getScannedResults();
+ // ...
+ break;
+ // ... handle other cases of CanGetScannedResults values
+}
+```
+
+> **NOTE:** `getScannedResults` API can be used independently of `startScan` API. This returns the latest available scanned results.
+
+For more details, you can read documentation of [`WiFiScan.getScannedResults`][doc_getScannedResults], `WiFiAccessPoint`[doc_WiFiAccessPoint], [`WiFiScan.canGetScannedResults`][doc_canGetScannedResults] and [`CanGetScannedResults`][doc_enum_CanGetScannedResults].
+
+### Get notified when scanned results available
+You can get notified when new scanned results are available, as shown below:
+```dart
+// initialize accessPoints and subscription
+List accessPoints = [];
+StreamSubscription>? subscription;
+
+void _startListeningToScannedResults(){
+// check if platform support and necessary requirements
+ final can = await WiFiScan.instance.canGetScannedResults(askPermissions: true);
+ switch(can) {
+ case CanGetScannedResults.yes:
+ // listen to onScannedResultsAvailable stream
+ subscription = WiFiScan.instance.onScannedResultsAvailable.listen((results) {
+ // update accessPoints
+ setState(() => accessPoints = results);
+ });
+ // ...
+ break;
+ // ... handle other cases of CanGetScannedResults values
+ }
+}
+
+// make sure to cancel subscription after you are done
+@override
+dispose() {
+ super.dispose();
+ subscription?.cancel();
+}
+```
+
+Additionally, `WiFiScan.onScannedResultsAvailable` API can also be used with Flutter's [`StreamBuilder`][flutter_StreamBuilder] widget.
+
+> **NOTE:** `onScannedResultsAvailable` API can be used independently of `startScan` API. The notification can also be result of a full scan performed by platform or other app.
+
+For more details, you can read documentation of [`WiFiScan.onScannedResultsAvailable`][doc_onScannedResultsAvailable], `WiFiAccessPoint`[doc_WiFiAccessPoint], [`WiFiScan.canGetScannedResults`][doc_canGetScannedResults] and [`CanGetScannedResults`][doc_enum_CanGetScannedResults].
+
+## Resources
+- 📖[API docs][docs]
+- 💻[Example app][example]
+
+## Issues and feedback
+
+Please file WiFiFlutter specific issues, bugs, or feature requests in our [issue tracker][wf_issue].
+
+To contribute a change to this plugin, please review plugin [checklist for 1.0][checklist], our [contribution guide][wf_contrib] and open a [pull request][wf_pull].
+
+
+[wf_home]: https://wifi.flutternetwork.dev/
+[wf_issue]: https://github.com/flutternetwork/WiFiFlutter/issues/new
+[wf_contrib]: https://github.com/flutternetwork/WiFiFlutter/blob/master/CONTRIBUTING.md
+[wf_pull]: https://github.com/flutternetwork/WiFiFlutter/pulls
+
+[checklist]: https://github.com/flutternetwork/WiFiFlutter/issues/188
+[docs]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/wifi_scan-library.html
+[example]: https://github.com/flutternetwork/WiFiFlutter/tree/master/packages/wifi_scan/example
+
+[doc_startScan]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiScan/startScan.html
+[doc_canStartScan]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiScan/canStartScan.html
+[doc_enum_CanStartScan]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/CanStartScan.html
+[doc_getScannedResults]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiScan/getScannedResults.html
+[doc_WiFiAccessPoint]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiAccessPoint.html
+[doc_canGetScannedResults]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiScan/canGetScannedResults.html
+[doc_enum_CanGetScannedResults]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/CanGetScannedResults.html
+[doc_onScannedResultsAvailable]: https://pub.dev/documentation/wifi_scan/latest/wifi_scan/WiFiScan/onScannedResultsAvailable.html
+
+[flutter_StreamBuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
+
+[android_guide]: https://developer.android.com/guide/topics/connectivity/wifi-scan
+[android_throttling]: https://developer.android.com/guide/topics/connectivity/wifi-scan#wifi-scan-throttling
+[android_WifiManager]: https://developer.android.com/reference/android/net/wifi/WifiManager
+
+[ios_dummy]: https://github.com/flutternetwork/WiFiFlutter/blob/master/packages/wifi_scan/ios/Classes/SwiftWifiScanPlugin.swift
diff --git a/packages/wifi_scan/android/.gitignore b/packages/wifi_scan/android/.gitignore
new file mode 100644
index 00000000..c6cbe562
--- /dev/null
+++ b/packages/wifi_scan/android/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/packages/wifi_scan/android/build.gradle b/packages/wifi_scan/android/build.gradle
new file mode 100644
index 00000000..32ba7bfc
--- /dev/null
+++ b/packages/wifi_scan/android/build.gradle
@@ -0,0 +1,50 @@
+group 'dev.flutternetwork.wifi.wifi_scan'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.3.50'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.1.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 {
+ compileSdkVersion 30
+
+ 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/gradle.properties b/packages/wifi_scan/android/gradle.properties
new file mode 100644
index 00000000..94adc3a3
--- /dev/null
+++ b/packages/wifi_scan/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties b/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..3c9d0852
--- /dev/null
+++ b/packages/wifi_scan/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/packages/wifi_scan/android/settings.gradle b/packages/wifi_scan/android/settings.gradle
new file mode 100644
index 00000000..a528fecb
--- /dev/null
+++ b/packages/wifi_scan/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'wifi_scan'
diff --git a/packages/wifi_scan/android/src/main/AndroidManifest.xml b/packages/wifi_scan/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..54f7b9a0
--- /dev/null
+++ b/packages/wifi_scan/android/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 00000000..db0a1b53
--- /dev/null
+++ b/packages/wifi_scan/android/src/main/kotlin/dev/flutternetwork/wifi/wifi_scan/WifiScanPlugin.kt
@@ -0,0 +1,328 @@
+package dev.flutternetwork.wifi.wifi_scan
+
+import android.Manifest
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.location.LocationManager
+import android.net.wifi.WifiManager
+import android.os.Build
+import android.util.Log
+import androidx.annotation.NonNull
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.core.location.LocationManagerCompat
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.EventChannel
+import io.flutter.plugin.common.EventChannel.EventSink
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+import io.flutter.plugin.common.PluginRegistry
+import kotlin.random.Random
+
+/** Error Codes */
+private const val ERROR_INVALID_ARGS = "InvalidArgs"
+private const val ERROR_NULL_ACTIVITY = "NullActivity"
+
+/** CanStartScan codes */
+private const val CAN_START_SCAN_NOT_SUPPORTED = 0
+private const val CAN_START_SCAN_YES = 1
+private const val CAN_START_SCAN_NO_LOC_PERM_REQUIRED = 2
+private const val CAN_START_SCAN_NO_LOC_PERM_DENIED = 3
+private const val CAN_START_SCAN_NO_LOC_PERM_UPGRADE_ACCURACY = 4
+private const val CAN_START_SCAN_NO_LOC_DISABLED = 5
+
+/** CanGetScannedResults codes */
+private const val CAN_GET_RESULTS_NOT_SUPPORTED = 0
+private const val CAN_GET_RESULTS_YES = 1
+private const val CAN_GET_RESULTS_NO_LOC_PERM_REQUIRED = 2
+private const val CAN_GET_RESULTS_NO_LOC_PERM_DENIED = 3
+private const val CAN_GET_RESULTS_NO_LOC_PERM_UPGRADE_ACCURACY = 4
+private const val CAN_GET_RESULTS_NO_LOC_DISABLED = 5
+
+/** Magic codes */
+private const val ASK_FOR_LOC_PERM = -1
+
+/** 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
+
+ // plugin interfaces
+ private lateinit var channel: MethodChannel
+ private lateinit var eventChannel: EventChannel
+ // 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)) {
+ eventSink?.success(getScannedResults())
+ }
+ }
+ }
+ 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)
+ }
+
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ eventChannel.setStreamHandler(null)
+ wifi = null
+ context.unregisterReceiver(wifiScanReceiver)
+ wifiScanReceiver = null
+ }
+
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ binding.addRequestPermissionsResultListener(this)
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ activity = null
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ activity = binding.activity
+ binding.addRequestPermissionsResultListener(this)
+ }
+
+ override fun onDetachedFromActivity() {
+ activity = null
+ }
+
+ override fun onListen(arguments: Any?, events: EventSink?) {
+ eventSink = events
+ // put getScannedResults in sink - to start with
+ eventSink?.success(getScannedResults())
+ }
+
+ override fun onCancel(arguments: Any?) {
+ eventSink?.endOfStream()
+ eventSink = null
+ }
+
+ 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
+ )
+ 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)
+ }
+ }
+ "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)
+ }
+ }
+ "getScannedResults" -> result.success(getScannedResults())
+ else -> result.notImplemented()
+ }
+ }
+
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int, permissions: Array?, grantResults: IntArray?
+ ): Boolean {
+ Log.d(logTag, "onRequestPermissionsResult: arguments ($requestCode, $permissions, $grantResults)")
+ if (grantResults != null) {
+ Log.d(logTag, "requestPermissionCookie: $requestPermissionCookie")
+ return requestPermissionCookie[requestCode]?.invoke(grantResults) ?: false
+ }
+ return false
+ }
+
+ /**
+ * 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 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 = 6560000 + Random.Default.nextInt(10000)
+ 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 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 canGetScannedResults(askPermission: Boolean): Int {
+ // 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