From 15c3c5a6d7e097bfa74af9348f916ff1a2a38746 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2023 18:45:28 -0500 Subject: [PATCH] Bump to minapi 28 --- README.md | 91 +++++++++--------- mobile/build.gradle.kts | 2 +- .../2.json | 14 +-- .../be/mygod/vpnhotspot/util/UpdateChecker.kt | 2 +- mobile/src/main/AndroidManifest.xml | 36 ++----- .../src/main/java/be/mygod/vpnhotspot/App.kt | 29 ++---- .../java/be/mygod/vpnhotspot/BootReceiver.kt | 2 - .../java/be/mygod/vpnhotspot/EBegFragment.kt | 1 - .../vpnhotspot/LocalOnlyHotspotService.kt | 10 +- .../java/be/mygod/vpnhotspot/MainActivity.kt | 11 +-- .../be/mygod/vpnhotspot/RepeaterService.kt | 39 ++++---- .../be/mygod/vpnhotspot/RoutingManager.kt | 7 +- .../mygod/vpnhotspot/ServiceNotification.kt | 57 +++++------ .../vpnhotspot/SettingsPreferenceFragment.kt | 30 +++--- .../be/mygod/vpnhotspot/TetheringService.kt | 6 +- .../java/be/mygod/vpnhotspot/client/Client.kt | 6 +- .../vpnhotspot/client/ClientViewModel.kt | 11 +-- .../vpnhotspot/client/ClientsFragment.kt | 25 ++--- .../be/mygod/vpnhotspot/client/MacLookup.kt | 20 ++-- .../vpnhotspot/manage/BluetoothTethering.kt | 5 - .../vpnhotspot/manage/InterfaceManager.kt | 3 +- .../IpNeighbourMonitoringTileService.kt | 2 - .../manage/LocalOnlyHotspotManager.kt | 7 +- .../manage/LocalOnlyHotspotTileService.kt | 2 - .../be/mygod/vpnhotspot/manage/ManageBar.kt | 2 +- .../be/mygod/vpnhotspot/manage/Manager.kt | 7 +- .../vpnhotspot/manage/RepeaterManager.kt | 19 ++-- .../vpnhotspot/manage/RepeaterTileService.kt | 6 +- .../mygod/vpnhotspot/manage/TetherManager.kt | 45 +-------- .../vpnhotspot/manage/TetheringFragment.kt | 40 +++----- .../vpnhotspot/manage/TetheringTileService.kt | 26 +---- .../be/mygod/vpnhotspot/net/IpNeighbour.kt | 8 +- .../mygod/vpnhotspot/net/MacAddressCompat.kt | 79 ++------------- .../java/be/mygod/vpnhotspot/net/Routing.kt | 29 +++--- .../vpnhotspot/net/TetherOffloadManager.kt | 15 --- .../mygod/vpnhotspot/net/TetheringManager.kt | 24 +---- .../net/monitor/DefaultNetworkMonitor.kt | 28 +----- .../net/monitor/InterfaceMonitor.kt | 2 +- .../mygod/vpnhotspot/net/monitor/IpMonitor.kt | 1 - .../net/monitor/TetherTimeoutMonitor.kt | 5 +- .../vpnhotspot/net/monitor/TrafficRecorder.kt | 15 ++- .../vpnhotspot/net/monitor/UpstreamMonitor.kt | 9 -- .../vpnhotspot/net/monitor/VpnMonitor.kt | 2 +- .../net/wifi/P2pSupplicantConfiguration.kt | 9 +- .../net/wifi/SoftApConfigurationCompat.kt | 56 ++++------- .../net/wifi/WifiApDialogFragment.kt | 72 ++++++-------- .../vpnhotspot/net/wifi/WifiApManager.kt | 46 +-------- .../net/wifi/WifiP2pManagerHelper.kt | 10 +- ...CompleteNetworkPreferenceDialogFragment.kt | 2 +- .../preference/UpstreamsPreference.kt | 10 +- .../be/mygod/vpnhotspot/room/ClientRecord.kt | 23 ++--- .../be/mygod/vpnhotspot/room/Converters.kt | 11 +++ .../be/mygod/vpnhotspot/room/TrafficRecord.kt | 5 +- .../be/mygod/vpnhotspot/root/MiscCommands.kt | 20 +--- .../mygod/vpnhotspot/root/RepeaterCommands.kt | 7 +- .../be/mygod/vpnhotspot/root/RootManager.kt | 3 +- .../mygod/vpnhotspot/root/RoutingCommands.kt | 1 - .../mygod/vpnhotspot/root/WifiApCommands.kt | 5 - .../mygod/vpnhotspot/util/ConstantLookup.kt | 2 - .../mygod/vpnhotspot/util/DeviceStorageApp.kt | 2 - .../vpnhotspot/util/KillableTileService.kt | 2 - .../be/mygod/vpnhotspot/util/RootSession.kt | 2 +- .../java/be/mygod/vpnhotspot/util/Services.kt | 6 +- .../mygod/vpnhotspot/util/UnblockCentral.kt | 2 - .../java/be/mygod/vpnhotspot/util/Utils.kt | 49 +--------- mobile/src/main/res/mipmap-hdpi/banner.webp | Bin 1912 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1812 -> 0 bytes mobile/src/main/res/mipmap-mdpi/banner.webp | Bin 1300 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 1382 -> 0 bytes mobile/src/main/res/mipmap-xhdpi/banner.webp | Bin 2296 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 2518 -> 0 bytes mobile/src/main/res/mipmap-xxhdpi/banner.webp | Bin 3310 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 3468 -> 0 bytes .../src/main/res/mipmap-xxxhdpi/banner.webp | Bin 4394 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 4346 -> 0 bytes .../{mipmap-anydpi-v24 => mipmap}/banner.xml | 0 .../ic_launcher.xml | 0 mobile/src/main/res/values-it/strings.xml | 1 - mobile/src/main/res/values-pt-rBR/strings.xml | 1 - mobile/src/main/res/values-v25/bools.xml | 4 - mobile/src/main/res/values-v26/bools.xml | 4 - mobile/src/main/res/values-v28/arrays.xml | 13 --- mobile/src/main/res/values-zh-rCN/strings.xml | 1 - mobile/src/main/res/values-zh-rTW/strings.xml | 1 - mobile/src/main/res/values/arrays.xml | 2 + mobile/src/main/res/values/bools.xml | 2 - mobile/src/main/res/values/strings.xml | 1 - mobile/src/main/res/values/styles.xml | 2 +- .../vpnhotspot/net/MacAddressCompatTest.kt | 13 --- 89 files changed, 361 insertions(+), 809 deletions(-) delete mode 100644 mobile/src/main/res/mipmap-hdpi/banner.webp delete mode 100644 mobile/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 mobile/src/main/res/mipmap-mdpi/banner.webp delete mode 100644 mobile/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 mobile/src/main/res/mipmap-xhdpi/banner.webp delete mode 100644 mobile/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 mobile/src/main/res/mipmap-xxhdpi/banner.webp delete mode 100644 mobile/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 mobile/src/main/res/mipmap-xxxhdpi/banner.webp delete mode 100644 mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.webp rename mobile/src/main/res/{mipmap-anydpi-v24 => mipmap}/banner.xml (100%) rename mobile/src/main/res/{mipmap-anydpi-v26 => mipmap}/ic_launcher.xml (100%) delete mode 100644 mobile/src/main/res/values-v25/bools.xml delete mode 100644 mobile/src/main/res/values-v26/bools.xml delete mode 100644 mobile/src/main/res/values-v28/arrays.xml delete mode 100644 mobile/src/test/java/be/mygod/vpnhotspot/net/MacAddressCompatTest.kt diff --git a/README.md b/README.md index 0d6bf50e..d258ac60 100644 --- a/README.md +++ b/README.md @@ -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;->(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;->(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`
Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken) -* (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;->()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;->()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 `); * `sh`; * `su`. diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index cb897028..6108510d 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -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 diff --git a/mobile/schemas/be.mygod.vpnhotspot.room.AppDatabase/2.json b/mobile/schemas/be.mygod.vpnhotspot.room.AppDatabase/2.json index 0ff4cdb6..7bedd0ba 100644 --- a/mobile/schemas/be.mygod.vpnhotspot.room.AppDatabase/2.json +++ b/mobile/schemas/be.mygod.vpnhotspot.room.AppDatabase/2.json @@ -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')" ] } } \ No newline at end of file diff --git a/mobile/src/freedom/java/be/mygod/vpnhotspot/util/UpdateChecker.kt b/mobile/src/freedom/java/be/mygod/vpnhotspot/util/UpdateChecker.kt index 8e3b81cc..93f9e0d0 100644 --- a/mobile/src/freedom/java/be/mygod/vpnhotspot/util/UpdateChecker.kt +++ b/mobile/src/freedom/java/be/mygod/vpnhotspot/util/UpdateChecker.kt @@ -103,7 +103,7 @@ object UpdateChecker { } catch (e: Exception) { Timber.w(e) } finally { - conn.disconnectCompat() + conn.disconnect() putLong(KEY_LAST_FETCHED, System.currentTimeMillis()) } } diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 96963632..15579b5b 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -90,8 +90,7 @@ + android:foregroundServiceType="location|connectedDevice"/> + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> @@ -119,12 +117,10 @@ + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> @@ -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"> @@ -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"> @@ -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"> @@ -193,23 +186,6 @@ android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:value="true" /> - - - - - - - = 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 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() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt b/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt index 77443ce7..3540e698 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/BootReceiver.kt @@ -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 add(value: Startable) = add(T::class.java.name, value) inline fun delete() = delete(T::class.java.name) - @RequiresApi(24) fun migrateIfNecessary() { val oldFile = File(app.noBackupFilesDir, FILENAME) if (oldFile.canRead()) try { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt index a57715ea..2917a9b9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt @@ -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/") } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 953a2ec5..75c9a7cf 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -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) { 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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt index f57f9c20..3b964df7 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt @@ -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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 5620321c..85b37aa0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -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 @@ -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() } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt index 2d8f5279..1b5f8f81 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RoutingManager.kt @@ -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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index bbc5ebcf..68022e03 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -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") } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt index c3ae75d1..4ca10bdb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt @@ -47,24 +47,22 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { SummaryFallbackProvider(findPreference(UpstreamMonitor.KEY)!!) SummaryFallbackProvider(findPreference(FallbackUpstreamMonitor.KEY)!!) findPreference("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(BootReceiver.KEY)!!.setOnPreferenceChangeListener { _, value -> BootReceiver.onUserSettingUpdated(value as Boolean) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index 7fab57e1..8dd110b6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -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) : 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()) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/Client.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/Client.kt index 461068c6..a33bcfed 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/Client.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/Client.kt @@ -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() { 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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt index 69d08aad..307af3e1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt @@ -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 = emptyList() - private var wifiAp = emptyList>() + private var wifiAp = emptyList>() private var neighbours: Collection = emptyList() val clients = MutableLiveData>() val fullMode = object : DefaultLifecycleObserver { @@ -51,10 +50,10 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb } private fun populateClients() { - val clients = HashMap, Client>() + val clients = HashMap, 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) { wifiAp = clients.mapNotNull { val client = WifiClient(it) - client.apInstanceIdentifier?.run { this to client.macAddress.toCompat() } + client.apInstanceIdentifier?.run { this to client.macAddress } } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt index a648a063..e9c40828 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt @@ -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() { 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(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, TrafficRate>() + private var rates = mutableMapOf, TrafficRate>() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentClientsBinding.inflate(inflater, container, false) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt index fd86e7cf..baee1257 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt @@ -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>() + private val macLookupBusy = mutableMapOf>() // 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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 406ec471..fbeb1bad 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt index b390fe3b..942cfbca 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/InterfaceManager.kt @@ -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))) } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt index 93e966fb..11c66b6d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt @@ -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 = emptyList() abstract fun updateTile() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index 73cc4e0f..ee94145f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -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 { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt index 1469d46c..2983e40c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt @@ -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) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt index e1a22612..02c8ed62 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/ManageBar.kt @@ -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 { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt index e181a0a7..baa163ba 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/Manager.kt @@ -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)) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index 68794d91..4f1f252a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -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 } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterTileService.kt index d33ed8c9..1b394ac4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterTileService.kt @@ -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 -> { } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 4747b459..bbfcb1f4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -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() 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) - } - } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index 5a264a9b..9e1a2652 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -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), 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()?.adapter?.let { + requireContext().getSystemService()?.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() var localOnlyIfaces = emptyList() @@ -106,24 +101,18 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick val list = ArrayList() 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?) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index df622ad7..6e8ba3f3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -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) - } - } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt index bef8c845..99a3d282 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/IpNeighbour.kt @@ -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) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt index 7853b199..8a0b8ba1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt @@ -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()) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt index d3660f98..92fcb585 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/Routing.kt @@ -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)) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherOffloadManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherOffloadManager.kt index 73e9f576..573cb8c6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherOffloadManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherOffloadManager.kt @@ -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) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index e8b75748..acf3f46c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -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() + 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) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt index ff423a48..919f2f6f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt @@ -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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt index 81be3b7a..f2466bf1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt @@ -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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt index 026ad6e6..aafa1392 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/IpMonitor.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TetherTimeoutMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TetherTimeoutMonitor.kt index 7915b383..9823f7d2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TetherTimeoutMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TetherTimeoutMonitor.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt index 77e6501a..c37596d5 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt @@ -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() val foregroundListeners = Event2, LongSparseArray>() - 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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt index 9f872c43..07e54dc4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt @@ -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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt index 5aced6c9..1e3997ca 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt @@ -61,7 +61,7 @@ object VpnMonitor : UpstreamMonitor() { callback.onAvailable(currentLinkProperties) } } else { - Services.registerNetworkCallbackCompat(request, networkCallback) + Services.registerNetworkCallback(request, networkCallback) registered = true } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt index d291ac87..a0afe16f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt @@ -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') } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt index 483f32c0..08d94eec 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt @@ -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) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt index 711cbda5..0b0dedfc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt @@ -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 { - 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 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 band to RangeInput.fromString(text.text) } if (!arg.p2pMode && Build.VERSION.SDK_INT >= 33) { @@ -227,7 +221,6 @@ class WifiApDialogFragment : AlertDialogFragment= 30) { dialogView.timeoutWrapper.helperText = getString(R.string.wifi_hotspot_timeout_default, TetherTimeoutMonitor.defaultTimeout) @@ -239,12 +232,10 @@ class WifiApDialogFragment : AlertDialogFragment= 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= 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= 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= 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 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) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 771b5597..7faa83a8 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -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("SAP_START_FAILURE_", "GENERAL", "NO_CHANNEL") @get:RequiresApi(30) val clientBlockLookup by lazy { ConstantLookup("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 - } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt index 9fbc2a7a..4baa0ad5 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt @@ -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?): Any? = when { - method.matchesCompat("onPersistentGroupInfoAvailable", args, classWifiP2pGroupList) -> { + method.matches("onPersistentGroupInfoAvailable", classWifiP2pGroupList) -> { @Suppress("UNCHECKED_CAST") result.complete(getGroupList(args!![0]) as Collection) } @@ -142,11 +142,11 @@ object WifiP2pManagerHelper { CompletableDeferred().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() 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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt index d2f5dad7..15190301 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt @@ -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) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt index 8f7e37fd..43b3c9e0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt @@ -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("" to true) - onUpdate() - } - } + private val fallback = Monitor() init { (context as LifecycleOwner).lifecycle.addObserver(this) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/room/ClientRecord.kt b/mobile/src/main/java/be/mygod/vpnhotspot/room/ClientRecord.kt index 64dd9750..1fec6f22 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/room/ClientRecord.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/room/ClientRecord.kt @@ -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 - fun lookupOrDefaultSync(mac: MacAddressCompat) = lookupSync(mac.addr).map { it ?: ClientRecord(mac.addr) } + protected abstract fun lookupSync(mac: MacAddress): LiveData + 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) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt b/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt index 56c612f1..ebfcf246 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/room/Converters.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/room/TrafficRecord.kt b/mobile/src/main/java/be/mygod/vpnhotspot/room/TrafficRecord.kt index 2a4dfac0..d0b6e315 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/room/TrafficRecord.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/room/TrafficRecord.kt @@ -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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt index e1714073..ea8a17d4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt @@ -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 { @@ -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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt index 89a4da0c..1ead2991 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt @@ -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 { - override suspend fun execute() = Services.p2p!!.run { - requestDeviceAddress(obtainChannel())?.let { ParcelableLong(it.addr) } - } + class RequestDeviceAddress : RootCommand { + override suspend fun execute() = Services.p2p!!.run { requestDeviceAddress(obtainChannel()) } } @Parcelize diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/RootManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/RootManager.kt index 59296baa..b4f3baa1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/RootManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/RootManager.kt @@ -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 } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/RoutingCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/RoutingCommands.kt index 185d57b4..87b57535 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/RoutingCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/RoutingCommands.kt @@ -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) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt index 5359ada3..a849e4e4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt @@ -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 { override fun create(scope: CoroutineScope) = scope.produce(capacity = capacity) { val finish = CompletableDeferred() @@ -115,7 +113,6 @@ object WifiApCommands { private val callbacks = mutableSetOf() private val lastCallback = AutoFiringCallbacks() private var rootCallbackJob: Job? = null - @RequiresApi(28) private suspend fun handleChannel(channel: ReceiveChannel) = 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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt index ed5b520f..422a3ba0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt @@ -30,10 +30,8 @@ class ConstantLookup(private val prefix: String, private val lookup29: Array Class<*>) = ConstantLookup(prefix, lookup29, clazz) -@Suppress("FunctionName") inline fun ConstantLookup(prefix: String, vararg lookup29: String?) = ConstantLookup(prefix, lookup29) { T::class.java } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/DeviceStorageApp.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/DeviceStorageApp.kt index 3df4e7f8..2309fba4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/DeviceStorageApp.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/DeviceStorageApp.kt @@ -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()) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/KillableTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/KillableTileService.kt index 2cbf8ff5..3ab14bf0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/KillableTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/KillableTileService.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt index 7b38a814..8cad8b51 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt @@ -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 } /** diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt index acb6b108..38ae4328 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt @@ -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()!! } - 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) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt index cfef3829..2a8827e6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt @@ -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 /** diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 2f41f5ce..a98bc7ba 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -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 Method.matches1(name: String) = matches(name, T::class.java) -fun Method.matchesCompat(name: String, args: Array?, 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?) = 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) } diff --git a/mobile/src/main/res/mipmap-hdpi/banner.webp b/mobile/src/main/res/mipmap-hdpi/banner.webp deleted file mode 100644 index 7fe32e6be62fd4d90a860ca752a9c93e1ac0fa29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1912 zcmV-;2Z#7lNk&F+2LJ$9MM6+kP&iCv2LJ#s?|>ly-{i1uBS(^V_dfPt)_0SCfpLr)@qcM?u;KOy^(rwGa^#z$|a#q^)g}A=z4Y;Ilz{+4fjft!3M`ZF}wE-F8Pr zW@Xir`Rt>WkrB~<2W}%NQmV3ie7Ae_0$J9!HMK)3kRlTGB)YsNWgMqE{}IU#yUEV& z|NVbN{~@@Mq)3XYJ{U|j_f)?ia&G?l&p-e9zfZMZK?zj{u3J>>&XEbc$UBU9aXzP& ze25)2Rq4)YCwLc4ECy2vCKfwqe^JOKj;}g}hH7ajfGDp_!y%N(yR)m@h>2ZEqs-)0 zDm#Nxjv&<3SBmLtWsZfXByhGfB=H?cL37{WgZXyOvLziRjmT!hx)Y5{6pz$;%GdR; zTF4vkY%`WQ?zSDD4R+0-Z=6MD?!q|WYsZFIU;eFlD99r+>#4 z;in$g?Bz`+(EquLL&~tTE2ZIMl?ZYLOerU>2#hzo$XS}RYX$BRs0KmTEa=^jnVZl1ppwkhTK0!fjGS z$2P*mWk}J`tXauTwZl~n145Hj(Vjj6+!#-k8v?@cntCG)hDQ_5MmtE0ETYfx!g#_6 zeTHm=#dN{U=d-}INS+>lP0Ck#l#h(W8|$|7(Fn9hOdrQDjwg<%-zUU|W)UwzeS*jEzPU7=c*flZS z=p?78buQm0+Sh2p2czj7j;xn1@-6Hqg&jGN4sR9fvrQei}lf26j z@n-3hVM{YXF=3LHwrdrkamvY*=o3(f4*F1Szc>Z@hn;ycq(`mIasFH5J`%OE=$a5O z;LgwMps)#<{ct^c-}Jow;iLPlJbT{CizhVqK!CWq+@Pj;q=wgJpAYP3<}IgNIpa5I zoIkA}7&!-KJ`J;~o~fZm!|U#;{cTnP{SNwqbY%W=lDPxGl8-SQb5_KFiz&C36dhHi zs+u)uB*!e6heLf=L9=E?pZ5b!0gv|37o;VB|8Rl<=pqBdCLoq{G&V$UcvD8$Re6w5 zNb<(@qTsZ7WQ(nSH#-kl5A^F4XvyC`|A@pwW`UhmRV)@w6C!iY-7KXKIX~^Bssfu- z0}WdW0?G5lt$r814#&rUeyakd2zhUaWrCS%(H$wnR!gOK80fRXo1jx-3_0Fmk&?OZ zy+1(LL09xBP>K14hiSJV<8-6uY)Ss7pHdmFEQ5Ljn8IMfvEv%=6T9sc@a#qv3NfGl zKFVuxIKkICdU6|aXMnKQa+BffpVi{A(ETOqjV^EZLk>fa^l&eYmi%>R_sdY7hYohF zuo6ZJ3XUF^y!4+Ad>Oe0>NofFD$sH;lbz;jSM(7A)(R_aE^%k9>_M0jC@?}eeB620 zqhBh1KiG|2k6hN%t3pcvae}}nI>wEeK5KA9%9trsxm+3@J;Cs_M>+n!g1&uMzfs7w zJ^PiO*xsu|M}jALFl5RCH%zSbUW;C4?csMR7R!#)EP*IoEW(IjWT62~)x+-_={qEQ zXCAlnXnM~9#;!y9&An~z;oJ>D!!{rPk|aOhk&KGg07CO@5Ma{X0l1Njh#de+Uqm#3 zh4h>q;Pq&>nD}2%O!Qzo2bc+WF2}9~_L1+^^$b!ZdH(lSbo3&%rOHB1Od@6HIZwh` z*fPmd({SW{UtM)n4L2Rbt}v#QX}7mqufC4#-4Uu%K9#1#uR)_;iEvf&>J%;f-XIn9 z{+&p_!X7>jT$O@X@*%dm@46n5RmdA!p4^b4FZ_0o_SO4H=+ZJDIm=s9^&Yt_u8OL( zym>9BEWh3%7g3g!Iel>#JyT}xp=V7Sb|6y%EZ8h*&1Of7HX~BluvycJ&6bv|YZV#H zjuvfJv}&`VWt#=9+w`??)7HvOS4%fdt=;srcwMW=P&!(^u2p2%ybh>oolvtnq9%1l y&FPSu(kXSVBHp!%c-Jc8U8{(9ts>sFim+=HVb?0cu2qCxs|dSRk)YCvwSofXP_Oj> diff --git a/mobile/src/main/res/mipmap-hdpi/ic_launcher.webp b/mobile/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index 806ef1c269f80751ee90e9292d3feefd61648831..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1812 zcmV+v2kZD!Nk&Et2LJ$9MM6+kP&iBg2LJ#sN5ByfO)zNSHjaFD_nAN98AL??C&1dF zqktu^DuW^cByI~Ru?y9|Tv}c*h-9nH4*q2!011%Z69qjY(6((Gju~@;(EpF?RCIJ3 zNwyO;T9bcRgs>)>v(E1S1VF)mmOmv6Awfwgg5UG>YVi$7V~u}?X|)3Y?Wr~@=V2fG z0PE!RN5<}U*EYNxSlqf5-TFZRkZ!UZepw`RZQE9DDQjPs=Z?EWWSM0Z67efUrQbkA z&Ih==ySw{yf7g!evyoFrv2fhl)wr*C+=k@XwpCjh>zw;>%YXpf>3&F$AjpJR=()Sx zdlnsA+m3C|KA-=^Z`-zQ+eTR0NvX={GJ{MY6JTe10DQaOelNJSZL3!1xgU3L!U-MX zKZvZ5-wJKGyJch!O%S?0ao|Xj)AVhN3`53#f)M=eZiYBHh~IqYXK3rQbpv4Y6UTviX3l0{`NInSL&jPGD=WF zD?YmDpp)V^a-^h;l}S-W5hvd)!yRmwUdU^XMwvt%Haf^~tCW>oD4>a2v7m#ev}QQ) zQwAlJ!s-9Y0k{N>k_Q77;NQKC&5iCx5CBl6oHDjz>Y!SngaR@rfCvTfuylsjM)X;l z{~r?zumQ4u1a$$pEl%)82q@^A>EF)rtH%6SA@?{&+1wZS!BXxR$Rtph+!=s|Ga_5s zEdx-AKtMscv|qYud}OA7krV(YBYd(S^ZN0qPKE&^6rkIpOO`l^7NDZ+L$jCNmJeO( z6DoZG04V9~X>btOebcuYRrL86-yd zWF5D4Oh6F}30Q_JGNU3YK#RSAO5b&tK2~uZ06Hz*F`UuT8-%%NAL6M`kJ;YGlQ4_% zAZV3kA>|~-uly@5HGrdN2*yXx+}U3N;HK--xy^=o0v&IeU?puKYK{1SH$QuyUs6(4 zpa&S={#MUrAwJ*MS?e+)=~0j&f`j1$gDz)!_;!I@v3 zfBpvmP#kxR9T>Q2H(Ei=A`f(USf)6c{;){h+0WItX zin-^K3*RWaDJIVO^EcCablXHUOBI;XM&wjL1^RlB6oY2VcmjjXmH>ikZWv2XG&R>v z=%HW2OaodC5AmjXC(I-)E;zU@A zalo^A$k$eI4Pp{d)Tpn)=1>S!~3yN4vXDgasbB+8kG0K*7uuG(Rep!xJ8f&TA9Ou|C|6abY1 z{kXN+%*#iBtTd@IoBBVID*ZAJC6T_@1Rn;HlB{1v#1YgN8I(qll1LN0;Wv);Pjw3e zl}KMfh<9Lh*oZq8i71Yd3*}ue?w=jr>R_TZu9D28k$$9)er&%L>ehI?=feFxp}ae? zC{EvT1L6C=y{R4AtWGjgNyBDP#7*^6*45L)OHKH=?Uji6E92qk?=^l)k1%li^>%gs ze1EVMO%jDtk(K~v5e#G@gQ6Is1`PL2LsVHHxuf}68?Bf7&bJPQ*DtA$OZFUbHqDE4 z2<<{c_WxxGrSK=b&Hudqk>H2$_0(@%)9N1TbfwHGQdG#MU;q0ld?t!>0NHo=`_uq; CySK~I4Bt`0;W9`J#-hT_1x1Nk( z*fx@s%pOk*GywhAc6a=+ zHn8b`4z)Bgm}d8&Qa`lcTzvIvtw7Hniu7HsoR%F@7) zNWkV2A{rqQ3>*yzz#&E~Y+%&YuR|69m<#{_4C7A#8Gy9}2?jzG0*GiF;k99%ZalsG zr}4ff=KLA9tAuA=L>#&eEVMINrcOvgaxZ_v93dp@cvf(`&;Cpb1^#= z$lx9+OPOJTj0xPhY5D{<7rr9iq*-B7OOjE`D#0uh3njV$jA&Y}liMHJ62TY9%JTGu zwm>G?sw8Edr;@EEGW32vPv3r=GxNu{Pn{30;=v-&-E#nF1&(FAd2IW9O_nf|1+RU% zU2V1%SxP%6F*0o&8wF(TAM*tPvXYd)WgkpWT0i+sss@V8aLjw0N&0Q##;c` ztTu{3HXxfQje=C_hOtSC1~OeD;1(*Ir7<#XB^G28fS#!>b)(pIe|WFwrQ)^wv)wH} zn~0wCJY3L<8pX)H>yPF5=svlTEgP{^b}>ECD7Kt}_36B1JavlYt zT|T{ef;T*`7PEcpiPT@iB17}))d7Gj#k}j|0f5I9^Xhcpu1>h6*4*LEfW(`M0spWvMN?q?L>eQd_ih;>`A>wuwrS#WG{1(|X&*|1IX<>&*Q9 zMF0;ycqDvr{>!sTBvP#`fJD*7Ev5SwM)XN%gFGgr!dPYM?pOR-W||)r{&3BME9Lwc z$NNU42|+t>qC_jDKUbqt0cRgJwfD37)eKZw8CF*mlt1iE{-lAS!7+~RI-^xX1bX*BJG=E7ocx?^qLqo#SY00 zOVT6%M5gao5Fj%1VYxn~NSpS*-nJz%b-nhm@b*$q$r4yT3e{_w6dhlnN}<175MLL*8GloYdl~Weu)ghE8A-I)O>(1jeBg7>7<^ z96Et<=mf^06Bvh1;5c*w$DtEA3Z1}F=md^JCvX%xfy2^iN&kXn>F=5B-;6rSl$l{9*S4)%(OTyj?vMj*Z;}N05E_wamsWXwxp&jCw(Zzn z9oHAzw(X>4YbRsi08*vW2$ITX>7;aNv*h;v{ojxy+g7dgx$pbv58ffQWx zu@o3Gb{RSMzQ@3ABSpz{j~U;f5Ofo_JgZJIEX;STF=-?eib6K#*Q-~JqEK`1)5L(^ zKbGxH_r{R;&>9~;etgv+BW?_eQk!zQ>aZ{}>cl{n4mpu)N{MhwSX$OqaWX7ej z`>48{q!FGo`0Lu6Nu?3G`xCV^7$Ts%KNOAajn`&BegZhfuu#Z_zinGOX`|3mCK)&X zQB*s#*L#dp2mmOyUZDpK&Cy*8yh;~M!bQ+cjUqt9`=%kGL;8hQNq_|oR8tDZV@ic+ z4jmICAtiXsgga6tny}Q+lp#h`D>5VbqB7tZ@s!pc8PK4h4N{>Q;o(&7&(tb_O2w0r zj}KBxK%@;*`lbvvxRdg54zbhdyfxJ)hWcLICMAZr<^uhOpWasso1(xrsuVNgmLY}( z58$CB(`32V&8&M;`~zV2{vE@nFT^4tPEbfOo2PH56{_KWicnV7C9}Q3q5}Z5`WGi% zX`(f>Kn-43Dhv>!ukhGn8xxjiW_g<@kZU8{-b`kN!G;C*Q+^l_w^Pz^Lyk}|=kT7G zt-ww%H?l&hk2h$1YDm2RWZFs{cqmh0r+Z%0Ob_6Sdi*)bG5)k z9)Oy?-Rvur`gnuJCti#`*On`0fL2L?w%G*#1)Q>>MidDN*g=CYcA^q2=r9x<|FuS^ z2mQk1z09!d-+mSRai&WsT z;Zzq7j1XL?iv{?bH6A1wZaf;fvxHMoow}$^T_`{(VJeL3w2A}wq^FocHib|^eZK%f zR{H&ZAV;|)E*zi82fkILgujhvXoVBt@#;dq)4}P{vX{iEt1} zk|I^l+%x#M^#b0-;!(SeBuSB~XZLkd-+#|oqNT&0{~tJL*EWvLI5yg}t&UACRVzg+ zg|@b@v}r5bs+FRZqSmS(8!`ION>NKC=*LC?rcuVg{?SH;fg};;w$SFeEEs}Pf95Pwo7KCt}Ok?%HyHabki=L;NG4<>SVxEg zOD9-|sH$ADMPd8=STb9#lK^aD>)Yz~yAsTK_WB>WEd&BE<0wemfNA~P-mwr70)j%; z@EC6OCv|Ik(@4^l^r=c^(sfdlEN{`$Ff%hVb2BsDa`(iN3|Z@6%MtyD;I@$?Nwy|z zn9VnRbGILmY-?{DH*jM~5y?binhw`oy4&ynW*GtyJ&CAU0ulX(AW3fAW&p*Zd~FqM z5~M&sAZK6p{b%2Q_Wfr+GFtT{u0%?zagw$;MLUfnQbO8h5m{P^G!oK`J9LsrJ4xWr z65kfGdSonho@}O7i3}#R7{X{|IHT;?W-Y?0CH0n)dP2Heyx3&B8X<(FiT-HKnhAd2 z+Kixqy(9;k)VtRVSQ~s09TpiJST#6eto_p<#P(}p;;10;UNR7yW`u&6dV-1==rclU z;(msz?JGo($}oeh207Lc?NngF5*4|b4@f@1gR#ro|0N+I*I{sOyCU`H*GxMl(=8&pI3;T0qekrsI$ z^kQsr1&NHkAn)s1<|$m$g5L~{2ak0mkad7$72@)X*%eac1#z)r;UI_GY&|0^$T9_1oz;?t01xdrB1MPk?fY*yF8j;t~M+VyAb!@CA(hm0hzuTCEF1lZ2U|x*7OAw+bB&$Z2JU!fXLX^O5g(cj z$|+eR;6FB>ESf^@<3G47x$ziC?MWX;9O$@c z1BYpIg{c)|nIfFsHckN7UhE7g$TTccm8SaJ&Wgc^dL-QHs*~ydgV5A|Sj6g>Ggp{3 ziKzFrs*W6N9Q+ZP&qB510=Tq|c?i$&3!5jlrR}pI%dlwS>tkja!&h6zS=-9?d5~#T zr1+STw3Vf=Z{JAxy0l_7-Jfz#B~cGt>#UED6z|%|xK(<8`l>N{&N_j25s+XNZ?%dZ zHJl|ZM_|Tn;;rI4oO%2jBQPUNR_X56hpeD{jgWk`NmusjVcuc{d+h0_cJz6J@y6nY ziuvQ=u>Q2&2k52N=t8~F9?}JS>@Va1@A}s-vb-I#`6V`Q1jN3*s*}K9I_nmy)XzrE zTK^GimV;S0Q-pdZnmGMOc<@79xBWgG6ms(3kK7621bKgts`~nH6i9@g@!Tv=2SqL@ zhCUA2H+}u_|1-ktRx<;In~GNm&{h3))yb+}Cat9NOdy(Z7h2QgMGNVTMKpRzt#j=W ztXbQYYSlWr>!uqDCl$kK0vhU{+p8Yx1jtOnA6{fg(`>MYh`$X~;H3APg82d{-PLvb z*uvlKC)D|6#c-4WQOw=V0}m}Y)TGgh1RfL$6O+1=B&_nrLwk%&gU5JP0{BK`!XQusJjqQ+L{a%$-iyfZS&zwUoA!zb0_s&@}kpvdy|%jZuXnMF+C z_+0@!0Zitzftf8n?huR_UESulfy!0Yi+u_Nd0tgt9@x{CDFG945P6De9cyN&T!Iuj z1Y`DHH~h_{r&Z+$30$7rSL7TZ3!gY{1sJ~7Sr#OfV7E;f= zr_KhnHVWHKbO^@me&5c6NsaO5!vVl0z_WW&(D1cEoD#vZL({TB;r2*15<7%-2*&Jx z-&l8Nn#T0z0LlfxTL*U~p&`#Rp)gfowe6&3DW3MM1hZ0orgjM4AJU45{by%w8Dn{M zYd};^>`H?!f*c~xWZn}&0!l0$iC0AldKPgcF%Ruo83dXF$ssYLF!LcFm9>`=4J8FycA-N z%gdv``bSyxTFas5S_ZvW_w``Qq_<|BMM6+kP&iD%2><{uU%(d-O(<&Hh*8I{$Di<|5Yhh$z&!_% z8~GAoPow~#CZ{*sQKCk`k`A7XH6-@pf=wh_k=nm1Bp~63gujT6-Hbzn(@2sWDNkbk z|FHbG?$F0UMv){b%3}uq>KI;p0{TAzV8A1T2Je6A^9cy9VF3Vz0-FJjA|B)-7Z*=Y zO)g0HW&_ZY3TZR^-J6VmTG!M;^g}lQ*hm@#fNTK50Kjhma=pt707G-w*oJfiVZfs} z6trz4Is9pF_b`Zv3Glol^j9#YQ)4NQ>tvRj{r}=NzrPe_#+GS>j1l+2IW$MG=PbZb zrDtZ2nL)CB_MQHBm8;JHO3nGNkm?s|)rDb+r1cP~JwiH0PGbGjjS5?={ z+TO^x0^UJ~dn4X>b4AKFH&Yv_ZQHx0e*&ml+rL_F4s)0pRmx0`Z%Rm@OTi8&6XDbJR~*;xhYSm5lNM# zVMLBE*%LW`TRJ=^-%^YkO{Yqv=yad~A|RcRDPEGGp2I8CO||g2Y`)?C zU`*fZO{zFuj35nyD3n4bu?-^2n|7gj>Gp{G`c}AeMu~)-5-0&gV?p3$v9-Tc3SR>H z+0yMeNj1&{l=pO^<;O&MxxQm+!iAFidiX{9jN>K#igSU8sR@9&%iv+rm zhFxhq=vM~DB826iTG7I?R7spci^1>(^8U6R^&vwQBT_~n3_&4(YOJ-!VOIq4)-X8e zGe{y{URT0<-bJq;VTyzh1c2;74O>$Igf|EPxult;ppeAa3xBNzYXHRWP6g;eA4gN^ zifk;WskL+#8{qe{lbHZur%FS#oqS~~yr>p{JGYnF{kCq0N)qshMDl0w@%xb{FJk%Z zdP@uL2+$WLX86QUHMjHV&B2yI!4|e0C$Yea0}8_?d0E*nl@d z0J#y6i6YBsEx>F5e}cPSp5FIrPjLQVQHx{B#r{zK0yw_|tKy-ILS;e#5d}av{|>p| zHu?0TD`43zGVJ6|Y`z0@_X2IKT0WyN5D*IC3ef62;&J_i(`)4I6?DY_r(Xclr9G65 zkHrAooeaPMw;MGM3Rst#W4b(^B)Df%06D#MgmQ_}Y^b&oFyZ*dl#2rt^0^C4on~H( z5EBsrw3V4bhNLe9%=;5O!h7Ft^XGp-h$sEXwW4tfU~m}`Aj59NS0UL3d?8`P@zdc) z_b|@=yL5lMIzAKQzwssGXT4zW0LGtP@FrlQ zOze$E110GvBT4evwP5bXj16&o?5ScuJNE$0wkcC9e}YXcQE!X?|X`50RQ3h zZnL&uUlm7-e(H5f#8-3*0sW6&8F_?9t&z$h-=>#&Ul+Naxsc6!n*2v%q|HD2v-C*Wtw18|JN}6 zZQ7`B?iI^H*BZMN=1A!AuL#igmv54*Yv%iaL16nh*1;kaQ2m z^D=R{4)BLj#TEPSCUtv$@bzkw6T9fv#0K2r1~7gS~Q=C$Tpx8uF8_q zdLuBO;t!$OP`w)n`QLATZp<$>-}}rDAjvUwIe>IoMPVHluIvXOjX!~n3eR)-s9XSc zXNUw?diR>bIIJ9iUeID)kU%!>8U?AuS$eEyuj^cCx@4%r(iA!Z7DZdmf!YvYu>d@@ z20)4Hs;RqZ+FK{xBsoki=OZZ&K&z^-R5q5X73+$X!D==jT}1#|9BtCh(s|s>pJb@t zC=gWYqLKJp7^r?y|RVCO)wwoqq-t>vycTVU_KzoSFu(MVnfM`>W$@H^tVAz56(erxEg;W zvoVVJ2Ad;`k)bQ9hcp1~!8sIrYM5SrN-V7@!W-;%u(9YIs<8TcydU~ug-yH>1U`sHA_n9RmrLnoh}$9 z0z2CXRsj(TB>A_3RasR4Q81)H07ZvSY{lDN$^6Q+5SK3Aw1yUGq%zw@fPgn%Jzh%d z#xz|yn%dvd{X(zj{8Y+<-hfF`1ZNW{EJq;V4W5dp0+`n5cWv@`)bAZzVSWUEb+_gJ zm;cPRq*NF&QqU^a*a8Heil+jkp#;7*k#`1d_3~(btpy8yxVqcQbw1USz84MA!~8WVZD<@)e+_WdYM0N<(IBOLatgv2R-RZs`!(}_$Jfz?%s zq2j5uP9SO$d27&ajtoc3u|dC_pRlbMf4nG;h!DLx?$0+<3wM*_;+FShKcs=ufJIa_ z=oC){BuFYCVXNfvcFX4B#eV;3PG+D_@GDRNgD>g`IB9*@wbZ?P(p!hskWE5_J5eiC z5BAKZaord5A^c3?=Qn+a diff --git a/mobile/src/main/res/mipmap-xxhdpi/banner.webp b/mobile/src/main/res/mipmap-xxhdpi/banner.webp deleted file mode 100644 index bb1e4d2170c69f3330133e3999231503d176dee7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3310 zcmV;gIZ1N8Y7fU_$<<7s*G{I!s9mb7q-_F>t z{@b;+TGnD&3uDW>GB76AJ-cNsmbGYGwSGH~Zdr@AR)V#5201$3kL8>YAc@2Ntc+O) z>R`+|7$Zi^I{X0yVgv$#n8Du(2n1q%O93DV0XU!#ArcfKh(tgF5`YCH03nE$C?Nbe zCR+h0HB2=gGY`hvhAx8_r4?9fofRdDC7#F&#VbUoC;3_QzDS!Y{y@^vvx^Wg@4#TuL{vHIx0@`&#u-BcySUH{k z!JyMqKhBh+plur_Eq~VgLl6-Yz`3HE2|Y+?dh(T$Z99r2XR zGc&_hGt*r%Dbp0vDD1mM^j{7eNsc7t1abQQEj=^4wk+GW+qM~r7K==9k)X+$F<-s+ zUQ{>%Yp5=K4-Yp5sQ_L?{~bt@)Hb_Z9Y}|AKnwN*DBHFjMULbq(tz6l8E_Sgv4&*( z|G!qDr~8B=R|(I!QbhkDL6RG{8Gvzo163&SG^zK}@E%kDH}!v0|2Or2Q~x*he^dWA z^?(0>#?ZCfTvjX8oKrUhBF4U{D>hTpYv?^MOi?*gjSV)O$FdTlnjB!5`nnQj>ZicU zwOWZ&(-d5}aLruL`N{-QQ@w~@sVR7BomJkcQ_#Sc6#z8_3`_+=6D$g7?e;?7n$`|C zXxo_8?JIl?leIa7)yA@3=FytkA!-N^^!#?3n<5r_emk%akB@Ec+n^J8!fVHUL$rZ` zreYzBwZLtTEH=SoS@hjKWL^pzQ}M57tN3X#ch)%ijGe$z&=whTjkrIDpaAp+e#LOeI5mAGcpk%* z=~TlNEyfwL-6Ehn4Y`CAkyNscp|1mW*nH>7o85AQyEj>9b`*V`8nj}E8GVER)cRro zczfGiV}ZZKbJ;Bn5)=ch!zs5@aZq95c{&I5#JsiAt^tw%8fcMdA!VcFYGu zjv1zKUeSZYB0_!Oyq*FG6Ap=043tJ<^#-473~R|Qt*gj2e_q6>^u&m(k2c}mAQw!L zEJkVYg%Np$?Zmi#!Dm0O9!bjLDJ-plcG0`}zFIF{3biOl7>$rpmi!kpwE}+FK z4k$VFm=Z|iOSh@4)v$Gd4BwJLFBWlTG6vSQh9^oP3qqZ^axkI5wg4c%w~1liQ;X^b z5KWCA42$I)6@y?ie+kL%Z86d2KSc#~i@%GS@NswSxlsX{Trx^eiI?0Oek)k22O6sg zTX>OgQ^7gM<`bgfwWU_Hcq6r*4BCHXeRdB=PEzB1OMlHJ=Pt_DeDDXIT~xUYgH_%g z4O!EII^Sy^F)se|;*oD%oPio$(8>`~mmXFz6q84YXv+$Mh_OU4SkZ}2_Z@Je^PNR3 zp-WTK{6cRz=;{Mj(Z6&`hZ>Znq@-ozVBkC8Fxd9aQ@XXk>C7wa2)k$~aCTK4Dg>|~orFnz>@3)`~$H10SWonX<( zlQTj~y)l=V2@4livPWdSaadYpF(ISxy@;AJksBToC>o0Q;&&H1d~Zi4GbS1ud3yJ` z$e5VZ%F#Ppo1ehZFMxQI7pVDXZ{Wtm+(3>fo|E|KR zvqkgYMLCUv`TrV>RVVVJB0A4%Ql@c1$e6_G&;ZG~QiJD79=%>C9xkugeej>76B& z?UJdfKfhc)?4mbV&S;8a9KdyW5RGeWqRlr&uBYo=ZBI>(JPn79rQBU&!qi| zVqhqx_(&-?83IF_b9O6x@o$JX?wn?x^4K+((iwa2tMXwM zEHM zn>5ZCdSBu`LB!l;?4*@lvPrP>rQ9@%M&HT@ZoQ_a^XhcKE{m{T?&6)XatZK%#h($j z;VkXKP&NU6xAN~z{`oV9(%%62AR<`i7<#YPMVYAPhiQj1R_-;~qqcl7|LOr+*)LqW z7#_--m)A%AC8Hi1a(iinw(l0ZVJbTY-{jWCNu06#aXsFjciR{jHqC92zGN{x+z1pB z85(4C2sqGbzK-$Glg1hAUWnd>zvlNV>V`9v_RE1_YU&S9sP}^$J2ZZBcT9cp3b^C( z52`=vqY<~ml=jPkWNO|7KqGKkZr>FGZtCSnH_IL(oiTNIL;ARQcfQcum$z9v8D-BL z(bPzgV&=#ydfF8Nt8GLZK48Txo+Qo~__&tBhh`Z3qx8=1?-6@!IkWsy?mg;js4D(k! z(FCE@vO~Ya8B@39d4;;0fBuxd^2e=Fic^%<*9^o{5psd$rm#uYDT7}pP6g106I(YU z@IzHxw?`UhOng!$3atYk{Pu0i%I^=yMX3G0fqWW5E<{DB2TnQ!6BTm_MzUeZty`JM zQqj7BSxi#^C`Z#)IbWI58Ec=HMZy=XYaBQJ|MO$wQ$PRskN<9sOPZ}`SchgPOhL$n zR0{dqkji-wZEw#`%LA<85&&Ss)pF=$`~--niakrPejueY=02~IhFl;t$2+ksc!g?UNGq#;!z#p$HVN!XX>f&W4L4RztU(3K{BZfR}56nsEjLFYS>bbYslx#bbVAsMI=-tH{GIKvu zl|MD9zJ?74F6AiHcR_xy3l3Zgub)eCC?DtZyCApvD%j-T?Qnu43m?|^KxXy%CE$}9 zZakmg>zW=N@Fur6fP{p)ZeMm^q3iw0tG<}_GwTU;-5L7t+xa=qr=tVjd0ze)&^@7U z2We}Dp6k=m0ZU$#KU1upOxGQ6JFzKCUJsAFN<2bvdNSSCmi=9p4wAn8T5^9)XXuI&={8-sTn-^;wLWeK=)o7?Tnf#G%(3V6_F>+h$`L&Dw8!9& z>>Wh4%wbf!97r_^;1H^Dj-i^yiy6j^tuc!yGm0-Wi8nKdKQo6%vqjQ8m~~v3ZJd~8 z+?ZV)nN?hwO`MrU+*x!1tF7VEY~j=_;nwWn*sS2%Y~b81;NJ4*;Bx2U^5*1n=H~L{ z=yK)i^5pDtEw^3nm*b@Q_Ey^PqElN*%b3B-vH^3rhfNeslAWg&+t)OUrd6$xhSAK12R- z5yF1T>jI#t{!ailjZ4C|Bvse|q|#uqK2iZ%Sb6fmo@&Jd;MI1~IF6aeOwK zj_$m&{$2K0#g6vbXspeD1pqFS=%awZ>wN+G;|&~;kR+tw1TL>$`sD_^W|GJaDJcbj zAg&*!I{-+qP!pik0Hi`l$e)A&sUG{MLW04{<{fx4sif&XLP^#RF zZS=rwgI$@8PT01q4))%Qf!jukGM(i;X7~VQZQIl4ktDN2WoE|J&|%Dx^2{N0HOyGX z;7>7=uB-keL*!mven}`ulB7tI)N*k7|9{*)=Ibh_bKACQYwGjdr*_`^RJQHNj+Gj} zz^<^;`a2aW`Vf|FMxXU~?hFWS+enhM?YSrA)Ya+PAAsn;X=X$=Ig!~cQuz!Opb#Lr zVD|$C`Qkfa@b9N(qRuBeR2>gp;DG}I4e^o61H;*lp*aR=_Q{q(Zo}e`Kkal;cc9Lr zG8Os_F@}r)01wJo1$U+%_h!xRMQ2)7odO-ekP@X(JOaRp0tmqbz3NIh0%pm^MHe6& zd8;Zo1VRZ(aU4A;twhYBBW42^309ayAe|smc?f)Fk%)*LJx`7jb=lKXv0>AqvyI5C zub;~SgTy5v^^|bx$M1T96GR)hzbK&1%6uU}iVH{;VdeLqTbuDc&o9j;zj2=UeELv3 zLZFnms0m&D?dSRRlkHK<7oAvSH_a0Po%Z!_f-sIyipNEgka^#KUM~7GP7~B3Vx}PF zIChz&OQSO+R`h@jzdTf%qjerCczg(ysLh@5Lj*Jv(+I6Z)2r5t%|um5jF?9BdukxD z&g@_EMnPnXNZ_ms;jGu)e~)3TE^aZ<&{kG9gq&jXU`j9nX$m+UhS8s4+|?|U3M-ss zf^slk3Ob6ehn6F;sA(u>XZC6)9mLdQatMtErgHX}Ya(5nuYDO}(9l@Q6iTPCbgR?c z8fVC+L$A8yAno*gjY>3W%_d_@(AnzM*kD(Cw8-2!xI^g*T46{mq-!9{gh%pBwv1VW zd2zNdcr0vX%U&zpfQq(Cx;tTyT@q^e8nY{F$%|VmU3ga$(Lttrvp7knp0k8q4%SRX z4;!k=Q&6bzn5aLmiAsHQ$?Q%%M1@`}v_Nq`{%HK>~lo7j+vFvo?REt&iR=I0;0s2V8BIbQq5Hu{!Ao~+)iEX&^7x{ov#dM zDO3rFyn&b0_{Mb)-Tufe;Tgpd%tV+_H`GvtNySHyuA-CSIeP!gCwD%6?sSZqq|S;K zg7*Bi`v&K@94|Th73}>u+Z8WIAYj> zpP6d1f(H81A60kY6M`j18H^B=L282mWKG-v=lKkdU|3D8)eV-63Ih<`V=T=)#?~Su z{0v-pZmd;JMg^95a8<;{#s8OcX$Ci&@ABlVfK6|<=0MA87U@F(@ql|S&;GtMF*bD> zpVepX0Fiwx14{2nDYNeS{d$*Ml0YkKY8@g3F5Z&7$W6qN z;-B+>x<5>Ifdu1BZd3b0)Eqo19e8$tahA5_9rSlEzquYKB0%)$S$8e0Kor0Po|GuS zL|*~pj>{bdo%fqJ{QE|M-t*~8N)4-!`ce|$Rl^*>_&!x(eE;e4|L>O$uG@Hv<%dR3 zXRkkPwS5oeO$8ee$qC?jN7K^^Ps!8jYTHR4dx&j=bTCzMFuXef(qyPN-*k`}JQ9|b&1Il!L`fvL` z{q~WRFaLYzpZfvfU5OJ?DZm8o1HZtE&IoAY%G(#`-@jAD>97<0)KpxRF5w1?inJsY8GrT|_%)DqurY^kXus{QTLk19M4yP3fc#t0t zg8bSBQ!D$<&(3l^a>LRr2{e!p0)#%nv?4lQlD+8x2gpb85w!N=zQuDcFV*lY@AF>% zcXMPUK!V}GF!NenT&VFFMrH_WT=>~GZ{eZ4e%qr<=k^_w*II=vPkzax|L@GVAQTxz z6}%h}Eb0n%wGndU%0)=RbIy17Q#U$v<9YwZzP7B><`e>wg`aSVPM?>5<>3*DV37=0 zqL8pm($4AJo0*FtVww```4iOJI_96F{X6K0**YPSWm#l#WI{z!z>-BUV6J2He9A=U zk1HPh`LuNT&aG9;XO*Lx3UPaE1j0A@6S_$Lhar)M3lRvzo^Sy<&acZK{PDE()UA?m zZ3z*Oc{~S}=qht3^>jLR@h^qDB_jKykz`goBEhZH^Yf8dS6>y4`-Dap;LJRdC3#a05th2Q7z#*Dj9DX z7D|I$LiwM?AYw)=oQ?=_9csin#?Y!<0OT;O)RYjFZ5DTkq8NrOR zV#C!JR}SQ4QJWyxWViA~gs)wz*{B=1czVjQ;uUdU|8}bD8oL01ra>t)Ml5 zn-sFakE<*HKk4AV{M*@cy5p0+zRMNrqUuwC#DOqWh^w?Do_ct5pC6OHlKb0gi@(N4 zY%eSDcqvy5h@gRpz>OP_gwEi_kelE`x0%J%r|yr>3CfO*g$r+$mz9`FQXrB7ffH{U zN-7h3rMx@1-XDR|^+mGTGOzEimkHQgs&ne@Hwu(sl4KwVH5a5n;KGIQ7_MgA-^rf| z*h78-Hqk?>5%_`1t!uIKxll@IO0q}};Xm%CLUVz_m!G`Q)QKjEOG1G?Nw~%SW8K9O z@Q@1J#-TuZ$^W5)yt6rRcFgAc%Db0X7Dl3yh<^lWF49t@(S$S_jrvk86W%7gNZ#Mz z@bO+R4joE<$v;R?td1lg3Il_a5go+AQ#&yCZ@*}y%<9ji_A=D0M(I3+R{6rE!Mi5J zl(uIozk21}v(f#N>+`2N`uKY=ZAbFM{;&qU&_5^{>cm>dCV&n}Moju^OiS-W7=zEL zHll%_frdiK_g9}y$xL^(Kal2vNmKP_f2gJEz}KYl06`xU=SJ* uHGu$i(12@#0vQst+Nc4wmx890>Ig_s>blmX>Ll@nlF$oWUNc0(-$|y^*t}W* diff --git a/mobile/src/main/res/mipmap-xxxhdpi/banner.webp b/mobile/src/main/res/mipmap-xxxhdpi/banner.webp deleted file mode 100644 index 669943fda9ab4046fbc38fb9e0cae6a1dc6896f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4394 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 o*zad)Cvv)# Tethering USB Hotspot Wi\u2011Fi - Hotspot Wi\u2011Fi (legacy) Tethering Bluetooth "Tethering Ethernet" diff --git a/mobile/src/main/res/values-pt-rBR/strings.xml b/mobile/src/main/res/values-pt-rBR/strings.xml index 536370ff..ef8501af 100644 --- a/mobile/src/main/res/values-pt-rBR/strings.xml +++ b/mobile/src/main/res/values-pt-rBR/strings.xml @@ -50,7 +50,6 @@ Por favor, desative a Aceleração de hardware de tethering nas configurações de desenvolvedor caso o VPN Hotspot não funcionar corretamente. Tethering USB Ponto de acesso Wi\u2011Fi - Ponto de acesso Wi\u2011Fi (legacy) Tethering Bluetooth Tethering Ethernet Tethering USB (NCM) diff --git a/mobile/src/main/res/values-v25/bools.xml b/mobile/src/main/res/values-v25/bools.xml deleted file mode 100644 index 34904e41..00000000 --- a/mobile/src/main/res/values-v25/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - false - diff --git a/mobile/src/main/res/values-v26/bools.xml b/mobile/src/main/res/values-v26/bools.xml deleted file mode 100644 index e3b6da48..00000000 --- a/mobile/src/main/res/values-v26/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - true - diff --git a/mobile/src/main/res/values-v28/arrays.xml b/mobile/src/main/res/values-v28/arrays.xml deleted file mode 100644 index d37b03dd..00000000 --- a/mobile/src/main/res/values-v28/arrays.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - @string/settings_service_masquerade_none - @string/settings_service_masquerade_simple - @string/settings_service_masquerade_netd - - - None - Simple - Netd - - diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index ca87b40a..450af0b4 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -57,7 +57,6 @@ --> USB 网络共享 WLAN 热点 - WLAN 热点 (旧 API) 蓝牙网络共享 "以太网络共享" USB 网络共享 (NCM) diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index e1fab558..c6b21b25 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -66,7 +66,6 @@ --> USB 網路共用 Wi\u2011Fi 無線基地台 - Wi\u2011Fi 無線基地台 (舊版) 藍牙網路共用 "乙太網路網路共用" USB 網路共用 (NCM) diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index b0af652d..1b313614 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -9,10 +9,12 @@ @string/settings_service_masquerade_none @string/settings_service_masquerade_simple + @string/settings_service_masquerade_netd None Simple + Netd diff --git a/mobile/src/main/res/values/bools.xml b/mobile/src/main/res/values/bools.xml index a84b400b..3c344c71 100644 --- a/mobile/src/main/res/values/bools.xml +++ b/mobile/src/main/res/values/bools.xml @@ -1,7 +1,5 @@ false - false - true true diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index d4b5aa99..765ab197 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -69,7 +69,6 @@ if VPN tethering does not work. USB tethering Wi\u2011Fi hotspot - Wi\u2011Fi hotspot (legacy) Bluetooth tethering Ethernet tethering USB tethering (NCM) diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index b7d2fad4..4104fb57 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -2,7 +2,7 @@