diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b75303
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4f2d460
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2019 Wideverse
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5ee74f1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,163 @@
+# Headless Wifi Manager
+
+
+
+
+## Why this library
+Imagine the classic "Google Home" situation.
+
+You have an Headless device (your Google Home) that isn't connected to WiFi.
+Using your phone, you can configure your Google Home to connect to a specific network and then communicate with it more easily.
+
+## What this library do
+This library assumes you have 2 devices.
+ * An **advertiser**, your headless device that is totally disconnected to WiFi and any other network.
+ * A **discoverer**, your phone that has a screen indeed (\o/) and can scan for nearby WiFi access points.
+
+Using Android **Nearby API**, the **discoverer** and the **advertiser** communicate without the need to be on the same network using a combination of Wifi hotspots and Bluetooth.
+
+The whole process can be summarized as follows:
+ 1. The **advertiser** starts advertising it's presence to nearby devices;
+ 2. The **discover** connects to an available advertiser and **receives a list of Wifi Networks from it**;
+ 3. The **discover** selects a network and sends it's credentials back to the **advertiser**;
+ 4. The **advertiser** connects to the network with the given credentials;
+ 5. The **advertiser** sends an acknowledgment with a positive or negative result to the **discoverer**;
+
+At the end of the procedure, the **advertiser** will be connected to the WiFi network and you'll be able to communicate with it, for example via standard HTTP requests.
+
+ ## How to use it
+1. **Add the JitPack repository to your build file**
+
+ Add it in your root build.gradle at the end of repositories:
+```gradle
+ allprojects {
+ repositories {
+ ...
+ maven { url 'https://jitpack.io' }
+ }
+ }
+```
+
+2. **Add the dependency**
+
+```gradle
+ dependencies {
+ // AndroidX capable version
+ implementation 'com.github.wideverse:headless-wifi-manager:1.0.0'
+ }
+```
+
+**Initialize** the main object:
+
+
+
+```kotlin
+ val headlessWifiManager = HeadlessWifiManager(applicationContext, APP_ID)
+```
+
+**APP_ID** is an unique identifier that will allow your discoverers to filter and talk only with your advertisers and not other Nearby devices.
+
+## Device configuration
+The following steps differs if you're deploying on your **Advertiser** or your **Discoverer**
+
+### Advertiser
+```kotlin
+headlessWifiManager.startAdvertising(object: AdvertisingCallback {
+
+ override fun onAdvertisingStarted() {
+ Log.d(TAG, "Successfully started Advertising.")
+ }
+
+ override fun onError(e: Exception) {
+ Log.e(TAG, "Procedure failed")
+ e.printStackTrace()
+ }
+
+ override fun onSuccess() {
+ Log.d(TAG, "Successfully connected to Wifi.")
+ }
+ })
+```
+
+### Discoverer
+```kotlin
+headlessWifiManager.startDiscovery(object: DiscoveryCallback {
+ override fun onDiscoveryStarted() {
+ Log.d(TAG, "Successfully started looking for nearby devices to configure")
+ }
+
+ override fun onDeviceFound(endpointId: String,deviceName: String) {
+ Log.d(TAG, "Trying to connect to $deviceName")
+ // Here you can show a list with all the devices available
+
+ // Let's assume we want to configure the first one discovered
+ headlessWifiManager.connectToEndpoint(endpointId)
+ }
+
+ override fun onConnected() {
+ Log.d(TAG, "Sucessifully connected to a hub device")
+ Log.d(TAG, "Now waiting to get WiFi List from advertiser")
+ }
+
+ override fun onNetworkListAvailable(results: List) {
+ Log.d(TAG, "Successfully made a connection. Wifi list is available.")
+
+ // Here you can show a list with all the WiFi network available
+ // that are stored in results
+
+ // Let's take the first discovered network for semplicity
+ results[0].password = "sherLocked"
+ // If the network doesn't have a protection, leave the password filed empty
+
+ // Then call sendWifiCredentials to send data back to the advertiser
+ headlessWifiManager.sendWifiCredentials(result,
+ object : NetworkCallback {
+ override fun onError(e: Exception) {
+ Log.e(TAG, "An error has occurred")
+ }
+
+ override fun onConnected(SSID: String) {
+ Log.d(TAG, "ALL DONE! \o/")
+
+ // Your advertiser is now connected to internet!
+ }
+ })
+ }
+
+ override fun onError(e: Exception) {
+ Log.e(TAG, "An error has occurred.")
+ }
+ })
+
+```
+
+## WifiHelper
+This library contains an helper you can use to show users more accurate info about available WiFi networks in a nicer way.
+
+Using the method ```getDrawableFromRSSI(level: Int, protected: Boolean)``` you can directly get the appropriate resource drawable to show the **detected network RSSI** and a **lock if the network is protected** in an immediate way, similar to Android WiFi dialog.
+
+
+
+## WifiScanResult
+```WifiScanResult``` is our internal object to pass info about WiFi networks.
+
+We decided to convert system object [ScanResult](https://developer.android.com/reference/android/net/wifi/ScanResult) available in the Android SDK to our [WifiScanResult](https://github.com/wideverse/headless-wifi-manager/blob/master/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiScanResult.kt).
+
+This allows us to **transfer only the useful data** between advertiser and discoverer and keep the payload simple and small.
+
+You can expect a ```List``` after a successful scanning and pass a ```WifiScanResult``` with a valorized ```password``` field to advertiser to connect on. You can find how the mapping between the two objects is done [here](https://github.com/wideverse/headless-wifi-manager/blob/master/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/ScanResultConverter.kt).
+
+If you think more fields of ```ScanResults``` needs to be added to our ```WifiScanResult``` to improve your logic, please feel free to **open a Pull Request**.
+
+# Example
+An fully working Example app is available for you on this repo to see how to use the API and for insipration.
+When you ```Import``` the project in Android Studio, you will see two modules ```advertiser``` and ```discoverer``` that you can deploy on your testing devices.
+
+The Example app also shows how to handle edge cases like **errors** and **empty WiFi list** received from the advertiser.
+
+The adveriser module DOESN'T have an UI since the device is supposed to be headless. Refer to system logs to figure what's happening.
+
+JavaDoc is available [here](https://wideverse.github.io/headless-wifi-manager/headlesswifimanager/).
+
+# About
+This library has been developed at [Wideverse](https://www.wideverse.com/it/home-it/) @ [Polytechnic University of Bari](https://www.poliba.it/) and it's shared under Apache 2.0.
diff --git a/advertiser/.gitignore b/advertiser/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/advertiser/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/advertiser/build.gradle b/advertiser/build.gradle
new file mode 100644
index 0000000..84ff51b
--- /dev/null
+++ b/advertiser/build.gradle
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+
+
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.wideverse.headlesswifimanager_advertiser"
+ minSdkVersion 21
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'androidx.core:core-ktx:1.1.0-alpha04'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
+ implementation project(':headlesswifimanager')
+}
diff --git a/advertiser/proguard-rules.pro b/advertiser/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/advertiser/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/advertiser/src/main/AndroidManifest.xml b/advertiser/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e244e88
--- /dev/null
+++ b/advertiser/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/advertiser/src/main/java/com/wideverse/headlesswifimanager_advertiser/MainActivity.kt b/advertiser/src/main/java/com/wideverse/headlesswifimanager_advertiser/MainActivity.kt
new file mode 100644
index 0000000..2d7168b
--- /dev/null
+++ b/advertiser/src/main/java/com/wideverse/headlesswifimanager_advertiser/MainActivity.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_advertiser
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.util.Log
+import com.wideverse.headlesswifimanager.interfaces.AdvertisingCallback
+import com.wideverse.headlesswifimanager.HeadlessWifiManager
+import java.lang.Exception
+
+const val TAG = "HEADLESS_ADVERTISER"
+const val APP_ID = "headless_wifi_configurator"
+
+
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ // Headless device starts Advertising as soon as connected
+ HeadlessWifiManager(applicationContext, APP_ID)
+ .startAdvertising(object: AdvertisingCallback {
+
+ override fun onAdvertisingStarted() {
+ Log.d(TAG, "Successfully started Advertising.")
+ }
+
+ override fun onError(e: Exception) {
+ Log.e(TAG, "Procedure failed")
+ e.printStackTrace()
+ }
+
+ override fun onSuccess() {
+ Log.d(TAG, "Successfully connected to Wifi.")
+ }
+ })
+ }
+}
diff --git a/advertiser/src/main/res/drawable-v24/ic_launcher_foreground.xml b/advertiser/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..44ce3cf
--- /dev/null
+++ b/advertiser/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/advertiser/src/main/res/drawable/ic_launcher_background.xml b/advertiser/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..3db51b1
--- /dev/null
+++ b/advertiser/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/advertiser/src/main/res/layout/activity_main.xml b/advertiser/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..df0afdd
--- /dev/null
+++ b/advertiser/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..419eb95
--- /dev/null
+++ b/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..419eb95
--- /dev/null
+++ b/advertiser/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/advertiser/src/main/res/mipmap-hdpi/ic_launcher.png b/advertiser/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/advertiser/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/advertiser/src/main/res/mipmap-hdpi/ic_launcher_round.png b/advertiser/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/advertiser/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/advertiser/src/main/res/mipmap-mdpi/ic_launcher.png b/advertiser/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/advertiser/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/advertiser/src/main/res/mipmap-mdpi/ic_launcher_round.png b/advertiser/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/advertiser/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/advertiser/src/main/res/mipmap-xhdpi/ic_launcher.png b/advertiser/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/advertiser/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/advertiser/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher.png b/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/advertiser/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/advertiser/src/main/res/values/colors.xml b/advertiser/src/main/res/values/colors.xml
new file mode 100644
index 0000000..1919c1f
--- /dev/null
+++ b/advertiser/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/advertiser/src/main/res/values/strings.xml b/advertiser/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e5ecc44
--- /dev/null
+++ b/advertiser/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Headless Wifi Advertiser
+
diff --git a/advertiser/src/main/res/values/styles.xml b/advertiser/src/main/res/values/styles.xml
new file mode 100644
index 0000000..4f3b965
--- /dev/null
+++ b/advertiser/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..8ba4aa4
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.3.21'
+ repositories {
+ google()
+ jcenter()
+
+ }
+ ext {
+ dokkaVersion = '0.9.17'
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$dokkaVersion"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/discoverer/.gitignore b/discoverer/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/discoverer/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/discoverer/build.gradle b/discoverer/build.gradle
new file mode 100644
index 0000000..f915577
--- /dev/null
+++ b/discoverer/build.gradle
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+android {
+ compileSdkVersion 28
+
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+
+
+ defaultConfig {
+ applicationId "com.wideverse.headlesswifimanager_discoverer"
+ minSdkVersion 21
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ vectorDrawables.useSupportLibrary = true
+
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'com.github.ybq:Android-SpinKit:1.2.0'
+ implementation "org.jetbrains.anko:anko:0.10.8"
+ implementation 'com.karumi:dexter:5.0.0'
+
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
+ implementation project(':headlesswifimanager')
+ implementation 'androidx.cardview:cardview:1.0.0'
+}
diff --git a/discoverer/proguard-rules.pro b/discoverer/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/discoverer/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/discoverer/src/main/AndroidManifest.xml b/discoverer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..134d8f3
--- /dev/null
+++ b/discoverer/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/MainActivity.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/MainActivity.kt
new file mode 100644
index 0000000..d89d756
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/MainActivity.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer
+
+import android.Manifest
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProviders
+import com.wideverse.headlesswifimanager.interfaces.DiscoveryCallback
+import com.wideverse.headlesswifimanager.HeadlessWifiManager
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import com.wideverse.headlesswifimanager.interfaces.NetworkCallback
+import com.karumi.dexter.Dexter
+import com.wideverse.headlesswifimanager_discoverer.adapter.MainPagerAdapter
+import com.wideverse.headlesswifimanager_discoverer.fragment.BaseFragment
+import com.wideverse.headlesswifimanager_discoverer.fragment.OnFragmentWifiSelected
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_ADVERTISER_CONNECTED
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_SCANNING
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WELCOME
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WIFI_CONNECTING
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WIFI_DONE
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WIFI_ERROR
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WIFI_LIST
+import com.wideverse.headlesswifimanager_discoverer.viewmodel.MainViewModel
+import kotlinx.android.synthetic.main.activity_main.*
+import java.lang.Exception
+import com.karumi.dexter.PermissionToken
+import com.karumi.dexter.listener.PermissionDeniedResponse
+import com.karumi.dexter.listener.PermissionGrantedResponse
+import com.karumi.dexter.listener.PermissionRequest
+import com.karumi.dexter.listener.single.PermissionListener
+
+
+const val APP_ID = "headless_wifi_configurator"
+const val TAG = "HEADLESS_DISCOVERER"
+
+class MainActivity : AppCompatActivity(),
+ BaseFragment.OnFragmentInteractionListener,
+ OnFragmentWifiSelected {
+
+ lateinit var viewModel: MainViewModel
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Dexter.withActivity(this)
+ .withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ .withListener(object : PermissionListener {
+ override fun onPermissionGranted(response: PermissionGrantedResponse) {/* ... */
+ }
+
+ override fun onPermissionDenied(response: PermissionDeniedResponse) {/* ... */
+ }
+
+ override fun onPermissionRationaleShouldBeShown(
+ permission: PermissionRequest,
+ token: PermissionToken
+ ) {/* ... */
+ }
+ }).check()
+ setContentView(R.layout.activity_main)
+
+ viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
+
+ viewModel.headlessWifiManager = HeadlessWifiManager(applicationContext, APP_ID)
+
+ viewModel.pagerAdapter =
+ MainPagerAdapter(supportFragmentManager)
+
+ mainPager.adapter = viewModel.pagerAdapter
+
+ mainPager.setCurrentItem(VIEW_WELCOME, false)
+ }
+
+ private fun initiateProcedure() {
+ mainPager.invalidate()
+
+ // Set Scanning fragment at start
+ mainPager.setCurrentItem(VIEW_SCANNING, false)
+
+ viewModel.headlessWifiManager
+ .startDiscovery(object : DiscoveryCallback {
+ override fun onDiscoveryStarted() {
+ Log.d(TAG, "Successfully started looking for nearby devices to configure")
+ mainPager.setCurrentItem(VIEW_SCANNING, false)
+ }
+
+ override fun onDeviceFound(endpointId: String, deviceName: String) {
+ Log.d(TAG, "Trying to connect to $deviceName")
+ // Right now first device discovered is automatically
+ // targeted for connection
+ viewModel.headlessWifiManager.connectToEndpoint(endpointId)
+ // TODO: add here multi device support
+ }
+
+ override fun onConnected() {
+ Log.d(TAG, "Sucessifully connected to a hub device")
+ Log.d(TAG, "Now waiting to get WiFi List from advertiser")
+ mainPager.setCurrentItem(VIEW_ADVERTISER_CONNECTED, false)
+ }
+
+ override fun onNetworkListAvailable(results: List) {
+ Log.d(TAG, "Successfully made a connection. Wifi list is available.")
+ mainPager.setCurrentItem(VIEW_WIFI_LIST, false)
+ viewModel.pagerAdapter.getWifiFragment().setWifiScanList(results)
+ }
+
+ override fun onError(e: Exception) {
+ mainPager.setCurrentItem(VIEW_WIFI_ERROR, false)
+ }
+ })
+ }
+
+ override fun onFragmentWifiSelected(result: WifiScanResult?) {
+ if (result != null) {
+ mainPager.setSlide(VIEW_WIFI_CONNECTING)
+ viewModel.headlessWifiManager.sendWifiCredentials(result,
+ object : NetworkCallback {
+ override fun onError(e: Exception) {
+ mainPager.setCurrentItem(VIEW_WIFI_ERROR, false)
+ }
+
+ override fun onConnected(SSID: String) {
+ mainPager.setCurrentItem(VIEW_WIFI_DONE, false)
+ }
+ })
+ } else {
+ cancelProcedure(false)
+ }
+ }
+
+ override fun onFragmentInteraction(id: String) {
+ when (id) {
+ "welcomeButton" -> initiateProcedure()
+ "cancelButton" -> cancelProcedure(false)
+ "doneButton" -> cancelProcedure(true)
+ }
+ }
+
+ private fun cancelProcedure(finish: Boolean) {
+ mainPager.setCurrentItem(VIEW_WELCOME, false)
+ viewModel.headlessWifiManager.abortProcedure()
+
+ if (finish) {
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/adapter/MainPagerAdapter.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/adapter/MainPagerAdapter.kt
new file mode 100644
index 0000000..500b9ec
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/adapter/MainPagerAdapter.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.adapter
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import com.wideverse.headlesswifimanager_discoverer.fragment.*
+import com.wideverse.headlesswifimanager_discoverer.helper.DiscovererViewPager.Companion.VIEW_WIFI_LIST
+
+
+class MainPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
+
+ private val fragmentList: List = listOf(
+ WelcomeFragment(),
+ ScanningFragment(),
+ AdvertiserConnectedFragment(),
+ WifiSelectFragment(),
+ WifiConnectingFragment(),
+ DoneFragment(),
+ ErrorFragment())
+
+ fun getWifiFragment(): WifiSelectFragment = fragmentList[VIEW_WIFI_LIST] as WifiSelectFragment
+
+ override fun getItem(position: Int) = fragmentList[position]
+
+ override fun getCount() = 7
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/AdvertiserConnectedFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/AdvertiserConnectedFragment.kt
new file mode 100644
index 0000000..6f3780b
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/AdvertiserConnectedFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_scanning.view.*
+
+class AdvertiserConnectedFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_connected, container, false)
+
+ view.cancelButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("cancelButton")
+ }
+
+ return view
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ AdvertiserConnectedFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/BaseFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/BaseFragment.kt
new file mode 100644
index 0000000..1173191
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/BaseFragment.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+
+@LayoutRes
+private var customLayout: Int = -1
+
+open class BaseFragment : Fragment() {
+ protected var fragmentListener: OnFragmentInteractionListener? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(customLayout, container, false)
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (context is OnFragmentInteractionListener) {
+ fragmentListener = context
+ } else {
+ throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
+ }
+ }
+
+ interface OnFragmentInteractionListener {
+ fun onFragmentInteraction(id: String)
+ }
+
+
+ companion object {
+ @JvmStatic
+ fun newInstance(@LayoutRes layout: Int): BaseFragment =
+ BaseFragment().apply {
+ customLayout = layout
+ }
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/DoneFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/DoneFragment.kt
new file mode 100644
index 0000000..efe9baa
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/DoneFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_done.view.*
+
+class DoneFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_done, container, false)
+
+ view.doneButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("doneButton")
+ }
+
+ return view
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ DoneFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ErrorFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ErrorFragment.kt
new file mode 100644
index 0000000..683e472
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ErrorFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_error.view.*
+
+class ErrorFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_error, container, false)
+
+ view.retryButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("cancelButton")
+ }
+
+ return view
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ ErrorFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/PasswordDialogFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/PasswordDialogFragment.kt
new file mode 100644
index 0000000..6fc6218
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/PasswordDialogFragment.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.text.method.HideReturnsTransformationMethod
+import android.text.method.PasswordTransformationMethod
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.dialog_password.view.*
+
+class PasswordDialogFragment: DialogFragment(){
+ lateinit var listener: OnFragmentWifiSelected
+ var wifiSelected: WifiScanResult? = null
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ try {
+ listener = context as OnFragmentWifiSelected
+ } catch (e: ClassCastException) {
+ throw ClassCastException((context.toString() +
+ " must implement NoticeDialogListener"))
+ }
+ }
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return activity?.let {
+ val builder = AlertDialog.Builder(it)
+ val inflater = requireActivity().layoutInflater
+ val view = inflater.inflate(R.layout.dialog_password, null)
+ view.passwordLabel.text = "Type the password for the network ${wifiSelected?.SSID}"
+ view.passwordCheckbox.setOnCheckedChangeListener { buttonView, isChecked ->
+ if (isChecked) {
+ // hide password
+ view.passwordEditText.transformationMethod = HideReturnsTransformationMethod.getInstance()
+ view.passwordEditText.moveCursorToVisibleOffset()
+ } else {
+ // show password
+ view.passwordEditText.transformationMethod = PasswordTransformationMethod.getInstance()
+ view.passwordEditText.moveCursorToVisibleOffset()
+ }
+ }
+ builder.setView(view)
+ // Add action button
+ .setPositiveButton("Confirm"
+ ) { dialog, id ->
+ var password = view.passwordEditText.text.toString()
+ wifiSelected?.password = password
+ listener.onFragmentWifiSelected(wifiSelected!!)
+
+
+ // sign in the user ...
+ }
+ .setNegativeButton(
+ "Cancel"
+ ) { dialog, id ->
+ getDialog().cancel()
+ //fragmentListener.onDialogNegativeClick(this)
+
+ }
+ builder.create()
+ } ?: throw IllegalStateException("Activity cannot be null")
+ }
+}
\ No newline at end of file
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ScanningFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ScanningFragment.kt
new file mode 100644
index 0000000..85c9b36
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/ScanningFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_scanning.view.*
+
+class ScanningFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_scanning, container, false)
+
+ view.cancelButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("cancelButton")
+ }
+
+ return view
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ ScanningFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WelcomeFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WelcomeFragment.kt
new file mode 100644
index 0000000..c2007bc
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WelcomeFragment.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_welcome.view.*
+
+class WelcomeFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_welcome, container, false)
+
+ view.welcomeButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("welcomeButton")
+ }
+
+ return view
+ }
+
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ ScanningFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiConnectingFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiConnectingFragment.kt
new file mode 100644
index 0000000..f120431
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiConnectingFragment.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.wideverse.headlesswifimanager_discoverer.R
+import kotlinx.android.synthetic.main.view_scanning.view.*
+
+class WifiConnectingFragment : BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.view_wificonnecting, container, false)
+
+ view.cancelButton.setOnClickListener {
+ fragmentListener?.onFragmentInteraction("cancelButton")
+ }
+
+ return view
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance() =
+ WifiConnectingFragment()
+ }
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiSelectFragment.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiSelectFragment.kt
new file mode 100644
index 0000000..6e48a43
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/fragment/WifiSelectFragment.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import com.wideverse.headlesswifimanager.helper.WifiHelper
+import com.wideverse.headlesswifimanager_discoverer.R
+import com.wideverse.headlesswifimanager_discoverer.viewmodel.WifiViewModel
+import kotlinx.android.synthetic.main.fragment_wifi_select.view.*
+import kotlinx.android.synthetic.main.view_item_wifi.view.*
+import org.jetbrains.anko.okButton
+import org.jetbrains.anko.sdk27.coroutines.onClick
+import org.jetbrains.anko.support.v4.alert
+
+class WifiSelectFragment : BaseFragment(), OnFragmentWifiSelected {
+ lateinit var viewModel: WifiViewModel
+ private var wifiListener: OnFragmentWifiSelected? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel = ViewModelProviders.of(this).get(WifiViewModel::class.java)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ val view = inflater.inflate(R.layout.fragment_wifi_select, container, false)
+
+ view.cancelButton.onClick {
+ fragmentListener?.onFragmentInteraction("cancelButton")
+ }
+
+ viewModel.recyclerAdapter = WifiViewAdapter(viewModel.wifiList, wifiListener!!)
+ viewModel.recyclerLayout = LinearLayoutManager(inflater.context)
+
+ view.wifiRecycler.layoutManager = viewModel.recyclerLayout
+ view.wifiRecycler.adapter = viewModel.recyclerAdapter
+
+ // Inflate the layout for this fragment
+ return view
+ }
+
+
+ override fun onFragmentWifiSelected(result: WifiScanResult?) {
+ if (result == null) {
+ wifiListener?.onFragmentWifiSelected(null)
+ } else {
+ viewModel.selectedWifi = result
+ val passwordDialog = PasswordDialogFragment()
+ passwordDialog.wifiSelected = result
+
+ if (result.protected) {
+ passwordDialog.show(fragmentManager, "passwordFragment")
+ } else {
+ wifiListener?.onFragmentWifiSelected(result)
+ }
+ }
+ }
+
+ fun setWifiScanList(wifiList: List) {
+ if (wifiList.isEmpty()) {
+ alert("No Wi-Fi network found. Try again later.") {
+ okButton {
+ wifiListener?.onFragmentWifiSelected(null)
+ }
+ }.apply {
+ isCancelable = false
+ show()
+ }
+ }
+
+ viewModel.wifiList = wifiList
+
+ view?.wifiRecycler?.adapter =
+ WifiViewAdapter(viewModel.wifiList, this)
+
+ view?.wifiRecycler?.invalidate()
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (context is OnFragmentWifiSelected) {
+ wifiListener = context
+ } else {
+ throw RuntimeException("$context must implement OnFragmentInteractionListener")
+ }
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ wifiListener = null
+ }
+
+ /*override fun onDialogPositiveClick(dialog: DialogFragment, password: String) {
+ selectedWifi?.password = password
+ if (selectedWifi != null){
+ wifiListener?.onFragmentWifiSelected(selectedWifi!!)
+ }
+
+ print("$TAG: $password")
+ }
+
+ override fun onDialogNegativeClick(dialog: DialogFragment) {
+ }*/
+}
+
+interface OnFragmentWifiSelected {
+ fun onFragmentWifiSelected(result: WifiScanResult?)
+}
+
+class WifiViewAdapter(private val wifiList: List, private val listener: OnFragmentWifiSelected) :
+ RecyclerView.Adapter() {
+
+ class WifiListHolder(val view: View) : RecyclerView.ViewHolder(view)
+
+
+ // Create new views (invoked by the layout manager)
+ override fun onCreateViewHolder(parent: ViewGroup,
+ viewType: Int): WifiListHolder {
+
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.view_item_wifi, parent, false)
+
+ return WifiListHolder(view)
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ override fun onBindViewHolder(holder: WifiListHolder, position: Int) {
+ val currentWifi = wifiList[position]
+ holder.view.wifiRssiIcon.setImageResource(WifiHelper.getDrawableFromRSSI(currentWifi.level, currentWifi.protected))
+ holder.view.wifiTitle.text = wifiList[position].SSID
+
+ holder.view.wifiItem.setOnClickListener {
+ listener.onFragmentWifiSelected(wifiList[position])
+ }
+ }
+
+ // Return the size of your dataset (invoked by the layout manager)
+ override fun getItemCount() = wifiList.size
+}
\ No newline at end of file
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/helper/DiscovererViewPager.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/helper/DiscovererViewPager.kt
new file mode 100644
index 0000000..3bda8b0
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/helper/DiscovererViewPager.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.helper
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.viewpager.widget.ViewPager
+
+
+class DiscovererViewPager : ViewPager {
+
+ companion object {
+ const val VIEW_WELCOME = 0
+ const val VIEW_SCANNING = 1
+ const val VIEW_ADVERTISER_CONNECTED = 2
+ const val VIEW_WIFI_LIST = 3
+ const val VIEW_WIFI_CONNECTING = 4
+ const val VIEW_WIFI_DONE = 5
+ const val VIEW_WIFI_ERROR = 6
+ }
+
+ fun setSlide(position: Int){
+ this.currentItem = position
+ }
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ // Never allow swiping to switch between pages
+ return false
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ // Never allow swiping to switch between pages
+ return false
+ }
+}
\ No newline at end of file
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/MainViewModel.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/MainViewModel.kt
new file mode 100644
index 0000000..19aa1d0
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/MainViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.viewmodel
+
+import androidx.lifecycle.ViewModel
+import com.wideverse.headlesswifimanager.HeadlessWifiManager
+import com.wideverse.headlesswifimanager_discoverer.adapter.MainPagerAdapter
+
+
+class MainViewModel: ViewModel() {
+ lateinit var headlessWifiManager: HeadlessWifiManager
+ lateinit var pagerAdapter: MainPagerAdapter
+}
diff --git a/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/WifiViewModel.kt b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/WifiViewModel.kt
new file mode 100644
index 0000000..f33eabc
--- /dev/null
+++ b/discoverer/src/main/java/com/wideverse/headlesswifimanager_discoverer/viewmodel/WifiViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager_discoverer.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import com.wideverse.headlesswifimanager_discoverer.fragment.WifiViewAdapter
+
+class WifiViewModel: ViewModel() {
+ var wifiList: List = ArrayList()
+ var selectedWifi: WifiScanResult? = null
+ lateinit var recyclerAdapter: WifiViewAdapter
+ lateinit var recyclerLayout: LinearLayoutManager
+}
\ No newline at end of file
diff --git a/discoverer/src/main/res/drawable-v24/ic_launcher_foreground.xml b/discoverer/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..44ce3cf
--- /dev/null
+++ b/discoverer/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discoverer/src/main/res/drawable/cancel.png b/discoverer/src/main/res/drawable/cancel.png
new file mode 100644
index 0000000..844c8a8
Binary files /dev/null and b/discoverer/src/main/res/drawable/cancel.png differ
diff --git a/discoverer/src/main/res/drawable/checked.png b/discoverer/src/main/res/drawable/checked.png
new file mode 100644
index 0000000..951ed8f
Binary files /dev/null and b/discoverer/src/main/res/drawable/checked.png differ
diff --git a/discoverer/src/main/res/drawable/ic_launcher_background.xml b/discoverer/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..3db51b1
--- /dev/null
+++ b/discoverer/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discoverer/src/main/res/drawable/ic_logo_nearby_48dp.xml b/discoverer/src/main/res/drawable/ic_logo_nearby_48dp.xml
new file mode 100644
index 0000000..3d95876
--- /dev/null
+++ b/discoverer/src/main/res/drawable/ic_logo_nearby_48dp.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discoverer/src/main/res/layout/activity_main.xml b/discoverer/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e9bc40e
--- /dev/null
+++ b/discoverer/src/main/res/layout/activity_main.xml
@@ -0,0 +1,22 @@
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/dialog_password.xml b/discoverer/src/main/res/layout/dialog_password.xml
new file mode 100644
index 0000000..69b30ef
--- /dev/null
+++ b/discoverer/src/main/res/layout/dialog_password.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/fragment_wifi_select.xml b/discoverer/src/main/res/layout/fragment_wifi_select.xml
new file mode 100644
index 0000000..fc434e7
--- /dev/null
+++ b/discoverer/src/main/res/layout/fragment_wifi_select.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_connected.xml b/discoverer/src/main/res/layout/view_connected.xml
new file mode 100644
index 0000000..4db4b4d
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_connected.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_done.xml b/discoverer/src/main/res/layout/view_done.xml
new file mode 100644
index 0000000..30a8edf
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_done.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_error.xml b/discoverer/src/main/res/layout/view_error.xml
new file mode 100644
index 0000000..2fbb9df
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_error.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_item_wifi.xml b/discoverer/src/main/res/layout/view_item_wifi.xml
new file mode 100644
index 0000000..8771a5c
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_item_wifi.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_scanning.xml b/discoverer/src/main/res/layout/view_scanning.xml
new file mode 100644
index 0000000..41665bc
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_scanning.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_welcome.xml b/discoverer/src/main/res/layout/view_welcome.xml
new file mode 100644
index 0000000..484096a
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_welcome.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/layout/view_wificonnecting.xml b/discoverer/src/main/res/layout/view_wificonnecting.xml
new file mode 100644
index 0000000..1f90448
--- /dev/null
+++ b/discoverer/src/main/res/layout/view_wificonnecting.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..419eb95
--- /dev/null
+++ b/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..419eb95
--- /dev/null
+++ b/discoverer/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/discoverer/src/main/res/mipmap-hdpi/ic_launcher.png b/discoverer/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/discoverer/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/discoverer/src/main/res/mipmap-hdpi/ic_launcher_round.png b/discoverer/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/discoverer/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/discoverer/src/main/res/mipmap-mdpi/ic_launcher.png b/discoverer/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/discoverer/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/discoverer/src/main/res/mipmap-mdpi/ic_launcher_round.png b/discoverer/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/discoverer/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/discoverer/src/main/res/mipmap-xhdpi/ic_launcher.png b/discoverer/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/discoverer/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/discoverer/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher.png b/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/discoverer/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/discoverer/src/main/res/values/colors.xml b/discoverer/src/main/res/values/colors.xml
new file mode 100644
index 0000000..78b2812
--- /dev/null
+++ b/discoverer/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ #2196f3
+ #1976d2
+ #D81B60
+ #bdbdbd
+
diff --git a/discoverer/src/main/res/values/strings.xml b/discoverer/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0585020
--- /dev/null
+++ b/discoverer/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Headless Wifi Discoverer
+
diff --git a/discoverer/src/main/res/values/styles.xml b/discoverer/src/main/res/values/styles.xml
new file mode 100644
index 0000000..c37ac38
--- /dev/null
+++ b/discoverer/src/main/res/values/styles.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
diff --git a/docs/headlesswifimanager/alltypes/index.html b/docs/headlesswifimanager/alltypes/index.html
new file mode 100644
index 0000000..e3e5445
--- /dev/null
+++ b/docs/headlesswifimanager/alltypes/index.html
@@ -0,0 +1,83 @@
+
+
+
+alltypes - headlesswifimanager
+
+
+
+
Internal class to transfer data about Wifi networks
+You can expect a List after a successful scanning and
+pass a WifiScanResult with a valid password field to advertiser to connect on.
Internal class to transfer data about Wifi networks
+You can expect a List after a successful scanning and
+pass a WifiScanResult with a valid password field to advertiser to connect on.
Internal class to transfer data about Wifi networks
+You can expect a List after a successful scanning and
+pass a WifiScanResult with a valid password field to advertiser to connect on.
Internal class to transfer data about Wifi networks
+You can expect a List after a successful scanning and
+pass a WifiScanResult with a valid password field to advertiser to connect on.
Internal class to transfer data about Wifi networks
+You can expect a List after a successful scanning and
+pass a WifiScanResult with a valid password field to advertiser to connect on.
+All Types
+
+
diff --git a/docs/headlesswifimanager/package-list b/docs/headlesswifimanager/package-list
new file mode 100644
index 0000000..95cdac5
--- /dev/null
+++ b/docs/headlesswifimanager/package-list
@@ -0,0 +1,7 @@
+$dokka.format:html
+$dokka.linkExtension:html
+
+com.wideverse.headlesswifimanager
+com.wideverse.headlesswifimanager.data
+com.wideverse.headlesswifimanager.helper
+com.wideverse.headlesswifimanager.interfaces
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 0000000..914be69
--- /dev/null
+++ b/docs/style.css
@@ -0,0 +1,283 @@
+@import url(https://fonts.googleapis.com/css?family=Open+Sans:300i,400,700);
+
+body, table {
+ padding:50px;
+ font:14px/1.5 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color:#555;
+ font-weight:300;
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 1440px;
+}
+
+.keyword {
+ color:black;
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ font-size:12px;
+}
+
+.symbol {
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ font-size:12px;
+}
+
+.identifier {
+ color: darkblue;
+ font-size:12px;
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color:#222;
+ margin:0 0 20px;
+}
+
+p, ul, ol, table, pre, dl {
+ margin:0 0 20px;
+}
+
+h1, h2, h3 {
+ line-height:1.1;
+}
+
+h1 {
+ font-size:28px;
+}
+
+h2 {
+ color:#393939;
+}
+
+h3, h4, h5, h6 {
+ color:#494949;
+}
+
+a {
+ color:#258aaf;
+ font-weight:400;
+ text-decoration:none;
+}
+
+a:hover {
+ color: inherit;
+ text-decoration:underline;
+}
+
+a small {
+ font-size:11px;
+ color:#555;
+ margin-top:-0.6em;
+ display:block;
+}
+
+.wrapper {
+ width:860px;
+ margin:0 auto;
+}
+
+blockquote {
+ border-left:1px solid #e5e5e5;
+ margin:0;
+ padding:0 0 0 20px;
+ font-style:italic;
+}
+
+code, pre {
+ font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
+ color:#333;
+ font-size:12px;
+}
+
+pre {
+ display: block;
+/*
+ padding:8px 8px;
+ background: #f8f8f8;
+ border-radius:5px;
+ border:1px solid #e5e5e5;
+*/
+ overflow-x: auto;
+}
+
+table {
+ width:100%;
+ border-collapse:collapse;
+}
+
+th, td {
+ text-align:left;
+ vertical-align: top;
+ padding:5px 10px;
+}
+
+dt {
+ color:#444;
+ font-weight:700;
+}
+
+th {
+ color:#444;
+}
+
+img {
+ max-width:100%;
+}
+
+header {
+ width:270px;
+ float:left;
+ position:fixed;
+}
+
+header ul {
+ list-style:none;
+ height:40px;
+
+ padding:0;
+
+ background: #eee;
+ background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
+ background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+ background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
+
+ border-radius:5px;
+ border:1px solid #d2d2d2;
+ box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
+ width:270px;
+}
+
+header li {
+ width:89px;
+ float:left;
+ border-right:1px solid #d2d2d2;
+ height:40px;
+}
+
+header ul a {
+ line-height:1;
+ font-size:11px;
+ color:#999;
+ display:block;
+ text-align:center;
+ padding-top:6px;
+ height:40px;
+}
+
+strong {
+ color:#222;
+ font-weight:700;
+}
+
+header ul li + li {
+ width:88px;
+ border-left:1px solid #fff;
+}
+
+header ul li + li + li {
+ border-right:none;
+ width:89px;
+}
+
+header ul a strong {
+ font-size:14px;
+ display:block;
+ color:#222;
+}
+
+section {
+ width:500px;
+ float:right;
+ padding-bottom:50px;
+}
+
+small {
+ font-size:11px;
+}
+
+hr {
+ border:0;
+ background:#e5e5e5;
+ height:1px;
+ margin:0 0 20px;
+}
+
+footer {
+ width:270px;
+ float:left;
+ position:fixed;
+ bottom:50px;
+}
+
+@media print, screen and (max-width: 960px) {
+
+ div.wrapper {
+ width:auto;
+ margin:0;
+ }
+
+ header, section, footer {
+ float:none;
+ position:static;
+ width:auto;
+ }
+
+ header {
+ padding-right:320px;
+ }
+
+ section {
+ border:1px solid #e5e5e5;
+ border-width:1px 0;
+ padding:20px 0;
+ margin:0 0 20px;
+ }
+
+ header a small {
+ display:inline;
+ }
+
+ header ul {
+ position:absolute;
+ right:50px;
+ top:52px;
+ }
+}
+
+@media print, screen and (max-width: 720px) {
+ body {
+ word-wrap:break-word;
+ }
+
+ header {
+ padding:0;
+ }
+
+ header ul, header p.view {
+ position:static;
+ }
+
+ pre, code {
+ word-wrap:normal;
+ }
+}
+
+@media print, screen and (max-width: 480px) {
+ body {
+ padding:15px;
+ }
+
+ header ul {
+ display:none;
+ }
+}
+
+@media print {
+ body {
+ padding:0.4in;
+ font-size:12pt;
+ color:#444;
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..4378952
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,37 @@
+#
+# Copyright 2019 Wideverse
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..fcb1361
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# Copyright 2019 Wideverse
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#Mon Feb 25 10:04:19 CET 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/headlesswifimanager/.gitignore b/headlesswifimanager/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/headlesswifimanager/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/headlesswifimanager/build.gradle b/headlesswifimanager/build.gradle
new file mode 100644
index 0000000..56dc5dc
--- /dev/null
+++ b/headlesswifimanager/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-android'
+apply plugin: 'org.jetbrains.dokka-android'
+
+
+android {
+ compileSdkVersion 28
+
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ dokka {
+ outputFormat = 'html'
+ outputDirectory = "$buildDir/javadoc"
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'com.google.code.gson:gson:2.8.5'
+
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.google.android.gms:play-services-nearby:16.0.0'
+ implementation 'com.thanosfisherman.wifiutils:wifiutils:1.4.0'
+
+
+}
+repositories {
+ mavenCentral()
+}
diff --git a/headlesswifimanager/proguard-rules.pro b/headlesswifimanager/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/headlesswifimanager/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/headlesswifimanager/src/main/AndroidManifest.xml b/headlesswifimanager/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b7f076f
--- /dev/null
+++ b/headlesswifimanager/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManager.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManager.kt
new file mode 100644
index 0000000..6d37311
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManager.kt
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager
+
+import android.content.Context
+import android.net.wifi.ScanResult
+import android.util.Log
+import com.wideverse.headlesswifimanager.data.GenericPayload
+import com.wideverse.headlesswifimanager.data.WifiConnectionAck
+import com.wideverse.headlesswifimanager.data.WifiCredentialsPayload
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import com.wideverse.headlesswifimanager.helper.ScanResultConverter
+import com.wideverse.headlesswifimanager.interfaces.AdvertisingCallback
+import com.wideverse.headlesswifimanager.interfaces.DiscoveryCallback
+import com.wideverse.headlesswifimanager.interfaces.NetworkCallback
+import com.google.android.gms.nearby.Nearby
+import com.google.android.gms.nearby.connection.*
+import com.google.gson.Gson
+import com.thanosfisherman.wifiutils.WifiUtils
+import java.lang.Exception
+import java.nio.charset.Charset
+
+
+class HeadlessWifiManager{
+
+ lateinit var advertisingCallback: AdvertisingCallback
+ lateinit var discoveryCallback: DiscoveryCallback
+ lateinit var networkCallback: NetworkCallback
+ private lateinit var applicationContext: Context
+ var appID: String
+
+ var currentConnectedEndpointId: String? = null
+
+ /**
+ * Main constructor for HeadlessWifiManager.
+ * @param applicationContext: Context.
+ * @param appID: String. Unique identifier for the application
+ */
+ constructor(applicationContext: Context, appID: String){
+ this.applicationContext = applicationContext
+ this.appID = appID
+ }
+
+ /**
+ * Starts advertising
+ * Called on the advertiser that awaits configuration
+ * @param callback: AdvertisingCallback. Callback used by the advertiser to manage
+ * all the steps of the configuration process
+ */
+ fun startAdvertising(callback: AdvertisingCallback) {
+ advertisingCallback = callback
+ val advertisingOptions = AdvertisingOptions.Builder().setStrategy(Strategy.P2P_STAR).build()
+
+ Nearby.getConnectionsClient(applicationContext)
+ .startAdvertising(
+ "adv", appID, advertisingNearbyCallback, advertisingOptions
+ )
+ .addOnSuccessListener {
+ // We're advertising!
+ Log.d("start advertising"," ok")
+ advertisingCallback.onAdvertisingStarted()
+
+ }
+ .addOnFailureListener {
+ advertisingCallback.onError(it)
+ }
+ }
+
+ /**
+ * Starts searching for advertisers
+ * Called on the phone to configure the advertiser
+ * @param callback: DiscoveryCallback. Used by the discoverer to manage all the steps of
+ * the process
+ */
+ fun startDiscovery(callback: DiscoveryCallback) {
+ discoveryCallback = callback
+ val discoveryOptions = DiscoveryOptions.Builder().setStrategy(Strategy.P2P_STAR).build()
+ Nearby.getConnectionsClient(applicationContext)
+ .startDiscovery(appID, object: EndpointDiscoveryCallback(){
+ override fun onEndpointFound(endpointId: String, info: DiscoveredEndpointInfo) {
+ discoveryCallback.onDeviceFound(endpointId, info.endpointName)
+ //connectToEndpoint(endpointId)
+
+ }
+
+ override fun onEndpointLost(p0: String) {
+ }
+ }, discoveryOptions)
+ .addOnSuccessListener {
+ Log.d("start discovery"," ok")
+ discoveryCallback.onDiscoveryStarted()
+
+ }
+ .addOnFailureListener {
+ Log.d("start discovery"," ex ${it.localizedMessage}")
+ discoveryCallback.onError(it)
+ }
+
+ }
+
+ /**
+ * Connects the discoverer to a specific advertiser.
+ * @param endpointId: String. Identifier of the advertiser requested for the connection
+ */
+ fun connectToEndpoint(endpointId: String){
+ Nearby.getConnectionsClient(applicationContext)
+ .requestConnection("discov", endpointId, discoveryNearbyCallback)
+ .addOnSuccessListener {
+ Log.d("connected"," ok")
+
+ }
+ .addOnFailureListener {
+ Log.d("connected",it.localizedMessage)
+
+ }
+ }
+
+ /**
+ * Sends WiFi credentials from Advertiser to Discoverer
+ * @param chosenOne: WifiScanResult. Wifi Network you want to connect to.
+ * @param callback: NetworkCallback. Callback used to manage the result of the communication
+ */
+ fun sendWifiCredentials(chosenOne: WifiScanResult, callback: NetworkCallback) {
+
+ networkCallback = callback
+
+ if (currentConnectedEndpointId != null) {
+
+ // Object wrapping WiFi credentials
+ val credentials = WifiCredentialsPayload().apply {
+ this.result = chosenOne
+ this.password = chosenOne.password
+ }
+
+ val genericPayload = GenericPayload().apply {
+ id = HeadlessWifiManagerConstants.PAYLOAD_CHOSEN_WIFI_ID
+ theChosenOne = credentials
+ }
+
+ // Byte array that contains a serialized object of WiFi credentials to send over Nearby
+ val payload = Gson().toJson(genericPayload).toByteArray()
+
+ Nearby.getConnectionsClient(applicationContext)
+ .sendPayload(
+ currentConnectedEndpointId!!,
+ Payload.fromBytes(payload)
+ )
+ } else {
+ // Endpoint ID was null
+ advertisingCallback.onError(Exception("Endpoint ID is null"))
+ }
+ }
+
+ /**
+ * Closes the current connection and stops discovery
+ */
+ fun abortProcedure() {
+ Nearby.getConnectionsClient(applicationContext).stopDiscovery()
+ Nearby.getConnectionsClient(applicationContext).stopAllEndpoints()
+ }
+
+ /**
+ * Connects the Hub to WiFi with the given credentials
+ *
+ */
+ private fun connectAdvertiserToWifi(payload: GenericPayload) {
+ // Call after successfully connection to WiFi
+ WifiUtils.withContext(applicationContext)
+ .connectWith(payload.theChosenOne!!.result!!.SSID, payload.theChosenOne!!.password)
+ .setTimeout(30000)
+ .onConnectionResult {
+ sendWifiConnectionAck(payload.theChosenOne!!.result!!.SSID, it)
+
+ if (it){
+ advertisingCallback.onSuccess()
+ }else {
+ advertisingCallback.onError(Exception("Unable to connect to WiFi"))
+ }
+ }.start()
+ }
+
+ private fun sendWifiConnectionAck(name: String, ackResult: Boolean) {
+ val genericPayload = GenericPayload().apply {
+ id = HeadlessWifiManagerConstants.PAYLOAD_WIFI_ACK_ID
+ connectionAck = WifiConnectionAck().apply {
+ SSID = name
+ result = ackResult
+ }
+ }
+
+ val payload = Gson().toJson(genericPayload).toByteArray()
+ Nearby.getConnectionsClient(applicationContext).sendPayload(
+ currentConnectedEndpointId!!,
+ Payload.fromBytes(payload)
+ )
+ }
+
+ /**
+ * Populates a list of WiFi ScanResults trough system's WifiManager
+ */
+ interface ScanResultListener {
+ fun onScanResultAvailable(results: List)
+ }
+
+ private fun getWifiScanResults(listener: ScanResultListener){
+ WifiUtils.withContext(applicationContext).scanWifi {
+ val filteredScanResults = it.filter { it.SSID != "" }
+ listener.onScanResultAvailable(filteredScanResults)
+
+ }.start()
+ }
+
+
+
+ /**
+ * NEARBY CALLBACKS
+ * Internal callbacks used by Nearby Connections API
+ */
+ private var advertisingNearbyCallback = object: ConnectionLifecycleCallback() {
+ override fun onConnectionResult(endpointId: String, result: ConnectionResolution) {
+ when (result.status.statusCode) {
+ ConnectionsStatusCodes.STATUS_OK -> {
+ currentConnectedEndpointId = endpointId
+
+ Log.d("connection result"," ok")
+ getWifiScanResults(object : ScanResultListener {
+ override fun onScanResultAvailable(scanResults: List) {
+ val genericPayload = GenericPayload().apply {
+ id = HeadlessWifiManagerConstants.PAYLOAD_SCAN_RESULTS_ID
+ results = ScanResultConverter.scanResultsToWifiScanResults(scanResults)
+ }
+
+ val payload = Gson().toJson(genericPayload).toByteArray()
+ Nearby.getConnectionsClient(applicationContext)
+ .sendPayload(
+ currentConnectedEndpointId!!,
+ Payload.fromBytes(payload)
+ )
+ }
+ })
+ }
+ ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> {
+ Log.d("connection result"," rejected")
+
+ }
+ else -> {
+ }
+ }
+ }
+
+ override fun onDisconnected(p0: String) {
+ }
+
+ override fun onConnectionInitiated(endpointId: String, connectionInfo: ConnectionInfo) {
+ Nearby.getConnectionsClient(applicationContext).acceptConnection(endpointId,
+ object : PayloadCallback() {
+ override fun onPayloadReceived(p0: String, payload: Payload) {
+ Log.d("onPayloadReceived"," ok")
+ val payloadBytes = payload.asBytes()
+
+ // Connect to Wifi here
+ if (payloadBytes != null) {
+ val genericPayload = Gson().fromJson(String(payloadBytes), GenericPayload::class.java)
+ when {
+ genericPayload.id == HeadlessWifiManagerConstants.PAYLOAD_CHOSEN_WIFI_ID -> connectAdvertiserToWifi(genericPayload)
+ }
+ } else {
+ // Payload was empty
+ advertisingCallback.onError(Exception("Nearby payload was empty"))
+ }
+
+ }
+
+ override fun onPayloadTransferUpdate(p0: String, p1: PayloadTransferUpdate) {
+ }
+
+ })
+ }
+ }
+
+ private fun reactToNetworkAck(payload: GenericPayload?) {
+ if (payload != null) {
+ val ack = payload.connectionAck
+ if (ack!!.result) {
+ networkCallback.onConnected(ack.SSID)
+ } else {
+ networkCallback.onError(Exception("Unable to connect to WiFi"))
+ }
+ } else {
+ networkCallback.onError(Exception("WiFi ack payload was null"))
+ }
+ }
+
+ private var discoveryNearbyCallback = object: ConnectionLifecycleCallback() {
+
+ override fun onConnectionResult(endpointId: String, result: ConnectionResolution) {
+ when (result.status.statusCode) {
+ ConnectionsStatusCodes.STATUS_OK -> {
+ Log.d("connection result"," ok")
+
+ currentConnectedEndpointId = endpointId
+ discoveryCallback.onConnected()
+ }
+ ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> {
+ Log.d("connection result"," rejected")
+
+ }
+ else -> {
+ }
+ }
+ }
+
+ override fun onDisconnected(p0: String) {
+
+ }
+
+ override fun onConnectionInitiated(endpointId: String, connectionInfo: ConnectionInfo) {
+ Nearby.getConnectionsClient(applicationContext).acceptConnection(endpointId,
+ object : PayloadCallback() {
+ override fun onPayloadReceived(p0: String, payload: Payload) {
+ val receivedString = String(payload.asBytes()!!, Charset.defaultCharset())
+ val genericPayload = Gson().fromJson(receivedString, GenericPayload::class.java)
+
+ when {
+ genericPayload.id == HeadlessWifiManagerConstants.PAYLOAD_SCAN_RESULTS_ID -> discoveryCallback.onNetworkListAvailable(genericPayload.results!!)
+ genericPayload.id == HeadlessWifiManagerConstants.PAYLOAD_WIFI_ACK_ID -> reactToNetworkAck(genericPayload)
+ }
+ }
+
+ override fun onPayloadTransferUpdate(p0: String, p1: PayloadTransferUpdate) {
+ }
+ })
+ }
+ }
+}
+
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManagerConstants.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManagerConstants.kt
new file mode 100644
index 0000000..881f87b
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/HeadlessWifiManagerConstants.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager
+
+
+class HeadlessWifiManagerConstants {
+ companion object {
+ const val PAYLOAD_SCAN_RESULTS_ID = 0
+ const val PAYLOAD_CHOSEN_WIFI_ID = 1
+ const val PAYLOAD_WIFI_ACK_ID = 2
+ }
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/GenericPayload.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/GenericPayload.kt
new file mode 100644
index 0000000..ba82fe0
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/GenericPayload.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.data
+
+
+class GenericPayload {
+
+ var id: Int = -1
+ var results: List? = null
+ var theChosenOne: WifiCredentialsPayload? = null
+ var connectionAck: WifiConnectionAck? = null
+}
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiConnectionAck.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiConnectionAck.kt
new file mode 100644
index 0000000..ee627a3
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiConnectionAck.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.data
+
+class WifiConnectionAck {
+ var SSID = ""
+ var result = false
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiCredentialsPayload.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiCredentialsPayload.kt
new file mode 100644
index 0000000..9c370f9
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiCredentialsPayload.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.data
+
+
+class WifiCredentialsPayload {
+ var result: WifiScanResult? = null
+ var password: String = ""
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiScanResult.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiScanResult.kt
new file mode 100644
index 0000000..e2ace28
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/data/WifiScanResult.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.data
+
+/**
+ * Internal class to transfer data about Wifi networks
+ * You can expect a List after a successful scanning and
+ * pass a WifiScanResult with a valid password field to advertiser to connect on.
+ *
+ */
+class WifiScanResult {
+ var SSID: String = ""
+ var level: Int = -1
+ var protected: Boolean = false
+ var password: String = ""
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/ScanResultConverter.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/ScanResultConverter.kt
new file mode 100644
index 0000000..046bd7e
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/ScanResultConverter.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.helper
+
+import android.net.wifi.ScanResult
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+
+class ScanResultConverter {
+ companion object {
+
+ fun scanResultsToWifiScanResults(scanResults: List): List {
+ return scanResults.map {
+ WifiScanResult().apply {
+ SSID = it.SSID
+ level = it.level
+ protected = WifiHelper.isNetworkProtected(it)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/WifiHelper.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/WifiHelper.kt
new file mode 100644
index 0000000..3e1b225
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/helper/WifiHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.helper
+
+import android.net.wifi.WifiManager
+import androidx.annotation.DrawableRes
+import android.net.wifi.ScanResult
+import com.wideverse.headlesswifimanager.R
+
+
+class WifiHelper {
+ companion object {
+
+
+ // HeadlessWifiManagerConstants used for different security types
+ val WPA2 = "WPA2"
+ val WPA = "WPA"
+ val WEP = "WEP"
+
+ /* For EAP Enterprise fields */
+ val WPA_EAP = "WPA-EAP"
+ val IEEE8021X = "IEEE8021X"
+
+
+ fun isNetworkProtected(scanResult: ScanResult): Boolean {
+ val cap = scanResult.capabilities
+ val securityModes = arrayOf(WEP, WPA, WPA2, WPA_EAP, IEEE8021X)
+ val num = securityModes.filter { it in cap }
+
+ return num.isNotEmpty()
+ }
+
+ /**
+ * Get a good looking drawable resource to quickly identify
+ * network's level signal and its security policy.
+ *
+ * @param level: RSSI of the scanned network
+ * @param protected: false if the network does not require a password
+ *
+ * @return a drawable to represent the network to the user
+ */
+ @DrawableRes
+ fun getDrawableFromRSSI(level: Int, protected: Boolean): Int {
+ val signal = WifiManager.calculateSignalLevel(level, 3)
+ if (protected) {
+ when (signal) {
+ 0 -> return R.drawable.ic_signal_wifi_1_bar_lock_black_24dp
+ 1 -> return R.drawable.ic_signal_wifi_2_bar_lock_black_24dp
+ 2 -> return R.drawable.ic_signal_wifi_3_bar_lock_black_24dp
+ 3 -> return R.drawable.ic_signal_wifi_4_bar_lock_black_24dp
+ }
+
+ return R.drawable.ic_signal_wifi_4_bar_lock_black_24dp
+ } else {
+ when (signal) {
+ 0 -> return R.drawable.ic_signal_wifi_1_bar_black_24dp
+ 1 -> return R.drawable.ic_signal_wifi_2_bar_black_24dp
+ 2 -> return R.drawable.ic_signal_wifi_3_bar_black_24dp
+ 3 -> return R.drawable.ic_signal_wifi_4_bar_black_24dp
+ }
+
+ return R.drawable.ic_signal_wifi_4_bar_black_24dp
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/AdvertisingCallback.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/AdvertisingCallback.kt
new file mode 100644
index 0000000..c5e853e
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/AdvertisingCallback.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.interfaces
+
+import java.lang.Exception
+
+interface AdvertisingCallback{
+ /**
+ * The procedure has been interrupted by an error
+ */
+ fun onError(e: Exception)
+
+ /**
+ * Successfully connected to WiFi
+ */
+ fun onSuccess() // TODO: consider passing wifi info here for user
+
+ /**
+ * Successfully started advertising on Nearby
+ */
+ fun onAdvertisingStarted()
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/DiscoveryCallback.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/DiscoveryCallback.kt
new file mode 100644
index 0000000..c3b282e
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/DiscoveryCallback.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.interfaces
+
+import com.wideverse.headlesswifimanager.data.WifiScanResult
+import java.lang.Exception
+
+interface DiscoveryCallback{
+ fun onError(e: Exception)
+ fun onDiscoveryStarted()
+ fun onConnected()
+ fun onNetworkListAvailable(results: List)
+ fun onDeviceFound(deviceId: String, deviceName: String)
+
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/NetworkCallback.kt b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/NetworkCallback.kt
new file mode 100644
index 0000000..d70b9d3
--- /dev/null
+++ b/headlesswifimanager/src/main/java/com/wideverse/headlesswifimanager/interfaces/NetworkCallback.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wideverse.headlesswifimanager.interfaces
+
+import java.lang.Exception
+
+interface NetworkCallback{
+ fun onError(e: Exception)
+ fun onConnected(SSID: String)
+}
\ No newline at end of file
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_0_bar_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_0_bar_black_24dp.xml
new file mode 100644
index 0000000..8957bf2
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_0_bar_black_24dp.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml
new file mode 100644
index 0000000..ffc5329
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_lock_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..5a265f7
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_1_bar_lock_black_24dp.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml
new file mode 100644
index 0000000..d8b0c9e
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_lock_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..f70e7da
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_2_bar_lock_black_24dp.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml
new file mode 100644
index 0000000..c9b288e
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_lock_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..7ee59ba
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_3_bar_lock_black_24dp.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml
new file mode 100644
index 0000000..c423bbf
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_lock_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_lock_black_24dp.xml
new file mode 100644
index 0000000..04d14ad
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_4_bar_lock_black_24dp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml
new file mode 100644
index 0000000..e2bbb29
--- /dev/null
+++ b/headlesswifimanager/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/headlesswifimanager/src/main/res/values/strings.xml b/headlesswifimanager/src/main/res/values/strings.xml
new file mode 100644
index 0000000..cd26591
--- /dev/null
+++ b/headlesswifimanager/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Headless Wifi Manager
+
diff --git a/images/demo01.png b/images/demo01.png
new file mode 100755
index 0000000..0c48d98
Binary files /dev/null and b/images/demo01.png differ
diff --git a/images/demo02.png b/images/demo02.png
new file mode 100755
index 0000000..5856783
Binary files /dev/null and b/images/demo02.png differ
diff --git a/images/demo03.png b/images/demo03.png
new file mode 100755
index 0000000..49e237f
Binary files /dev/null and b/images/demo03.png differ
diff --git a/images/demo04.png b/images/demo04.png
new file mode 100755
index 0000000..3b800ca
Binary files /dev/null and b/images/demo04.png differ
diff --git a/images/demo05.png b/images/demo05.png
new file mode 100755
index 0000000..7e9d5cc
Binary files /dev/null and b/images/demo05.png differ
diff --git a/images/demo06.png b/images/demo06.png
new file mode 100755
index 0000000..ead7e12
Binary files /dev/null and b/images/demo06.png differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..46b73cd
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2019 Wideverse
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+include ':advertiser', ':discoverer', ':headlesswifimanager'