Huge refactor for better maintainability
This commit is contained in:
11
mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt
Normal file
11
mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.databinding.BaseObservable
|
||||
|
||||
abstract class Data : BaseObservable() {
|
||||
abstract val icon: Int
|
||||
abstract val title: CharSequence
|
||||
abstract val text: CharSequence
|
||||
abstract val active: Boolean
|
||||
abstract val selectable: Boolean
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.content.Intent
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import be.mygod.vpnhotspot.TetheringService
|
||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||
import be.mygod.vpnhotspot.net.TetherType
|
||||
import be.mygod.vpnhotspot.util.formatAddresses
|
||||
import java.util.*
|
||||
|
||||
class InterfaceManager(private val parent: TetheringFragment, val iface: String) : Manager() {
|
||||
class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener {
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
lateinit var iface: String
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val context = itemView.context
|
||||
val data = binding.data as Data
|
||||
if (data.active) context.startService(Intent(context, TetheringService::class.java)
|
||||
.putExtra(TetheringService.EXTRA_REMOVE_INTERFACE, iface))
|
||||
else ContextCompat.startForegroundService(context, Intent(context, TetheringService::class.java)
|
||||
.putExtra(TetheringService.EXTRA_ADD_INTERFACE, iface))
|
||||
}
|
||||
}
|
||||
private inner class Data : be.mygod.vpnhotspot.manage.Data() {
|
||||
override val icon get() = TetherType.ofInterface(iface).icon
|
||||
override val title get() = iface
|
||||
override val text get() = addresses
|
||||
override val active get() = parent.tetheringBinder?.isActive(iface) == true
|
||||
override val selectable get() = true
|
||||
}
|
||||
|
||||
val addresses = parent.ifaceLookup[iface]?.formatAddresses() ?: ""
|
||||
override val type get() = VIEW_TYPE_INTERFACE
|
||||
private val data = Data()
|
||||
|
||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||
viewHolder as ViewHolder
|
||||
viewHolder.binding.data = data
|
||||
viewHolder.iface = iface
|
||||
}
|
||||
|
||||
override fun isSameItemAs(other: Manager) = when (other) {
|
||||
is InterfaceManager -> iface == other.iface
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
other as InterfaceManager
|
||||
if (iface != other.iface) return false
|
||||
if (addresses != other.addresses) return false
|
||||
return true
|
||||
}
|
||||
override fun hashCode(): Int = Objects.hash(iface, addresses)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.TargetApi
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.IBinder
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||
import be.mygod.vpnhotspot.net.TetherType
|
||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||
import be.mygod.vpnhotspot.util.formatAddresses
|
||||
import java.net.NetworkInterface
|
||||
|
||||
@TargetApi(26)
|
||||
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
||||
class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener {
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
lateinit var manager: LocalOnlyHotspotManager
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val binder = manager.binder
|
||||
if (binder?.iface != null) binder.stop() else {
|
||||
val context = manager.parent.requireContext()
|
||||
if (context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
context.startForegroundService(Intent(context, LocalOnlyHotspotService::class.java))
|
||||
} else {
|
||||
manager.parent.requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
|
||||
TetheringFragment.START_LOCAL_ONLY_HOTSPOT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private inner class Data : be.mygod.vpnhotspot.manage.Data() {
|
||||
private val lookup: Map<String, NetworkInterface> get() = parent.ifaceLookup
|
||||
|
||||
override val icon: Int get() {
|
||||
val iface = binder?.iface ?: return TetherType.WIFI.icon
|
||||
return TetherType.ofInterface(iface).icon
|
||||
}
|
||||
override val title: CharSequence get() {
|
||||
val configuration = binder?.configuration ?: return parent.getString(R.string.tethering_temp_hotspot)
|
||||
return "${configuration.SSID} - ${configuration.preSharedKey}"
|
||||
}
|
||||
override val text: CharSequence get() {
|
||||
return lookup[binder?.iface ?: return ""]?.formatAddresses() ?: ""
|
||||
}
|
||||
override val active get() = binder?.iface != null
|
||||
override val selectable get() = active
|
||||
}
|
||||
|
||||
init {
|
||||
ServiceForegroundConnector(parent, this, LocalOnlyHotspotService::class)
|
||||
}
|
||||
|
||||
override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT
|
||||
private val data = Data()
|
||||
private var binder: LocalOnlyHotspotService.Binder? = null
|
||||
|
||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||
viewHolder as ViewHolder
|
||||
viewHolder.binding.data = data
|
||||
viewHolder.manager = this
|
||||
}
|
||||
|
||||
fun update() = data.notifyChange()
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
binder = service as LocalOnlyHotspotService.Binder
|
||||
service.manager = this
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
binder?.manager = null
|
||||
binder = null
|
||||
}
|
||||
}
|
||||
24
mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt
Normal file
24
mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
|
||||
object ManageBar : Manager() {
|
||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
||||
init {
|
||||
view.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) = try {
|
||||
itemView.context.startActivity(Intent()
|
||||
.setClassName("com.android.settings", "com.android.settings.Settings\$TetherSettingsActivity"))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
itemView.context.startActivity(Intent()
|
||||
.setClassName("com.android.settings", "com.android.settings.TetherSettings"))
|
||||
}
|
||||
}
|
||||
|
||||
override val type: Int get() = VIEW_TYPE_MANAGE
|
||||
}
|
||||
46
mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt
Normal file
46
mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.support.v7.util.DiffUtil
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
|
||||
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
||||
|
||||
abstract class Manager {
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<Manager>() {
|
||||
const val VIEW_TYPE_INTERFACE = 0
|
||||
const val VIEW_TYPE_MANAGE = 1
|
||||
const val VIEW_TYPE_WIFI = 2
|
||||
const val VIEW_TYPE_USB = 3
|
||||
const val VIEW_TYPE_BLUETOOTH = 4
|
||||
const val VIEW_TYPE_WIFI_LEGACY = 5
|
||||
const val VIEW_TYPE_LOCAL_ONLY_HOTSPOT = 6
|
||||
const val VIEW_TYPE_REPEATER = 7
|
||||
|
||||
override fun areItemsTheSame(oldItem: Manager, newItem: Manager) = oldItem.isSameItemAs(newItem)
|
||||
override fun areContentsTheSame(oldItem: Manager, newItem: Manager) = oldItem == newItem
|
||||
|
||||
fun createViewHolder(inflater: LayoutInflater, parent: ViewGroup, type: Int): RecyclerView.ViewHolder = when (type) {
|
||||
VIEW_TYPE_INTERFACE ->
|
||||
InterfaceManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false))
|
||||
VIEW_TYPE_MANAGE -> ManageBar.ViewHolder(inflater.inflate(R.layout.listitem_manage, parent, false))
|
||||
VIEW_TYPE_WIFI, VIEW_TYPE_USB, VIEW_TYPE_BLUETOOTH, VIEW_TYPE_WIFI_LEGACY ->
|
||||
TetherManager.ViewHolder(ListitemManageTetherBinding.inflate(inflater, parent, false))
|
||||
VIEW_TYPE_LOCAL_ONLY_HOTSPOT -> @TargetApi(26) {
|
||||
LocalOnlyHotspotManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false))
|
||||
}
|
||||
VIEW_TYPE_REPEATER -> RepeaterManager.ViewHolder(ListitemRepeaterBinding.inflate(inflater, parent, false))
|
||||
else -> throw IllegalArgumentException("Invalid view type")
|
||||
}
|
||||
}
|
||||
|
||||
abstract val type: Int
|
||||
|
||||
open fun bindTo(viewHolder: RecyclerView.ViewHolder) { }
|
||||
|
||||
open fun isSameItemAs(other: Manager) = javaClass == other.javaClass
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.databinding.BaseObservable
|
||||
import android.databinding.Bindable
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.net.wifi.p2p.WifiP2pGroup
|
||||
import android.os.IBinder
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.app.AppCompatDialog
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import be.mygod.vpnhotspot.App
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.BR
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.RepeaterService
|
||||
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
||||
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
|
||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pDialog
|
||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||
import be.mygod.vpnhotspot.util.formatAddresses
|
||||
import java.net.NetworkInterface
|
||||
import java.net.SocketException
|
||||
|
||||
class RepeaterManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
||||
class ViewHolder(val binding: ListitemRepeaterBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
inner class Data : BaseObservable() {
|
||||
val switchEnabled: Boolean
|
||||
@Bindable get() = when (binder?.service?.status) {
|
||||
RepeaterService.Status.IDLE, RepeaterService.Status.ACTIVE -> true
|
||||
else -> false
|
||||
}
|
||||
val serviceStarted: Boolean
|
||||
@Bindable get() = when (binder?.service?.status) {
|
||||
RepeaterService.Status.STARTING, RepeaterService.Status.ACTIVE -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val ssid @Bindable get() = binder?.service?.group?.networkName ?: ""
|
||||
val addresses: CharSequence @Bindable get() {
|
||||
return try {
|
||||
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
|
||||
} catch (e: SocketException) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
}
|
||||
var oc: CharSequence
|
||||
@Bindable get() {
|
||||
val oc = app.operatingChannel
|
||||
return if (oc in 1..165) oc.toString() else ""
|
||||
}
|
||||
set(value) = app.pref.edit().putString(App.KEY_OPERATING_CHANNEL, value.toString()).apply()
|
||||
|
||||
fun onStatusChanged() {
|
||||
notifyPropertyChanged(BR.switchEnabled)
|
||||
notifyPropertyChanged(BR.serviceStarted)
|
||||
notifyPropertyChanged(BR.addresses)
|
||||
}
|
||||
fun onGroupChanged(group: WifiP2pGroup? = null) {
|
||||
notifyPropertyChanged(BR.ssid)
|
||||
p2pInterface = group?.`interface`
|
||||
notifyPropertyChanged(BR.addresses)
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
val binder = binder
|
||||
when (binder?.service?.status) {
|
||||
RepeaterService.Status.IDLE -> {
|
||||
val context = parent.requireContext()
|
||||
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
|
||||
}
|
||||
RepeaterService.Status.ACTIVE -> binder.shutdown()
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
fun wps() {
|
||||
if (binder?.active != true) return
|
||||
val dialog = AlertDialog.Builder(parent.requireContext())
|
||||
.setTitle(R.string.repeater_wps_dialog_title)
|
||||
.setView(R.layout.dialog_wps)
|
||||
.setPositiveButton(android.R.string.ok, { dialog, _ -> binder?.startWps((dialog as AppCompatDialog)
|
||||
.findViewById<EditText>(android.R.id.edit)!!.text.toString()) })
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.repeater_wps_dialog_pbc, { _, _ -> binder?.startWps(null) })
|
||||
.create()
|
||||
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun editConfigurations() {
|
||||
val binder = binder
|
||||
val group = binder?.service?.group
|
||||
val ssid = group?.networkName
|
||||
val context = parent.requireContext()
|
||||
if (ssid != null) {
|
||||
val wifi = WifiConfiguration()
|
||||
val conf = P2pSupplicantConfiguration()
|
||||
wifi.SSID = ssid
|
||||
wifi.preSharedKey = group.passphrase
|
||||
if (wifi.preSharedKey == null) wifi.preSharedKey = conf.readPsk()
|
||||
if (wifi.preSharedKey != null) {
|
||||
var dialog: WifiP2pDialog? = null
|
||||
dialog = WifiP2pDialog(context, DialogInterface.OnClickListener { _, which ->
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> when (conf.update(dialog!!.config!!)) {
|
||||
true -> App.app.handler.postDelayed(binder::requestGroupUpdate, 1000)
|
||||
false -> Toast.makeText(context, R.string.noisy_su_failure, Toast.LENGTH_SHORT).show()
|
||||
null -> Toast.makeText(context, R.string.root_unavailable, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
DialogInterface.BUTTON_NEUTRAL -> binder.resetCredentials()
|
||||
}
|
||||
}, wifi)
|
||||
dialog.show()
|
||||
return
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, R.string.repeater_configure_failure, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
ServiceForegroundConnector(parent, this, RepeaterService::class)
|
||||
}
|
||||
|
||||
override val type get() = VIEW_TYPE_REPEATER
|
||||
private val data = Data()
|
||||
private var binder: RepeaterService.Binder? = null
|
||||
private var p2pInterface: String? = null
|
||||
|
||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||
(viewHolder as ViewHolder).binding.data = data
|
||||
}
|
||||
|
||||
fun update() = data.notifyChange()
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
binder = service as RepeaterService.Binder
|
||||
service.statusChanged[this] = data::onStatusChanged
|
||||
service.groupChanged[this] = data::onGroupChanged
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
val binder = binder ?: return
|
||||
this.binder = null
|
||||
binder.statusChanged -= this
|
||||
binder.groupChanged -= this
|
||||
data.onStatusChanged()
|
||||
}
|
||||
}
|
||||
174
mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt
Normal file
174
mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt
Normal file
@@ -0,0 +1,174 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.arch.lifecycle.Lifecycle
|
||||
import android.arch.lifecycle.LifecycleObserver
|
||||
import android.arch.lifecycle.OnLifecycleEvent
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.Intent
|
||||
import android.databinding.BaseObservable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
|
||||
import be.mygod.vpnhotspot.net.TetherType
|
||||
import be.mygod.vpnhotspot.net.TetheringManager
|
||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
abstract class TetherManager private constructor(protected val parent: TetheringFragment) : Manager(),
|
||||
TetheringManager.OnStartTetheringCallback {
|
||||
class ViewHolder(val binding: ListitemManageTetherBinding) : RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener {
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
var manager: TetherManager? = null
|
||||
set(value) {
|
||||
field = value!!
|
||||
binding.data = value.data
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val manager = manager!!
|
||||
val context = manager.parent.requireContext()
|
||||
if (Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(context)) {
|
||||
manager.parent.startActivity(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
|
||||
Uri.parse("package:${context.packageName}")))
|
||||
return
|
||||
}
|
||||
val started = manager.isStarted
|
||||
try {
|
||||
if (started) manager.stop() else manager.start()
|
||||
} catch (e: InvocationTargetException) {
|
||||
e.printStackTrace()
|
||||
var cause: Throwable? = e
|
||||
while (cause != null) {
|
||||
cause = cause.cause
|
||||
if (cause != null && cause !is InvocationTargetException) {
|
||||
Toast.makeText(context, cause.message, Toast.LENGTH_LONG).show()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient class to delegate stuff to BaseObservable.
|
||||
*/
|
||||
inner class Data : BaseObservable() {
|
||||
val tetherType get() = this@TetherManager.tetherType
|
||||
val title get() = this@TetherManager.title
|
||||
val isStarted get() = this@TetherManager.isStarted
|
||||
}
|
||||
|
||||
val data = Data()
|
||||
abstract val title: CharSequence
|
||||
abstract val tetherType: TetherType
|
||||
open val isStarted get() = parent.enabledTypes.contains(tetherType)
|
||||
|
||||
protected abstract fun start()
|
||||
protected abstract fun stop()
|
||||
|
||||
override fun onTetheringStarted() = data.notifyChange()
|
||||
override fun onTetheringFailed() {
|
||||
app.handler.post {
|
||||
Toast.makeText(parent.requireContext(), R.string.tethering_manage_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||
(viewHolder as ViewHolder).manager = this
|
||||
}
|
||||
|
||||
@RequiresApi(24)
|
||||
class Wifi(parent: TetheringFragment) : TetherManager(parent) {
|
||||
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
||||
override val tetherType get() = TetherType.WIFI
|
||||
override val type get() = VIEW_TYPE_WIFI
|
||||
|
||||
override fun start() = TetheringManager.start(TetheringManager.TETHERING_WIFI, true, this)
|
||||
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_WIFI)
|
||||
}
|
||||
@RequiresApi(24)
|
||||
class Usb(parent: TetheringFragment) : TetherManager(parent) {
|
||||
override val title get() = parent.getString(R.string.tethering_manage_usb)
|
||||
override val tetherType get() = TetherType.USB
|
||||
override val type get() = VIEW_TYPE_USB
|
||||
|
||||
override fun start() = TetheringManager.start(TetheringManager.TETHERING_USB, true, this)
|
||||
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_USB)
|
||||
}
|
||||
@RequiresApi(24)
|
||||
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), LifecycleObserver,
|
||||
BluetoothProfile.ServiceListener {
|
||||
companion object {
|
||||
/**
|
||||
* PAN Profile
|
||||
* From BluetoothProfile.java.
|
||||
*/
|
||||
private const val PAN = 5
|
||||
private val isTetheringOn by lazy @SuppressLint("PrivateApi") {
|
||||
Class.forName("android.bluetooth.BluetoothPan").getDeclaredMethod("isTetheringOn")
|
||||
}
|
||||
}
|
||||
|
||||
private var pan: BluetoothProfile? = null
|
||||
|
||||
init {
|
||||
parent.lifecycle.addObserver(this)
|
||||
BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(parent.requireContext(), this, PAN)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(profile: Int) {
|
||||
pan = null
|
||||
}
|
||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||
pan = proxy
|
||||
}
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun onDestroy() {
|
||||
pan = null
|
||||
}
|
||||
|
||||
override val title get() = parent.getString(R.string.tethering_manage_bluetooth)
|
||||
override val tetherType get() = TetherType.BLUETOOTH
|
||||
override val type get() = VIEW_TYPE_BLUETOOTH
|
||||
/**
|
||||
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java
|
||||
*/
|
||||
override val isStarted: Boolean
|
||||
get() {
|
||||
val pan = pan
|
||||
return BluetoothAdapter.getDefaultAdapter()?.state == BluetoothAdapter.STATE_ON && pan != null &&
|
||||
isTetheringOn.invoke(pan) as Boolean
|
||||
}
|
||||
|
||||
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this)
|
||||
override fun stop() {
|
||||
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
|
||||
Thread.sleep(1) // give others a room to breathe
|
||||
onTetheringStarted() // force flush state
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Not usable since API 26")
|
||||
class WifiLegacy(parent: TetheringFragment) : TetherManager(parent) {
|
||||
override val title get() = parent.getString(R.string.tethering_manage_wifi_legacy)
|
||||
override val tetherType get() = TetherType.WIFI
|
||||
override val type get() = VIEW_TYPE_WIFI_LEGACY
|
||||
|
||||
override fun start() = WifiApManager.start()
|
||||
override fun stop() = WifiApManager.stop()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package be.mygod.vpnhotspot.manage
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.databinding.DataBindingUtil
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.recyclerview.extensions.ListAdapter
|
||||
import android.support.v7.widget.DefaultItemAnimator
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
||||
import be.mygod.vpnhotspot.R
|
||||
import be.mygod.vpnhotspot.TetheringService
|
||||
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
|
||||
import be.mygod.vpnhotspot.net.TetherType
|
||||
import be.mygod.vpnhotspot.net.TetheringManager
|
||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||
import java.net.NetworkInterface
|
||||
import java.net.SocketException
|
||||
|
||||
class TetheringFragment : Fragment(), ServiceConnection {
|
||||
companion object {
|
||||
const val START_LOCAL_ONLY_HOTSPOT = 1
|
||||
}
|
||||
|
||||
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
||||
private val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
||||
private val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
|
||||
private val tetherManagers by lazy @TargetApi(24) {
|
||||
listOf(TetherManager.Wifi(this@TetheringFragment),
|
||||
TetherManager.Usb(this@TetheringFragment),
|
||||
TetherManager.Bluetooth(this@TetheringFragment))
|
||||
}
|
||||
private val wifiManagerLegacy by lazy @Suppress("Deprecation") {
|
||||
TetherManager.WifiLegacy(this@TetheringFragment)
|
||||
}
|
||||
|
||||
fun update(activeIfaces: List<String>, localOnlyIfaces: List<String>) {
|
||||
ifaceLookup = try {
|
||||
NetworkInterface.getNetworkInterfaces().asSequence().associateBy { it.name }
|
||||
} catch (e: SocketException) {
|
||||
e.printStackTrace()
|
||||
emptyMap()
|
||||
}
|
||||
this@TetheringFragment.enabledTypes =
|
||||
(activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet()
|
||||
|
||||
val list = arrayListOf<Manager>(repeaterManager)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
list.add(localOnlyHotspotManager)
|
||||
localOnlyHotspotManager.update()
|
||||
}
|
||||
list.addAll(activeIfaces.map { InterfaceManager(this@TetheringFragment, it) }.sortedBy { it.iface })
|
||||
list.add(ManageBar)
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
list.addAll(tetherManagers)
|
||||
tetherManagers.forEach { it.onTetheringStarted() }
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
list.add(wifiManagerLegacy)
|
||||
wifiManagerLegacy.onTetheringStarted()
|
||||
}
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = getItem(position).type
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
Manager.createViewHolder(LayoutInflater.from(parent.context), parent, viewType)
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = getItem(position).bindTo(holder)
|
||||
}
|
||||
|
||||
var ifaceLookup: Map<String, NetworkInterface> = emptyMap()
|
||||
var enabledTypes = emptySet<TetherType>()
|
||||
private lateinit var binding: FragmentTetheringBinding
|
||||
var tetheringBinder: TetheringService.Binder? = null
|
||||
val adapter = ManagerAdapter()
|
||||
private val receiver = broadcastReceiver { _, intent ->
|
||||
adapter.update(TetheringManager.getTetheredIfaces(intent.extras),
|
||||
TetheringManager.getLocalOnlyTetheredIfaces(intent.extras))
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tethering, container, false)
|
||||
binding.interfaces.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
||||
binding.interfaces.adapter = adapter
|
||||
ServiceForegroundConnector(this, this, TetheringService::class)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode == START_LOCAL_ONLY_HOTSPOT) @TargetApi(26) {
|
||||
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
||||
val context = requireContext()
|
||||
context.startForegroundService(Intent(context, LocalOnlyHotspotService::class.java))
|
||||
}
|
||||
} else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
tetheringBinder = service as TetheringService.Binder
|
||||
service.fragment = this
|
||||
requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
val context = requireContext()
|
||||
tetheringBinder?.fragment = null
|
||||
tetheringBinder = null
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user