diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt b/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt index bf25d73a..86581a2c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt @@ -27,6 +27,8 @@ class BootReceiver : BroadcastReceiver() { Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_LOCKED_BOOT_COMPLETED -> started = true else -> return } - ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java)) + if (RepeaterService.supported) { + ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java)) + } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index bc4de313..0e9882fe 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -27,13 +27,28 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar import timber.log.Timber import java.lang.reflect.InvocationTargetException +/** + * Service for handling Wi-Fi P2P. `supported` must be checked before this service is started otherwise it would crash. + */ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPreferences.OnSharedPreferenceChangeListener { companion object { private const val TAG = "RepeaterService" + /** + * This is only a "ServiceConnection" to system service and its impact on system is minimal. + */ + private val p2pManager: WifiP2pManager? by lazy { + try { + app.getSystemService() + } catch (e: RuntimeException) { + Timber.w(e) + null + } + } + val supported get() = p2pManager != null } enum class Status { - IDLE, STARTING, ACTIVE + IDLE, STARTING, ACTIVE, DESTROYED } inner class Binder : android.os.Binder() { @@ -92,7 +107,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere } } - private lateinit var p2pManager: WifiP2pManager + private val p2pManager get() = RepeaterService.p2pManager!! private var channel: WifiP2pManager.Channel? = null var group: WifiP2pGroup? = null private set(value) { @@ -138,12 +153,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere override fun onCreate() { super.onCreate() - try { - p2pManager = getSystemService()!! - onChannelDisconnected() - } catch (e: RuntimeException) { - Timber.w(e) - } + onChannelDisconnected() app.pref.registerOnSharedPreferenceChangeListener(this) } @@ -169,7 +179,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere override fun onChannelDisconnected() { channel = null - try { + if (status != Status.DESTROYED) try { channel = p2pManager.initialize(this, Looper.getMainLooper(), this) setOperatingChannel() binder.requestGroupUpdate() @@ -282,6 +292,8 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere if (status != Status.IDLE) binder.shutdown() clean() // force clean to prevent leakage app.pref.unregisterOnSharedPreferenceChangeListener(this) + status = Status.DESTROYED + channel?.close() super.onDestroy() } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterTileService.kt index 2e8a0d94..e1b50b82 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterTileService.kt @@ -22,12 +22,14 @@ class RepeaterTileService : TileService(), ServiceConnection { override fun onStartListening() { super.onStartListening() - bindService(Intent(this, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE) + if (RepeaterService.supported) { + bindService(Intent(this, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE) + } } override fun onStopListening() { super.onStopListening() - stopAndUnbind(this) + if (RepeaterService.supported) stopAndUnbind(this) } override fun onClick() { @@ -72,7 +74,7 @@ class RepeaterTileService : TileService(), ServiceConnection { qsTile.icon = tileOn qsTile.label = group?.networkName } - null -> { + else -> { // null or DESTROYED, which should never occur qsTile.state = Tile.STATE_UNAVAILABLE qsTile.icon = tileOff qsTile.label = getString(R.string.title_repeater) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt index e7f1f137..2ac751a9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt @@ -32,11 +32,13 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { preferenceManager.preferenceDataStore = SharedPreferenceDataStore(app.pref) addPreferencesFromResource(R.xml.pref_settings) val boot = findPreference("service.repeater.startOnBoot") as SwitchPreference - boot.setOnPreferenceChangeListener { _, value -> - BootReceiver.enabled = value as Boolean - true - } - boot.isChecked = BootReceiver.enabled + if (RepeaterService.supported) { + boot.setOnPreferenceChangeListener { _, value -> + BootReceiver.enabled = value as Boolean + true + } + boot.isChecked = BootReceiver.enabled + } else boot.parent!!.removePreference(boot) findPreference("service.clean").setOnPreferenceClickListener { val cleaned = try { Routing.clean() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientMonitorService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientMonitorService.kt index 71051ea5..19b7acfa 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientMonitorService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientMonitorService.kt @@ -64,7 +64,9 @@ class ClientMonitorService : Service(), ServiceConnection, IpNeighbourMonitor.Ca override fun onCreate() { super.onCreate() - bindService(Intent(this, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE) + if (RepeaterService.supported) { + bindService(Intent(this, RepeaterService::class.java), this, Context.BIND_AUTO_CREATE) + } IpNeighbourMonitor.registerCallback(this) registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) } @@ -72,7 +74,7 @@ class ClientMonitorService : Service(), ServiceConnection, IpNeighbourMonitor.Ca override fun onDestroy() { unregisterReceiver(receiver) IpNeighbourMonitor.unregisterCallback(this) - stopAndUnbind(this) + if (RepeaterService.supported) stopAndUnbind(this) super.onDestroy() } 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 8152d50a..cecb4aa9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.LocalOnlyHotspotService import be.mygod.vpnhotspot.R +import be.mygod.vpnhotspot.RepeaterService import be.mygod.vpnhotspot.TetheringService import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding import be.mygod.vpnhotspot.net.TetherType @@ -59,7 +60,8 @@ class TetheringFragment : Fragment(), ServiceConnection { this@TetheringFragment.enabledTypes = (activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet() - val list = arrayListOf(repeaterManager) + val list = ArrayList() + if (RepeaterService.supported) list.add(repeaterManager) if (Build.VERSION.SDK_INT >= 26) { list.add(localOnlyHotspotManager) localOnlyHotspotManager.update()