Huge refactor for better maintainability

This commit is contained in:
Mygod
2018-06-01 20:21:05 +08:00
parent a5bec59bbe
commit 8aa7d6d8c7
35 changed files with 1072 additions and 824 deletions

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

View File

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

View File

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

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

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

View File

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

View 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()
}
}

View File

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