Copy/paste for Wi-Fi configurations
This commit is contained in:
@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.app.UiModeManager
|
||||
import android.content.ClipboardManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.wifi.WifiManager
|
||||
@@ -69,6 +70,7 @@ class App : Application() {
|
||||
}
|
||||
val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) }
|
||||
val connectivity by lazy { getSystemService<ConnectivityManager>()!! }
|
||||
val clipboard by lazy { getSystemService<ClipboardManager>()!! }
|
||||
val uiMode by lazy { getSystemService<UiModeManager>()!! }
|
||||
val wifi by lazy { getSystemService<WifiManager>()!! }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package be.mygod.vpnhotspot.net.wifi.configuration
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.ClipData
|
||||
import android.content.DialogInterface
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.WifiConfiguration.AuthAlgorithm
|
||||
@@ -8,14 +9,19 @@ import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Base64
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
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.toByteArray
|
||||
import be.mygod.vpnhotspot.util.toParcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.dialog_wifi_ap.view.*
|
||||
import java.lang.IllegalStateException
|
||||
@@ -27,7 +33,13 @@ import java.nio.charset.Charset
|
||||
* This dialog has been deprecated in API 28, but we are still using it since it works better for our purposes.
|
||||
* Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea
|
||||
*/
|
||||
class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), TextWatcher {
|
||||
class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), TextWatcher,
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
companion object {
|
||||
private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP
|
||||
private val channels by lazy { (1..165).map { BandOption.Channel(it) } }
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Arg(val configuration: WifiConfiguration,
|
||||
val readOnly: Boolean = false,
|
||||
@@ -59,8 +71,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
}
|
||||
|
||||
private lateinit var dialogView: View
|
||||
override val ret: Arg? get() {
|
||||
return Arg(WifiConfiguration().apply {
|
||||
private lateinit var bandOptions: MutableList<BandOption>
|
||||
private var started = false
|
||||
override val ret get() = Arg(WifiConfiguration().apply {
|
||||
SSID = dialogView.ssid.text.toString()
|
||||
allowedKeyManagement.set(
|
||||
if (arg.p2pMode) WifiConfiguration.KeyMgmt.WPA_PSK else dialogView.security.selectedItemPosition)
|
||||
@@ -72,16 +85,15 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
apChannel = bandOption.apChannel
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||
val activity = requireActivity()
|
||||
dialogView = activity.layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
|
||||
setView(dialogView)
|
||||
setTitle(R.string.configuration_view)
|
||||
if (!arg.readOnly) setPositiveButton(R.string.wifi_save, listener)
|
||||
setNegativeButton(R.string.donations__button_close, null)
|
||||
dialogView.ssid.setText(arg.configuration.SSID)
|
||||
dialogView.toolbar.inflateMenu(R.menu.toolbar_configuration)
|
||||
dialogView.toolbar.setOnMenuItemClickListener(this@WifiApDialogFragment)
|
||||
if (!arg.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment)
|
||||
if (arg.p2pMode) dialogView.security_wrapper.isGone = true else dialogView.security.apply {
|
||||
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0,
|
||||
@@ -95,35 +107,46 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
dialogView.password_wrapper.isGone = position == WifiConfiguration.KeyMgmt.NONE
|
||||
}
|
||||
}
|
||||
val selected = arg.configuration.allowedKeyManagement.nextSetBit(0)
|
||||
check(selected >= 0) { "No key management selected" }
|
||||
check(arg.configuration.allowedKeyManagement.nextSetBit(selected + 1) < 0) {
|
||||
"More than 1 key managements supplied"
|
||||
}
|
||||
setSelection(selected)
|
||||
}
|
||||
dialogView.password.setText(arg.configuration.preSharedKey)
|
||||
if (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment)
|
||||
if (Build.VERSION.SDK_INT >= 23) dialogView.band.apply {
|
||||
val options = mutableListOf<BandOption>().apply {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
bandOptions = mutableListOf<BandOption>().apply {
|
||||
if (arg.p2pMode) add(BandOption.BandAny) else {
|
||||
if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny)
|
||||
add(BandOption.Band2GHz)
|
||||
add(BandOption.Band5GHz)
|
||||
}
|
||||
addAll((1..165).map { BandOption.Channel(it) })
|
||||
addAll(channels)
|
||||
}
|
||||
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, options).apply {
|
||||
dialogView.band.adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0,
|
||||
bandOptions).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
setSelection(if (arg.configuration.apChannel in 1..165) {
|
||||
options.indexOfFirst { it.apChannel == arg.configuration.apChannel }
|
||||
} else options.indexOfFirst { it.apBand == arg.configuration.apBand })
|
||||
} else dialogView.band_wrapper.isGone = true
|
||||
populateFromConfiguration(arg.configuration)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
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)
|
||||
}
|
||||
dialogView.password.setText(configuration.preSharedKey)
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
dialogView.band.setSelection(if (configuration.apChannel in 1..165) {
|
||||
bandOptions.indexOfFirst { it.apChannel == configuration.apChannel }
|
||||
} else bandOptions.indexOfFirst { it.apBand == configuration.apBand })
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
started = true
|
||||
if (!arg.readOnly) validate()
|
||||
}
|
||||
|
||||
@@ -131,6 +154,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
* This function is reached only if not arg.readOnly.
|
||||
*/
|
||||
private fun validate() {
|
||||
if (!started) return
|
||||
val ssidValid = dialogView.ssid.length() != 0 &&
|
||||
Charset.forName("UTF-8").encode(dialogView.ssid.text.toString()).limit() <= 32
|
||||
val passwordValid = when (dialogView.security.selectedItemPosition) {
|
||||
@@ -146,4 +170,19 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
|
||||
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())
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
@@ -42,6 +43,16 @@ fun <T> useParcel(block: (Parcel) -> T) = Parcel.obtain().run {
|
||||
}
|
||||
}
|
||||
|
||||
fun Parcelable.toByteArray(parcelableFlags: Int = 0) = useParcel { p ->
|
||||
p.writeParcelable(this, parcelableFlags)
|
||||
p.marshall()
|
||||
}
|
||||
fun <T : Parcelable> ByteArray.toParcelable() = useParcel { p ->
|
||||
p.unmarshall(this, 0, size)
|
||||
p.setDataPosition(0)
|
||||
p.readParcelable<T>(javaClass.classLoader)
|
||||
}
|
||||
|
||||
fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/6b4a31c/res/layout/wifi_ap_dialog.xml -->
|
||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||
<!--
|
||||
Based on:
|
||||
* https://github.com/material-components/material-components-android/blob/da6096bb8df2ac5b0cabeaa7960501d4083e4ea9/lib/java/com/google/android/material/dialog/res/layout/mtrl_alert_dialog_title.xml
|
||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/6b4a31c/res/layout/wifi_ap_dialog.xml
|
||||
-->
|
||||
<!--
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
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
|
||||
@@ -11,10 +16,22 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="300sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
app:title="@string/configuration_view"/>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -25,8 +42,7 @@
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ssid_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dip">
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/ssid"
|
||||
android:layout_width="match_parent"
|
||||
@@ -93,3 +109,4 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
14
mobile/src/main/res/menu/toolbar_configuration.xml
Normal file
14
mobile/src/main/res/menu/toolbar_configuration.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@android:id/copy"
|
||||
android:alphabeticShortcut="c"
|
||||
android:icon="?attr/actionModeCopyDrawable"
|
||||
android:title="@android:string/copy"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item android:id="@android:id/paste"
|
||||
android:alphabeticShortcut="v"
|
||||
android:icon="?attr/actionModePasteDrawable"
|
||||
android:title="@android:string/paste"
|
||||
app:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
<!-- https://android.googlesource.com/platform/packages/apps/Settings/+/7efcc35/res/values/styles.xml -->
|
||||
<style name="wifi_item">
|
||||
<item name="android:layout_marginTop">8dip</item>
|
||||
<item name="android:layout_marginStart">8dip</item>
|
||||
<item name="android:layout_marginEnd">8dip</item>
|
||||
<item name="android:paddingStart">8dip</item>
|
||||
|
||||
Reference in New Issue
Block a user