Bump to minapi 28

This commit is contained in:
Mygod
2023-02-08 18:45:28 -05:00
parent 8ba727c1b8
commit 15c3c5a6d7
89 changed files with 361 additions and 809 deletions

View File

@@ -1,7 +1,7 @@
# VPN Hotspot
[![CircleCI](https://circleci.com/gh/Mygod/VPNHotspot.svg?style=shield)](https://circleci.com/gh/Mygod/VPNHotspot)
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
[![API](https://img.shields.io/badge/API-28%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=28)
[![Releases](https://img.shields.io/github/downloads/Mygod/VPNHotspot/total.svg)](https://github.com/Mygod/VPNHotspot/releases)
[![Language: Kotlin](https://img.shields.io/github/languages/top/Mygod/VPNHotspot.svg)](https://github.com/Mygod/VPNHotspot/search?l=kotlin)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e70e52b1a58045819b505c09edcae816)](https://www.codacy.com/gh/Mygod/VPNHotspot/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Mygod/VPNHotspot&utm_campaign=Badge_Grade)
@@ -10,10 +10,10 @@
Connecting things to your VPN made simple. Share your VPN connection over hotspot or repeater. (**root required**)
| Release channel | [GitHub](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)) |
|---------------------------------------------------------|:--------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|
| Auto update | Email updates via watching releases | ✓ |
| In-app update channel | GitHub | Google Play |
| [Sponsor/Donation](https://github.com/sponsors/Mygod) | | Google Play In-App Purchases only |
|---------------------------------------------------------|:------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------:|
| Auto update | Email updates via watching releases | ✓ |
| 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:
@@ -73,7 +73,7 @@ Default settings are picked to suit general use cases and maximize compatibility
I find turning this option off sometimes works better for dummy VPNs like ad-blockers and socksifiers than Simple mode, e.g. Shadowsocks.
But you should never use this for real VPNs like OpenVPN, etc.
- Simple: Source address/port from downstream packets will be remapped and that's about it.
- (since Android 9) Android Netd Service:
- Android Netd Service:
Let your system handle masquerade.
Android system will do a few extra things to make things like FTP and tethering traffic counter work.
You should probably not use this if you are trying to hide your tethering activity from your carrier.
@@ -82,7 +82,7 @@ Default settings are picked to suit general use cases and maximize compatibility
* Disable IPv6 tethering: Turning this option on will disable IPv6 for system tethering. Useful for stopping IPv6 leaks
as this app currently doesn't handle IPv6 VPN tethering (see [#6](https://github.com/Mygod/VPNHotspot/issues/6)).
* (since Android 8.1) Tethering hardware acceleration:
* Tethering hardware acceleration:
This is a shortcut to the same setting in system Developer options.
Turning this option off is probably a must for making VPN tethering over system tethering work,
but it might also decrease your battery life while tethering is enabled.
@@ -164,43 +164,41 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
* (since API 31) `Landroid/net/wifi/WifiClient;->getApInstanceIdentifier()Ljava/lang/String;,blocked`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->FT_PSK:I,lo-prio,max-target-o`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA_PSK_SHA256:I,blocked`
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_2GHZ:I,lo-prio,max-target-o`
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_5GHZ:I,lo-prio,max-target-o`
* (since API 28, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_ANY:I,lo-prio,max-target-o`
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->apBand:I,unsupported`
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->apChannel:I,unsupported`
* (since API 28, prior to API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onNumClientsChanged(I)V,greylist-max-o`
* (since API 26) `Landroid/net/wifi/WifiManager;->cancelLocalOnlyHotspotRequest()V,unsupported`
* (prior to API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_2GHZ:I,lo-prio,max-target-o`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_5GHZ:I,lo-prio,max-target-o`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_ANY:I,lo-prio,max-target-o`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration;->apBand:I,unsupported`
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration;->apChannel:I,unsupported`
* (prior to API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onNumClientsChanged(I)V,greylist-max-o`
* `Landroid/net/wifi/WifiManager;->cancelLocalOnlyHotspotRequest()V,unsupported`
* `Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->MAC_ANY_ADDRESS:Landroid/net/MacAddress;,blocked`
* (since API 29) `Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->mNetworkName:Ljava/lang/String;,blocked`
* `Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,unsupported`
* (since API 28, prior to API 30) `Landroid/provider/Settings$Global;->SOFT_AP_TIMEOUT_ENABLED:Ljava/lang/String;,lo-prio,max-target-o`
* (prior to API 30) `Landroid/provider/Settings$Global;->SOFT_AP_TIMEOUT_ENABLED:Ljava/lang/String;,lo-prio,max-target-o`
* (prior to API 30) `Lcom/android/internal/R$array;->config_tether_bluetooth_regexs:I,max-target-q`
* (prior to API 30) `Lcom/android/internal/R$array;->config_tether_usb_regexs:I,max-target-q`
* (prior to API 30) `Lcom/android/internal/R$array;->config_tether_wifi_regexs:I,max-target-q`
* (on API 29) `Lcom/android/internal/R$bool;->config_wifi_p2p_mac_randomization_supported:I,blacklist`
* (since API 28, prior to API 30) `Lcom/android/internal/R$integer;->config_wifi_framework_soft_ap_timeout_delay:I,greylist-max-o`
* (prior to API 30) `Lcom/android/internal/R$integer;->config_wifi_framework_soft_ap_timeout_delay:I,greylist-max-o`
* `Lcom/android/internal/R$string;->config_ethernet_iface_regex:I,lo-prio,max-target-o`
* (since API 27) `Lcom/android/server/connectivity/tethering/OffloadHardwareInterface;->DEFAULT_TETHER_OFFLOAD_DISABLED:I`
* (since API 30) `Lcom/android/server/wifi/WifiContext;->ACTION_RESOURCES_APK:Ljava/lang/String;`
* (since API 29) `Lcom/android/server/wifi/p2p/WifiP2pServiceImpl;->ANONYMIZED_DEVICE_ADDRESS:Ljava/lang/String;`
* (since API 30) `Lcom/android/server/SystemServer;->TETHERING_CONNECTOR_CLASS:Ljava/lang/String;`
* (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V,unsupported`
* (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o`
* `Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V,unsupported`
* `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o`
* (prior to API 29) `Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,max-target-p`
<details>
<summary>Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken)</summary>
* (since API 24) `Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,sdk,system-api,test-api`
* (since API 24) `Landroid/bluetooth/BluetoothProfile;->PAN:I,sdk,system-api,test-api`
* `Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,sdk,system-api,test-api`
* `Landroid/bluetooth/BluetoothProfile;->PAN:I,sdk,system-api,test-api`
* (since API 30) `Landroid/content/Context;->TETHERING_SERVICE:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;-><init>()V,sdk,system-api,test-api`
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;->onTetheringFailed()V,sdk,system-api,test-api`
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;->onTetheringStarted()V,sdk,system-api,test-api`
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,sdk,system-api,test-api`
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager;->stopTethering(I)V,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;-><init>()V,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;->onTetheringFailed()V,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;->onTetheringStarted()V,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/ConnectivityManager;->stopTethering(I)V,sdk,system-api,test-api`
* `Landroid/net/LinkProperties;->getAllInterfaceNames()Ljava/util/List;,sdk,system-api,test-api`
* `Landroid/net/LinkProperties;->getAllRoutes()Ljava/util/List;,sdk,system-api,test-api`
* (since API 30) `Landroid/net/TetheringManager$StartTetheringCallback;->onTetheringFailed(I)V,sdk,system-api,test-api`
@@ -217,13 +215,13 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
* (since API 30) `Landroid/net/TetheringManager$TetheringRequest$Builder;->setExemptFromEntitlementCheck(Z)Landroid/net/TetheringManager$TetheringRequest$Builder;,sdk,system-api,test-api`
* (since API 30) `Landroid/net/TetheringManager$TetheringRequest$Builder;->setShouldShowEntitlementUi(Z)Landroid/net/TetheringManager$TetheringRequest$Builder;,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->ACTION_TETHER_STATE_CHANGED:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 26) `Landroid/net/TetheringManager;->EXTRA_ACTIVE_LOCAL_ONLY:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->EXTRA_ACTIVE_LOCAL_ONLY:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->EXTRA_ACTIVE_TETHER:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->EXTRA_ERRORED_TETHER:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_BLUETOOTH:I,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->TETHERING_BLUETOOTH:I,sdk,system-api,test-api`
* (since API 30) `Landroid/net/TetheringManager;->TETHERING_ETHERNET:I,sdk,system-api,test-api`
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_USB:I,sdk,system-api,test-api`
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_WIFI:I,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->TETHERING_USB:I,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->TETHERING_WIFI:I,sdk,system-api,test-api`
* `Landroid/net/TetheringManager;->TETHER_ERROR_*:I,sdk,system-api,test-api`
* (since API 30) `Landroid/net/TetheringManager;->TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:I,sdk,system-api,test-api`
* (since API 30) `Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_FAILED:I,sdk,system-api,test-api`
@@ -308,25 +306,25 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
* (since API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onConnectedClientsChanged(Ljava/util/List;)V,sdk,system-api,test-api`
* (on API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Landroid/net/wifi/SoftApInfo;)V,sdk,system-api,test-api`
* (since API 31) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Ljava/util/List;)V,sdk,system-api,test-api`
* (since API 28) `Landroid/net/wifi/WifiManager$SoftApCallback;->onStateChanged(II)V,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_FAILURE_REASON:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_INTERFACE_NAME:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager$SoftApCallback;->onStateChanged(II)V,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_FAILURE_REASON:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_INTERFACE_NAME:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 30) `Landroid/net/wifi/WifiManager;->SAP_CLIENT_BLOCK_REASON_CODE_*:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api`
* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api`
* (since API 30) `Landroid/net/wifi/WifiManager;->getSoftApConfiguration()Landroid/net/wifi/SoftApConfiguration;,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,sdk,system-api,test-api`
* (since API 30) `Landroid/net/wifi/WifiManager;->isApMacRandomizationSupported()Z,sdk,system-api,test-api`
* (since API 28) `Landroid/net/wifi/WifiManager;->registerSoftApCallback(Ljava/util/concurrent/Executor;Landroid/net/wifi/WifiManager$SoftApCallback;)V,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->registerSoftApCallback(Ljava/util/concurrent/Executor;Landroid/net/wifi/WifiManager$SoftApCallback;)V,sdk,system-api,test-api`
* (since API 30) `Landroid/net/wifi/WifiManager;->setSoftApConfiguration(Landroid/net/wifi/SoftApConfiguration;)Z,sdk,system-api,test-api`
* (prior to API 30) `Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,sdk,system-api,test-api`
* (since API 28) `Landroid/net/wifi/WifiManager;->unregisterSoftApCallback(Landroid/net/wifi/WifiManager$SoftApCallback;)V,sdk,system-api,test-api`
* `Landroid/net/wifi/WifiManager;->unregisterSoftApCallback(Landroid/net/wifi/WifiManager$SoftApCallback;)V,sdk,system-api,test-api`
* `Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/List;,sdk,system-api,test-api`
* `Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;->onPersistentGroupInfoAvailable(Landroid/net/wifi/p2p/WifiP2pGroupList;)V,sdk,system-api,test-api`
* `Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,sdk,system-api,test-api`
@@ -351,15 +349,14 @@ Nonexported system resources:
Other: Activity `com.android.settings/.Settings$TetherSettingsActivity` is assumed to be exported.
For `ip rule` priorities, `RULE_PRIORITY_SECURE_VPN` and `RULE_PRIORITY_TETHERING` is assumed to be 12000 and 18000 respectively;
(prior to API 24) `RULE_PRIORITY_DEFAULT_NETWORK` is assumed to be 22000 (or at least > 18000).
DHCP server like `dnsmasq` is assumed to run and send DHCP packets as root.
Undocumented system binaries are all bundled and executable:
* (since API 24) `iptables-save`, `ip6tables-save`;
* `iptables-save`, `ip6tables-save`;
* `echo`;
* `/system/bin/ip` (`monitor neigh rule unreachable`);
* `ndc` (`ipfwd` since API 23, `nat` since API 28);
* `ndc` (`ipfwd nat`);
* `iptables`, `ip6tables` (with correct version corresponding to API level, `-nvx -L <chain>`);
* `sh`;
* `su`.

View File

@@ -22,7 +22,7 @@ android {
kotlinOptions.jvmTarget = javaVersion.toString()
defaultConfig {
applicationId = "be.mygod.vpnhotspot"
minSdk = 21
minSdk = 28
targetSdk = 33
resourceConfigurations.addAll(arrayOf("it", "pt-rBR", "ru", "zh-rCN", "zh-rTW"))
versionCode = 1000

View File

@@ -34,10 +34,10 @@
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"mac"
],
"autoGenerate": false
]
},
"indices": [],
"foreignKeys": []
@@ -114,10 +114,10 @@
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
],
"autoGenerate": true
]
},
"indices": [
{
@@ -126,7 +126,8 @@
"columnNames": [
"previousId"
],
"createSql": "CREATE UNIQUE INDEX `index_TrafficRecord_previousId` ON `${TABLE_NAME}` (`previousId`)"
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TrafficRecord_previousId` ON `${TABLE_NAME}` (`previousId`)"
}
],
"foreignKeys": [
@@ -144,9 +145,10 @@
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"92a6c0406ed7265dbd98eb3c24095651\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '92a6c0406ed7265dbd98eb3c24095651')"
]
}
}

View File

@@ -103,7 +103,7 @@ object UpdateChecker {
} catch (e: Exception) {
Timber.w(e)
} finally {
conn.disconnectCompat()
conn.disconnect()
putLong(KEY_LAST_FETCHED, System.currentTimeMillis())
}
}

View File

@@ -90,8 +90,7 @@
<service
android:name=".LocalOnlyHotspotService"
android:directBootAware="true"
android:foregroundServiceType="location|connectedDevice"
tools:targetApi="26"/>
android:foregroundServiceType="location|connectedDevice"/>
<service
android:name=".RepeaterService"
android:directBootAware="true"
@@ -107,8 +106,7 @@
android:exported="true"
android:icon="@drawable/ic_action_settings_input_antenna"
android:label="@string/title_repeater"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="24">
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -119,12 +117,10 @@
<service
android:name=".manage.LocalOnlyHotspotTileService"
android:directBootAware="true"
android:enabled="@bool/api_ge_26"
android:exported="true"
android:icon="@drawable/ic_action_perm_scan_wifi"
android:label="@string/tethering_temp_hotspot"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="26">
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -138,8 +134,7 @@
android:exported="true"
android:icon="@drawable/ic_device_wifi_tethering"
android:label="@string/tethering_manage_wifi"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="24">
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -153,8 +148,7 @@
android:exported="true"
android:icon="@drawable/ic_device_usb"
android:label="@string/tethering_manage_usb"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="24">
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -168,8 +162,7 @@
android:exported="true"
android:icon="@drawable/ic_device_bluetooth"
android:label="@string/tethering_manage_bluetooth"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="24">
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -193,23 +186,6 @@
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<!--suppress DeprecatedClassUsageInspection -->
<service
android:name=".manage.TetheringTileService$WifiLegacy"
android:directBootAware="true"
android:enabled="@bool/api_lt_25"
android:exported="true"
android:icon="@drawable/ic_device_wifi_tethering"
android:label="@string/tethering_manage_wifi_legacy"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="24">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<receiver
android:name=".BootReceiver"

View File

@@ -15,7 +15,6 @@ import android.widget.Toast
import androidx.annotation.Size
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
import be.mygod.librootkotlinx.NoShellException
@@ -46,13 +45,11 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
app = this
if (Build.VERSION.SDK_INT >= 24) @SuppressLint("RestrictedApi") {
deviceStorage = DeviceStorageApp(this)
// alternative to PreferenceManager.getDefaultSharedPreferencesName(this)
deviceStorage.moveSharedPreferencesFrom(this, PreferenceManager(this).sharedPreferencesName)
deviceStorage.moveDatabaseFrom(this, AppDatabase.DB_NAME)
BootReceiver.migrateIfNecessary()
} else deviceStorage = this
deviceStorage = DeviceStorageApp(this)
// alternative to PreferenceManager.getDefaultSharedPreferencesName(this)
deviceStorage.moveSharedPreferencesFrom(this, PreferenceManager(this).sharedPreferencesName)
deviceStorage.moveDatabaseFrom(this, AppDatabase.DB_NAME)
BootReceiver.migrateIfNecessary()
Services.init { this }
// overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode
@@ -62,7 +59,7 @@ class App : Application() {
"REL" -> { }
else -> FirebaseCrashlytics.getInstance().apply {
setCustomKey("codename", codename)
if (Build.VERSION.SDK_INT >= 23) setCustomKey("preview_sdk", Build.VERSION.PREVIEW_SDK_INT)
setCustomKey("preview_sdk", Build.VERSION.PREVIEW_SDK_INT)
}
}
Timber.plant(object : Timber.DebugTree() {
@@ -115,19 +112,13 @@ class App : Application() {
* 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) {
val canStart = 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
}
if (canStart) ContextCompat.startForegroundService(context, Intent(context, T::class.java)) else try {
if (Build.VERSION.SDK_INT < 33 && location?.isLocationEnabled != true) 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()
}
} else context.startForegroundService(Intent(context, T::class.java))
}
lateinit var deviceStorage: Application
@@ -145,10 +136,10 @@ class App : Application() {
CustomTabsIntent.Builder().apply {
setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_LIGHT, CustomTabColorSchemeParams.Builder().apply {
setToolbarColor(ContextCompat.getColor(app, R.color.light_colorPrimary))
setToolbarColor(resources.getColor(R.color.light_colorPrimary, theme))
}.build())
setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, CustomTabColorSchemeParams.Builder().apply {
setToolbarColor(ContextCompat.getColor(app, R.color.dark_colorPrimary))
setToolbarColor(resources.getColor(R.color.dark_colorPrimary, theme))
}.build())
}.build()
}

View File

@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Parcelable
import androidx.annotation.RequiresApi
import be.mygod.librootkotlinx.toByteArray
import be.mygod.librootkotlinx.toParcelable
import be.mygod.vpnhotspot.App.Companion.app
@@ -74,7 +73,6 @@ class BootReceiver : BroadcastReceiver() {
inline fun <reified T> add(value: Startable) = add(T::class.java.name, value)
inline fun <reified T> delete() = delete(T::class.java.name)
@RequiresApi(24)
fun migrateIfNecessary() {
val oldFile = File(app.noBackupFilesDir, FILENAME)
if (oldFile.canRead()) try {

View File

@@ -94,7 +94,6 @@ class EBegFragment : AppCompatDialogFragment() {
}.build()))
}.build()) else SmartSnackbar.make(R.string.donations__google_android_market_not_supported).show()
}
@Suppress("ConstantConditionIf")
if (BuildConfig.DONATIONS) (binding.donationsMoreStub.inflate() as Button).setOnClickListener {
requireContext().launchUrl("https://mygod.be/donate/")
}

View File

@@ -5,7 +5,6 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.WifiManager
import android.os.Build
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
@@ -21,7 +20,6 @@ import kotlinx.parcelize.Parcelize
import timber.log.Timber
import java.net.Inet4Address
@RequiresApi(26)
class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
inner class Binder : android.os.Binder() {
/**
@@ -162,7 +160,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
override fun onIpNeighbourAvailable(neighbours: Collection<IpNeighbour>) {
super.onIpNeighbourAvailable(neighbours)
if (Build.VERSION.SDK_INT >= 28) timeoutMonitor?.onClientsChanged(neighbours.none {
timeoutMonitor?.onClientsChanged(neighbours.none {
it.ip is Inet4Address && it.state == IpNeighbour.State.VALID
})
}
@@ -183,10 +181,8 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
private fun unregisterReceiver(exit: Boolean = false) {
IpNeighbourMonitor.unregisterCallback(this)
if (Build.VERSION.SDK_INT >= 28) {
timeoutMonitor?.close()
timeoutMonitor = null
}
timeoutMonitor?.close()
timeoutMonitor = null
launch {
routingManager?.stop()
routingManager = null

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@@ -50,16 +49,14 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
setContentView(binding.root)
binding.navigation.setOnItemSelectedListener(this)
val badge = binding.navigation.getOrCreateBadge(R.id.navigation_clients).apply {
backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.colorSecondary)
badgeTextColor = ContextCompat.getColor(this@MainActivity,
androidx.appcompat.R.color.primary_text_default_material_light)
backgroundColor = resources.getColor(R.color.colorSecondary, theme)
badgeTextColor = resources.getColor(androidx.appcompat.R.color.primary_text_default_material_light, theme)
}
updateItem = binding.navigation.menu.findItem(R.id.navigation_update)
updateItem.isCheckable = false
updateBadge = binding.navigation.getOrCreateBadge(R.id.navigation_update).apply {
backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.colorSecondary)
badgeTextColor = ContextCompat.getColor(this@MainActivity,
androidx.appcompat.R.color.primary_text_default_material_light)
backgroundColor = resources.getColor(R.color.colorSecondary, theme)
badgeTextColor = resources.getColor(androidx.appcompat.R.color.primary_text_default_material_light, theme)
}
if (savedInstanceState == null) displayFragment(TetheringFragment())
val model by viewModels<ClientViewModel>()

View File

@@ -8,6 +8,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.location.LocationManager
import android.net.MacAddress
import android.net.wifi.ScanResult
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.*
@@ -16,10 +17,10 @@ import android.os.Looper
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toLong
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
import be.mygod.vpnhotspot.net.wifi.VendorElements
@@ -98,17 +99,19 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
var shutdownTimeoutMillis: Long
get() = app.pref.getLong(KEY_SHUTDOWN_TIMEOUT, 0)
set(value) = app.pref.edit { putLong(KEY_SHUTDOWN_TIMEOUT, value) }
var deviceAddress: MacAddressCompat?
var deviceAddress: MacAddress?
get() = try {
MacAddressCompat(app.pref.getLong(KEY_DEVICE_ADDRESS, MacAddressCompat.ANY_ADDRESS.addr)).run {
validate()
if (this == MacAddressCompat.ANY_ADDRESS) null else this
MacAddressCompat(app.pref.getLong(KEY_DEVICE_ADDRESS, 2)).run {
require(addr and ((1L shl 48) - 1).inv() == 0L)
if (addr == 2L) null else toPlatform()
}
} catch (e: IllegalArgumentException) {
Timber.w(e)
null
}
set(value) = app.pref.edit { putLong(KEY_DEVICE_ADDRESS, (value ?: MacAddressCompat.ANY_ADDRESS).addr) }
set(value) = app.pref.edit {
putLong(KEY_DEVICE_ADDRESS, (value ?: MacAddressCompat.ANY_ADDRESS).toLong())
}
@get:RequiresApi(33)
@set:RequiresApi(33)
var vendorElements: List<ScanResult.InformationElement>
@@ -128,19 +131,17 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
set(value) {
field = value
groupChanged(value)
if (Build.VERSION.SDK_INT >= 28) value?.clientList?.let {
timeoutMonitor?.onClientsChanged(it.isEmpty())
}
value?.clientList?.let { timeoutMonitor?.onClientsChanged(it.isEmpty()) }
}
val groupChanged = StickyEvent1 { group }
suspend fun obtainDeviceAddress(): MacAddressCompat? {
suspend fun obtainDeviceAddress(): MacAddress? {
return if (Build.VERSION.SDK_INT >= 29) p2pManager.requestDeviceAddress(channel ?: return null) ?: try {
RootManager.use { it.execute(RepeaterCommands.RequestDeviceAddress()) }
} catch (e: Exception) {
Timber.d(e)
null
}?.let { MacAddressCompat(it.value) } else lastMac?.let { MacAddressCompat.fromString(it) }
} else lastMac?.let { MacAddress.fromString(it) }
}
@SuppressLint("NewApi") // networkId is available since Android 4.2
@@ -152,7 +153,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
val ownedGroups = filter {
if (!it.isGroupOwner) return@filter false
val address = try {
MacAddressCompat.fromString(it.owner.deviceAddress)
MacAddress.fromString(it.owner.deviceAddress)
} catch (e: IllegalArgumentException) {
Timber.w(e)
return@filter true // assuming it was changed due to privacy
@@ -220,7 +221,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
@Parcelize
class Starter : BootReceiver.Startable {
override fun start(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
context.startForegroundService(Intent(context, RepeaterService::class.java))
}
}
@@ -400,7 +401,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
val channel = channel ?: return START_NOT_STICKY.also { stopSelf() }
status = Status.STARTING
// bump self to foreground location service (API 29+) to use location later, also to avoid getting killed
if (Build.VERSION.SDK_INT >= 26) showNotification()
showNotification()
launch {
val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
@@ -479,7 +480,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc))
}
}
setDeviceAddress(deviceAddress?.toPlatform())
setDeviceAddress(deviceAddress)
}.build(), listener)
}
}
@@ -554,10 +555,8 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
p2pPoller?.cancel()
receiverRegistered = false
}
if (Build.VERSION.SDK_INT >= 28) {
timeoutMonitor?.close()
timeoutMonitor = null
}
timeoutMonitor?.close()
timeoutMonitor = null
routingManager?.stop()
routingManager = null
status = Status.IDLE
@@ -574,7 +573,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
app.pref.unregisterOnSharedPreferenceChangeListener(this)
if (Build.VERSION.SDK_INT < 29) unregisterReceiver(deviceListener)
status = Status.DESTROYED
if (Build.VERSION.SDK_INT >= 27) channel?.close()
channel?.close()
super.onDestroy()
}
}

View File

@@ -1,6 +1,5 @@
package be.mygod.vpnhotspot
import android.annotation.TargetApi
import android.os.Build
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing
@@ -15,15 +14,11 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p
companion object {
private const val KEY_MASQUERADE_MODE = "service.masqueradeMode"
var masqueradeMode: Routing.MasqueradeMode
@TargetApi(28) get() = app.pref.run {
get() = app.pref.run {
getString(KEY_MASQUERADE_MODE, null)?.let { return@run Routing.MasqueradeMode.valueOf(it) }
if (getBoolean("service.masquerade", true)) { // legacy settings
Routing.MasqueradeMode.Simple
} else Routing.MasqueradeMode.None
}.let {
// older app version enabled netd for everyone. should check again here
if (Build.VERSION.SDK_INT >= 28 || it != Routing.MasqueradeMode.Netd) it
else Routing.MasqueradeMode.Simple
}
set(value) = app.pref.edit().putString(KEY_MASQUERADE_MODE, value.name).apply()

View File

@@ -1,13 +1,8 @@
package be.mygod.vpnhotspot
import android.annotation.TargetApi
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import be.mygod.vpnhotspot.App.Companion.app
import java.util.*
@@ -25,16 +20,15 @@ object ServiceNotification {
val deviceCounts = deviceCountsMap.values.flatMap { it.entries }.sortedBy { it.key }
val inactive = inactiveMap.values.flatten()
val isInactive = inactive.isNotEmpty() && deviceCounts.isEmpty()
val builder = NotificationCompat.Builder(context, if (isInactive) CHANNEL_INACTIVE else CHANNEL_ACTIVE).apply {
val builder = Notification.Builder(context, if (isInactive) CHANNEL_INACTIVE else CHANNEL_ACTIVE).apply {
setWhen(0)
setCategory(NotificationCompat.CATEGORY_SERVICE)
color = ContextCompat.getColor(context, R.color.colorPrimary)
setCategory(Notification.CATEGORY_SERVICE)
setColor(context.resources.getColor(R.color.colorPrimary, context.theme))
setContentTitle(context.getText(R.string.notification_tethering_title))
setSmallIcon(R.drawable.ic_quick_settings_tile_on)
setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
priority = if (isInactive) NotificationCompat.PRIORITY_MIN else NotificationCompat.PRIORITY_LOW
setVisibility(Notification.VISIBILITY_PUBLIC)
}
var lines = deviceCounts.map { (dev, size) ->
context.resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev)
@@ -45,13 +39,13 @@ object ServiceNotification {
return if (lines.size <= 1) builder.setContentText(lines.singleOrNull()).build() else {
val deviceCount = deviceCounts.sumOf { it.value }
val interfaceCount = deviceCounts.size + inactive.size
NotificationCompat.BigTextStyle(builder
.setContentText(context.resources.getQuantityString(R.plurals.notification_connected_devices,
deviceCount, deviceCount,
context.resources.getQuantityString(R.plurals.notification_interfaces,
interfaceCount, interfaceCount))))
.bigText(lines.joinToString("\n"))
.build()!!
Notification.BigTextStyle().apply {
setBuilder(builder.setContentText(context.resources.getQuantityString(
R.plurals.notification_connected_devices, deviceCount, deviceCount,
context.resources.getQuantityString(R.plurals.notification_interfaces,
interfaceCount, interfaceCount))))
bigText(lines.joinToString("\n"))
}.build()!!
}
}
@@ -65,26 +59,23 @@ object ServiceNotification {
fun stopForeground(service: Service) = synchronized(this) {
deviceCountsMap.remove(service) ?: return@synchronized
val shutdown = deviceCountsMap.isEmpty()
ServiceCompat.stopForeground(service,
if (shutdown) ServiceCompat.STOP_FOREGROUND_REMOVE else ServiceCompat.STOP_FOREGROUND_DETACH)
service.stopForeground(if (shutdown) Service.STOP_FOREGROUND_REMOVE else Service.STOP_FOREGROUND_DETACH)
if (!shutdown) manager.notify(NOTIFICATION_ID, buildNotification(service))
}
fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) {
NotificationChannel(CHANNEL_ACTIVE,
app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW).apply {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
NotificationChannel(CHANNEL_INACTIVE,
app.getText(R.string.notification_channel_monitor), NotificationManager.IMPORTANCE_LOW).apply {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
// remove old service channels
manager.deleteNotificationChannel("hotspot")
manager.deleteNotificationChannel("repeater")
NotificationChannel(CHANNEL_ACTIVE,
app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW).apply {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
NotificationChannel(CHANNEL_INACTIVE,
app.getText(R.string.notification_channel_monitor), NotificationManager.IMPORTANCE_LOW).apply {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
// remove old service channels
manager.deleteNotificationChannel("hotspot")
manager.deleteNotificationChannel("repeater")
}
}

View File

@@ -47,24 +47,22 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
SummaryFallbackProvider(findPreference(UpstreamMonitor.KEY)!!)
SummaryFallbackProvider(findPreference(FallbackUpstreamMonitor.KEY)!!)
findPreference<TwoStatePreference>("system.enableTetherOffload")!!.apply {
if (TetherOffloadManager.supported) {
isChecked = TetherOffloadManager.enabled
setOnPreferenceChangeListener { _, newValue ->
if (TetherOffloadManager.enabled != newValue) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
isEnabled = false
try {
TetherOffloadManager.setEnabled(newValue as Boolean)
} catch (_: CancellationException) {
} catch (e: Exception) {
Timber.w(e)
SmartSnackbar.make(e).show()
}
isChecked = TetherOffloadManager.enabled
isEnabled = true
isChecked = TetherOffloadManager.enabled
setOnPreferenceChangeListener { _, newValue ->
if (TetherOffloadManager.enabled != newValue) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
isEnabled = false
try {
TetherOffloadManager.setEnabled(newValue as Boolean)
} catch (_: CancellationException) {
} catch (e: Exception) {
Timber.w(e)
SmartSnackbar.make(e).show()
}
false
isChecked = TetherOffloadManager.enabled
isEnabled = true
}
} else parent!!.removePreference(this)
false
}
}
findPreference<TwoStatePreference>(BootReceiver.KEY)!!.setOnPreferenceChangeListener { _, value ->
BootReceiver.onUserSettingUpdated(value as Boolean)

View File

@@ -2,9 +2,7 @@ package be.mygod.vpnhotspot
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.TetheringManager
@@ -46,7 +44,7 @@ class TetheringService : IpNeighbourMonitoringService(), TetheringManager.Tether
@Parcelize
data class Starter(val monitored: ArrayList<String>) : BootReceiver.Startable {
override fun start(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, TetheringService::class.java).apply {
context.startForegroundService(Intent(context, TetheringService::class.java).apply {
putStringArrayListExtra(EXTRA_ADD_INTERFACES_MONITOR, monitored)
})
}
@@ -113,7 +111,7 @@ class TetheringService : IpNeighbourMonitoringService(), TetheringManager.Tether
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
BootReceiver.startIfEnabled()
// call this first just in case we are shutting down immediately
if (Build.VERSION.SDK_INT >= 26) updateNotification()
updateNotification()
launch {
if (intent != null) {
for (iface in intent.getStringArrayExtra(EXTRA_ADD_INTERFACES) ?: emptyArray()) {

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.client
import android.net.MacAddress
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.StrikethroughSpan
@@ -9,7 +10,6 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.InetAddressComparator
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.room.ClientRecord
@@ -18,7 +18,7 @@ import be.mygod.vpnhotspot.util.makeMacSpan
import java.net.InetAddress
import java.util.*
open class Client(val mac: MacAddressCompat, val iface: String) {
open class Client(val mac: MacAddress, val iface: String) {
companion object DiffCallback : DiffUtil.ItemCallback<Client>() {
override fun areItemsTheSame(oldItem: Client, newItem: Client) =
oldItem.iface == newItem.iface && oldItem.mac == newItem.mac
@@ -65,7 +65,7 @@ open class Client(val mac: MacAddressCompat, val iface: String) {
}.trimEnd()
}
fun obtainRecord() = record.value ?: ClientRecord(mac.addr)
fun obtainRecord() = record.value ?: ClientRecord(mac)
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.client
import android.content.ComponentName
import android.content.IntentFilter
import android.content.ServiceConnection
import android.net.MacAddress
import android.net.wifi.p2p.WifiP2pDevice
import android.os.Build
import android.os.IBinder
@@ -15,8 +16,6 @@ import androidx.lifecycle.ViewModel
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
@@ -38,7 +37,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
private var repeater: RepeaterService.Binder? = null
private var p2p: Collection<WifiP2pDevice> = emptyList()
private var wifiAp = emptyList<Pair<String, MacAddressCompat>>()
private var wifiAp = emptyList<Pair<String, MacAddress>>()
private var neighbours: Collection<IpNeighbour> = emptyList()
val clients = MutableLiveData<List<Client>>()
val fullMode = object : DefaultLifecycleObserver {
@@ -51,10 +50,10 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
}
private fun populateClients() {
val clients = HashMap<Pair<String, MacAddressCompat>, Client>()
val clients = HashMap<Pair<String, MacAddress>, Client>()
repeater?.group?.`interface`?.let { p2pInterface ->
for (client in p2p) {
val addr = MacAddressCompat.fromString(client.deviceAddress!!)
val addr = MacAddress.fromString(client.deviceAddress!!)
clients[p2pInterface to addr] = object : Client(addr, p2pInterface) {
override val icon: Int get() = TetherType.WIFI_P2P.icon
}
@@ -118,7 +117,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
override fun onConnectedClientsChanged(clients: List<Parcelable>) {
wifiAp = clients.mapNotNull {
val client = WifiClient(it)
client.apInstanceIdentifier?.run { this to client.macAddress.toCompat() }
client.apInstanceIdentifier?.run { this to client.macAddress }
}
}
}

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.client
import android.content.DialogInterface
import android.net.MacAddress
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
@@ -30,7 +31,6 @@ import be.mygod.vpnhotspot.Empty
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.FragmentClientsBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
@@ -41,18 +41,21 @@ import be.mygod.vpnhotspot.util.format
import be.mygod.vpnhotspot.util.showAllowingStateLoss
import be.mygod.vpnhotspot.util.toPluralInt
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import java.text.NumberFormat
class ClientsFragment : Fragment() {
// FIXME: value class does not work with Parcelize
@Parcelize
data class NicknameArg(val mac: Long, val nickname: CharSequence) : Parcelable
data class NicknameArg(val mac: MacAddress, val nickname: CharSequence) : Parcelable
class NicknameDialogFragment : AlertDialogFragment<NicknameArg, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setView(R.layout.dialog_nickname)
setTitle(getString(R.string.clients_nickname_title, MacAddressCompat(arg.mac).toString()))
setTitle(getString(R.string.clients_nickname_title, arg.mac))
setPositiveButton(android.R.string.ok, listener)
setNegativeButton(android.R.string.cancel, null)
setNeutralButton(getText(R.string.clients_nickname_set_to_vendor), listener)
@@ -64,7 +67,7 @@ class ClientsFragment : Fragment() {
}
override fun onClick(dialog: DialogInterface?, which: Int) {
val mac = MacAddressCompat(arg.mac)
val mac = arg.mac
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val newNickname = this.dialog!!.findViewById<EditText>(android.R.id.edit).text
@@ -135,7 +138,7 @@ class ClientsFragment : Fragment() {
R.id.nickname -> {
val client = binding.client ?: return false
NicknameDialogFragment().apply {
arg(NicknameArg(client.mac.addr, client.nickname))
arg(NicknameArg(client.mac, client.nickname))
}.showAllowingStateLoss(parentFragmentManager)
true
}
@@ -160,7 +163,7 @@ class ClientsFragment : Fragment() {
withContext(Dispatchers.Unconfined) {
StatsDialogFragment().apply {
arg(StatsArg(client.title.value ?: return@withContext,
AppDatabase.instance.trafficRecordDao.queryStats(client.mac.addr)))
AppDatabase.instance.trafficRecordDao.queryStats(client.mac)))
}.showAllowingStateLoss(parentFragmentManager)
}
}
@@ -201,9 +204,7 @@ class ClientsFragment : Fragment() {
check(newRecord.receivedPackets == oldRecord.receivedPackets)
check(newRecord.receivedBytes == oldRecord.receivedBytes)
} else {
val rate = rates.computeIfAbsent(newRecord.downstream to MacAddressCompat(newRecord.mac)) {
TrafficRate()
}
val rate = rates.computeIfAbsent(newRecord.downstream to newRecord.mac) { TrafficRate() }
if (rate.send < 0 || rate.receive < 0) {
rate.send = 0
rate.receive = 0
@@ -218,7 +219,7 @@ class ClientsFragment : Fragment() {
private lateinit var binding: FragmentClientsBinding
private val adapter = ClientAdapter()
private var rates = mutableMapOf<Pair<String, MacAddressCompat>, TrafficRate>()
private var rates = mutableMapOf<Pair<String, MacAddress>, TrafficRate>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentClientsBinding.inflate(inflater, container, false)

View File

@@ -1,12 +1,11 @@
package be.mygod.vpnhotspot.client
import android.content.Context
import android.content.Context
import android.net.MacAddress
import androidx.annotation.MainThread
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.util.disconnectCompat
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -22,25 +21,26 @@ import java.net.URL
* This class generates a default nickname for new clients.
*/
object MacLookup {
class UnexpectedError(val mac: MacAddressCompat, val error: String) : JSONException("") {
class UnexpectedError(val mac: MacAddress, val error: String) : JSONException("") {
private fun formatMessage(context: Context) =
context.getString(R.string.clients_mac_lookup_unexpected_error, mac.toOui(), error)
context.getString(R.string.clients_mac_lookup_unexpected_error,
mac.toByteArray().joinToString("") { "%02x".format(it) }.substring(0, 9), error)
override val message get() = formatMessage(app.english)
override fun getLocalizedMessage() = formatMessage(app)
}
private val macLookupBusy = mutableMapOf<MacAddressCompat, Pair<HttpURLConnection, Job>>()
private val macLookupBusy = mutableMapOf<MacAddress, Pair<HttpURLConnection, Job>>()
// http://en.wikipedia.org/wiki/ISO_3166-1
private val countryCodeRegex = "(?:^|[^A-Z])([A-Z]{2})[\\s\\d]*$".toRegex()
@MainThread
fun abort(mac: MacAddressCompat) = macLookupBusy.remove(mac)?.let { (conn, job) ->
fun abort(mac: MacAddress) = macLookupBusy.remove(mac)?.let { (conn, job) ->
job.cancel()
conn.disconnectCompat()
conn.disconnect()
}
@MainThread
fun perform(mac: MacAddressCompat, explicit: Boolean = false) {
fun perform(mac: MacAddress, explicit: Boolean = false) {
abort(mac)
val conn = URL("https://macvendors.co/api/$mac").openConnection() as HttpURLConnection
macLookupBusy[mac] = conn to GlobalScope.launch(Dispatchers.IO) {
@@ -70,7 +70,7 @@ object MacLookup {
}
}
private fun extractCountry(mac: MacAddressCompat, response: String, obj: JSONObject): MatchResult? {
private fun extractCountry(mac: MacAddress, response: String, obj: JSONObject): MatchResult? {
countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
val address = obj.optString("address")
if (address.isBlank()) return null

View File

@@ -1,7 +1,6 @@
package be.mygod.vpnhotspot.manage
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
@@ -9,7 +8,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -38,7 +36,6 @@ class BluetoothTethering(context: Context, private val adapter: BluetoothAdapter
/**
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#215
*/
@TargetApi(24)
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
BluetoothAdapter.STATE_ON -> {
@@ -109,7 +106,6 @@ class BluetoothTethering(context: Context, private val adapter: BluetoothAdapter
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384
*/
@SuppressLint("MissingPermission")
@RequiresApi(24)
fun start(callback: TetheringManager.StartTetheringCallback, context: Context) {
if (pendingCallback == null) try {
if (adapter.state == BluetoothAdapter.STATE_OFF) {
@@ -123,7 +119,6 @@ class BluetoothTethering(context: Context, private val adapter: BluetoothAdapter
pendingCallback = null
}
}
@RequiresApi(24)
fun stop(callback: (Exception) -> Unit) {
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, callback)
stoppedByUser = true

View File

@@ -2,7 +2,6 @@ package be.mygod.vpnhotspot.manage
import android.content.Intent
import android.view.View
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.TetheringService
@@ -25,7 +24,7 @@ class InterfaceManager(private val parent: TetheringFragment, val iface: String)
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)
else context.startForegroundService(Intent(context, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, arrayOf(iface)))
}
}

View File

@@ -1,14 +1,12 @@
package be.mygod.vpnhotspot.manage
import android.service.quicksettings.Tile
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.KillableTileService
import java.net.Inet4Address
@RequiresApi(24)
abstract class IpNeighbourMonitoringTileService : KillableTileService(), IpNeighbourMonitor.Callback {
private var neighbours: Collection<IpNeighbour> = emptyList()
abstract fun updateTile()

View File

@@ -1,14 +1,14 @@
package be.mygod.vpnhotspot.manage
import android.Manifest
import android.content.*
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
import android.view.View
import androidx.annotation.RequiresApi
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.LocalOnlyHotspotService
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
@@ -16,7 +16,6 @@ import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.formatAddresses
import java.net.NetworkInterface
@RequiresApi(26)
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
companion object {
val permission = when {

View File

@@ -6,12 +6,10 @@ import android.content.Intent
import android.graphics.drawable.Icon
import android.os.IBinder
import android.service.quicksettings.Tile
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.LocalOnlyHotspotService
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.util.stopAndUnbind
@RequiresApi(26)
class LocalOnlyHotspotTileService : IpNeighbourMonitoringTileService() {
private val tile by lazy { Icon.createWithResource(application, R.drawable.ic_action_perm_scan_wifi) }

View File

@@ -16,7 +16,7 @@ object ManageBar : Manager() {
private const val SETTINGS_2 = "com.android.settings.TetherSettings"
object Data : BaseObservable() {
val offloadEnabled get() = TetherOffloadManager.supported && TetherOffloadManager.enabled
val offloadEnabled get() = TetherOffloadManager.enabled
}
class ViewHolder(binding: ListitemManageBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
init {

View File

@@ -1,7 +1,6 @@
package be.mygod.vpnhotspot.manage
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
@@ -18,7 +17,6 @@ abstract class Manager {
const val VIEW_TYPE_USB = 3
const val VIEW_TYPE_BLUETOOTH = 4
const val VIEW_TYPE_ETHERNET = 8
const val VIEW_TYPE_WIFI_LEGACY = 5
const val VIEW_TYPE_LOCAL_ONLY_HOTSPOT = 6
const val VIEW_TYPE_REPEATER = 7
@@ -33,11 +31,10 @@ abstract class Manager {
VIEW_TYPE_WIFI,
VIEW_TYPE_USB,
VIEW_TYPE_BLUETOOTH,
VIEW_TYPE_ETHERNET,
VIEW_TYPE_WIFI_LEGACY -> {
VIEW_TYPE_ETHERNET -> {
TetherManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false))
}
VIEW_TYPE_LOCAL_ONLY_HOTSPOT -> @TargetApi(26) {
VIEW_TYPE_LOCAL_ONLY_HOTSPOT -> {
LocalOnlyHotspotManager.ViewHolder(ListitemInterfaceBinding.inflate(inflater, parent, false))
}
VIEW_TYPE_REPEATER -> RepeaterManager.ViewHolder(ListitemRepeaterBinding.inflate(inflater, parent, false))

View File

@@ -5,6 +5,7 @@ import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
import android.content.ServiceConnection
import android.net.MacAddress
import android.net.wifi.SoftApConfiguration
import android.net.wifi.p2p.WifiP2pGroup
import android.os.Build
@@ -16,16 +17,18 @@ import android.view.WindowManager
import android.widget.EditText
import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.*
import be.mygod.vpnhotspot.AlertDialogFragment
import be.mygod.vpnhotspot.BR
import be.mygod.vpnhotspot.Empty
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
@@ -34,7 +37,11 @@ import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.formatAddresses
import be.mygod.vpnhotspot.util.showAllowingStateLoss
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import timber.log.Timber
import java.net.NetworkInterface
@@ -91,7 +98,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
val binder = binder
when (binder?.service?.status) {
RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context ->
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
context.startForegroundService(Intent(context, RepeaterService::class.java))
} else parent.startRepeater.launch(if (Build.VERSION.SDK_INT >= 33) {
Manifest.permission.NEARBY_WIFI_DEVICES
} else Manifest.permission.ACCESS_FINE_LOCATION)
@@ -229,7 +236,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
if (e !is CancellationException) Timber.w(e)
passphrase = group.passphrase
try {
bssid = group.owner?.deviceAddress?.let(MacAddressCompat.Companion::fromString)
bssid = group.owner?.deviceAddress?.let(MacAddress::fromString)
} catch (_: IllegalArgumentException) { }
this to true
}

View File

@@ -7,15 +7,12 @@ import android.graphics.drawable.Icon
import android.net.wifi.p2p.WifiP2pGroup
import android.os.IBinder
import android.service.quicksettings.Tile
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.util.KillableTileService
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.stopAndUnbind
@RequiresApi(24)
class RepeaterTileService : KillableTileService() {
private val tile by lazy { Icon.createWithResource(application, R.drawable.ic_action_settings_input_antenna) }
@@ -37,8 +34,7 @@ class RepeaterTileService : KillableTileService() {
val binder = binder
if (binder == null) tapPending = true else when (binder.service.status) {
RepeaterService.Status.ACTIVE -> binder.shutdown()
RepeaterService.Status.IDLE -> ContextCompat.startForegroundService(this,
Intent(this, RepeaterService::class.java))
RepeaterService.Status.IDLE -> startForegroundService(Intent(this, RepeaterService::class.java))
else -> { }
}
}

View File

@@ -5,7 +5,6 @@ import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Parcelable
@@ -27,7 +26,6 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.*
import be.mygod.vpnhotspot.net.wifi.WifiApManager.wifiApState
import be.mygod.vpnhotspot.root.WifiApCommands
import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.widget.SmartSnackbar
@@ -57,7 +55,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun onClick(v: View?) {
val manager = manager!!
val mainActivity = manager.parent.activity as MainActivity
if (Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(mainActivity)) try {
if (!Settings.System.canWrite(mainActivity)) try {
manager.parent.startActivity(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
"package:${mainActivity.packageName}".toUri()))
return
@@ -134,41 +132,29 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
TetheringManager.getLastTetherError(iface)
} else lastErrors[iface] ?: 0)
} catch (e: InvocationTargetException) {
if (Build.VERSION.SDK_INT !in 24..25 || e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
if (e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
e.readableMessage
}
}
data.notifyChange()
}
@RequiresApi(24)
class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver,
WifiApManager.SoftApCallbackCompat {
private val receiver = broadcastReceiver { _, intent ->
failureReason = if (intent.wifiApState == WifiApManager.WIFI_AP_STATE_FAILED) {
intent.getIntExtra(WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0)
} else null
data.notifyChange()
}
private var failureReason: Int? = null
private var numClients: Int? = null
private var info = emptyList<Parcelable>()
private var capability: Parcelable? = null
init {
if (Build.VERSION.SDK_INT >= 23) parent.viewLifecycleOwner.lifecycle.addObserver(this)
parent.viewLifecycleOwner.lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
if (Build.VERSION.SDK_INT < 28) {
parent.requireContext().registerReceiver(receiver,
IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))
} else WifiApCommands.registerSoftApCallback(this)
WifiApCommands.registerSoftApCallback(this)
}
override fun onStop(owner: LifecycleOwner) {
if (Build.VERSION.SDK_INT < 28) {
parent.requireContext().unregisterReceiver(receiver)
} else WifiApCommands.unregisterSoftApCallback(this)
WifiApCommands.unregisterSoftApCallback(this)
}
override fun onStateChanged(state: Int, failureReason: Int) {
@@ -277,7 +263,6 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException)
}
@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
@@ -286,7 +271,6 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_USB, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException)
}
@RequiresApi(24)
class Bluetooth(parent: TetheringFragment, adapter: BluetoothAdapter) :
TetherManager(parent), DefaultLifecycleObserver {
private val tethering = BluetoothTethering(parent.requireContext(), adapter) { data.notifyChange() }
@@ -332,23 +316,4 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_ETHERNET, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET, this::onException)
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26, malfunctioning on API 25")
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() = try {
WifiApManager.start()
} catch (e: Exception) {
onException(e)
}
override fun stop() = try {
WifiApManager.stop()
} catch (e: Exception) {
onException(e)
}
}
}

View File

@@ -15,7 +15,6 @@ import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
@@ -51,16 +50,13 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager),
TetheringManager.TetheringEventCallback {
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
@get:RequiresApi(26)
internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
@get:RequiresApi(24)
internal val localOnlyHotspotManager by lazy { LocalOnlyHotspotManager(this@TetheringFragment) }
internal val bluetoothManager by lazy {
if (Build.VERSION.SDK_INT >= 24) requireContext().getSystemService<BluetoothManager>()?.adapter?.let {
requireContext().getSystemService<BluetoothManager>()?.adapter?.let {
TetherManager.Bluetooth(this@TetheringFragment, it)
} else null
}
}
@get:RequiresApi(24)
private val tetherManagers by lazy @TargetApi(24) {
private val tetherManagers by lazy {
listOfNotNull(
TetherManager.Wifi(this@TetheringFragment),
TetherManager.Usb(this@TetheringFragment),
@@ -69,7 +65,6 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
}
@get:RequiresApi(30)
private val ethernetManager by lazy @TargetApi(30) { TetherManager.Ethernet(this@TetheringFragment) }
private val wifiManagerLegacy by lazy { TetherManager.WifiLegacy(this@TetheringFragment) }
var activeIfaces = emptyList<String>()
var localOnlyIfaces = emptyList<String>()
@@ -106,24 +101,18 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
val list = ArrayList<Manager>()
if (Services.p2p != null) list.add(repeaterManager)
if (Build.VERSION.SDK_INT >= 26) list.add(localOnlyHotspotManager)
list.add(localOnlyHotspotManager)
val monitoredIfaces = binder?.monitoredIfaces ?: emptyList()
updateMonitorList(activeIfaces - monitoredIfaces)
updateMonitorList(activeIfaces - monitoredIfaces.toSet())
list.addAll((activeIfaces + monitoredIfaces).toSortedSet()
.map { InterfaceManager(this@TetheringFragment, it) })
list.add(ManageBar)
if (Build.VERSION.SDK_INT >= 24) {
list.addAll(tetherManagers)
tetherManagers.forEach { it.updateErrorMessage(erroredIfaces, lastErrors) }
}
list.addAll(tetherManagers)
tetherManagers.forEach { it.updateErrorMessage(erroredIfaces, lastErrors) }
if (Build.VERSION.SDK_INT >= 30) {
list.add(ethernetManager)
ethernetManager.updateErrorMessage(erroredIfaces, lastErrors)
}
if (Build.VERSION.SDK_INT < 26) {
list.add(wifiManagerLegacy)
wifiManagerLegacy.onTetheringStarted()
}
submitList(list) { deferred.complete(list) }
}
@@ -140,7 +129,6 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show()
}
}
@RequiresApi(26)
val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
adapter.localOnlyHotspotManager.start(requireContext())
}
@@ -170,7 +158,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
item.subMenu!!.apply {
clear()
for (iface in canMonitor.sorted()) add(iface).setOnMenuItemClickListener {
ContextCompat.startForegroundService(activity, Intent(activity, TetheringService::class.java)
activity.startForegroundService(Intent(activity, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, iface))
true
}
@@ -183,14 +171,14 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
R.id.configuration -> item.subMenu!!.run {
findItem(R.id.configuration_repeater).isNotGone = Services.p2p != null
findItem(R.id.configuration_temp_hotspot).isNotGone =
Build.VERSION.SDK_INT >= 26 && adapter.localOnlyHotspotManager.binder?.configuration != null
adapter.localOnlyHotspotManager.binder?.configuration != null
true
}
R.id.configuration_repeater -> {
adapter.repeaterManager.configure()
true
}
R.id.configuration_temp_hotspot -> @TargetApi(26) {
R.id.configuration_temp_hotspot -> {
WifiApDialogFragment().apply {
arg(WifiApDialogFragment.Arg(adapter.localOnlyHotspotManager.binder?.configuration ?: return false,
readOnly = true))
@@ -216,7 +204,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
null
} catch (eRoot: Exception) {
eRoot.addSuppressed(e)
if (Build.VERSION.SDK_INT !in 26..29 || eRoot.getRootCause() !is SecurityException) {
if (Build.VERSION.SDK_INT >= 29 || eRoot.getRootCause() !is SecurityException) {
Timber.w(eRoot)
}
SmartSnackbar.make(eRoot).show()
@@ -245,7 +233,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
if (which == DialogInterface.BUTTON_POSITIVE) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
val configuration = ret!!.configuration
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT in 28 until 30 &&
if (Build.VERSION.SDK_INT < 30 &&
configuration.isAutoShutdownEnabled != TetherTimeoutMonitor.enabled) try {
TetherTimeoutMonitor.setEnabled(configuration.isAutoShutdownEnabled)
} catch (e: Exception) {
@@ -299,7 +287,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange()
ManageBar.Data.notifyChange()
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {

View File

@@ -11,14 +11,12 @@ import android.os.IBinder
import android.service.quicksettings.Tile
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.TetheringService
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.readableMessage
import be.mygod.vpnhotspot.util.stopAndUnbind
@@ -27,7 +25,6 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
@RequiresApi(24)
sealed class TetheringTileService : IpNeighbourMonitoringTileService(), TetheringManager.StartTetheringCallback {
protected val tileOff by lazy { Icon.createWithResource(application, icon) }
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
@@ -110,7 +107,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
stop()
} catch (e: Exception) {
onException(e)
} else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
} else startForegroundService(Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
}
}
@@ -211,7 +208,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
stop()
} catch (e: Exception) {
onException(e)
} else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
} else startForegroundService(Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
}
}
@@ -228,23 +225,4 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_ETHERNET, true, this)
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_ETHERNET, this::onException)
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 25")
class WifiLegacy : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_wifi_legacy
override val tetherType get() = TetherType.WIFI
override val icon get() = R.drawable.ic_device_wifi_tethering
override fun start() = try {
WifiApManager.start()
} catch (e: Exception) {
onException(e)
}
override fun stop() = try {
WifiApManager.stop()
} catch (e: Exception) {
onException(e)
}
}
}

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.net
import android.net.MacAddress
import android.os.Build
import android.system.ErrnoException
import android.system.Os
@@ -15,7 +16,7 @@ import java.io.IOException
import java.net.Inet4Address
import java.net.InetAddress
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddressCompat, val state: State) {
data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddress, val state: State) {
enum class State {
INCOMPLETE, VALID, FAILED, DELETING
}
@@ -65,7 +66,7 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
return devs.map { IpNeighbour(ip, it, lladdr, State.DELETING) }
}
if (match.groups[4] != null) try {
lladdr = MacAddressCompat.fromString(match.groupValues[4])
lladdr = MacAddress.fromString(match.groupValues[4])
} catch (e: IllegalArgumentException) {
if (state != State.INCOMPLETE && state != State.DELETING) {
Timber.w(IOException("Failed to find MAC address for $line", e))
@@ -79,7 +80,7 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
val list = arp()
.asSequence()
.filter { parseNumericAddress(it[ARP_IP_ADDRESS]) == ip && it[ARP_DEVICE] in devs }
.map { MacAddressCompat.fromString(it[ARP_HW_ADDRESS]) }
.map { MacAddress.fromString(it[ARP_HW_ADDRESS]) }
.filter { it != MacAddressCompat.ALL_ZEROS_ADDRESS }
.distinct()
.toList()
@@ -138,5 +139,4 @@ data class IpNeighbour(val ip: InetAddress, val dev: String, val lladdr: MacAddr
data class IpDev(val ip: InetAddress, val dev: String) {
override fun toString() = "$ip%$dev"
}
@Suppress("FunctionName")
fun IpDev(neighbour: IpNeighbour) = IpDev(neighbour.ip, neighbour.dev)

View File

@@ -1,95 +1,34 @@
package be.mygod.vpnhotspot.net
import android.net.MacAddress
import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* Compat support class for [MacAddress].
* This used to be a compat support class for [MacAddress].
* Now it is just a convenient class for backwards compatibility.
*/
@JvmInline
value class MacAddressCompat(val addr: Long) {
companion object {
private const val ETHER_ADDR_LEN = 6
/**
* The MacAddress zero MAC address.
*
* Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
* @hide
*/
val ALL_ZEROS_ADDRESS = MacAddressCompat(0)
val ANY_ADDRESS = MacAddressCompat(2)
val ALL_ZEROS_ADDRESS = MacAddress.fromBytes(byteArrayOf(0, 0, 0, 0, 0, 0))
val ANY_ADDRESS = MacAddress.fromBytes(byteArrayOf(2, 0, 0, 0, 0, 0))
/**
* Creates a MacAddress from the given byte array representation.
* A valid byte array representation for a MacAddress is a non-null array of length 6.
*
* @param addr a byte array representation of a MAC address.
* @return the MacAddress corresponding to the given byte array representation.
* @throws IllegalArgumentException if the given byte array is not a valid representation.
*/
fun fromBytes(addr: ByteArray): MacAddressCompat {
val buffer = when (addr.size) {
ETHER_ADDR_LEN -> ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr)
8 -> {
require(addr.take(2).all { it == 0.toByte() }) {
"Unrecognized padding " + addr.joinToString(":") { "%02x".format(it) }
}
ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr, 2, ETHER_ADDR_LEN)
}
else -> return fromString(String(addr))
}
buffer.rewind()
return MacAddressCompat(buffer.long)
}
/**
* Creates a MacAddress from the given String representation. A valid String representation
* for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal
* and joined by ':' characters.
*
* @param addr a String representation of a MAC address.
* @return the MacAddress corresponding to the given String representation.
* @throws IllegalArgumentException if the given String is not a valid representation.
*/
fun fromString(addr: String) = ByteBuffer.allocate(Long.SIZE_BYTES).run {
fun MacAddress.toLong() = ByteBuffer.allocate(Long.SIZE_BYTES).apply {
order(ByteOrder.LITTLE_ENDIAN)
var start = 0
var i = 0
while (position() < ETHER_ADDR_LEN && start < addr.length) {
val end = i
if (addr.getOrElse(i) { ':' } == ':') ++i else if (i < start + 2) {
++i
continue
}
put(if (start == end) 0 else try {
Integer.parseInt(addr.substring(start, end), 16).toByte()
} catch (e: NumberFormatException) {
throw IllegalArgumentException(e)
})
start = i
}
require(position() == ETHER_ADDR_LEN) { "MAC address too short" }
put(toByteArray())
rewind()
MacAddressCompat(long)
}
@RequiresApi(28)
fun MacAddress.toCompat() = fromBytes(toByteArray())
}.long
}
fun validate() = require(addr and ((1L shl 48) - 1).inv() == 0L)
fun toList() = ByteBuffer.allocate(8).run {
fun toPlatform() = MacAddress.fromBytes(ByteBuffer.allocate(8).run {
order(ByteOrder.LITTLE_ENDIAN)
putLong(addr)
array().take(6)
}
@RequiresApi(28)
fun toPlatform() = MacAddress.fromBytes(toList().toByteArray())
override fun toString() = toList().joinToString(":") { "%02x".format(it) }
fun toOui() = toList().joinToString("") { "%02x".format(it) }.substring(0, 9)
}.toByteArray())
}

View File

@@ -1,11 +1,9 @@
package be.mygod.vpnhotspot.net
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.net.LinkProperties
import android.net.MacAddress
import android.net.RouteInfo
import android.os.Build
import androidx.annotation.RequiresApi
import android.system.Os
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
@@ -15,7 +13,9 @@ import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.root.RoutingCommands
import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.util.allInterfaceNames
import be.mygod.vpnhotspot.util.allRoutes
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.CancellationException
import timber.log.Timber
@@ -125,7 +125,6 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
*
* Source: https://android.googlesource.com/platform/system/netd/+/3b47c793ff7ade843b1d85a9be8461c3b4dc693e
*/
@RequiresApi(28)
Netd,
}
@@ -155,14 +154,14 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
* The only case when upstream is null is on API 23- and we are using system default rules.
*/
inner class Subrouting(priority: Int, val upstream: String) {
val ifindex = if (upstream.isEmpty()) 0 else if_nametoindex(upstream).also {
val ifindex = if (upstream.isEmpty()) 0 else Os.if_nametoindex(upstream).also {
if (it <= 0) throw InterfaceGoneException(upstream)
}
val transaction = RootSession.beginTransaction().safeguard {
if (upstream.isEmpty()) {
ipRule("goto $RULE_PRIORITY_TETHERING", priority) // skip unreachable rule
} else ipRuleLookup(ifindex, priority)
@TargetApi(28) when (masqueradeMode) {
when (masqueradeMode) {
MasqueradeMode.None -> { } // nothing to be done here
MasqueradeMode.Simple -> {
// note: specifying -i wouldn't work for POSTROUTING
@@ -225,16 +224,10 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
updateDnsRoute()
}
}
private val fallbackUpstream = object : Upstream(RULE_PRIORITY_UPSTREAM_FALLBACK) {
@SuppressLint("NewApi")
override fun onFallback() = onAvailable(LinkProperties().apply {
interfaceName = ""
setDnsServers(listOf(parseNumericAddress("8.8.8.8")))
})
}
private val fallbackUpstream = Upstream(RULE_PRIORITY_UPSTREAM_FALLBACK)
private val upstream = Upstream(RULE_PRIORITY_UPSTREAM)
private inner class Client(private val ip: Inet4Address, mac: MacAddressCompat) : AutoCloseable {
private inner class Client(private val ip: Inet4Address, mac: MacAddress) : AutoCloseable {
private val transaction = RootSession.beginTransaction().safeguard {
val address = ip.hostAddress
iptablesInsert("vpnhotspot_acl -i $downstream -s $address -j ACCEPT")
@@ -287,9 +280,9 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
* but may be broken when system tethering shutdown before local-only interfaces.
*/
fun ipForward() {
if (Build.VERSION.SDK_INT >= 23) try {
try {
transaction.ndc("ipfwd", "ndc ipfwd enable vpnhotspot_$downstream",
"ndc ipfwd disable vpnhotspot_$downstream")
"ndc ipfwd disable vpnhotspot_$downstream")
return
} catch (e: RoutingCommands.UnexpectedOutputException) {
Timber.w(IOException("ndc ipfwd enable failure", e))

View File

@@ -1,10 +1,8 @@
package be.mygod.vpnhotspot.net
import android.os.Build
import android.provider.Settings
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.root.SettingsGlobalPut
import timber.log.Timber
/**
* It's hard to change tethering rules with Tethering hardware acceleration enabled for now.
@@ -15,19 +13,6 @@ import timber.log.Timber
* https://android.googlesource.com/platform/hardware/qcom/data/ipacfg-mgr/+/master/msm8998/ipacm/src/IPACM_OffloadManager.cpp
*/
object TetherOffloadManager {
val supported by lazy {
Build.VERSION.SDK_INT >= 27 || try {
Settings.Global::class.java.getDeclaredField("TETHER_OFFLOAD_DISABLED").get(null).let {
require(it == TETHER_OFFLOAD_DISABLED) { "Unknown field $it" }
}
true
} catch (_: NoSuchFieldException) {
false
} catch (e: Exception) {
Timber.w(e)
false
}
}
private const val TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"
val enabled get() = Settings.Global.getInt(app.contentResolver, TETHER_OFFLOAD_DISABLED, 0) == 0
suspend fun setEnabled(value: Boolean) = SettingsGlobalPut.int(TETHER_OFFLOAD_DISABLED, if (value) 0 else 1)

View File

@@ -94,9 +94,7 @@ object TetheringManager {
* https://android.googlesource.com/platform/frameworks/base.git/+/2a091d7aa0c174986387e5d56bf97a87fe075bdb%5E%21/services/java/com/android/server/connectivity/Tethering.java
*/
const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"
@RequiresApi(26)
private const val EXTRA_ACTIVE_LOCAL_ONLY_LEGACY = "localOnlyArray"
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)
@@ -107,7 +105,6 @@ object TetheringManager {
* 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
@@ -131,7 +128,6 @@ object TetheringManager {
* Wifi tethering type.
* @see [startTethering].
*/
@RequiresApi(24)
const val TETHERING_WIFI = 0
/**
* USB tethering type.
@@ -141,7 +137,6 @@ object TetheringManager {
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
* @see startTethering
*/
@RequiresApi(24)
const val TETHERING_USB = 1
/**
* Bluetooth tethering type.
@@ -149,7 +144,6 @@ object TetheringManager {
* Requires BLUETOOTH permission.
* @see startTethering
*/
@RequiresApi(24)
const val TETHERING_BLUETOOTH = 2
/**
* Ethernet tethering type.
@@ -178,16 +172,13 @@ object TetheringManager {
}
}.first()
@get:RequiresApi(24)
private val classOnStartTetheringCallback by lazy {
Class.forName("android.net.ConnectivityManager\$OnStartTetheringCallback")
}
@get:RequiresApi(24)
private val startTetheringLegacy by lazy @TargetApi(24) {
private val startTetheringLegacy by lazy {
ConnectivityManager::class.java.getDeclaredMethod("startTethering",
Int::class.java, Boolean::class.java, classOnStartTetheringCallback, Handler::class.java)
}
@get:RequiresApi(24)
private val stopTetheringLegacy by lazy {
ConnectivityManager::class.java.getDeclaredMethod("stopTethering", Int::class.java)
}
@@ -232,7 +223,6 @@ object TetheringManager {
private val stopTethering by lazy @TargetApi(30) { clazz.getDeclaredMethod("stopTethering", Int::class.java) }
@Deprecated("Legacy API")
@RequiresApi(24)
fun startTetheringLegacy(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
val reference = WeakReference(callback)
@@ -299,7 +289,6 @@ object TetheringManager {
* configures tethering with the preferred local IPv4 link address to use.
* *@see setStaticIpv4Addresses
*/
@RequiresApi(24)
fun startTethering(type: Int, showProvisioningUi: Boolean, callback: StartTetheringCallback,
handler: Handler? = null, cacheDir: File = app.deviceStorage.codeCacheDir) {
if (Build.VERSION.SDK_INT >= 30) try {
@@ -371,12 +360,10 @@ object TetheringManager {
* {@link ConnectivityManager.TETHERING_USB}, or
* {@link ConnectivityManager.TETHERING_BLUETOOTH}.
*/
@RequiresApi(24)
fun stopTethering(type: Int) {
if (Build.VERSION.SDK_INT >= 30) stopTethering(instance, type)
else stopTetheringLegacy(Services.connectivity, type)
}
@RequiresApi(24)
fun stopTethering(type: Int, callback: (Exception) -> Unit) {
try {
stopTethering(type)
@@ -625,10 +612,7 @@ object TetheringManager {
@RequiresApi(30)
const val TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14
val Intent.tetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
val Intent.localOnlyTetheredIfaces get() = if (Build.VERSION.SDK_INT >= 26) {
getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 30) EXTRA_ACTIVE_LOCAL_ONLY else EXTRA_ACTIVE_LOCAL_ONLY_LEGACY)
} else emptyList<String>()
val Intent.tetheredIfaces get() = getStringArrayListExtra(EXTRA_ACTIVE_TETHER)
val Intent.localOnlyTetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 30) EXTRA_ACTIVE_LOCAL_ONLY else EXTRA_ACTIVE_LOCAL_ONLY_LEGACY)
}

View File

@@ -1,6 +1,5 @@
package be.mygod.vpnhotspot.net.monitor
import android.annotation.TargetApi
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
@@ -52,29 +51,10 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
when (Build.VERSION.SDK_INT) {
in 31..Int.MAX_VALUE -> @TargetApi(31) {
Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
Services.mainHandler)
}
in 28..30 -> @TargetApi(28) {
Services.connectivity.requestNetwork(networkRequest, networkCallback, Services.mainHandler)
}
in 26..27 -> @TargetApi(26) {
Services.connectivity.registerDefaultNetworkCallback(networkCallback, Services.mainHandler)
}
in 24..25 -> @TargetApi(24) {
Services.connectivity.registerDefaultNetworkCallback(networkCallback)
}
else -> try {
Services.connectivity.requestNetwork(networkRequest, networkCallback)
} catch (e: RuntimeException) {
// SecurityException would be thrown in requestNetwork on Android 6.0 thanks to Google's stupid bug
if (Build.VERSION.SDK_INT != 23) throw e
GlobalScope.launch { callback.onFallback() }
return
}
}
if (Build.VERSION.SDK_INT >= 31) {
Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
Services.mainHandler)
} else Services.connectivity.requestNetwork(networkRequest, networkCallback, Services.mainHandler)
registered = true
}
}

View File

@@ -77,7 +77,7 @@ class InterfaceMonitor(private val ifaceRegex: String) : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
Services.registerNetworkCallbackCompat(request, networkCallback)
Services.registerNetworkCallback(request, networkCallback)
registered = true
}
}

View File

@@ -5,7 +5,6 @@ import androidx.core.content.edit
import be.mygod.librootkotlinx.RootServer
import be.mygod.librootkotlinx.isEBADF
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.root.ProcessData

View File

@@ -33,13 +33,12 @@ class TetherTimeoutMonitor(private val timeout: Long = 0,
private const val MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000 // 10 minutes
@Deprecated("Use SoftApConfigurationCompat instead")
@get:RequiresApi(28)
val enabled get() = Settings.Global.getInt(app.contentResolver, SOFT_AP_TIMEOUT_ENABLED, 1) == 1
@Deprecated("Use SoftApConfigurationCompat instead")
suspend fun setEnabled(value: Boolean) = SettingsGlobalPut.int(SOFT_AP_TIMEOUT_ENABLED, if (value) 1 else 0)
val defaultTimeout: Int get() {
val delay = if (Build.VERSION.SDK_INT >= 28) try {
val delay = try {
if (Build.VERSION.SDK_INT < 30) Resources.getSystem().run {
getInteger(getIdentifier("config_wifi_framework_soft_ap_timeout_delay", "integer", "android"))
} else {
@@ -52,7 +51,7 @@ class TetherTimeoutMonitor(private val timeout: Long = 0,
} catch (e: RuntimeException) {
Timber.w(e)
MIN_SOFT_AP_TIMEOUT_DELAY_MS
} else MIN_SOFT_AP_TIMEOUT_DELAY_MS
}
return if (Build.VERSION.SDK_INT < 30 && delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) {
Timber.w("Overriding timeout delay with minimum limit value: $delay < $MIN_SOFT_AP_TIMEOUT_DELAY_MS")
MIN_SOFT_AP_TIMEOUT_DELAY_MS

View File

@@ -1,9 +1,9 @@
package be.mygod.vpnhotspot.net.monitor
import android.net.MacAddress
import androidx.collection.LongSparseArray
import androidx.collection.set
import be.mygod.vpnhotspot.net.IpDev
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.room.TrafficRecord
@@ -11,7 +11,12 @@ import be.mygod.vpnhotspot.util.Event2
import be.mygod.vpnhotspot.util.RootSession
import be.mygod.vpnhotspot.util.parseNumericAddress
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.net.InetAddress
import java.util.concurrent.TimeUnit
@@ -23,8 +28,8 @@ object TrafficRecorder {
private val records = mutableMapOf<IpDev, TrafficRecord>()
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
fun register(ip: InetAddress, downstream: String, mac: MacAddressCompat) {
val record = TrafficRecord(mac = mac.addr, ip = ip, downstream = downstream)
fun register(ip: InetAddress, downstream: String, mac: MacAddress) {
val record = TrafficRecord(mac = mac, ip = ip, downstream = downstream)
AppDatabase.instance.trafficRecordDao.insert(record)
synchronized(this) {
val key = IpDev(ip, downstream)
@@ -156,5 +161,5 @@ object TrafficRecorder {
/**
* Possibly inefficient. Don't call this too often.
*/
fun isWorking(mac: MacAddressCompat) = records.values.any { it.mac == mac.addr }
fun isWorking(mac: MacAddress) = records.values.any { it.mac == mac }
}

View File

@@ -46,15 +46,6 @@ abstract class UpstreamMonitor {
* Called if some possibly stacked interface is available
*/
fun onAvailable(properties: LinkProperties? = null)
/**
* Called on API 23- from DefaultNetworkMonitor. This indicates that there isn't a good way of telling the
* default network (see DefaultNetworkMonitor) and we are using rules at priority 22000
* (RULE_PRIORITY_DEFAULT_NETWORK) as our fallback rules, which would work fine until Android 9.0 broke it in
* commit: https://android.googlesource.com/platform/system/netd/+/758627c4d93392190b08e9aaea3bbbfb92a5f364
*/
fun onFallback() {
throw UnsupportedOperationException()
}
}
val callbacks = mutableSetOf<Callback>()

View File

@@ -61,7 +61,7 @@ object VpnMonitor : UpstreamMonitor() {
callback.onAvailable(currentLinkProperties)
}
} else {
Services.registerNetworkCallbackCompat(request, networkCallback)
Services.registerNetworkCallback(request, networkCallback)
registered = true
}
}

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.net.wifi
import android.net.MacAddress
import android.net.wifi.p2p.WifiP2pGroup
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.net.MacAddressCompat
@@ -53,8 +54,8 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
var bssids = listOfNotNull(group?.owner?.deviceAddress, ownerAddress)
.distinct()
.filter {
val mac = MacAddress.fromString(it)
try {
val mac = MacAddressCompat.fromString(it)
mac != MacAddressCompat.ALL_ZEROS_ADDRESS && mac != MacAddressCompat.ANY_ADDRESS
} catch (_: IllegalArgumentException) {
false
@@ -126,7 +127,7 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
add("\tmode=3")
add("\tdisabled=2")
add("}")
if (target == null) target = this
target = this
})
}
content = Content(result, target!!, persistentMacLine, legacy)
@@ -141,10 +142,10 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
}
val psk by lazy { group?.passphrase ?: content.target.psk!! }
val bssid by lazy {
content.target.bssid?.let { MacAddressCompat.fromString(it) }
content.target.bssid?.let { MacAddress.fromString(it) }
}
suspend fun update(ssid: String, psk: String, bssid: MacAddressCompat?) {
suspend fun update(ssid: String, psk: String, bssid: MacAddress?) {
val (lines, block, persistentMacLine, legacy) = content
block[block.ssidLine!!] = "\tssid=" + ssid.toByteArray()
.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }

View File

@@ -9,10 +9,9 @@ import android.os.Build
import android.os.Parcelable
import android.util.SparseIntArray
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.requireSingleBand
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.setChannel
import be.mygod.vpnhotspot.util.ConstantLookup
import be.mygod.vpnhotspot.util.UnblockCentral
import kotlinx.parcelize.Parcelize
@@ -22,8 +21,7 @@ import java.lang.reflect.InvocationTargetException
@Parcelize
data class SoftApConfigurationCompat(
var ssid: String? = null,
@Deprecated("Workaround for using inline class with Parcelize, use bssid")
var bssidAddr: Long? = null,
var bssid: MacAddress? = null,
var passphrase: String? = null,
var isHiddenSsid: Boolean = false,
/**
@@ -31,14 +29,11 @@ data class SoftApConfigurationCompat(
* see also [android.net.wifi.WifiManager.isBridgedApConcurrencySupported].
* Otherwise, use [requireSingleBand] and [setChannel].
*/
@TargetApi(23)
var channels: SparseIntArray = SparseIntArray(1).apply { append(BAND_2GHZ, 0) },
var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN,
@TargetApi(30)
var maxNumberOfClients: Int = 0,
@TargetApi(28)
var isAutoShutdownEnabled: Boolean = true,
@TargetApi(28)
var shutdownTimeoutMillis: Long = 0,
@TargetApi(30)
var isClientControlByUserEnabled: Boolean = false,
@@ -166,7 +161,6 @@ data class SoftApConfigurationCompat(
*
* https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#242
*/
@get:RequiresApi(23)
@Suppress("DEPRECATION")
/**
* The band which AP resides on
@@ -174,7 +168,6 @@ data class SoftApConfigurationCompat(
* By default, 2G is chosen
*/
private val apBand by lazy { android.net.wifi.WifiConfiguration::class.java.getDeclaredField("apBand") }
@get:RequiresApi(23)
@Suppress("DEPRECATION")
/**
* The channel which AP resides on
@@ -356,17 +349,17 @@ data class SoftApConfigurationCompat(
@Suppress("DEPRECATION")
fun android.net.wifi.WifiConfiguration.toCompat() = SoftApConfigurationCompat(
SSID,
BSSID?.let { MacAddressCompat.fromString(it) }?.addr,
BSSID?.let { MacAddress.fromString(it) },
preSharedKey,
hiddenSSID,
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344
SparseIntArray(1).also {
if (Build.VERSION.SDK_INT >= 23) it.append(when (val band = apBand.getInt(this)) {
it.append(when (val band = apBand.getInt(this)) {
0 -> BAND_2GHZ
1 -> BAND_5GHZ
-1 -> BAND_LEGACY
else -> throw IllegalArgumentException("Unexpected band $band")
}, apChannel.getInt(this)) else it.append(BAND_LEGACY, 0)
}, apChannel.getInt(this))
},
allowedKeyManagement.nextSetBit(0).let { selected ->
require(allowedKeyManagement.nextSetBit(selected + 1) < 0) {
@@ -389,14 +382,14 @@ data class SoftApConfigurationCompat(
}
}
},
isAutoShutdownEnabled = if (Build.VERSION.SDK_INT >= 28) TetherTimeoutMonitor.enabled else false,
isAutoShutdownEnabled = TetherTimeoutMonitor.enabled,
underlying = this)
@RequiresApi(30)
@Suppress("UNCHECKED_CAST")
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
ssid,
bssid?.toCompat()?.addr,
bssid,
passphrase,
isHiddenSsid,
if (Build.VERSION.SDK_INT >= 31) getChannels(this) as SparseIntArray else SparseIntArray(1).also {
@@ -466,13 +459,6 @@ data class SoftApConfigurationCompat(
setBridgedModeOpportunisticShutdownTimeoutMillis(staticBuilder, timeout)
}
@Suppress("DEPRECATION")
inline var bssid: MacAddressCompat?
get() = bssidAddr?.let { MacAddressCompat(it) }
set(value) {
bssidAddr = value?.addr
}
fun setChannel(channel: Int, band: Int = BAND_LEGACY) {
channels = SparseIntArray(1).apply {
append(when {
@@ -500,18 +486,15 @@ data class SoftApConfigurationCompat(
result.SSID = ssid
result.preSharedKey = passphrase
result.hiddenSSID = isHiddenSsid
if (Build.VERSION.SDK_INT >= 23) {
apBand.setInt(result, when (band) {
BAND_2GHZ -> 0
BAND_5GHZ -> 1
else -> {
require(Build.VERSION.SDK_INT >= 28) { "A band must be specified on this platform" }
require(isLegacyEitherBand(band)) { "Convert fail, unsupported band setting :$band" }
-1
}
})
apChannel.setInt(result, channel)
} else require(isLegacyEitherBand(band)) { "Specifying band is unsupported on this platform" }
apBand.setInt(result, when (band) {
BAND_2GHZ -> 0
BAND_5GHZ -> 1
else -> {
require(isLegacyEitherBand(band)) { "Convert fail, unsupported band setting :$band" }
-1
}
})
apChannel.setInt(result, channel)
if (original?.securityType != securityType) {
result.allowedKeyManagement.clear()
result.allowedKeyManagement.set(when (securityType) {
@@ -545,9 +528,8 @@ data class SoftApConfigurationCompat(
else -> passphrase
}, securityType)
setChannelsCompat(builder, channels)
setBssid(builder, bssid?.run {
if (Build.VERSION.SDK_INT >= 31 && macRandomizationSetting != RANDOMIZATION_NONE) null else toPlatform()
})
setBssid(builder,
if (Build.VERSION.SDK_INT < 31 || macRandomizationSetting == RANDOMIZATION_NONE) bssid else null)
setMaxNumberOfClients(builder, maxNumberOfClients)
try {
setShutdownTimeoutMillis(builder, shutdownTimeoutMillis)

View File

@@ -1,10 +1,10 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.ClipData
import android.content.ClipDescription
import android.content.DialogInterface
import android.net.MacAddress
import android.net.wifi.SoftApConfiguration
import android.os.Build
import android.os.Parcelable
@@ -31,7 +31,6 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.databinding.DialogWifiApBinding
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
import be.mygod.vpnhotspot.util.QRCodeDialog
import be.mygod.vpnhotspot.util.RangeInput
@@ -68,17 +67,12 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
private val p2pSafeOptions by lazy { genAutoOptions(SoftApConfigurationCompat.BAND_LEGACY) + channels5G }
private val softApOptions by lazy {
when (Build.VERSION.SDK_INT) {
in 30..Int.MAX_VALUE -> {
genAutoOptions(SoftApConfigurationCompat.BAND_ANY_31) +
channels5G +
(1..253).map { ChannelOption(SoftApConfigurationCompat.BAND_6GHZ, it) } +
(1..6).map { ChannelOption(SoftApConfigurationCompat.BAND_60GHZ, it) }
}
in 28 until 30 -> p2pSafeOptions
else -> listOf(ChannelOption(SoftApConfigurationCompat.BAND_2GHZ),
ChannelOption(SoftApConfigurationCompat.BAND_5GHZ)) + channels5G
}
if (Build.VERSION.SDK_INT >= 30) {
genAutoOptions(SoftApConfigurationCompat.BAND_ANY_31) +
channels5G +
(1..253).map { ChannelOption(SoftApConfigurationCompat.BAND_6GHZ, it) } +
(1..6).map { ChannelOption(SoftApConfigurationCompat.BAND_60GHZ, it) }
} else p2pSafeOptions
}
@get:RequiresApi(30)
@@ -149,24 +143,24 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
securityType = dialogView.security.selectedItemPosition
isHiddenSsid = dialogView.hiddenSsid.isChecked
}
if (full) @TargetApi(28) {
if (full) {
isAutoShutdownEnabled = dialogView.autoShutdown.isChecked
shutdownTimeoutMillis = dialogView.timeout.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
}
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) channels = generateChannels()
channels = generateChannels()
maxNumberOfClients = dialogView.maxClient.text.let { text ->
if (text.isNullOrEmpty()) 0 else text.toString().toInt()
}
isClientControlByUserEnabled = dialogView.clientUserControl.isChecked
allowedClientList = (dialogView.allowedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
.filter { it.isNotEmpty() }.map(MacAddress::fromString)
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
.filter { it.isNotEmpty() }.map(MacAddress::fromString)
macRandomizationSetting = dialogView.macRandomization.selectedItemPosition
bssid = if ((arg.p2pMode || Build.VERSION.SDK_INT < 31 && macRandomizationSetting ==
SoftApConfigurationCompat.RANDOMIZATION_NONE) && dialogView.bssid.length() != 0) {
MacAddressCompat.fromString(dialogView.bssid.text.toString())
MacAddress.fromString(dialogView.bssid.text.toString())
} else null
isBridgedModeOpportunisticShutdownEnabled = dialogView.bridgedModeOpportunisticShutdown.isChecked
isIeee80211axEnabled = dialogView.ieee80211ax.isChecked
@@ -177,7 +171,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
vendorElements = VendorElements.deserialize(dialogView.vendorElements.text)
persistentRandomizedMacAddress = if (dialogView.persistentRandomizedMac.length() != 0) {
MacAddressCompat.fromString(dialogView.persistentRandomizedMac.text.toString()).toPlatform()
MacAddress.fromString(dialogView.persistentRandomizedMac.text.toString())
} else null
allowedAcsChannels = acsList.associate { (band, text, _) -> band to RangeInput.fromString(text.text) }
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 33) {
@@ -227,7 +221,6 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
}
if (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment)
if (!arg.p2pMode && Build.VERSION.SDK_INT < 28) dialogView.autoShutdown.isGone = true
if (arg.p2pMode || Build.VERSION.SDK_INT >= 30) {
dialogView.timeoutWrapper.helperText = getString(R.string.wifi_hotspot_timeout_default,
TetherTimeoutMonitor.defaultTimeout)
@@ -239,12 +232,10 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
if (!arg.readOnly) onItemSelectedListener = this@WifiApDialogFragment
}
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
dialogView.bandPrimary.configure(currentChannels)
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.configure(listOf(ChannelOption.Disabled) + currentChannels)
} else dialogView.bandSecondary.isGone = true
} else dialogView.bandGroup.isGone = true
dialogView.bandPrimary.configure(currentChannels)
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.configure(listOf(ChannelOption.Disabled) + currentChannels)
} else dialogView.bandSecondary.isGone = true
if (arg.p2pMode || Build.VERSION.SDK_INT < 30) dialogView.accessControlGroup.isGone = true
else if (!arg.readOnly) {
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
@@ -307,11 +298,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.password.setText(base.passphrase)
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
dialogView.timeout.setText(base.shutdownTimeoutMillis.let { if (it <= 0) "" else it.toString() })
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
dialogView.bandPrimary.setSelection(locate(0))
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.setSelection(if (base.channels.size() > 1) locate(1) + 1 else 0)
}
dialogView.bandPrimary.setSelection(locate(0))
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) {
dialogView.bandSecondary.setSelection(if (base.channels.size() > 1) locate(1) + 1 else 0)
}
dialogView.bssid.setText(base.bssid?.toString())
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
@@ -343,7 +332,6 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
validate()
}
@TargetApi(28)
private fun validate() {
if (!started) return
val ssidLength = dialogView.ssid.text.toString().toByteArray().size
@@ -389,10 +377,8 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.bssidWrapper.isGone = hideBssid
dialogView.bssidWrapper.error = null
val bssidValid = hideBssid || dialogView.bssid.length() == 0 || try {
val mac = MacAddressCompat.fromString(dialogView.bssid.text.toString())
if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) {
SoftApConfigurationCompat.testPlatformValidity(mac.toPlatform())
}
val mac = MacAddress.fromString(dialogView.bssid.text.toString())
if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) SoftApConfigurationCompat.testPlatformValidity(mac)
true
} catch (e: Exception) {
dialogView.bssidWrapper.error = e.readableMessage
@@ -409,15 +395,15 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.maxClientWrapper.error = maxClientError
val listsNoError = if (Build.VERSION.SDK_INT >= 30) {
val (blockedList, blockedListError) = try {
(dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }.toSet() to null
(dialogView.blockedList.text ?: "").split(nonMacChars).filter { it.isNotEmpty() }
.map(MacAddress::fromString).toSet() to null
} catch (e: IllegalArgumentException) {
null to e.readableMessage
}
dialogView.blockedListWrapper.error = blockedListError
val allowedListError = try {
(dialogView.allowedList.text ?: "").split(nonMacChars).filter { it.isNotEmpty() }.forEach {
val mac = MacAddressCompat.fromString(it).toPlatform()
val mac = MacAddress.fromString(it)
require(blockedList?.contains(mac) != true) { "A MAC address exists in both client lists" }
}
null
@@ -449,9 +435,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
dialogView.vendorElementsWrapper.error = vendorElementsError
dialogView.persistentRandomizedMacWrapper.error = null
val persistentRandomizedMacValid = dialogView.persistentRandomizedMac.length() == 0 || try {
MacAddressCompat.fromString(dialogView.persistentRandomizedMac.text.toString())
MacAddress.fromString(dialogView.persistentRandomizedMac.text.toString())
true
} catch (e: Exception) {
} catch (e: IllegalArgumentException) {
dialogView.persistentRandomizedMacWrapper.error = e.readableMessage
false
}
@@ -496,9 +482,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
android.R.id.copy -> try {
app.clipboard.setPrimaryClip(ClipData.newPlainText(null,
Base64.encodeToString(generateConfig().toByteArray(), BASE64_FLAGS)).apply {
if (Build.VERSION.SDK_INT >= 24) {
description.extras = persistableBundleOf(ClipDescription.EXTRA_IS_SENSITIVE to true)
}
description.extras = persistableBundleOf(ClipDescription.EXTRA_IS_SENSITIVE to true)
})
true
} catch (e: RuntimeException) {

View File

@@ -88,7 +88,6 @@ object WifiApManager {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#210
*/
@get:RequiresApi(23)
val EXTRA_WIFI_AP_FAILURE_REASON get() =
if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_FAILURE_REASON" else "wifi_ap_error_code"
/**
@@ -98,7 +97,6 @@ object WifiApManager {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#413
*/
@get:RequiresApi(26)
val EXTRA_WIFI_AP_INTERFACE_NAME get() =
if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME" else "wifi_ap_interface_name"
@@ -177,7 +175,6 @@ object WifiApManager {
setWifiApConfiguration(Services.wifi, value) as Boolean
fun setConfiguration(value: SoftApConfiguration) = setSoftApConfiguration(Services.wifi, value) as Boolean
@RequiresApi(28)
interface SoftApCallbackCompat {
/**
* Called when soft AP state changes.
@@ -239,7 +236,6 @@ object WifiApManager {
@RequiresApi(30)
fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) { }
}
@RequiresApi(23)
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_", "GENERAL", "NO_CHANNEL")
@get:RequiresApi(30)
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
@@ -255,7 +251,6 @@ object WifiApManager {
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
}
@RequiresApi(28)
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
arrayOf(interfaceSoftApCallback), object : InvocationHandler {
@@ -270,7 +265,7 @@ object WifiApManager {
method.matches("onStateChanged", Integer.TYPE, Integer.TYPE) -> {
callback.onStateChanged(args!![0] as Int, args[1] as Int)
}
method.matches("onNumClientsChanged", Integer.TYPE) -> @Suppress("DEPRECATION") {
method.matches("onNumClientsChanged", Integer.TYPE) -> {
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
callback.onNumClientsChanged(args!![0] as Int)
}
@@ -307,7 +302,6 @@ object WifiApManager {
} else registerSoftApCallback(Services.wifi, proxy, null)
return proxy
}
@RequiresApi(28)
fun unregisterSoftApCallback(key: Any) = unregisterSoftApCallback(Services.wifi, key)
private val cancelLocalOnlyHotspotRequest by lazy {
@@ -317,43 +311,5 @@ object WifiApManager {
* This is the only way to unregister requests besides app exiting.
* Therefore, we are happy with crashing the app if reflection fails.
*/
@RequiresApi(26)
fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(Services.wifi)
@Suppress("DEPRECATION")
private val setWifiApEnabled by lazy {
WifiManager::class.java.getDeclaredMethod("setWifiApEnabled",
android.net.wifi.WifiConfiguration::class.java, Boolean::class.java)
}
/**
* Start AccessPoint mode with the specified
* configuration. If the radio is already running in
* AP mode, update the new configuration
* Note that starting in access point mode disables station
* mode operation
* @param wifiConfig SSID, security and channel details as
* part of WifiConfiguration
* @return {@code true} if the operation succeeds, {@code false} otherwise
*/
@Suppress("DEPRECATION")
private fun WifiManager.setWifiApEnabled(wifiConfig: android.net.wifi.WifiConfiguration?, enabled: Boolean) =
setWifiApEnabled(this, wifiConfig, enabled) as Boolean
/**
* Although the functionalities were removed in API 26, it is already not functioning correctly on API 25.
*
* See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/
*/
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26, malfunctioning on API 25")
fun start(wifiConfig: android.net.wifi.WifiConfiguration? = null) {
Services.wifi.isWifiEnabled = false
Services.wifi.setWifiApEnabled(wifiConfig, true)
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26")
fun stop() {
Services.wifi.setWifiApEnabled(null, false)
Services.wifi.isWifiEnabled = true
}
}

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.net.MacAddress
import android.net.wifi.ScanResult
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup
@@ -9,9 +10,8 @@ import android.net.wifi.p2p.WifiP2pManager
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.matchesCompat
import be.mygod.vpnhotspot.util.matches
import kotlinx.coroutines.CompletableDeferred
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
@@ -128,7 +128,7 @@ object WifiP2pManagerHelper {
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when {
method.matchesCompat("onPersistentGroupInfoAvailable", args, classWifiP2pGroupList) -> {
method.matches("onPersistentGroupInfoAvailable", classWifiP2pGroupList) -> {
@Suppress("UNCHECKED_CAST")
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
}
@@ -142,11 +142,11 @@ object WifiP2pManagerHelper {
CompletableDeferred<WifiP2pInfo?>().apply { requestConnectionInfo(c) { complete(it) } }.await()
@SuppressLint("MissingPermission") // missing permission simply leads to null result
@RequiresApi(29)
suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? {
suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddress? {
val future = CompletableDeferred<String?>()
requestDeviceInfo(c) { future.complete(it?.deviceAddress) }
return future.await()?.let {
val address = if (it.isEmpty()) null else MacAddressCompat.fromString(it)
val address = if (it.isEmpty()) null else MacAddress.fromString(it)
if (address == MacAddressCompat.ANY_ADDRESS) null else address
}
}

View File

@@ -63,7 +63,7 @@ class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFrag
override fun onStart() {
super.onStart()
Services.registerNetworkCallbackCompat(globalNetworkRequestBuilder().apply {
Services.registerNetworkCallback(globalNetworkRequestBuilder().apply {
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)

View File

@@ -3,7 +3,6 @@ package be.mygod.vpnhotspot.preference
import android.content.Context
import android.graphics.Typeface
import android.net.LinkProperties
import android.os.Build
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.util.AttributeSet
@@ -41,7 +40,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co
internet == true || try {
route.matches(internetV4Address) || route.matches(internetV6Address)
} catch (e: RuntimeException) {
if (Build.VERSION.SDK_INT >= 23) Timber.w(e) else Timber.d(e)
Timber.w(e)
false
}
}
@@ -52,12 +51,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co
}
private val primary = Monitor()
private val fallback: Monitor = object : Monitor() {
override fun onFallback() {
currentInterfaces = mapOf("<default>" to true)
onUpdate()
}
}
private val fallback = Monitor()
init {
(context as LifecycleOwner).lifecycle.addObserver(this)

View File

@@ -1,37 +1,38 @@
package be.mygod.vpnhotspot.room
import android.net.MacAddress
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
import androidx.room.*
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toLong
@Entity
data class ClientRecord(@PrimaryKey
val mac: Long,
val mac: MacAddress,
var nickname: CharSequence = "",
var blocked: Boolean = false,
var macLookupPending: Boolean = true) {
@androidx.room.Dao
abstract class Dao {
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
protected abstract fun lookupBlocking(mac: Long): ClientRecord?
fun lookupOrDefaultBlocking(mac: MacAddressCompat) = lookupBlocking(mac.addr) ?: ClientRecord(mac.addr)
protected abstract fun lookupBlocking(mac: MacAddress): ClientRecord?
fun lookupOrDefaultBlocking(mac: MacAddress) = lookupBlocking(mac) ?: ClientRecord(mac)
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
protected abstract suspend fun lookup(mac: Long): ClientRecord?
suspend fun lookupOrDefault(mac: Long) = lookup(mac) ?: ClientRecord(mac)
protected abstract suspend fun lookup(mac: MacAddress): ClientRecord?
suspend fun lookupOrDefault(mac: MacAddress) = lookup(mac) ?: ClientRecord(mac)
@Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
protected abstract fun lookupSync(mac: Long): LiveData<ClientRecord?>
fun lookupOrDefaultSync(mac: MacAddressCompat) = lookupSync(mac.addr).map { it ?: ClientRecord(mac.addr) }
protected abstract fun lookupSync(mac: MacAddress): LiveData<ClientRecord?>
fun lookupOrDefaultSync(mac: MacAddress) = lookupSync(mac).map { it ?: ClientRecord(mac) }
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun updateInternal(value: ClientRecord): Long
suspend fun update(value: ClientRecord) = check(updateInternal(value) == value.mac)
suspend fun update(value: ClientRecord) = check(updateInternal(value) == value.mac.toLong())
@Transaction
open suspend fun upsert(mac: MacAddressCompat, operation: suspend ClientRecord.() -> Unit) = lookupOrDefault(
mac.addr).apply {
open suspend fun upsert(mac: MacAddress, operation: suspend ClientRecord.() -> Unit) = lookupOrDefault(
mac).apply {
operation()
update(this)
}

View File

@@ -1,8 +1,11 @@
package be.mygod.vpnhotspot.room
import android.net.MacAddress
import android.text.TextUtils
import androidx.room.TypeConverter
import be.mygod.librootkotlinx.useParcel
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toLong
import timber.log.Timber
import java.net.InetAddress
@@ -27,6 +30,14 @@ object Converters {
}
}
@JvmStatic
@TypeConverter
fun persistMacAddress(address: MacAddress) = address.toLong()
@JvmStatic
@TypeConverter
fun unpersistMacAddress(address: Long) = MacAddressCompat(address).toPlatform()
@JvmStatic
@TypeConverter
fun persistInetAddress(address: InetAddress): ByteArray = address.address

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.room
import android.net.MacAddress
import android.os.Parcelable
import androidx.room.*
import kotlinx.parcelize.Parcelize
@@ -22,7 +23,7 @@ data class TrafficRecord(
/**
* Foreign key/ID for (possibly non-existent, i.e. default) entry in ClientRecord.
*/
val mac: Long,
val mac: MacAddress,
/**
* For now only stats for IPv4 will be recorded. But I'm going to put the more general class here just in case.
*/
@@ -58,7 +59,7 @@ data class TrafficRecord(
/* We only want to find the last record for each chain so that we don't double count */
WHERE TrafficRecord.mac = :mac AND Next.id IS NULL
""")
abstract suspend fun queryStats(mac: Long): ClientStats
abstract suspend fun queryStats(mac: MacAddress): ClientStats
}
}

View File

@@ -1,7 +1,6 @@
package be.mygod.vpnhotspot.root
import android.content.Context
import android.os.Build
import android.os.Parcelable
import android.os.RemoteException
import android.provider.Settings
@@ -30,18 +29,10 @@ fun ProcessBuilder.fixPath(redirect: Boolean = false) = apply {
@Parcelize
data class Dump(val path: String, val cacheDir: File = app.deviceStorage.codeCacheDir) : RootCommandNoResult {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun execute() = withContext(Dispatchers.IO) {
FileOutputStream(path, true).use { out ->
val process = ProcessBuilder("sh").fixPath(true).start()
process.outputStream.bufferedWriter().use { commands ->
// https://android.googlesource.com/platform/external/iptables/+/android-7.0.0_r1/iptables/Android.mk#34
val iptablesSave = if (Build.VERSION.SDK_INT < 24) File(cacheDir, "iptables-save").absolutePath.also {
commands.appendLine("ln -sf /system/bin/iptables $it")
} else "iptables-save"
val ip6tablesSave = if (Build.VERSION.SDK_INT < 24) File(cacheDir, "ip6tables-save").absolutePath.also {
commands.appendLine("ln -sf /system/bin/ip6tables $it")
} else "ip6tables-save"
commands.appendLine("""
|echo dumpsys ${Context.WIFI_P2P_SERVICE}
|dumpsys ${Context.WIFI_P2P_SERVICE}
@@ -50,13 +41,13 @@ data class Dump(val path: String, val cacheDir: File = app.deviceStorage.codeCac
|dumpsys ${Context.CONNECTIVITY_SERVICE} tethering
|echo
|echo iptables -t filter
|$iptablesSave -t filter
|iptables-save -t filter
|echo
|echo iptables -t nat
|$iptablesSave -t nat
|iptables-save -t nat
|echo
|echo ip6tables-save
|$ip6tablesSave
|ip6tables-save
|echo
|echo ip rule
|$IP rule
@@ -125,7 +116,7 @@ class ProcessListener(private val terminateRegex: Regex,
parent.join()
} finally {
parent.cancel()
if (Build.VERSION.SDK_INT < 26) process.destroy() else if (process.isAlive) process.destroyForcibly()
if (process.isAlive) process.destroyForcibly()
parent.join()
}
}
@@ -162,7 +153,6 @@ data class StartTethering(private val type: Int,
@Deprecated("Old API since API 30")
@Parcelize
@RequiresApi(24)
@Suppress("DEPRECATION")
data class StartTetheringLegacy(private val cacheDir: File, private val type: Int,
private val showProvisioningUi: Boolean) : RootCommand<ParcelableBoolean> {
@@ -184,7 +174,6 @@ data class StartTetheringLegacy(private val cacheDir: File, private val type: In
}
@Parcelize
@RequiresApi(24)
data class StopTethering(private val type: Int) : RootCommandNoResult {
override suspend fun execute(): Parcelable? {
TetheringManager.stopTethering(type)
@@ -209,7 +198,6 @@ data class SettingsGlobalPut(val name: String, val value: String) : RootCommandN
}
}
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun execute() = withContext(Dispatchers.IO) {
val process = ProcessBuilder("settings", "put", "global", name, value).fixPath(true).start()
val error = process.inputStream.bufferedReader().readText()

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.root
import android.net.MacAddress
import android.net.wifi.ScanResult
import android.net.wifi.p2p.WifiP2pManager
import android.os.Looper
@@ -37,10 +38,8 @@ object RepeaterCommands {
@Parcelize
@RequiresApi(29)
class RequestDeviceAddress : RootCommand<ParcelableLong?> {
override suspend fun execute() = Services.p2p!!.run {
requestDeviceAddress(obtainChannel())?.let { ParcelableLong(it.addr) }
}
class RequestDeviceAddress : RootCommand<MacAddress?> {
override suspend fun execute() = Services.p2p!!.run { requestDeviceAddress(obtainChannel()) }
}
@Parcelize

View File

@@ -1,7 +1,6 @@
package be.mygod.vpnhotspot.root
import android.annotation.SuppressLint
import android.os.Build
import android.os.Parcelable
import android.util.Log
import be.mygod.librootkotlinx.*
@@ -34,7 +33,7 @@ object RootManager : RootSession(), Logger {
})
Logger.me = RootManager
Services.init { systemContext }
if (Build.VERSION.SDK_INT >= 28) UnblockCentral.needInit = false
UnblockCentral.needInit = false
return null
}
}

View File

@@ -14,7 +14,6 @@ import timber.log.Timber
object RoutingCommands {
@Parcelize
class Clean : RootCommandNoResult {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun execute() = withContext(Dispatchers.IO) {
val process = ProcessBuilder("sh").fixPath(true).start()
process.outputStream.bufferedWriter().use(Routing.Companion::appendCleanCommands)

View File

@@ -20,7 +20,6 @@ import kotlinx.parcelize.Parcelize
import timber.log.Timber
object WifiApCommands {
@RequiresApi(28)
sealed class SoftApCallbackParcel : Parcelable {
abstract fun dispatch(callback: WifiApManager.SoftApCallbackCompat)
@@ -60,7 +59,6 @@ object WifiApCommands {
}
@Parcelize
@RequiresApi(28)
class RegisterSoftApCallback : RootCommandChannel<SoftApCallbackParcel> {
override fun create(scope: CoroutineScope) = scope.produce(capacity = capacity) {
val finish = CompletableDeferred<Unit>()
@@ -115,7 +113,6 @@ object WifiApCommands {
private val callbacks = mutableSetOf<WifiApManager.SoftApCallbackCompat>()
private val lastCallback = AutoFiringCallbacks()
private var rootCallbackJob: Job? = null
@RequiresApi(28)
private suspend fun handleChannel(channel: ReceiveChannel<SoftApCallbackParcel>) = channel.consumeEach { parcel ->
when (parcel) {
is SoftApCallbackParcel.OnStateChanged -> synchronized(callbacks) { lastCallback.state = parcel }
@@ -141,7 +138,6 @@ object WifiApCommands {
}
for (callback in synchronized(callbacks) { callbacks.toList() }) parcel.dispatch(callback)
}
@RequiresApi(28)
fun registerSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
val wasEmpty = callbacks.isEmpty()
callbacks.add(callback)
@@ -158,7 +154,6 @@ object WifiApCommands {
null
} else lastCallback
}?.toSequence()?.forEach { it?.dispatch(callback) }
@RequiresApi(28)
fun unregisterSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
if (callbacks.remove(callback) && callbacks.isEmpty()) {
rootCallbackJob!!.cancel()

View File

@@ -30,10 +30,8 @@ class ConstantLookup(private val prefix: String, private val lookup29: Array<out
}
}
@Suppress("FunctionName")
fun ConstantLookup(prefix: String, vararg lookup29: String?, clazz: () -> Class<*>) =
ConstantLookup(prefix, lookup29, clazz)
@Suppress("FunctionName")
inline fun <reified T> ConstantLookup(prefix: String, vararg lookup29: String?) =
ConstantLookup(prefix, lookup29) { T::class.java }

View File

@@ -1,12 +1,10 @@
package be.mygod.vpnhotspot.util
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Application
import android.content.Context
@SuppressLint("Registered")
@TargetApi(24)
class DeviceStorageApp(context: Context) : Application() {
init {
attachBaseContext(context.createDeviceProtectedStorageContext())

View File

@@ -8,10 +8,8 @@ import android.os.DeadObjectException
import android.os.IBinder
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.BootReceiver
@RequiresApi(24)
abstract class KillableTileService : TileService(), ServiceConnection {
protected var tapPending = false

View File

@@ -28,8 +28,8 @@ class RootSession : AutoCloseable {
private var server: RootServer? = runBlocking { RootManager.acquire() }
override fun close() {
server = null
server?.let { runBlocking { RootManager.release(it) } }
server = null
}
/**

View File

@@ -5,7 +5,6 @@ import android.net.ConnectivityManager
import android.net.NetworkRequest
import android.net.wifi.WifiManager
import android.net.wifi.p2p.WifiP2pManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.core.content.getSystemService
@@ -30,7 +29,6 @@ object Services {
}
val wifi by lazy { context.getSystemService<WifiManager>()!! }
fun registerNetworkCallbackCompat(request: NetworkRequest, networkCallback: ConnectivityManager.NetworkCallback) =
if (Build.VERSION.SDK_INT >= 26) connectivity.registerNetworkCallback(request, networkCallback, mainHandler)
else connectivity.registerNetworkCallback(request, networkCallback)
fun registerNetworkCallback(request: NetworkRequest, networkCallback: ConnectivityManager.NetworkCallback) =
connectivity.registerNetworkCallback(request, networkCallback, mainHandler)
}

View File

@@ -13,9 +13,7 @@ import me.weishu.reflection.Reflection
*
* Lazy cannot be used directly as it will create inner classes.
*/
@RequiresApi(28)
@SuppressLint("BlockedPrivateApi", "DiscouragedPrivateApi")
@Suppress("FunctionName")
object UnblockCentral {
var needInit = true
/**

View File

@@ -1,21 +1,16 @@
package be.mygod.vpnhotspot.util
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.*
import android.content.res.Resources
import android.net.*
import android.os.Build
import android.os.RemoteException
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.text.*
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter
@@ -24,18 +19,11 @@ import androidx.fragment.app.FragmentManager
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.lang.invoke.MethodHandles
import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketException
@@ -57,23 +45,10 @@ fun Long.toPluralInt(): Int {
return (this % 1000000000).toInt() + 1000000000
}
@RequiresApi(26)
fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size &&
classes.indices.all { i -> parameters[i].type == classes[i] }
@RequiresApi(26)
inline fun <reified T> Method.matches1(name: String) = matches(name, T::class.java)
fun Method.matchesCompat(name: String, args: Array<out Any?>?, vararg classes: Class<*>) =
if (Build.VERSION.SDK_INT < 26) {
this.name == name && args?.size ?: 0 == classes.size && classes.indices.all { i ->
args!![i]?.let { classes[i].isInstance(it) } != false
}
} else matches(name, *classes)
fun HttpURLConnection.disconnectCompat() {
if (Build.VERSION.SDK_INT < 26) GlobalScope.launch(Dispatchers.IO) { disconnect() } else disconnect()
}
fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) {
try {
unregisterReceiver(receiver)
@@ -167,7 +142,7 @@ fun makeMacSpan(mac: String) = if (app.hasTouch) SpannableString(mac).apply {
fun NetworkInterface.formatAddresses(macOnly: Boolean = false) = SpannableStringBuilder().apply {
try {
val address = hardwareAddress?.let(MacAddressCompat::fromBytes)
val address = hardwareAddress?.let(MacAddress::fromBytes)
if (address != null && address != MacAddressCompat.ANY_ADDRESS) appendLine(makeMacSpan(address.toString()))
} catch (e: IllegalArgumentException) {
Timber.w(e)
@@ -219,8 +194,7 @@ fun Resources.findIdentifier(name: String, defType: String, defPackage: String,
if (alternativePackage != null && it == 0) getIdentifier(name, defType, alternativePackage) else it
}
@get:RequiresApi(26)
private val newLookup by lazy @TargetApi(26) {
private val newLookup by lazy {
MethodHandles.Lookup::class.java.getDeclaredConstructor(Class::class.java, Int::class.java).apply {
isAccessible = true
}
@@ -232,7 +206,7 @@ private val newLookup by lazy @TargetApi(26) {
* See also: https://stackoverflow.com/a/49532463/2245107
*/
fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Method, args: Array<out Any?>?) = when {
Build.VERSION.SDK_INT >= 26 && method.isDefault -> try {
method.isDefault -> try {
newLookup.newInstance(interfaceClass, 0xf) // ALL_MODES
} catch (e: ReflectiveOperationException) {
Timber.w(e)
@@ -258,20 +232,5 @@ fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Me
}
fun globalNetworkRequestBuilder() = NetworkRequest.Builder().apply {
if (Build.VERSION.SDK_INT >= 31) setIncludeOtherUidNetworks(true) else if (Build.VERSION.SDK_INT == 23) {
// workarounds for OEM bugs
removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)
}
}
@Suppress("FunctionName")
fun if_nametoindex(ifname: String) = if (Build.VERSION.SDK_INT >= 26) {
Os.if_nametoindex(ifname)
} else try {
File("/sys/class/net/$ifname/ifindex").inputStream().bufferedReader().use { it.readLine().trim().toInt() }
} catch (_: FileNotFoundException) {
NetworkInterface.getByName(ifname)?.index ?: 0
} catch (e: IOException) {
if ((e.cause as? ErrnoException)?.errno == OsConstants.ENODEV) 0 else throw e
if (Build.VERSION.SDK_INT >= 31) setIncludeOtherUidNetworks(true)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -53,7 +53,6 @@
se il tethering VPN non funziona.</string>
<string name="tethering_manage_usb">Tethering USB</string>
<string name="tethering_manage_wifi">Hotspot Wi\u2011Fi</string>
<string name="tethering_manage_wifi_legacy">Hotspot Wi\u2011Fi (legacy)</string>
<string name="tethering_manage_bluetooth">Tethering Bluetooth</string>
<string name="tethering_manage_ethernet" msgid="959743110824197356">"Tethering Ethernet"</string>

View File

@@ -50,7 +50,6 @@
<string name="tethering_manage_offload_enabled">Por favor, desative a Aceleração de hardware de tethering nas configurações de desenvolvedor caso o VPN Hotspot não funcionar corretamente.</string>
<string name="tethering_manage_usb">Tethering USB</string>
<string name="tethering_manage_wifi">Ponto de acesso Wi\u2011Fi</string>
<string name="tethering_manage_wifi_legacy">Ponto de acesso Wi\u2011Fi (legacy)</string>
<string name="tethering_manage_bluetooth">Tethering Bluetooth</string>
<string name="tethering_manage_ethernet">Tethering Ethernet</string>
<string name="tethering_manage_ncm">Tethering USB (NCM)</string>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="api_lt_25">false</bool>
</resources>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="api_ge_26">true</bool>
</resources>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string-array name="settings_service_masquerade" tools:ignore="InconsistentArrays">
<item>@string/settings_service_masquerade_none</item>
<item>@string/settings_service_masquerade_simple</item>
<item>@string/settings_service_masquerade_netd</item>
</string-array>
<string-array name="settings_service_masquerade_values" tools:ignore="InconsistentArrays">
<item>None</item>
<item>Simple</item>
<item>Netd</item>
</string-array>
</resources>

View File

@@ -57,7 +57,6 @@
-->
<string name="tethering_manage_usb">USB 网络共享</string>
<string name="tethering_manage_wifi">WLAN 热点</string>
<string name="tethering_manage_wifi_legacy">WLAN 热点 (旧 API)</string>
<string name="tethering_manage_bluetooth">蓝牙网络共享</string>
<string name="tethering_manage_ethernet" msgid="959743110824197356">"以太网络共享"</string>
<string name="tethering_manage_ncm">USB 网络共享 (NCM)</string>

View File

@@ -66,7 +66,6 @@
-->
<string name="tethering_manage_usb">USB 網路共用</string>
<string name="tethering_manage_wifi">Wi\u2011Fi 無線基地台</string>
<string name="tethering_manage_wifi_legacy">Wi\u2011Fi 無線基地台 (舊版)</string>
<string name="tethering_manage_bluetooth">藍牙網路共用</string>
<string name="tethering_manage_ethernet" msgid="959743110824197356">"乙太網路網路共用"</string>
<string name="tethering_manage_ncm">USB 網路共用 (NCM)</string>

View File

@@ -9,10 +9,12 @@
<string-array name="settings_service_masquerade" tools:ignore="InconsistentArrays">
<item>@string/settings_service_masquerade_none</item>
<item>@string/settings_service_masquerade_simple</item>
<item>@string/settings_service_masquerade_netd</item>
</string-array>
<string-array name="settings_service_masquerade_values" tools:ignore="InconsistentArrays">
<item>None</item>
<item>Simple</item>
<item>Netd</item>
</string-array>
<string-array name="settings_service_wifi_lock">

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="api_ge_30">false</bool>
<bool name="api_ge_26">false</bool>
<bool name="api_lt_25">true</bool>
<bool name="is_day">true</bool>
</resources>

View File

@@ -69,7 +69,6 @@
if VPN tethering does not work.</string>
<string name="tethering_manage_usb">USB tethering</string>
<string name="tethering_manage_wifi">Wi\u2011Fi hotspot</string>
<string name="tethering_manage_wifi_legacy">Wi\u2011Fi hotspot (legacy)</string>
<string name="tethering_manage_bluetooth">Bluetooth tethering</string>
<string name="tethering_manage_ethernet">Ethernet tethering</string>
<string name="tethering_manage_ncm">USB tethering (NCM)</string>

View File

@@ -2,7 +2,7 @@
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:navigationBarColor">@color/navigationBarColor</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/is_day</item>
<item name="android:windowLightNavigationBar">@bool/is_day</item>
<item name="actionBarStyle">@style/Widget.MaterialComponents.Light.ActionBar.Solid</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@@ -1,13 +0,0 @@
package be.mygod.vpnhotspot.net
import org.junit.Assert.*
import org.junit.Test
class MacAddressCompatTest {
@Test
fun macSerialization() {
for (test in listOf("01:23:45:67:89:ab", "DE:AD:88:88:BE:EF")) {
assertTrue(test.equals(MacAddressCompat.fromString(test).toString(), true))
}
}
}