diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt index ed6c4e62..3e58824f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Data.kt @@ -7,5 +7,5 @@ abstract class Data : BaseObservable() { abstract val title: CharSequence abstract val text: CharSequence abstract val active: Boolean - abstract val selectable: Boolean + open val selectable get() = true } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt index 4d9204c5..1518b803 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt @@ -33,7 +33,6 @@ class InterfaceManager(private val parent: TetheringFragment, val iface: String) 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() ?: "" diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt index 7bbe5c9e..c783ae64 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt @@ -7,7 +7,6 @@ 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 { @@ -29,7 +28,7 @@ abstract class Manager { 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)) + TetherManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false)) VIEW_TYPE_LOCAL_ONLY_HOTSPOT -> @TargetApi(26) { LocalOnlyHotspotManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false)) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 280d6789..8ee992dd 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -8,7 +8,6 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothProfile import android.content.ActivityNotFoundException import android.content.Intent -import android.databinding.BaseObservable import android.net.Uri import android.os.Build import android.provider.Settings @@ -18,18 +17,21 @@ 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.databinding.ListitemInterfaceBinding import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.wifi.WifiApManager +import be.mygod.vpnhotspot.util.setPaddingStart import com.crashlytics.android.Crashlytics 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), + class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener { init { + itemView.setPaddingStart(itemView.resources.getDimensionPixelOffset( + R.dimen.listitem_manage_tether_padding_start)) itemView.setOnClickListener(this) } @@ -72,10 +74,11 @@ abstract class TetherManager private constructor(protected val parent: Tethering /** * 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 + inner class Data : be.mygod.vpnhotspot.manage.Data() { + override val icon get() = tetherType.icon + override val title get() = this@TetherManager.title + override var text: CharSequence = "" + override val active get() = isStarted } val data = Data() @@ -97,6 +100,27 @@ abstract class TetherManager private constructor(protected val parent: Tethering (viewHolder as ViewHolder).manager = this } + fun updateErrorMessage(errored: List) { + data.text = errored.filter { TetherType.ofInterface(it) == tetherType }.joinToString("\n") { + val error = TetheringManager.getLastTetherError(it) + "$it: " + when (error) { + TetheringManager.TETHER_ERROR_NO_ERROR -> "TETHER_ERROR_NO_ERROR" + TetheringManager.TETHER_ERROR_UNKNOWN_IFACE -> "TETHER_ERROR_UNKNOWN_IFACE" + TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL -> "TETHER_ERROR_SERVICE_UNAVAIL" + TetheringManager.TETHER_ERROR_UNSUPPORTED -> "TETHER_ERROR_UNSUPPORTED" + TetheringManager.TETHER_ERROR_UNAVAIL_IFACE -> "TETHER_ERROR_UNAVAIL_IFACE" + TetheringManager.TETHER_ERROR_MASTER_ERROR -> "TETHER_ERROR_MASTER_ERROR" + TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR -> "TETHER_ERROR_TETHER_IFACE_ERROR" + TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR -> "TETHER_ERROR_UNTETHER_IFACE_ERROR" + TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR -> "TETHER_ERROR_ENABLE_NAT_ERROR" + TetheringManager.TETHER_ERROR_DISABLE_NAT_ERROR -> "TETHER_ERROR_DISABLE_NAT_ERROR" + TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR -> "TETHER_ERROR_IFACE_CFG_ERROR" + else -> app.getString(R.string.failure_reason_unknown, error) + } + } + data.notifyChange() + } + @RequiresApi(24) class Wifi(parent: TetheringFragment) : TetherManager(parent) { override val title get() = parent.getString(R.string.tethering_manage_wifi) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index 8f750351..1c918f83 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -47,7 +47,7 @@ class TetheringFragment : Fragment(), ServiceConnection { TetherManager.WifiLegacy(this@TetheringFragment) } - fun update(activeIfaces: List, localOnlyIfaces: List) { + fun update(activeIfaces: List, localOnlyIfaces: List, erroredIfaces: List) { ifaceLookup = try { NetworkInterface.getNetworkInterfaces().asSequence().associateBy { it.name } } catch (e: SocketException) { @@ -67,7 +67,7 @@ class TetheringFragment : Fragment(), ServiceConnection { list.add(ManageBar) if (Build.VERSION.SDK_INT >= 24) { list.addAll(tetherManagers) - tetherManagers.forEach { it.onTetheringStarted() } + tetherManagers.forEach { it.updateErrorMessage(erroredIfaces) } } if (Build.VERSION.SDK_INT < 26) { list.add(wifiManagerLegacy) @@ -89,7 +89,8 @@ class TetheringFragment : Fragment(), ServiceConnection { val adapter = ManagerAdapter() private val receiver = broadcastReceiver { _, intent -> adapter.update(TetheringManager.getTetheredIfaces(intent.extras), - TetheringManager.getLocalOnlyTetheredIfaces(intent.extras)) + TetheringManager.getLocalOnlyTetheredIfaces(intent.extras), + intent.extras.getStringArrayList(TetheringManager.EXTRA_ERRORED_TETHER)) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -97,7 +98,7 @@ class TetheringFragment : Fragment(), ServiceConnection { binding.interfaces.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) binding.interfaces.itemAnimator = DefaultItemAnimator() binding.interfaces.adapter = adapter - adapter.update(emptyList(), emptyList()) + adapter.update(emptyList(), emptyList(), emptyList()) ServiceForegroundConnector(this, this, TetheringService::class) return binding.root } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 2c715b6d..4cdbd24b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -41,10 +41,36 @@ object TetheringManager { */ const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED" private const val EXTRA_ACTIVE_TETHER_LEGACY = "activeArray" + /** + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ @RequiresApi(26) private const val EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray" + /** + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ @RequiresApi(26) private const val EXTRA_ACTIVE_TETHER = "tetherArray" + /** + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + const val EXTRA_ERRORED_TETHER = "erroredArray" + const val TETHER_ERROR_NO_ERROR = 0 + const val TETHER_ERROR_UNKNOWN_IFACE = 1 + const val TETHER_ERROR_SERVICE_UNAVAIL = 2 + const val TETHER_ERROR_UNSUPPORTED = 3 + const val TETHER_ERROR_UNAVAIL_IFACE = 4 + const val TETHER_ERROR_MASTER_ERROR = 5 + const val TETHER_ERROR_TETHER_IFACE_ERROR = 6 + const val TETHER_ERROR_UNTETHER_IFACE_ERROR = 7 + const val TETHER_ERROR_ENABLE_NAT_ERROR = 8 + const val TETHER_ERROR_DISABLE_NAT_ERROR = 9 + const val TETHER_ERROR_IFACE_CFG_ERROR = 10 + const val TETHER_ERROR_PROVISION_FAILED = 11 const val TETHERING_WIFI = 0 /** @@ -68,6 +94,9 @@ object TetheringManager { private val stopTethering by lazy { ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java) } + private val getLastTetherError by lazy { + ConnectivityManager::class.java.getDeclaredMethod("getLastTetherError", String::class.java) + } /** * Runs tether provisioning for the given type if needed and then starts tethering if @@ -126,6 +155,16 @@ object TetheringManager { stopTethering.invoke(app.connectivity, type) } + /** + * Get a more detailed error code after a Tethering or Untethering + * request asynchronously failed. + * + * @param iface The name of the interface of interest + * @return error The error code of the last error tethering or untethering the named + * interface + */ + fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int + fun getTetheredIfaces(extras: Bundle) = extras.getStringArrayList( if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)!! fun getLocalOnlyTetheredIfaces(extras: Bundle) = diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index d8226c5b..f29bf796 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -37,6 +37,8 @@ fun Bundle.put(key: String, map: Array): Bundle { return this } +fun View.setPaddingStart(value: Int) = setPaddingRelative(value, paddingTop, paddingEnd, paddingBottom) + @BindingAdapter("android:src") fun setImageResource(imageView: ImageView, @DrawableRes resource: Int) = imageView.setImageResource(resource) diff --git a/mobile/src/main/res/layout/listitem_manage_tether.xml b/mobile/src/main/res/layout/listitem_manage_tether.xml deleted file mode 100644 index 44a08cad..00000000 --- a/mobile/src/main/res/layout/listitem_manage_tether.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml new file mode 100644 index 00000000..7cc70bcc --- /dev/null +++ b/mobile/src/main/res/values/dimen.xml @@ -0,0 +1,4 @@ + + + 56dp +