Support sharing via QR code

Another popular format is barcode but unfortunately I cannot seem to find documentations of that anywhere. Feel free to send me.
This commit is contained in:
Mygod
2019-04-04 19:17:00 +08:00
parent a1dd7107e1
commit 6225367e42
9 changed files with 89 additions and 19 deletions

View File

@@ -87,6 +87,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.linkedin.dexmaker:dexmaker:2.25.0'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'net.glxn.qrgen:android:2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
for (dep in aux) {

View File

@@ -20,6 +20,7 @@ import androidx.core.view.isGone
import be.mygod.vpnhotspot.AlertDialogFragment
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.util.QRCodeDialog
import be.mygod.vpnhotspot.util.toByteArray
import be.mygod.vpnhotspot.util.toParcelable
import kotlinx.android.parcel.Parcelize
@@ -128,14 +129,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
private fun populateFromConfiguration(configuration: WifiConfiguration) {
dialogView.ssid.setText(configuration.SSID)
if (!arg.p2pMode) {
val selected = configuration.allowedKeyManagement.nextSetBit(0)
check(selected >= 0) { "No key management selected" }
check(configuration.allowedKeyManagement.nextSetBit(selected + 1) < 0) {
"More than 1 key managements supplied"
}
dialogView.security.setSelection(selected)
}
if (!arg.p2pMode) dialogView.security.setSelection(configuration.apKeyManagement)
dialogView.password.setText(configuration.preSharedKey)
if (Build.VERSION.SDK_INT >= 23) {
dialogView.band.setSelection(if (configuration.apChannel in 1..165) {
@@ -171,18 +165,25 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun afterTextChanged(editable: Editable) = validate()
override fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) {
android.R.id.copy -> {
app.clipboard.primaryClip =
ClipData.newPlainText(null, Base64.encodeToString(ret.configuration.toByteArray(), BASE64_FLAGS))
true
}
android.R.id.paste -> {
app.clipboard.primaryClip?.getItemAt(0)?.text?.let {
populateFromConfiguration(Base64.decode(it.toString(), BASE64_FLAGS).toParcelable())
override fun onMenuItemClick(item: MenuItem?): Boolean {
return when (item?.itemId) {
android.R.id.copy -> {
app.clipboard.primaryClip =
ClipData.newPlainText(null, Base64.encodeToString(ret.configuration.toByteArray(), BASE64_FLAGS))
true
}
true
android.R.id.paste -> {
app.clipboard.primaryClip?.getItemAt(0)?.text?.let {
populateFromConfiguration(Base64.decode(it.toString(), BASE64_FLAGS).toParcelable())
}
true
}
R.id.share_qr -> {
QRCodeDialog().withArg(ret.configuration.toQRString())
.show(fragmentManager ?: return false, "QRCodeDialog")
true
}
else -> false
}
else -> false
}
}

View File

@@ -65,6 +65,35 @@ fun channelToFrequency(channel: Int) = when (channel) {
else -> throw IllegalArgumentException("Invalid channel $channel")
}
val WifiConfiguration.apKeyManagement get() = allowedKeyManagement.nextSetBit(0).also { selected ->
check(selected >= 0) { "No key management selected" }
check(allowedKeyManagement.nextSetBit(selected + 1) < 0) { "More than 1 key managements supplied" }
}
private val qrSanitizer = Regex("([\\\\\":;,])")
/**
* Documentation: https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
*/
fun WifiConfiguration.toQRString() = StringBuilder("WIFI:").apply {
fun String.sanitize() = qrSanitizer.replace(this) { "\\${it.groupValues[1]}" }
var password = true
when (apKeyManagement) {
WifiConfiguration.KeyMgmt.NONE -> password = false
WifiConfiguration.KeyMgmt.WPA_PSK, WifiConfiguration.KeyMgmt.WPA_EAP, WPA2_PSK -> append("T:WPA;")
else -> throw IllegalArgumentException("Unsupported authentication type")
}
append("S:")
append(SSID.sanitize())
append(';')
if (password) {
append("P:")
append(preSharedKey.sanitize())
append(';')
}
if (hiddenSSID) append("H:true;")
append(';')
}.toString()
/**
* Based on:
* https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88

View File

@@ -0,0 +1,26 @@
package be.mygod.vpnhotspot.util
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import be.mygod.vpnhotspot.R
import net.glxn.qrgen.android.QRCode
class QRCodeDialog : DialogFragment() {
companion object {
private const val KEY_ARG = "arg"
}
fun withArg(arg: String) = apply { arguments = bundleOf(KEY_ARG to arg) }
private val arg get() = arguments?.getString(KEY_ARG)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
ImageView(context).apply {
val size = resources.getDimensionPixelSize(R.dimen.qr_code_size)
layoutParams = ViewGroup.LayoutParams(size, size)
setImageBitmap((QRCode.from(arg).withSize(size, size) as QRCode).bitmap())
}
}

View File

@@ -0,0 +1,6 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/colorControlNormal">
<path android:fillColor="#FF000000" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -11,4 +11,8 @@
android:icon="?attr/actionModePasteDrawable"
android:title="@android:string/paste"
app:showAsAction="ifRoom"/>
<item android:id="@+id/share_qr"
android:icon="@drawable/ic_social_share"
android:title="@string/configuration_share"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -126,6 +126,7 @@
<string name="noisy_su_failure">发生异常,详情请查看调试信息。</string>
<string name="configuration_view">设置 WLAN 中继</string>
<string name="configuration_share">使用 QR 码分享</string>
<string name="configuration_rejected">Android 系统拒绝使用此配置。(详情参见日志)</string>
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
<string name="wifi_security" msgid="6603611185592956936">"安全性"</string>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="listitem_manage_tether_padding_start">56dp</dimen>
<dimen name="qr_code_size">250dp</dimen>
</resources>

View File

@@ -134,6 +134,7 @@
<string name="noisy_su_failure">Something went wrong, please check the debug information.</string>
<string name="configuration_view">Wi\u2011Fi configuration</string>
<string name="configuration_share">Share via QR code</string>
<string name="configuration_rejected">Android system refuses such configuration. (see logcat)</string>
<string name="wifi_ssid">Network name</string>
<string name="wifi_security">Security</string>