diff --git a/mobile/build.gradle b/mobile/build.gradle index 3b847ad9..1dbbb997 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -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) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt index 1ef06bef..0187c67a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiApDialogFragment.kt @@ -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= 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 { - 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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt index d7e361ec..661be180 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/configuration/WifiConfiguration.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/QRCodeDialog.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/QRCodeDialog.kt new file mode 100644 index 00000000..34344c83 --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/QRCodeDialog.kt @@ -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()) + } +} diff --git a/mobile/src/main/res/drawable/ic_social_share.xml b/mobile/src/main/res/drawable/ic_social_share.xml new file mode 100644 index 00000000..479da079 --- /dev/null +++ b/mobile/src/main/res/drawable/ic_social_share.xml @@ -0,0 +1,6 @@ + + + diff --git a/mobile/src/main/res/menu/toolbar_configuration.xml b/mobile/src/main/res/menu/toolbar_configuration.xml index faad44a1..1c34fc4a 100644 --- a/mobile/src/main/res/menu/toolbar_configuration.xml +++ b/mobile/src/main/res/menu/toolbar_configuration.xml @@ -11,4 +11,8 @@ android:icon="?attr/actionModePasteDrawable" android:title="@android:string/paste" app:showAsAction="ifRoom"/> + diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index feea1ce4..e9794e4a 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -126,6 +126,7 @@ 发生异常,详情请查看调试信息。 设置 WLAN 中继 + 使用 QR 码分享 Android 系统拒绝使用此配置。(详情参见日志) "网络名称" "安全性" diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml index 7cc70bcc..3a4f06a6 100644 --- a/mobile/src/main/res/values/dimen.xml +++ b/mobile/src/main/res/values/dimen.xml @@ -1,4 +1,5 @@ 56dp + 250dp diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 656a3228..c38548fe 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -134,6 +134,7 @@ Something went wrong, please check the debug information. Wi\u2011Fi configuration + Share via QR code Android system refuses such configuration. (see logcat) Network name Security