Support new permission in Tiramisu
This commit is contained in:
13
README.md
13
README.md
@@ -9,12 +9,13 @@
|
|||||||
|
|
||||||
Connecting things to your VPN made simple. Share your VPN connection over hotspot or repeater. (**root required**)
|
Connecting things to your VPN made simple. Share your VPN connection over hotspot or repeater. (**root required**)
|
||||||
|
|
||||||
| Release channel | [GitHub (recommended)](https://github.com/Mygod/VPNHotspot/releases) | [Google Play](https://play.google.com/store/apps/details?id=be.mygod.vpnhotspot) ([beta](https://play.google.com/apps/testing/be.mygod.vpnhotspot)) |
|
| Release channel | [GitHub (recommended)](https://github.com/Mygod/VPNHotspot/releases) | [Google Play](https://play.google.com/store/apps/details?id=be.mygod.vpnhotspot) ([beta](https://play.google.com/apps/testing/be.mygod.vpnhotspot)) |
|
||||||
| --- | :---: | :---: |
|
|---------------------------------------------------------|:--------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||||
| Monitor connected clients without root | ✓ | Up to Android 10 |
|
| Monitor connected clients without root | ✓ | Up to Android 10 |
|
||||||
| Auto update | Email updates via watching releases | ✓ |
|
| Use repeater/temporary hotspot without location enabled | Up to Android 10/9 | Up to Android 10/9 or Android 13+ |
|
||||||
| In-app update channel | GitHub | Google Play |
|
| Auto update | Email updates via watching releases | ✓ |
|
||||||
| [Sponsor/Donation](https://github.com/sponsors/Mygod) | ✓ | Google Play In-App Purchases only |
|
| In-app update channel | GitHub | Google Play |
|
||||||
|
| [Sponsor/Donation](https://github.com/sponsors/Mygod) | ✓ | Google Play In-App Purchases only |
|
||||||
|
|
||||||
This app is useful for:
|
This app is useful for:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<!-- Required since API 31, when targeting API 31 -->
|
<!-- Required since API 31, when targeting API 31 -->
|
||||||
<uses-permission-sdk-23 android:name="android.permission.BLUETOOTH_CONNECT"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
|
||||||
|
<!-- Required since API 33, when targeting API 33 -->
|
||||||
|
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
|
||||||
|
android:usesPermissionFlags="neverForLocation"/>
|
||||||
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"
|
||||||
|
android:maxSdkVersion="32"/>
|
||||||
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||||
|
android:maxSdkVersion="32"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ package be.mygod.vpnhotspot
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.Size
|
import androidx.annotation.Size
|
||||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
@@ -19,6 +24,7 @@ import be.mygod.vpnhotspot.room.AppDatabase
|
|||||||
import be.mygod.vpnhotspot.root.RootManager
|
import be.mygod.vpnhotspot.root.RootManager
|
||||||
import be.mygod.vpnhotspot.util.DeviceStorageApp
|
import be.mygod.vpnhotspot.util.DeviceStorageApp
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import com.google.firebase.analytics.ktx.ParametersBuilder
|
import com.google.firebase.analytics.ktx.ParametersBuilder
|
||||||
import com.google.firebase.analytics.ktx.analytics
|
import com.google.firebase.analytics.ktx.analytics
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
@@ -104,6 +110,26 @@ class App : Application() {
|
|||||||
Firebase.analytics.logEvent(event, builder.bundle)
|
Firebase.analytics.logEvent(event, builder.bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOH also requires location to be turned on. So does p2p for some reason. Source:
|
||||||
|
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiServiceImpl.java#1204
|
||||||
|
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiSettingsStore.java#228
|
||||||
|
*/
|
||||||
|
inline fun <reified T> startServiceWithLocation(context: Context) {
|
||||||
|
if (BuildConfig.TARGET_SDK >= 33 && Build.VERSION.SDK_INT >= 33 || if (Build.VERSION.SDK_INT >= 28) {
|
||||||
|
location?.isLocationEnabled == true
|
||||||
|
} else @Suppress("DEPRECATION") {
|
||||||
|
Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE,
|
||||||
|
Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF
|
||||||
|
}) ContextCompat.startForegroundService(context, Intent(context, T::class.java)) else try {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
|
||||||
|
Toast.makeText(context, R.string.tethering_location_off, Toast.LENGTH_LONG).show()
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
app.logEvent("location_settings") { param("message", e.toString()) }
|
||||||
|
SmartSnackbar.make(R.string.tethering_location_off).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var deviceStorage: Application
|
lateinit var deviceStorage: Application
|
||||||
val english by lazy {
|
val english by lazy {
|
||||||
createConfigurationContext(Configuration(resources.configuration).apply {
|
createConfigurationContext(Configuration(resources.configuration).apply {
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
launch {
|
launch {
|
||||||
val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
|
val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
|
||||||
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
|
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
|
||||||
if (Build.VERSION.SDK_INT >= 30) filter.addAction(LocationManager.MODE_CHANGED_ACTION)
|
if (Build.VERSION.SDK_INT in 30 until 33) filter.addAction(LocationManager.MODE_CHANGED_ACTION)
|
||||||
registerReceiver(receiver, filter)
|
registerReceiver(receiver, filter)
|
||||||
receiverRegistered = true
|
receiverRegistered = true
|
||||||
val group = p2pManager.requestGroupInfo(channel)
|
val group = p2pManager.requestGroupInfo(channel)
|
||||||
@@ -397,7 +397,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
override fun onSuccess() {
|
override fun onSuccess() {
|
||||||
// wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3
|
// wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3
|
||||||
// in order for this to happen, we need to make sure that the callbacks are firing
|
// in order for this to happen, we need to make sure that the callbacks are firing
|
||||||
if (Build.VERSION.SDK_INT >= 30) onLocationModeChanged(app.location?.isLocationEnabled == true)
|
if (Build.VERSION.SDK_INT in 30 until 33) onLocationModeChanged(app.location?.isLocationEnabled == true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val channel = channel ?: return listener.onFailure(WifiP2pManager.BUSY)
|
val channel = channel ?: return listener.onFailure(WifiP2pManager.BUSY)
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ import android.Manifest
|
|||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.Settings
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.BuildConfig
|
||||||
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
||||||
companion object {
|
companion object {
|
||||||
val permission = if (Build.VERSION.SDK_INT >= 29) {
|
val permission = when {
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
BuildConfig.TARGET_SDK >= 33 && Build.VERSION.SDK_INT >= 33 -> Manifest.permission.NEARBY_WIFI_DEVICES
|
||||||
} else Manifest.permission.ACCESS_COARSE_LOCATION
|
Build.VERSION.SDK_INT >= 29 -> Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
else -> Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
class ViewHolder(val binding: ListitemInterfaceBinding) : RecyclerView.ViewHolder(binding.root),
|
||||||
@@ -55,23 +55,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager()
|
|||||||
ServiceForegroundConnector(parent, this, LocalOnlyHotspotService::class)
|
ServiceForegroundConnector(parent, this, LocalOnlyHotspotService::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun start(context: Context) = app.startServiceWithLocation<LocalOnlyHotspotService>(context)
|
||||||
* LOH also requires location to be turned on. Source:
|
|
||||||
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiServiceImpl.java#1204
|
|
||||||
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiSettingsStore.java#228
|
|
||||||
*/
|
|
||||||
fun start(context: Context) {
|
|
||||||
if (if (Build.VERSION.SDK_INT < 28) @Suppress("DEPRECATION") {
|
|
||||||
Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE,
|
|
||||||
Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_OFF
|
|
||||||
} else app.location?.isLocationEnabled != true) try {
|
|
||||||
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
|
|
||||||
Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show()
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
app.logEvent("location_settings") { param("message", e.toString()) }
|
|
||||||
SmartSnackbar.make(R.string.tethering_temp_hotspot_location).show()
|
|
||||||
} else context.startForegroundService(Intent(context, LocalOnlyHotspotService::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT
|
override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT
|
||||||
private val data = Data()
|
private val data = Data()
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
when (binder?.service?.status) {
|
when (binder?.service?.status) {
|
||||||
RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context ->
|
RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context ->
|
||||||
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
|
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
|
||||||
} else parent.startRepeater.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
} else parent.startRepeater.launch(if (BuildConfig.TARGET_SDK >= 33 && Build.VERSION.SDK_INT >= 33) {
|
||||||
|
Manifest.permission.NEARBY_WIFI_DEVICES
|
||||||
|
} else Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
RepeaterService.Status.ACTIVE -> binder.shutdown()
|
RepeaterService.Status.ACTIVE -> binder.shutdown()
|
||||||
else -> { }
|
else -> { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.*
|
import be.mygod.vpnhotspot.*
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
|
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
@@ -136,7 +137,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
if (granted) requireActivity().startForegroundService(Intent(activity, RepeaterService::class.java)) else {
|
if (granted) app.startServiceWithLocation<RepeaterService>(requireContext()) else {
|
||||||
Snackbar.make((activity as MainActivity).binding.fragmentHolder,
|
Snackbar.make((activity as MainActivity).binding.fragmentHolder,
|
||||||
R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show()
|
R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<string name="repeater_failure_disconnected">Servizio non disponibile. Riprova dopo</string>
|
<string name="repeater_failure_disconnected">Servizio non disponibile. Riprova dopo</string>
|
||||||
|
|
||||||
<string name="tethering_temp_hotspot">Hotspot Wi\u2011Fi temporaneo</string>
|
<string name="tethering_temp_hotspot">Hotspot Wi\u2011Fi temporaneo</string>
|
||||||
<string name="tethering_temp_hotspot_location">L\'hotspot temporaneo richiede che la localizzazione sia attiva.</string>
|
<string name="tethering_location_off">L\'hotspot temporaneo richiede che la localizzazione sia attiva.</string>
|
||||||
<string name="tethering_temp_hotspot_failure">Avvio dell\'hotspot fallito (causa: %s)</string>
|
<string name="tethering_temp_hotspot_failure">Avvio dell\'hotspot fallito (causa: %s)</string>
|
||||||
<string name="tethering_temp_hotspot_failure_no_channel">nessun canale</string>
|
<string name="tethering_temp_hotspot_failure_no_channel">nessun canale</string>
|
||||||
<string name="tethering_temp_hotspot_failure_generic">errore generico</string>
|
<string name="tethering_temp_hotspot_failure_generic">errore generico</string>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<string name="repeater_location_off_configure">进入设置</string>
|
<string name="repeater_location_off_configure">进入设置</string>
|
||||||
|
|
||||||
<string name="tethering_temp_hotspot">临时 WLAN 热点</string>
|
<string name="tethering_temp_hotspot">临时 WLAN 热点</string>
|
||||||
<string name="tethering_temp_hotspot_location">使用临时热点需要打开位置服务。</string>
|
<string name="tethering_location_off">使用此功能需要打开位置服务。</string>
|
||||||
<string name="tethering_temp_hotspot_failure">打开热点失败 (原因:%s)</string>
|
<string name="tethering_temp_hotspot_failure">打开热点失败 (原因:%s)</string>
|
||||||
<string name="tethering_temp_hotspot_failure_no_channel">无频段</string>
|
<string name="tethering_temp_hotspot_failure_no_channel">无频段</string>
|
||||||
<string name="tethering_temp_hotspot_failure_generic">通用错误</string>
|
<string name="tethering_temp_hotspot_failure_generic">通用错误</string>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<string name="repeater_location_off_configure">設定</string>
|
<string name="repeater_location_off_configure">設定</string>
|
||||||
|
|
||||||
<string name="tethering_temp_hotspot">臨時 Wi\u2011Fi 無線基地台</string>
|
<string name="tethering_temp_hotspot">臨時 Wi\u2011Fi 無線基地台</string>
|
||||||
<string name="tethering_temp_hotspot_location">開啟臨時無線基地台須開啟定位</string>
|
<string name="tethering_location_off">開啟須開啟定位</string>
|
||||||
<string name="tethering_temp_hotspot_failure">啟動無線基地台失敗 (原因:%s)</string>
|
<string name="tethering_temp_hotspot_failure">啟動無線基地台失敗 (原因:%s)</string>
|
||||||
<string name="tethering_temp_hotspot_failure_no_channel">沒有頻道</string>
|
<string name="tethering_temp_hotspot_failure_no_channel">沒有頻道</string>
|
||||||
<string name="tethering_temp_hotspot_failure_generic">一般錯誤</string>
|
<string name="tethering_temp_hotspot_failure_generic">一般錯誤</string>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<string name="repeater_location_off_configure">Configure</string>
|
<string name="repeater_location_off_configure">Configure</string>
|
||||||
|
|
||||||
<string name="tethering_temp_hotspot">Temporary Wi\u2011Fi hotspot</string>
|
<string name="tethering_temp_hotspot">Temporary Wi\u2011Fi hotspot</string>
|
||||||
<string name="tethering_temp_hotspot_location">Temporary hotspot requires location to be turned on.</string>
|
<string name="tethering_location_off">This feature requires location to be turned on.</string>
|
||||||
<string name="tethering_temp_hotspot_failure">Failed to start hotspot (reason: %s)</string>
|
<string name="tethering_temp_hotspot_failure">Failed to start hotspot (reason: %s)</string>
|
||||||
<string name="tethering_temp_hotspot_failure_no_channel">no channel</string>
|
<string name="tethering_temp_hotspot_failure_no_channel">no channel</string>
|
||||||
<string name="tethering_temp_hotspot_failure_generic">generic error</string>
|
<string name="tethering_temp_hotspot_failure_generic">generic error</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user