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 @@ + + + + + + + + +