Support new permission in Tiramisu

This commit is contained in:
Mygod
2022-07-12 10:18:52 -04:00
parent 77faca0dfb
commit 4e1f7c9c40
11 changed files with 59 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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