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.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.app.UiModeManager import android.app.UiModeManager
import android.content.ClipboardManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
@@ -69,6 +70,7 @@ class App : Application() {
} }
val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) } val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) }
val connectivity by lazy { getSystemService<ConnectivityManager>()!! } val connectivity by lazy { getSystemService<ConnectivityManager>()!! }
val clipboard by lazy { getSystemService<ClipboardManager>()!! }
val uiMode by lazy { getSystemService<UiModeManager>()!! } val uiMode by lazy { getSystemService<UiModeManager>()!! }
val wifi by lazy { getSystemService<WifiManager>()!! } val wifi by lazy { getSystemService<WifiManager>()!! }

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.net.wifi.configuration package be.mygod.vpnhotspot.net.wifi.configuration
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.ClipData
import android.content.DialogInterface import android.content.DialogInterface
import android.net.wifi.WifiConfiguration import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiConfiguration.AuthAlgorithm import android.net.wifi.WifiConfiguration.AuthAlgorithm
@@ -8,14 +9,19 @@ import android.os.Build
import android.os.Parcelable import android.os.Parcelable
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Base64
import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.view.isGone import androidx.core.view.isGone
import be.mygod.vpnhotspot.AlertDialogFragment import be.mygod.vpnhotspot.AlertDialogFragment
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R 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.parcel.Parcelize
import kotlinx.android.synthetic.main.dialog_wifi_ap.view.* import kotlinx.android.synthetic.main.dialog_wifi_ap.view.*
import java.lang.IllegalStateException 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. * 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 * 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 @Parcelize
data class Arg(val configuration: WifiConfiguration, data class Arg(val configuration: WifiConfiguration,
val readOnly: Boolean = false, val readOnly: Boolean = false,
@@ -59,29 +71,29 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
} }
private lateinit var dialogView: View private lateinit var dialogView: View
override val ret: Arg? get() { private lateinit var bandOptions: MutableList<BandOption>
return Arg(WifiConfiguration().apply { private var started = false
SSID = dialogView.ssid.text.toString() override val ret get() = Arg(WifiConfiguration().apply {
allowedKeyManagement.set( SSID = dialogView.ssid.text.toString()
if (arg.p2pMode) WifiConfiguration.KeyMgmt.WPA_PSK else dialogView.security.selectedItemPosition) allowedKeyManagement.set(
allowedAuthAlgorithms.set(AuthAlgorithm.OPEN) if (arg.p2pMode) WifiConfiguration.KeyMgmt.WPA_PSK else dialogView.security.selectedItemPosition)
if (dialogView.password.length() != 0) preSharedKey = dialogView.password.text.toString() allowedAuthAlgorithms.set(AuthAlgorithm.OPEN)
if (Build.VERSION.SDK_INT >= 23) { if (dialogView.password.length() != 0) preSharedKey = dialogView.password.text.toString()
val bandOption = dialogView.band.selectedItem as BandOption if (Build.VERSION.SDK_INT >= 23) {
apBand = bandOption.apBand val bandOption = dialogView.band.selectedItem as BandOption
apChannel = bandOption.apChannel apBand = bandOption.apBand
} apChannel = bandOption.apChannel
}) }
} })
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
val activity = requireActivity() val activity = requireActivity()
dialogView = activity.layoutInflater.inflate(R.layout.dialog_wifi_ap, null) dialogView = activity.layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
setView(dialogView) setView(dialogView)
setTitle(R.string.configuration_view)
if (!arg.readOnly) setPositiveButton(R.string.wifi_save, listener) if (!arg.readOnly) setPositiveButton(R.string.wifi_save, listener)
setNegativeButton(R.string.donations__button_close, null) 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.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment)
if (arg.p2pMode) dialogView.security_wrapper.isGone = true else dialogView.security.apply { if (arg.p2pMode) dialogView.security_wrapper.isGone = true else dialogView.security.apply {
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, 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 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 (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment)
if (Build.VERSION.SDK_INT >= 23) dialogView.band.apply { if (Build.VERSION.SDK_INT >= 23) {
val options = mutableListOf<BandOption>().apply { bandOptions = mutableListOf<BandOption>().apply {
if (arg.p2pMode) add(BandOption.BandAny) else { if (arg.p2pMode) add(BandOption.BandAny) else {
if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny) if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny)
add(BandOption.Band2GHz) add(BandOption.Band2GHz)
add(BandOption.Band5GHz) 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) 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 } else dialogView.band_wrapper.isGone = true
populateFromConfiguration(arg.configuration)
} }
override fun onResume() { private fun populateFromConfiguration(configuration: WifiConfiguration) {
super.onResume() 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() if (!arg.readOnly) validate()
} }
@@ -131,6 +154,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
* This function is reached only if not arg.readOnly. * This function is reached only if not arg.readOnly.
*/ */
private fun validate() { private fun validate() {
if (!started) return
val ssidValid = dialogView.ssid.length() != 0 && val ssidValid = dialogView.ssid.length() != 0 &&
Charset.forName("UTF-8").encode(dialogView.ssid.text.toString()).limit() <= 32 Charset.forName("UTF-8").encode(dialogView.ssid.text.toString()).limit() <= 32
val passwordValid = when (dialogView.security.selectedItemPosition) { 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 onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { } override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun afterTextChanged(editable: Editable) = validate() 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.content.*
import android.os.Build import android.os.Build
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.SpannableStringBuilder 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() { fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent) override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
} }

View File

@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@@ -11,85 +16,97 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. 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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="300sp" android:layout_width="300sp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fadeScrollbars="false"> android:orientation="vertical">
<LinearLayout
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants" android:paddingLeft="20dp"
android:focusableInTouchMode="true" android:paddingRight="20dp"
style="@style/wifi_item"> app:title="@string/configuration_view"/>
<com.google.android.material.textfield.TextInputLayout <ScrollView
android:id="@+id/ssid_wrapper" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:fadeScrollbars="false">
android:layout_marginTop="8dip">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ssid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:hint="@string/wifi_ssid"
android:inputType="textMultiLine|textNoSuggestions"
android:maxLength="32" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout <LinearLayout
android:id="@+id/security_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dip" android:descendantFocusability="beforeDescendants"
android:orientation="vertical"> android:focusableInTouchMode="true"
<TextView style="@style/wifi_item">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ssid_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ssid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:hint="@string/wifi_ssid"
android:inputType="textMultiLine|textNoSuggestions"
android:maxLength="32" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/security_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/wifi_item_label" android:layout_marginTop="8dip"
android:text="@string/wifi_security" /> android:orientation="vertical">
<Spinner <TextView
android:id="@+id/security" android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_security" />
<Spinner
android:id="@+id/security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_content"
android:prompt="@string/wifi_security" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/wifi_item_content" android:layout_marginTop="8dip"
android:prompt="@string/wifi_security" /> app:passwordToggleEnabled="true"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:singleLine="true"
android:hint="@string/wifi_password"
android:inputType="textPassword"
android:typeface="monospace"
android:maxLength="63"
android:imeOptions="flagForceAscii" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/band_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_hotspot_ap_band_title" />
<Spinner
android:id="@+id/band"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_content"
android:prompt="@string/wifi_hotspot_ap_band_title" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<com.google.android.material.textfield.TextInputLayout </ScrollView>
android:id="@+id/password_wrapper" </LinearLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
app:passwordToggleEnabled="true"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_edit_content"
android:singleLine="true"
android:hint="@string/wifi_password"
android:inputType="textPassword"
android:typeface="monospace"
android:maxLength="63"
android:imeOptions="flagForceAscii" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/band_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_hotspot_ap_band_title" />
<Spinner
android:id="@+id/band"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_content"
android:prompt="@string/wifi_hotspot_ap_band_title" />
</LinearLayout>
</LinearLayout>
</ScrollView>

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 --> <!-- https://android.googlesource.com/platform/packages/apps/Settings/+/7efcc35/res/values/styles.xml -->
<style name="wifi_item"> <style name="wifi_item">
<item name="android:layout_marginTop">8dip</item>
<item name="android:layout_marginStart">8dip</item> <item name="android:layout_marginStart">8dip</item>
<item name="android:layout_marginEnd">8dip</item> <item name="android:layout_marginEnd">8dip</item>
<item name="android:paddingStart">8dip</item> <item name="android:paddingStart">8dip</item>