Copy/paste for Wi-Fi configurations

This commit is contained in:
Mygod
2019-04-04 17:59:21 +08:00
parent 1145b0f23b
commit 411f98c36f
6 changed files with 190 additions and 108 deletions

View File

@@ -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>()!! }

View File

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

View File

@@ -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)
}

View File

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

View 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>

View File

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